Add app-wide 3DoF for VITURE, RayNeo and XREAL glasses and 6DoF for Luma Ultra (#1035)

* Add VITURE glasses support: hardware control, 3D, head tracking

Glasses get their own controller over the MCU (permission, 120 Hz default, brightness, volume, sunblock, 2D/3D) and the game moves onto them when plugged in. The IMU drives Cardboard head tracking and the SteamVR driver. 3D falls back to one eye's view on phones that can't send the 3840x1080 signal. Options open from a glasses button in game and on the Steam page.

* Drop the glasses settings like Steam's: Play-style button, Settings lip, tabs

* Hang the glasses Settings lip over the top of the button

* Space glasses MCU commands so the IMU rate sticks, fit fixed resolutions to the glasses

* Keep Steam alive with SteamVR registered, run SteamVR at the glasses' rate, keep the glasses lip on top

* Fit VR games on the glasses: full view, seated origin at the head, pad hands reach with the d-pad

* Let the pad hands reach a metre out so buttons with deep colliders press

* Show every VR frame in order at 120 fps: wait for SteamVR's GPU work, present only finished copies, skip the grace on idle shared counters

* Glasses: SteamVR starts and stops with them, closer view with a field of view setting, Recenter on the pad

* Glasses: SteamVR field of view defaults to 90°

* VR pad hands: sticks move and aim the hands, left stick click switches the sticks to the game, d-pad pulls a held hand

* Glasses: Play SteamVR beside the glasses button instead of starting SteamVR with them, pad hands drawn as Index controllers and off while other VR controllers are connected, glasses sheet tabs sized like Steam's

* Track glasses mode, refresh-aware glasses window, align Play SteamVR with Play Steam

* Fix 3D on the glasses: full-res SteamVR presenter, revert VR 3D at session end, coalesce vsync ticks, no session restart after end

* Address review: glasses output after installs and session end, VR revert retry, display fallback, presenter swapchain retry, agent hex checks, drawer bumpers, glasses translations, docs

* Harden VITURE stereo negotiation and output refresh tracking

* Localize stereo recovery feedback and record Astra reconnect tests

* Improve test failure diagnostics and refresh Astra firmware findings

* Handle VITURE docks separately and reject truncated USB frames

* Glasses: remember a 3D fallback per model, docks as display only, VR timing fixes

- A 3D attempt that ends at the 640x480 fallback is remembered for that glasses
  model, so Play SteamVR no longer blacks out the glasses after every restart;
  Retry clears it and "3D for VR games" stays reachable
- A VITURE Mobile Dock on its own no longer counts as glasses; through a dock
  Play SteamVR is hidden and the sheet shows the output and why there is no control
- HDR is probed on the glasses when the session starts there
- Glasses frames with a mismatched checksum are marked in the status frame log
- SteamVR layer and driver vblank counters stay monotonic across refresh changes;
  one-eye render scale applies from activation and on mono/3D switches
- Compositor binds announce the refresh clients were last told; the presenter
  retries a failed first swapchain
- Dead strings and helpers removed; docs updated

* Glasses: drop USB frames with a bad checksum

* Glasses: count frames dropped for a bad checksum

* Feat/viture xr (#907)

* Add RayNeo Air and GT support to the XR glasses pipeline

* Add RayNeo Air and GT support to the XR glasses pipeline

* Checkpoint glasses tracking, calibration, and SteamVR launch integration

* Checkpoint glasses tracking, calibration, and SteamVR launch integration

* Add XREAL 1S support and correct GT and Pro 2 tracking

* Add XREAL 1S support and correct GT and Pro 2 tracking

* Fix mirrored glasses tracking and recover SteamVR after device loss

* Fix mirrored glasses tracking and recover SteamVR after device loss

* Document confirmed XREAL 1S brightness control

* Document confirmed XREAL 1S brightness control

* Fix SteamVR glasses defaults and rendering controls

* Fix SteamVR glasses defaults and rendering controls

* chore: keep only the 3 newest session log folders (#852)

* chore: add agent comment rules (#748)

Co-authored-by: Kurt Himebauch <[email protected]>

* Fix SteamVR polling, VITURE checksums, and fence tracking

Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter.

Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage.

* Fix SteamVR polling, VITURE checksums, and fence tracking

Keep manual SteamVR launches polled, discard corrupt VITURE packets, and retire Vulkan fences out of order without blocking submissions at a fixed capacity. Preserve the contiguous completion watermark used by the presenter.

Fix optional Windows launcher configuration and audited helper staging. Add Android, Windows, and native tracker regression coverage.

* fix: wireless ADB page names the child-process switch the way Android shows it (#848)

* l10n: refresh stale and untranslated strings in es, fr, ko, ru (#850)

* l10n: refresh stale and untranslated strings in es, fr, ko, ru

- mode_added_art_hint: now mentions Steam then SteamGridDB (all four locales).
- wincomp_needs_installer / wincomp_installers_note: es and ko still described
  the old "coming soon" behaviour; align with the current English text.
- summary_remove_game: was still English in all four locales.

Machine-assisted translation; native-speaker review is welcome.

* l10n(es): say registro, not grabación, for the missing installer record

---------

Co-authored-by: ovurrsl <[email protected]>

* fix: a failed cloud save recovery no longer ends the app (#846)

The cloud-recovery thread started at every app start had nothing to
catch an error, so one went to CrashHandler, which halts the process
with status 2. In unit tests the thread can outlive the test that
started it, fail on the torn-down preferences, and take the whole test
run down. The error is now logged and the thread ends.

Co-authored-by: Kirk <[email protected]>

* fix: an interrupted Linux runtime download resumes instead of starting again (#847)

* fix: an interrupted Linux runtime download resumes instead of starting again

installOnce() asked the downloader to resume but always deleted the archive in its finally block, so the next attempt began at zero. The archive is now named after the release version and checksum, a failed download keeps what it fetched, and archives of other releases (and the old fixed name) are removed. A checksum or extraction failure still deletes the file.

* test: Downloader resumes a cut-off download from the bytes it kept

Starts a socket server that promises the whole file, hangs up part way and then answers a Range request, and checks that the second downloadFile call resumes with the right offset and ends with identical bytes.

* fix: a resumed download discards a 206 that does not start where the kept bytes end

Downloader appended every 206 response to the partial file without
looking at Content-Range. A server or proxy that answers a Range request
with a different offset would leave a corrupt file. Check that the range
starts at the kept length; otherwise delete the partial file and start
again without a Range header.

Adds a test with a server that answers the Range request from offset 0.

---------

Co-authored-by: ovurrsl <[email protected]>

* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings (#849)

* l10n: German, French, Spanish, Italian, Portuguese and Russian for the zRAM, reset-settings and store free-space strings

Adds the strings values-de, values-it and values-pt were missing (14 each) and the six that values-fr, values-es and values-ru were missing. Machine-assisted drafts that follow each file's existing register and terms; native review is welcome.

* l10n: correct missing active swap translations

---------

Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>

* fix: droiddeckctl reaches variant-package builds (recover #842) (#854)

fix: droiddeckctl reaches builds shipped under a variant package

The provider authority and the start Activity's component were fixed to
com.droiddeck.launcher, so droiddeckctl could not talk to the antutu,
ludashi or pubg builds from tools/release/variants.txt. DROIDDECK_PACKAGE
now picks the package; the Activity's class name stays the same in every
variant.

Co-authored-by: angelrenefm <[email protected]>

* feat: droiddeckctl reports on-screen pacing (recover #843) (#855)

feat: droiddeckctl pacing reports the frames that reached the screen

Reads SurfaceFlinger's timestats for the game's layer (or the session's
SurfaceView) over a capture: frames on screen, fps on screen, the
present-to-present histogram and how many intervals were off the usual
cadence. Records split by a refresh-rate change are summed. A game's own fps
counter counts frames handed over, so it can't show dropped or doubled frames.

Co-authored-by: angelrenefm <[email protected]>

* fix: Steam can restart more than twice in a session (#754)

* fix: Steam can restart more than twice in a session

Every exit 42 counted toward the cap that stops an update which will
not install, and the count was never cleared, so the third restart of
a session ended it however far apart they were. A client that ran for
a minute before asking is not that loop: the count starts again.

* fix: measure the client's run time as it exits

The time spent collecting logs and checking FEX after the client exits
counted toward its run, so a slow cleanup could make a quick exit 42
look like a long one and clear the count.

---------

Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>

* fix: the front end keeps its page when the Fold is folded or unfolded (#832)

* fix: the front end keeps its page when the Fold is folded or unfolded

MainActivity, FileManagerActivity, FilePickerActivity and StoreLoginActivity did not declare the screen-size configuration changes, so folding or unfolding a foldable recreated them and dropped their in-memory state. They now handle orientation, screenSize, screenLayout, smallestScreenSize and density themselves, like SessionActivity already does. uiMode is left out on purpose.

* fix: store login keeps rebuilding its toolbar when the display density changes

StoreLoginActivity builds its toolbar in code with dp-to-pixel values taken at creation time and has no onConfigurationChanged. It no longer declares density in configChanges, so a density change recreates it as before this PR. Size and layout changes are still handled in place.

---------

Co-authored-by: ovurrsl <[email protected]>

* Release pointer buttons without replaying stale motion (#803)

* Fix cursor warping on native touch down

* Preserve input cleanup without replaying stale pointer motion

---------

Co-authored-by: siyuan.qsy <[email protected]>

* l10n: translate the strings added since #284 into zh-CN, zh-HK, zh-TW and ja (#539)

* l10n: translate the 184 strings added since #284 (zh-CN/HK/TW, ja)

The stores, cloud saves, Windows components, Steam touch, desktop
removal and background-downloads strings landed after #284's
translation snapshot, so every one of them fell back to English.
Translate all 184 into Simplified Chinese, Traditional Chinese
(Taiwan and Hong Kong) and Japanese, keeping each locale file's own
vocabulary (工作階段/會話, 解除安裝/卸載, 元件/組件, 設定).

Also fix four Hong Kong strings that kept the simplified 着色器.

* l10n(zh): fix stale and ambiguous strings found in review

Severe: summary_remove_game was untranslated English in all three
files; mode_added_art_hint, wincomp_installers_note and
wincomp_needs_installer still described the pre-#480/#492 English.

Also: distinguish the Setup wizard from Android system settings in
zh-HK/zh-TW (初始設定 / 系統設定 / Android 設定), unify library as
收藏庫, component as 組件 in zh-HK, session as 會話 in zh-HK, account
as 帳戶 in zh-HK, Trending as 趋势/趨勢, Forget as 清除, reword the
re-applied-at-launch, action-failed and already-in-Games strings, and
play as 執行/開啟遊戲 in zh-TW/zh-HK.

* l10n(ja): fix stale and ambiguous strings found in review

---------

Co-authored-by: Dantalian <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>

* Rebuild and verify Windows VR runtime components

* Rebuild and verify Windows VR runtime components

* Add top, bottom, and centered picture positions for Steam sessions (#871)

* fix: dependency scan ignores folders above the game folder (#859)

* DroidDeck-Proton: get releases from the Droid-Deck org repo

DroidDeck-Proton is now built and released on Droid-Deck/DroidDeck-Proton
(11-9.1 is Latest there), so the Components page, the update check and the
guest installer read releases from the org repo instead of The412Banner's.

* feat: low-latency controller and touch toggles in the session drawer (#808)

* fix: SteamGridDB lookups no longer stick as "no match" after a failure; add a search to the art chooser (#876)

* feat: Steam home wall fills from the signed-in library instead of store picks (#878)

* test: cover GPU family and support classification (#880)

* Fix local builds on Linux failing after the first Comet build (#866)

* feat: sort the store library and installed tabs, and switch between card and list views (#798)

* feat: three-finger tap toggles Steam's on-screen keyboard in game mode (#782)

* fix: keep Quest-patched Turnip as the auto default (#853)

* Fix deploy_local.sh and droiddeckctl not finding a phone connected over wireless debugging (#864)

* fix: keep proot sessions alive when the kernel loses fork events (#457)

* fix(quest): correct desktop and Steam display corruption across sessions (#733)

fix(quest): stabilize desktop and Steam layer presentation

Keep Quest SDR scenes and the black backdrop off the Android Vulkan swapchain regardless of session order or HDR preferences. Only report the first frame after client content is submitted.

Add backdrop lifecycle and first-frame regression coverage. Record the user's successful Quest 3 Test 2 result.

Co-authored-by: xXJsonDeruloXx <[email protected]>

* fix: keep SteamVR syscall hooks and report Steam crashes (#905)

* fix: preserve SteamVR syscall hooks

* fix: report Steam crashes through gamescope

* fix: honor glasses pacing and resolution in SteamVR launches

---------

Co-authored-by: connor g <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: Yassine Gherbi <[email protected]>
Co-authored-by: Resul Övür <[email protected]>
Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: angelrenefm <[email protected]>
Co-authored-by: asdf17128 <[email protected]>
Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: FX1922 <[email protected]>
Co-authored-by: Muaz Kadan <[email protected]>
Co-authored-by: kingokksa <[email protected]>
Co-authored-by: Jamie Schenker <[email protected]>
Co-authored-by: Jagger15 <[email protected]>

* Glasses: keep a pending 2D restore and the SteamVR recovery offer, show Play SteamVR only with a picture

* Translate the glasses calibration, XREAL and SteamVR recovery strings

* SteamVR recovery: give a finished teardown time to collect logs before restarting

* Credit the projects the XR glasses support builds on

* Joy-Cons: SteamVR hands from their gyro while SteamVR runs, one combined pad otherwise

* Add experimental Luma tracking and stabilize SteamVR launches

Add native Luma Ultra spatial tracking, optical controllers, and stereo
calibration with DroidDeck styling, imperial/metric measurements, and
pending recenter recovery. Include the pinned hand model and calibration board.

Keep live guest and SDK files outside Android's evictable cache. Filter the
Adreno 840 sysmem override in the shared SteamVR helper, serialize timeline
mirroring, retain invalid optical controllers without event floods, and
prevent an unprivileged scheduling fallback from lowering compositor RTTIME.

Validation: latest APK build and runtime freshness checks pass; 247 affected
glasses/calibration tests pass. Native VR, helper, and packaging regressions
pass. Two earlier phone library launches sustained 60 fps for 308 and 188
seconds without recovery or restart. The newer calibration APK is installed
and checksum-verified; calibrated-hand and sustained gameplay acceptance
remain pending. One existing Android test requires Windows symlink privileges.

* Headsets: settings badge on Play SteamVR instead of a top-right button, both shown only once connected

* Hand tracking: camera-tracked Joy-Cons and bare hands in SteamVR, drawn as white gloves

- Joy-Con movement from the accelerometer (report reordering, per-Joy-Con calibration, zero-velocity updates)
- MediaPipe pose and hand landmarks from the device camera, placement check, per-hand Kalman filter with the Joy-Con
- No controller connected: bare camera hands with Quest-style gestures, 1€ filtered, rendered as a white glove model built from a distance field
- SteamVR safe mode cleared before each start so one crash no longer blocks the driver

* Improve Luma Ultra tracking and isolate device-specific controls

Refine stereo hand and Joy-Con tracking, gesture release, calibration,
recovery UI, diagnostics, display modes, and compositor handling.
Keep Ultra controls and input routing behind a live SDK-confirmed Carina
connection; clear retained spatial mode when other glasses connect.
Add optional UVC transport with pinned dependencies and packaged notices.

Validation: 456 Android tests, ARM64 VR build and native regressions,
47 tooling tests, SDK/UVC-enabled and disabled builds, and 150 packaged
runtime checksums. Two shader compilation tests skipped because the
compiler is unavailable. Hardware interaction gates remain experimental.

* Delay Luma Ultra 6DoF until SteamVR settles

Suspend spatial SDK startup while SteamVR launches, then restore it two seconds
after RUNNING is confirmed. Preserve saved tracking preferences and automatically
recenter same-device restoration, including cancellation and early stop.

Discard superseded launch requests and status replies after session teardown.

Validation: 128 focused JVM tests passed on the isolated commit snapshot; 129
passed with the current local hand-tracking work. Debug build and runtime checks
passed, and the reviewed APK was installed and hash-verified on the phone.

* Clarify glasses and phone camera tracking options

Separate Luma Ultra camera controls from phone-camera tracking so the two
hand-tracking switches have clear sources. Label bare-hand controllers and
Joy-Con position tracking explicitly, explain physical Joy-Con inputs and
camera priority, and update the camera prompt and calibration guidance.

Use fresh phone section and toggle labels so existing translations do not
hide the distinction. Preserve tracking behavior and saved preferences.

Validation: 39 existing UI tests, debug build, and packaged runtime verification
passed. Reviewed changes and installed the APK on the connected phone.

* Fix tracking handoff and shared runtime regressions

Release spatial ownership and controller overrides when output returns to the phone. Preserve USB-loss recovery, keep Vulkan submissions asynchronous, clean persistent guest scratch on runtime removal, and clarify the 6DoF head-tracking control.

* Apply smart-glasses virtual screens across DroidDeck menus

* Finalize Luma hand baseline and document smart-glasses support

Keep raw grayscale-to-RGB Full hand inference, wrist-only Joy-Con reconstruction, bounded visual holds, and the dead-zone rollback. Retain opt-in steady aim, recenter controls, reproducible Lite models, and the JPEG-padding fix.

Remove unsuccessful benchmark-only and USB transport experiments. Document the cross-brand 3DoF matrix, Luma Ultra 6DoF, SDK requirements, and app-wide screen behavior.

* Fix executable permission for SteamVR launcher

* Address hand tracking and VR review findings

* Bound stereo recovery test image comparison memory

* Localize smart-glasses setup and tracking diagnostics

Move calibration controls, status messages, validation errors, distance labels,
and printed-board instructions into Android string resources. Add and update
machine-assisted translations in all 32 supported non-English locale catalogs,
with corrections for recentering, hand tracking, product names, and measurements.

Keep print instructions within the page using text layout that supports wrapping
and right-to-left text. Correct the Lite model toggle description to reflect its
Lite palm detector and Lite landmark model.

Add locale coverage, format-argument, line-break, and measurement checks, plus
French and Arabic calibration UI tests. Preserve tracking calculations and the
shared runtime storage behavior.

Validation: Android resource and Kotlin compilation; 3 localization checks;
68 focused tests; 1,597 JVM tests at a 512 MB heap; French and Arabic UI renders.

* Correct smart-glasses translations from PR review

Fix the 42 localization findings covering hand and head terminology, disabled states, calibration dimensions, camera visibility, capture rejection, and menu references. Correct the repeated capture-error ambiguity in related locales and clarify its English source. Keep Romanian setup instructions consistent with the existing informal voice.

Extend localization measurement validation to distance hints and retry guidance. Verify the recovery screen's palette-dependent text color with one row-sized pixel buffer and guaranteed bitmap cleanup.

Validation: Android resource and Kotlin compilation; 3 localization checks across all supported locales; 68 focused JVM tests at a 512 MB heap.

* Work around SteamVR device loss with Adreno 840 Turnip v0.2.0

* Withdraw ineffective SteamVR flushall workaround

---------

Co-authored-by: maxjivi05 <[email protected]>
Co-authored-by: MaxsTechReview <[email protected]>
Co-authored-by: connor g <[email protected]>
Co-authored-by: Kurt Himebauch <[email protected]>
Co-authored-by: Yassine Gherbi <[email protected]>
Co-authored-by: Resul Övür <[email protected]>
Co-authored-by: ovurrsl <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: Kirk <[email protected]>
Co-authored-by: xXJsonDeruloXx <[email protected]>
Co-authored-by: angelrenefm <[email protected]>
Co-authored-by: asdf17128 <[email protected]>
Co-authored-by: siyuan.qsy <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: Dantalian <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: The412Banner <[email protected]>
Co-authored-by: FX1922 <[email protected]>
Co-authored-by: Muaz Kadan <[email protected]>
Co-authored-by: kingokksa <[email protected]>
Co-authored-by: Jamie Schenker <[email protected]>
Co-authored-by: Jagger15 <[email protected]>
This commit is contained in:
213 files changed
+22572 -919

No files matched your search

+1
View File
@@ -9,3 +9,4 @@ tools/linuxfs/desktop/** text eol=lf
tools/mangoapp/mangoapp text eol=lf
app/src/main/assets/desktop-removal/*.json text eol=lf
app/src/main/assets/desktop-removal/*.txt text eol=lf
*.pdf binary
+4
View File
@@ -19,6 +19,10 @@ Use Android 9 or newer on a supported Adreno device (730 or newer, or 8xx). Adre
On Android 14 and newer, before Steam launches, you must turn on **Disable child process restrictions** in Developer options. If this option is not available in developer settings (Android 12 and 13 devices), first launch of Steam will present a "Fix it for me" button, which will help automate the setup process.
## Smart glasses
Use **Setup → Smart glasses** for app-wide virtual-screen settings across DroidDeck menus, launchers, desktop sessions, and games. Supported VITURE, RayNeo, and XREAL connections provide 3DoF; Luma Ultra adds optional experimental 6DoF and stereo-assisted hand/Joy-Con tracking. Model support and SDK requirements differ: see the [smart-glasses support matrix](docs/smart-glasses-support.md) for supported devices, controls, setup, and limitations.
## Community
Join the [DroidDeck Discord](https://discord.gg/JRGAvawjsm) for help, Preview builds, and device reports. Bug reports go in its **#bug-reports** forum; attach the zip from **Share logs** (Setup, or the session drawer) so the logs come with it.
+33 -2
View File
@@ -12,7 +12,14 @@ if (localBuildOptionsFile.asFile.exists()) {
}
def pythonExecutable = project.findProperty('pythonExecutable')
?: localBuildOptions.getProperty('droiddeck.python', 'python3')
def vitureSdkDir = project.findProperty('vitureSdkDir') ?: localBuildOptions.getProperty('droiddeck.vitureSdk', '')
def vitureSdkDir = project.hasProperty('vitureSdkDir') ? project.property('vitureSdkDir') : localBuildOptions.getProperty('droiddeck.vitureSdk', '')
def uvcDir = project.hasProperty('uvcDir') ? project.property('uvcDir') : localBuildOptions.getProperty('droiddeck.uvcDir', '')
if (uvcDir) {
['arm64-v8a/libusb.so', 'arm64-v8a/libuvc.a', 'include/libusb.h', 'include/libuvc/libuvc.h',
'licenses/libusb-COPYING', 'licenses/libuvc-LICENSE.txt'].each { required ->
if (!new File(uvcDir, required).isFile()) throw new GradleException("Incomplete UVC build: missing ${required}")
}
}
if (vitureSdkDir) {
['android/arm64-v8a/libglasses.so', 'android/arm64-v8a/libcarina_vio.so',
'android/arm64-v8a/libcloud_protocol.so', 'include/viture_glasses_provider.h',
@@ -120,6 +127,7 @@ android {
cmake {
arguments '-DANDROID_STL=c++_shared'
arguments "-DDROIDDECK_VITURE_SDK_DIR=${vitureSdkDir.toString().replace('\\', '/')}"
arguments "-DDROIDDECK_UVC_DIR=${uvcDir.toString().replace('\\', '/')}"
}
}
}
@@ -225,6 +233,14 @@ def stageRuntimeAssets = tasks.register('stageRuntimeAssets', Sync) {
}
android.sourceSets.main.assets.setSrcDirs([runtimeAssetsDir])
if (vitureSdkDir) android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('vitureAssets'))
if (uvcDir) {
def stageUvcNotices = tasks.register('stageUvcNotices', Copy) {
from(new File(uvcDir, 'licenses'))
into(layout.buildDirectory.dir('uvcAssets/licenses/uvc'))
}
tasks.named('preBuild').configure { dependsOn(stageUvcNotices) }
android.sourceSets.main.assets.srcDir(layout.buildDirectory.dir('uvcAssets'))
}
tasks.named('preBuild').configure { dependsOn(stageRuntimeAssets) }
def verifyRuntimeInputs = tasks.register('verifyRuntimeInputs', Exec) {
@@ -266,7 +282,22 @@ def buildRustStores = tasks.register('buildRustStores', Exec) {
if (!skipRust) android.sourceSets.main.jniLibs.srcDir(rustJniLibsDir)
tasks.named('preBuild').configure { dependsOn(buildRustStores) }
def verifyHandModel = tasks.register('verifyHandModel') {
def models = ['hand_landmarker.task': 'fbc2a30080c3c557093b5ddfc334698132eb341044ccee322ccf8bcf3607cde1',
'hand_landmarker_lite.task': '173c0f47ecee7e935bd4a1e93aa580756b4180c1307c07d709313e82c28672c8']
.collectEntries { name, hash -> [(file("src/main/assets/models/$name")): hash] }
inputs.files(models.keySet())
doLast {
models.each { model, expected ->
def actual = java.security.MessageDigest.getInstance('SHA-256').digest(model.bytes).encodeHex().toString()
if (actual != expected) throw new GradleException("Hand Landmarker model checksum mismatch: ${model.name}")
}
}
}
tasks.named('preBuild').configure { dependsOn(verifyHandModel) }
dependencies {
implementation 'org.opencv:opencv:4.13.0'
implementation 'org.khronos.openxr:openxr_loader_for_android:1.1.49'
implementation 'com.github.luben:zstd-jni:1.5.7-4@aar'
implementation 'org.apache.commons:commons-compress:1.27.1'
@@ -285,7 +316,7 @@ dependencies {
implementation 'com.termux.termux-app:terminal-view:0.118.0'
implementation 'androidx.camera:camera-camera2:1.4.2'
implementation 'androidx.camera:camera-lifecycle:1.4.2'
implementation('com.google.mediapipe:tasks-vision:0.10.21') {
implementation('com.google.mediapipe:tasks-vision:1.0.0') {
exclude group: 'com.google.auto.value', module: 'auto-value'
}
+3
View File
@@ -44,6 +44,8 @@
android:isGame="true">
<meta-data android:name="android.game_mode_config" android:resource="@xml/game_mode_config" />
<service android:name=".glasses.LumaRgbCaptureService" android:exported="false"
android:process=":rgb_diagnostic" />
<meta-data android:name="com.oculus.supportedDevices" android:value="quest2|quest3|quest3s" />
<activity android:name=".QuestXrActivity" android:exported="true" android:launchMode="singleTask"
android:permission="android.permission.DUMP"
@@ -200,6 +202,7 @@
android:exported="true"
android:permission="android.permission.DUMP"
android:grantUriPermissions="false" />
<activity android:name=".ui.SpatialTrackingActivity" android:exported="false" />
<service
android:name="com.google.android.datatransport.runtime.backends.TransportBackendDiscovery"
tools:node="merge">
@@ -0,0 +1,102 @@
{
"version": 1,
"identity": "luma-ultra-base-v1",
"width": 640,
"height": 480,
"fisheye": false,
"error": 1.1209395390660726,
"verified": false,
"k1": {
"rows": 3,
"cols": 3,
"data": [
226.0499636040297,
0,
321.62348950766386,
0,
228.36987999988767,
242.2674105020684,
0,
0,
1
]
},
"d1": {
"rows": 5,
"cols": 1,
"data": [
-0.28622915867944043,
0.1194613930041054,
0.007984341063494766,
-0.0008220922923559441,
-0.025579805296512992
]
},
"k2": {
"rows": 3,
"cols": 3,
"data": [
240.3338202496282,
0,
320.8790079027563,
0,
241.14047924325277,
260.45566640784716,
0,
0,
1
]
},
"d2": {
"rows": 5,
"cols": 1,
"data": [
-0.27073208928390136,
0.08065508519805098,
-0.001460778493930769,
-0.0003694718992635806,
-0.011452505620454932
]
},
"r": {
"rows": 3,
"cols": 3,
"data": [
0.900501479835182,
0.04473903344740378,
-0.43254537762047496,
-0.08255720039597626,
0.9941919046988283,
-0.06904176485356042,
0.4269442510181227,
0.0978819468416306,
0.8989648107712929
]
},
"t": {
"rows": 3,
"cols": 1,
"data": [
-0.14117171913441395,
0.0014051874404824838,
-0.007555740272006347
]
},
"cameraToHead": [
-0.05607918,
0.023585627,
-0.07998472,
0.01024512,
0.9945703,
0.005859665,
-0.10339546
],
"deviceModel": "VITURE Luma Ultra",
"cameraPair": "left0-right0",
"sourceValidation": {
"exportDate": "2026-10-16",
"sourceDeviceVerified": true,
"capturedViews": 25,
"scope": "Starting geometry from one calibrated Luma Ultra; other units have not been individually validated."
}
}
@@ -0,0 +1,9 @@
{
"url": "https://storage.googleapis.com/mediapipe-models/hand_landmarker/hand_landmarker/float16/1/hand_landmarker.task",
"sha256": "fbc2a30080c3c557093b5ddfc334698132eb341044ccee322ccf8bcf3607cde1",
"bytes": 7819105,
"mediapipe": "com.google.mediapipe:tasks-vision:1.0.0",
"opencv": "org.opencv:opencv:4.13.0",
"documentation": "https://ai.google.dev/edge/mediapipe/solutions/vision/hand_landmarker",
"runtime": "Two video-mode instances; CPU default; GPU requires separate performance acceptance"
}
Binary file not shown.
@@ -0,0 +1,12 @@
{
"sha256": "173c0f47ecee7e935bd4a1e93aa580756b4180c1307c07d709313e82c28672c8",
"composition": "Lite palm detector and lite hand landmarks with matching Tasks tensor metadata and associated files from the full bundle",
"palm_source": "https://storage.googleapis.com/mediapipe-assets/palm_detection_lite.tflite",
"palm_source_sha256": "e9a4aaddf90dda56a87235303cf00e4c2d3fb28725f68fd88772997dac905c18",
"landmark_source": "https://storage.googleapis.com/mediapipe-assets/hand_landmark_lite.tflite",
"landmark_source_sha256": "d7fde8ac11f8ce03f8663775bfc323f4fc9f2a38062b4f4efa142874ef5b2a48",
"metadata_source": "hand_landmarker.task",
"metadata_source_sha256": "fbc2a30080c3c557093b5ddfc334698132eb341044ccee322ccf8bcf3607cde1",
"builder": "tools/build_hand_lite.py; mediapipe==0.10.21; deterministic ZIP_STORED bundle and embedded associated-file archives",
"runtime": "com.google.mediapipe:tasks-vision:1.0.0; CPU by default; experimental stereo hand and wrist tracking"
}
Binary file not shown.
+12
View File
@@ -47,3 +47,15 @@ if(DROIDDECK_VITURE_SDK_DIR)
target_link_directories(droiddeck_viture PRIVATE "${DROIDDECK_VITURE_SDK_DIR}/android/${ANDROID_ABI}")
target_link_libraries(droiddeck_viture PRIVATE glasses log)
endif()
if(DROIDDECK_UVC_DIR)
add_library(rgb_usb SHARED IMPORTED)
set_target_properties(rgb_usb PROPERTIES IMPORTED_LOCATION "${DROIDDECK_UVC_DIR}/${ANDROID_ABI}/libusb.so")
add_library(rgb_uvc STATIC IMPORTED)
set_target_properties(rgb_uvc PROPERTIES IMPORTED_LOCATION "${DROIDDECK_UVC_DIR}/${ANDROID_ABI}/libuvc.a")
add_library(droiddeck_uvc SHARED viture/uvc_bridge.cpp)
target_compile_features(droiddeck_uvc PRIVATE cxx_std_17)
target_include_directories(droiddeck_uvc PRIVATE "${DROIDDECK_UVC_DIR}/include")
target_link_libraries(droiddeck_uvc PRIVATE rgb_uvc rgb_usb log)
target_link_options(droiddeck_uvc PRIVATE -Wl,-z,max-page-size=16384)
endif()
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <cstddef>
#include <cstdint>
inline std::size_t mjpeg_payload_size(const uint8_t* bytes, std::size_t size) {
if (!bytes || size < 4 || size > 4*1024*1024 || bytes[0] != 0xff || bytes[1] != 0xd8) return 0;
std::size_t end = size;
// Luma Ultra UVC frames can pad the JPEG to an eight-byte boundary with zeros.
while (end > 4 && size-end < 7 && bytes[end-1] == 0) --end;
return bytes[end-2] == 0xff && bytes[end-1] == 0xd9 ? end : 0;
}
+250 -2
View File
@@ -2,17 +2,32 @@
#include <android/log.h>
#include <algorithm>
#include <array>
#include <vector>
#include <deque>
#include <cmath>
#include <cstring>
#include <cstdint>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <string>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/usbdevice_fs.h>
#include <time.h>
#include "viture_glasses_provider.h"
#include "viture_device.h"
#include "viture_device_carina.h"
#include "viture_camera_provider.h"
#include "viture_protocol_public.h"
#include "viture_result.h"
#include "viture_version.h"
#include "display_modes.h"
#include "stereo_frame_gate.h"
namespace {
// SDK callbacks have no user-data parameter. Only one provider may exist at a time.
@@ -59,6 +74,61 @@ void clear_pose(bool accept) {
latest_time = 0;
}
int64_t monotonic_ns() {
timespec ts{}; clock_gettime(CLOCK_MONOTONIC, &ts);
return int64_t(ts.tv_sec) * 1000000000LL + ts.tv_nsec;
}
struct CameraPose { double time; std::array<float, 7> pose; };
std::mutex camera_mutex;
std::deque<CameraPose> camera_poses;
std::vector<uint8_t> camera_left, camera_right;
bool camera_accepting = false, camera_wanted = false;
int camera_width = 0, camera_height = 0;
uint64_t camera_sequence = 0, camera_consumed = 0;
StereoFrameGate camera_gate;
uint64_t camera_input_sequence = 0;
int64_t camera_receipt = 0;
int64_t camera_pose_receipt = 0;
double camera_source = 0;
const CameraPose *matched_camera_pose(double source) {
const CameraPose *best = nullptr;
double distance = 0.005;
for (const auto &p : camera_poses) if (std::abs(p.time-source) < distance) {
best = &p; distance = std::abs(p.time-source);
}
return best;
}
void on_carina_pose(float *pose, double stamp) {
if (!pose || !std::isfinite(stamp)) return;
std::lock_guard<std::mutex> lock(camera_mutex);
if (!camera_accepting) return;
camera_pose_receipt = monotonic_ns();
CameraPose p{stamp, {}}; std::copy_n(pose, 7, p.pose.begin());
camera_poses.push_back(p);
while (camera_poses.size() > 64) camera_poses.pop_front();
}
void on_camera(char *left, char *right, char *, char *, double stamp, int width, int height) {
if (!left || !right || width <= 0 || height <= 0 || width > 2048 || height > 2048 || !std::isfinite(stamp)) return;
const auto received = monotonic_ns();
std::lock_guard<std::mutex> lock(camera_mutex);
if (!camera_accepting || !camera_wanted) return;
if (!camera_gate.Accept(stamp)) return;
const size_t size = size_t(width) * height;
camera_left.resize(size); camera_right.resize(size);
memcpy(camera_left.data(), left, size); memcpy(camera_right.data(), right, size);
camera_width = width; camera_height = height; camera_source = stamp; camera_receipt = received;
camera_input_sequence = camera_gate.received;
++camera_sequence;
}
void clear_camera(bool enabled) {
std::lock_guard<std::mutex> lock(camera_mutex);
camera_accepting = enabled; camera_wanted = false;
camera_poses.clear(); camera_left.clear(); camera_right.clear();
camera_sequence = camera_consumed = 0;
camera_gate = {}; camera_input_sequence = 0;
camera_pose_receipt = 0;
}
struct Session {
XRDeviceProviderHandle device = nullptr;
int pid = 0;
@@ -72,6 +142,7 @@ struct Session {
int saved_dof = -1;
int64_t consumed_time = 0;
jlong token = 0;
int spatial_status = 1;
};
std::unique_ptr<Session> session;
jlong next_token = 0;
@@ -127,6 +198,7 @@ int tracking(Session& s, bool enabled) {
}
void destroy(Session& s) {
clear_camera(false);
clear_pose(false);
clear_state(false);
if (s.device) {
@@ -205,7 +277,7 @@ extern "C" JNIEXPORT jstring JNICALL JNI_METHOD(nativeMarketName)(JNIEnv* env, j
return env->NewStringUTF(name);
}
extern "C" JNIEXPORT jlong JNICALL JNI_METHOD(nativeOpen)(JNIEnv* env, jobject, jint pid, jint fd, jstring cache) {
extern "C" JNIEXPORT jlong JNICALL JNI_METHOD(nativeOpen)(JNIEnv* env, jobject, jint pid, jint fd, jstring cache, jboolean spatial) {
std::lock_guard<std::mutex> lock(api_mutex);
if (session || fd < 0 || !cache || !xr_device_provider_is_product_id_valid(pid)) return 0;
auto s = std::make_unique<Session>();
@@ -221,7 +293,7 @@ extern "C" JNIEXPORT jlong JNICALL JNI_METHOD(nativeOpen)(JNIEnv* env, jobject,
}
if (rc == 0) {
rc = s->type == XR_DEVICE_TYPE_VITURE_CARINA ?
xr_device_provider_set_dof_type_carina(s->device, 0) :
xr_device_provider_set_dof_type_carina(s->device, spatial ? 1 : 0) :
xr_device_provider_register_imu_pose_callback(s->device, on_pose);
}
if (rc == 0) {
@@ -231,6 +303,10 @@ extern "C" JNIEXPORT jlong JNICALL JNI_METHOD(nativeOpen)(JNIEnv* env, jobject,
env->ReleaseStringUTFChars(cache, directory);
s->initialized = rc == 0;
}
if (rc == 0 && s->type == XR_DEVICE_TYPE_VITURE_CARINA && spatial) {
clear_camera(true);
rc = xr_device_provider_register_callbacks_carina(s->device, on_carina_pose, nullptr, nullptr, on_camera);
}
if (rc == 0) {
rc = xr_device_provider_start(s->device);
s->started = rc == 0;
@@ -306,3 +382,175 @@ extern "C" JNIEXPORT void JNICALL JNI_METHOD(nativeClose)(JNIEnv*, jobject, jlon
std::lock_guard<std::mutex> lock(api_mutex);
if (session && session->token == token) { destroy(*session); session.reset(); }
}
extern "C" JNIEXPORT jlong JNICALL JNI_METHOD(nativeSpatialPose)(JNIEnv* env, jobject, jlong token,
jfloatArray output, jintArray state) {
std::lock_guard<std::mutex> lock(api_mutex);
if (!session || session->token != token || !session->tracking || session->type != XR_DEVICE_TYPE_VITURE_CARINA ||
!output || !state || env->GetArrayLength(output) != 7 || env->GetArrayLength(state) != 1) return 0;
float pose[7]{}; int status = 1;
int rc = xr_device_provider_get_gl_pose_carina(session->device, pose, 0.0, &status);
session->spatial_status = rc == 0 ? status : rc;
jint valid = rc == 0 ? status : rc;
if (valid == 0) {
std::lock_guard<std::mutex> lock(camera_mutex);
if (monotonic_ns()-camera_pose_receipt > 100000000LL) valid = 1;
}
env->SetIntArrayRegion(state, 0, 1, &valid);
env->SetFloatArrayRegion(output, 0, 7, pose);
return monotonic_ns();
}
extern "C" JNIEXPORT void JNICALL JNI_METHOD(nativeCapture)(JNIEnv*, jobject, jlong token, jboolean wanted, jint ratePermille) {
std::lock_guard<std::mutex> api(api_mutex);
if (!session || session->token != token) return;
std::lock_guard<std::mutex> lock(camera_mutex);
camera_wanted = wanted;
camera_gate.SetRatePermille(wanted ? ratePermille : 1000);
if (!wanted) camera_consumed = camera_sequence;
}
extern "C" JNIEXPORT jstring JNICALL JNI_METHOD(nativeFirmware)(JNIEnv* env, jobject, jlong token) {
std::lock_guard<std::mutex> api(api_mutex);
if (!session || session->token != token) return nullptr;
char value[256]{}; int length = 255;
if (xr_device_provider_get_glasses_version(session->device, value, &length) != 0) return nullptr;
value[255] = 0;
return env->NewStringUTF(value);
}
extern "C" JNIEXPORT jbyteArray JNICALL JNI_METHOD(nativeSerialHash)(JNIEnv* env, jobject, jlong token) {
std::lock_guard<std::mutex> api(api_mutex);
if (!session || session->token != token) return nullptr;
uint8_t hash[32]{};
const int rc = xr_device_provider_get_sn_hash(session->device, hash);
if (rc != 0) { report("read device identity", rc); return nullptr; }
auto result = env->NewByteArray(32);
if (result) env->SetByteArrayRegion(result, 0, 32, reinterpret_cast<const jbyte*>(hash));
return result;
}
extern "C" JNIEXPORT jdoubleArray JNICALL JNI_METHOD(nativeSpatialDiagnostics)(JNIEnv* env, jobject, jlong token) {
std::lock_guard<std::mutex> api(api_mutex);
if (!session || session->token != token) return nullptr;
std::lock_guard<std::mutex> lock(camera_mutex);
double values[3] = {double(session->spatial_status), camera_pose_receipt ? (monotonic_ns()-camera_pose_receipt)/1e6 : -1.0,
camera_poses.empty() ? -1.0 : (camera_poses.back().time-camera_source)*1000.0};
auto result=env->NewDoubleArray(3);
if(result)env->SetDoubleArrayRegion(result,0,3,values);
return result;
}
extern "C" JNIEXPORT jlong JNICALL JNI_METHOD(nativeStereo)(JNIEnv* env, jobject, jlong token,
jobject left, jobject right, jdoubleArray metadata, jfloatArray head, jboolean waitForPose) {
std::lock_guard<std::mutex> api(api_mutex);
if (!session || session->token != token || !left || !right || !metadata || !head ||
env->GetArrayLength(metadata) != 9 || env->GetArrayLength(head) != 7) return 0;
std::lock_guard<std::mutex> lock(camera_mutex);
if (!camera_wanted || camera_sequence == camera_consumed || camera_left.empty()) return 0;
const auto size = camera_left.size();
if (env->GetDirectBufferCapacity(left) < jlong(size) || env->GetDirectBufferCapacity(right) < jlong(size)) return -1;
auto l = env->GetDirectBufferAddress(left), r = env->GetDirectBufferAddress(right);
if (!l || !r) return -1;
const CameraPose *best = matched_camera_pose(camera_source);
if (waitForPose && !best && monotonic_ns()-camera_receipt < 35000000LL) return 0;
memcpy(l, camera_left.data(), size); memcpy(r, camera_right.data(), size);
double meta[9] = {double(camera_width), double(camera_height), camera_source, double(camera_receipt),
double(token), double(camera_sequence), best ? 1.0 : 0.0,
double(camera_input_sequence), double(camera_gate.skipped)};
env->SetDoubleArrayRegion(metadata, 0, 9, meta);
if (best) env->SetFloatArrayRegion(head, 0, 7, best->pose.data());
camera_consumed = camera_sequence;
return jlong(camera_sequence);
}
extern "C" JNIEXPORT jboolean JNICALL JNI_METHOD(nativeCameraSynchronized)(JNIEnv*, jobject, jlong token, jdouble source) {
std::lock_guard<std::mutex> api(api_mutex);
if (!session || session->token != token || !std::isfinite(source)) return JNI_FALSE;
std::lock_guard<std::mutex> lock(camera_mutex);
return camera_accepting && matched_camera_pose(source) != nullptr;
}
namespace {
struct RgbUsbLog {
int savedStderr;
bool hadLevel;
std::string oldLevel;
explicit RgbUsbLog(int fd) : savedStderr(dup(STDERR_FILENO)),
hadLevel(getenv("LIBUSB_DEBUG") != nullptr), oldLevel(hadLevel ? getenv("LIBUSB_DEBUG") : "") {
fflush(stderr);
if (savedStderr >= 0) dup2(fd, STDERR_FILENO);
setenv("LIBUSB_DEBUG", "4", 1);
}
~RgbUsbLog() {
fflush(stderr);
if (savedStderr >= 0) { dup2(savedStderr, STDERR_FILENO); close(savedStderr); }
if (hadLevel) setenv("LIBUSB_DEBUG", oldLevel.c_str(), 1); else unsetenv("LIBUSB_DEBUG");
}
};
void rgb_usb_state(int fd, const char* stage) {
uint8_t alternate = 0;
usbdevfs_ctrltransfer request{};
request.bRequestType=0x81; request.bRequest=0x0a; request.wIndex=1;
request.wLength=1; request.timeout=200; request.data=&alternate;
int result=ioctl(fd, USBDEVFS_CONTROL, &request);
fprintf(stderr,"RGB %s GET_INTERFACE result=%d errno=%d alternate=%u\n",stage,result,result<0 ? errno : 0,alternate);
for (int selector : {1, 2}) {
uint8_t size[2]{};
request.bRequestType=0xa1; request.bRequest=0x85; request.wValue=selector << 8;
request.wLength=2; request.data=size;
result=ioctl(fd,USBDEVFS_CONTROL,&request);
const unsigned length=result==2 ? unsigned(size[0]) | unsigned(size[1]) << 8 : 26;
uint8_t bytes[64]{};
request.bRequest=0x81; request.wLength=std::clamp(length,26u,64u); request.data=bytes;
result=ioctl(fd,USBDEVFS_CONTROL,&request);
fprintf(stderr,"RGB %s %s result=%d errno=%d bytes=",stage,selector==1 ? "PROBE" : "COMMIT",result,result<0 ? errno : 0);
for (int i=0;i<std::max(0,result);++i) fprintf(stderr,"%02x",bytes[i]);
fprintf(stderr,"\n");
}
fflush(stderr);
}
}
extern "C" JNIEXPORT jbyteArray JNICALL JNI_METHOD(nativeRgbSnapshot)(JNIEnv* env, jobject,
jint vid, jint pid, jint fd, jlongArray metadata, jint logFd) {
if (fd < 0 || !metadata || env->GetArrayLength(metadata) != 7 ||
!xr_camera_provider_is_valid_camera(vid, pid)) return nullptr;
RgbUsbLog usbLog(logFd);
rgb_usb_state(fd,"before-create");
struct Capture {
std::mutex mutex;
std::condition_variable ready;
std::vector<uint8_t> jpeg;
unsigned frames = 0;
jlong values[7]{};
} capture;
auto camera = xr_camera_provider_create(vid, pid, fd);
if (!camera) { fprintf(stderr,"RGB provider create failed\n"); return nullptr; }
const int result = xr_camera_provider_start(camera, [](const XRCameraFrame* frame, void* data) {
auto& c = *static_cast<Capture*>(data);
std::lock_guard<std::mutex> lock(c.mutex);
++c.frames;
if (!frame || !frame->data || frame->format != XR_CAMERA_FORMAT_MJPEG ||
frame->size < 4 || frame->size > 4 * 1024 * 1024) return;
c.jpeg.assign(frame->data, frame->data + frame->size);
c.values[0] = frame->width; c.values[1] = frame->height;
c.values[2] = frame->timestamp; c.values[3] = frame->sequence;
c.values[6] = monotonic_ns();
c.ready.notify_one();
}, &capture);
fprintf(stderr,"RGB start result=%d streaming=%d\n",result,xr_camera_provider_is_streaming(camera));
rgb_usb_state(fd,"after-start");
if (result == 0) {
{
std::unique_lock<std::mutex> lock(capture.mutex);
capture.ready.wait_for(lock, std::chrono::seconds(5), [&] { return capture.frames >= 10 && !capture.jpeg.empty(); });
}
fprintf(stderr,"RGB stop result=%d\n",xr_camera_provider_stop(camera));
}
xr_camera_provider_destroy(camera);
rgb_usb_state(fd,"after-destroy");
capture.values[4] = result;
capture.values[5] = capture.frames;
env->SetLongArrayRegion(metadata, 0, 7, capture.values);
if (capture.jpeg.empty()) return nullptr;
auto bytes = env->NewByteArray(static_cast<jsize>(capture.jpeg.size()));
if (bytes) env->SetByteArrayRegion(bytes, 0, static_cast<jsize>(capture.jpeg.size()),
reinterpret_cast<const jbyte*>(capture.jpeg.data()));
return bytes;
}
@@ -0,0 +1,29 @@
#pragma once
#include <cmath>
#include <cstdint>
struct StereoFrameGate {
uint64_t received = 0, skipped = 0;
void SetRatePermille(int rate) {
if (rate != 500 && rate != 600 && rate != 800 && rate != 1000) rate = 500;
if (ratePermille_ != rate) phase_ = 1000-rate;
ratePermille_ = rate;
}
void SetHalfRate(bool enabled) {
SetRatePermille(enabled ? 500 : 1000);
}
bool Accept(double source) {
if (!std::isfinite(source) || source <= 0) return false;
++received;
if (source <= lastSource_) phase_ = 1000-ratePermille_;
lastSource_ = source;
phase_ += ratePermille_;
const bool copy = phase_ >= 1000;
if (copy) phase_ -= 1000;
if (!copy) ++skipped;
return copy;
}
private:
int ratePermille_ = 1000, phase_ = 0;
double lastSource_ = 0;
};
+134
View File
@@ -0,0 +1,134 @@
#include <jni.h>
#include <libusb.h>
#include <libuvc/libuvc.h>
#include "mjpeg_payload.h"
#include <algorithm>
#include <chrono>
#include <condition_variable>
#include <cstdio>
#include <cstdlib>
#include <mutex>
#include <string>
#include <vector>
#include <time.h>
#include <unistd.h>
namespace {
struct UsbLog {
int saved;
bool hadLevel;
std::string level;
explicit UsbLog(int fd) : saved(dup(STDERR_FILENO)), hadLevel(getenv("LIBUSB_DEBUG") != nullptr),
level(hadLevel ? getenv("LIBUSB_DEBUG") : "") {
fflush(stderr);
if (saved >= 0) dup2(fd, STDERR_FILENO);
setenv("LIBUSB_DEBUG", "3", 1);
libusb_set_log_cb(nullptr, [](libusb_context*, libusb_log_level, const char* text) {
fputs(text, stderr);
}, LIBUSB_LOG_CB_GLOBAL);
}
~UsbLog() {
libusb_set_log_cb(nullptr, nullptr, LIBUSB_LOG_CB_GLOBAL);
fflush(stderr);
if (saved >= 0) { dup2(saved, STDERR_FILENO); close(saved); }
if (hadLevel) setenv("LIBUSB_DEBUG", level.c_str(), 1); else unsetenv("LIBUSB_DEBUG");
}
};
struct Capture {
std::mutex mutex;
std::condition_variable ready;
std::vector<uint8_t> jpeg;
jlong values[14]{};
bool accepting = true;
};
jlong monotonic_ns() {
timespec now{};
clock_gettime(CLOCK_MONOTONIC, &now);
return now.tv_sec*1000000000LL+now.tv_nsec;
}
void finish_observation(Capture& capture) {
std::lock_guard<std::mutex> lock(capture.mutex);
if (!capture.accepting) return;
capture.accepting = false;
capture.values[12] = monotonic_ns();
if (capture.values[6]) capture.values[13] = capture.values[12]-capture.values[6];
capture.values[8] = std::max(capture.values[8],capture.values[13]);
}
void frame_received(uvc_frame_t* frame, void* user) {
auto& capture = *static_cast<Capture*>(user);
std::lock_guard<std::mutex> lock(capture.mutex);
if (!capture.accepting) return;
++capture.values[5];
const auto* bytes = frame ? static_cast<const uint8_t*>(frame->data) : nullptr;
const size_t size = frame ? frame->data_bytes : 0;
const size_t jpegSize = frame && frame->frame_format == UVC_FRAME_FORMAT_MJPEG ? mjpeg_payload_size(bytes,size) : 0;
if (!jpegSize) {
++capture.values[10];
return;
}
const jlong time = monotonic_ns();
capture.values[0]=frame->width; capture.values[1]=frame->height; capture.values[3]=frame->sequence;
if (!capture.values[7]) capture.values[7]=time;
if (capture.values[6]) capture.values[8]=std::max(capture.values[8],time-capture.values[6]);
capture.values[6]=time;
++capture.values[9];
capture.values[11]+=jpegSize;
capture.jpeg.assign(bytes,bytes+jpegSize);
capture.ready.notify_one();
}
}
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_droiddeck_launcher_glasses_UvcRgbDiagnostic_nativeSnapshot(JNIEnv* env, jobject,
jint fd, jint width, jint height, jint fps, jint durationMs, jlongArray metadata, jint logFd) {
if (fd<0 || logFd<0 || !metadata || env->GetArrayLength(metadata)!=14 || width<160 || width>1920 ||
height<120 || height>1080 || fps<5 || fps>30 || durationMs<1000 || durationMs>30000) return nullptr;
UsbLog log(logFd);
Capture capture;
uvc_context_t* context=nullptr;
uvc_device_handle_t* camera=nullptr;
libusb_set_option(nullptr,LIBUSB_OPTION_NO_DEVICE_DISCOVERY);
int result=uvc_init(&context,nullptr);
fprintf(stderr,"UVC init=%d\n",result);
if (result==0) {
result=uvc_wrap(fd,context,&camera);
fprintf(stderr,"UVC wrap=%d\n",result);
}
if (result==0) {
uvc_print_diag(camera,stderr);
uvc_stream_ctrl_t control{};
result=uvc_get_stream_ctrl_format_size(camera,&control,UVC_FRAME_FORMAT_MJPEG,width,height,fps);
fprintf(stderr,"UVC negotiate=%d requested=%dx%d@%d\n",result,width,height,fps);
if (result==0) {
uvc_print_stream_ctrl(&control,stderr);
result=uvc_start_streaming(camera,&control,frame_received,&capture,0);
fprintf(stderr,"UVC start=%d\n",result);
if (result==0) {
{
std::unique_lock<std::mutex> lock(capture.mutex);
if (capture.ready.wait_for(lock,std::chrono::seconds(5),[&] { return !capture.jpeg.empty(); }))
capture.ready.wait_for(lock,std::chrono::milliseconds(durationMs),[] { return false; });
}
finish_observation(capture);
uvc_stop_streaming(camera);
}
}
}
finish_observation(capture);
if (camera) uvc_close(camera);
if (context) uvc_exit(context);
capture.values[4]=result;
fprintf(stderr,"UVC result=%d callbacks=%lld complete=%lld invalid=%lld maxGapMs=%.3f tailMs=%.3f\n",result,
static_cast<long long>(capture.values[5]),static_cast<long long>(capture.values[9]),
static_cast<long long>(capture.values[10]),capture.values[8]/1e6,capture.values[13]/1e6);
env->SetLongArrayRegion(metadata,0,14,capture.values);
if (result!=0 || capture.jpeg.empty()) return nullptr;
auto bytes=env->NewByteArray(static_cast<jsize>(capture.jpeg.size()));
if (bytes) env->SetByteArrayRegion(bytes,0,static_cast<jsize>(capture.jpeg.size()),
reinterpret_cast<const jbyte*>(capture.jpeg.data()));
return bytes;
}
@@ -3,6 +3,7 @@ add_library(droiddeckwayland SHARED
src/vk_present.c
src/effects_chain.c
src/cardboard.c
src/spatialpose.c
src/vk_loader.c
src/sc_layer.c
src/ahb_swapchain.c
+19 -4
View File
@@ -28,7 +28,7 @@ static float clampf(float v, float lo, float hi) { return v < lo ? lo : v > hi ?
void cardboard_clamp(struct cardboard_params *p) {
p->enabled = p->enabled ? 1 : 0;
p->source = p->source == CARDBOARD_SOURCE_SBS ? CARDBOARD_SOURCE_SBS : CARDBOARD_SOURCE_SCREEN;
if (p->source < CARDBOARD_SOURCE_SCREEN || p->source > CARDBOARD_SOURCE_FLAT) p->source = CARDBOARD_SOURCE_SCREEN;
p->ipd_m = clampf(p->ipd_m, 0.0f, 0.1f);
p->screen_dist_m = clampf(p->screen_dist_m, 0.5f, 50.0f);
p->screen_width_m = clampf(p->screen_width_m, 0.2f, 60.0f);
@@ -62,8 +62,11 @@ void cardboard_push_constants(const struct cardboard_params *p, const float pose
out[21] = out[22] = out[23] = 0.0f;
}
#ifndef CARDBOARD_HOST_TEST
void cardboard_push_translation(const struct cardboard_params *p, const float position[3], float out[24]) {
for (int i = 0; i < 3; ++i) out[21+i] = p->source == CARDBOARD_SOURCE_SCREEN && isfinite(position[i]) ? position[i] : 0.0f;
}
#ifndef CARDBOARD_HOST_TEST
static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
static struct cardboard_params g_set = {
@@ -72,6 +75,7 @@ static struct cardboard_params g_set = {
static float g_pose[9] = {1, 0, 0, 0, 1, 0, 0, 0, 1};
static volatile int g_changed = 1;
static float g_position[3];
static int g_live;
static void shm_publish(int live);
@@ -86,7 +90,8 @@ void vkp_cardboard_set(const struct cardboard_params *p) {
pthread_mutex_unlock(&g_lock);
if (was != c.enabled || c.enabled)
droiddeck_log("cardboard", "%s: %s%s, ipd %.0f mm, screen %.1f m wide at %.1f m, lens k1 %.2f k2 %.2f, fov %.0f, inset %.3f, chroma %.3f",
c.enabled ? "on" : "off", c.source == CARDBOARD_SOURCE_SBS ? "side-by-side source" : "virtual screen",
c.enabled ? "on" : "off", c.source == CARDBOARD_SOURCE_SBS ? "side-by-side source" :
c.source == CARDBOARD_SOURCE_FLAT ? "flat image in both eyes" : "virtual screen",
c.mono ? " (one eye)" : "", c.ipd_m * 1000.0f, c.screen_width_m, c.screen_dist_m, c.k1, c.k2, c.fov_deg, c.lens_inset, c.chroma);
}
@@ -146,22 +151,31 @@ static void shm_publish(int live) {
}
void vkp_cardboard_set_pose(const float m[9], int live) {
void vkp_cardboard_set_spatial_pose(const float m[9], const float position[3], int live) {
pthread_mutex_lock(&g_lock);
memcpy(g_pose, m, sizeof(g_pose));
memcpy(g_position, position, sizeof(g_position));
g_live = live;
if (g_set.enabled && g_set.source == CARDBOARD_SOURCE_SCREEN) g_changed = 1;
shm_publish(live);
pthread_mutex_unlock(&g_lock);
}
void vkp_cardboard_set_pose(const float m[9], int live) {
const float zero[3] = {0};
vkp_cardboard_set_spatial_pose(m, zero, live);
}
static struct cardboard_params g_cur;
static float g_cur_pose[9];
static float g_cur_position[3];
static void snapshot(void) {
pthread_mutex_lock(&g_lock);
g_cur = g_set;
memcpy(g_cur_pose, g_pose, sizeof(g_cur_pose));
memcpy(g_cur_position, g_position, sizeof(g_cur_position));
pthread_mutex_unlock(&g_lock);
}
@@ -428,6 +442,7 @@ VkImage vkp_cardboard_run(VkCommandBuffer cmd, VkImage src, int src_w, int src_h
}
struct { float ndc[4]; float rest[24]; } pc = {{-1.0f, -1.0f, 1.0f, 1.0f}, {0}};
cardboard_push_constants(&g_cur, g_cur_pose, src_w, src_h, out_w, out_h, pc.rest);
cardboard_push_translation(&g_cur, g_cur_position, pc.rest);
VkRenderPassBeginInfo rpi = {.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = g_rp,
.framebuffer = g_t.fb, .renderArea = {{0, 0}, {(uint32_t)out_w, (uint32_t)out_h}}};
@@ -24,14 +24,21 @@ vec3 eyeSample(vec2 tan, float eye) {
if (pc.view.z > 0.5) {
float tanH = pc.lens.z;
vec2 s = vec2(tan.x / (tanH * pc.view.w), tan.y / tanH) * 0.5 + 0.5;
if (pc.view.z > 1.5) {
vec2 fit = max(vec2(pc.view.w / pc.view.y, pc.view.y / pc.view.w), vec2(1.0));
s = (s - 0.5) * fit + 0.5;
if (any(lessThan(s, vec2(0.0))) || any(greaterThan(s, vec2(1.0)))) return vec3(0.0);
return texture(Source, s).rgb;
}
if (any(lessThan(s, vec2(0.0))) || any(greaterThan(s, vec2(1.0)))) return vec3(0.0);
return texture(Source, vec2((s.x + eye) * 0.5, s.y)).rgb;
}
mat3 R = transpose(mat3(pc.rot0.xyz, pc.rot1.xyz, pc.rot2.xyz));
vec3 d = R * normalize(vec3(tan.x, -tan.y, -1.0));
vec3 o = R * vec3((eye < 0.5 ? -1.0 : 1.0) * pc.rot0.w, 0.0, 0.0);
vec3 o = pc.extra.yzw + R * vec3((eye < 0.5 ? -1.0 : 1.0) * pc.rot0.w, 0.0, 0.0);
if (d.z > -1e-4) return BACKDROP;
float t = (-pc.rot1.w - o.z) / d.z;
if (t <= 0.0) return BACKDROP;
vec3 hit = o + d * t;
float halfW = pc.rot2.w, halfH = halfW / pc.view.y;
vec2 s = vec2(hit.x / halfW, -hit.y / halfH) * 0.5 + 0.5;
+3 -1
View File
@@ -3,7 +3,7 @@
#define VK_USE_PLATFORM_ANDROID_KHR
#include <vulkan/vulkan.h>
enum { CARDBOARD_SOURCE_SCREEN = 0, CARDBOARD_SOURCE_SBS = 1 };
enum { CARDBOARD_SOURCE_SCREEN = 0, CARDBOARD_SOURCE_SBS = 1, CARDBOARD_SOURCE_FLAT = 2 };
struct cardboard_params {
int enabled;
@@ -21,6 +21,7 @@ struct cardboard_params {
void vkp_cardboard_set(const struct cardboard_params *p);
void vkp_cardboard_set_pose(const float m[9], int live);
void vkp_cardboard_set_spatial_pose(const float m[9], const float position[3], int live);
void vkp_cardboard_set_pose_file(const char *path);
void vkp_cardboard_set_render_height(int height);
@@ -33,5 +34,6 @@ VkImage vkp_cardboard_run(VkCommandBuffer cmd, VkImage src, int src_w, int src_h
void cardboard_clamp(struct cardboard_params *p);
void cardboard_push_constants(const struct cardboard_params *p, const float pose[9],
int src_w, int src_h, int out_w, int out_h, float out[24]);
void cardboard_push_translation(const struct cardboard_params *p, const float position[3], float out[24]);
#endif
+303 -269
View File
@@ -1,8 +1,9 @@
// 1115.1.0
#pragma once
const uint32_t cardboard_frag_code[] = {
0x07230203,0x00010000,0x0008000b,0x00000185,0x00000000,0x00020011,0x00000001,0x0006000b,
0x07230203,0x00010000,0x0008000b,0x000001ba,0x00000000,0x00020011,0x00000001,0x0006000b,
0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000,0x0003000e,0x00000000,0x00000001,
0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x000000f8,0x0000017d,0x00030010,
0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000,0x0000012d,0x000001b2,0x00030010,
0x00000004,0x00000007,0x00030003,0x00000002,0x000001c2,0x00040005,0x00000004,0x6e69616d,
0x00000000,0x00070005,0x0000000d,0x69646e75,0x726f7473,0x66762874,0x31663b32,0x0000003b,
0x00030005,0x0000000b,0x00000074,0x00040005,0x0000000c,0x6c616373,0x00000065,0x00070005,
@@ -13,271 +14,304 @@ const uint32_t cardboard_frag_code[] = {
0x00050006,0x0000001e,0x00000003,0x32746f72,0x00000000,0x00050006,0x0000001e,0x00000004,
0x736e656c,0x00000000,0x00050006,0x0000001e,0x00000005,0x77656976,0x00000000,0x00050006,
0x0000001e,0x00000006,0x72747865,0x00000061,0x00030005,0x00000020,0x00006370,0x00040005,
0x0000003f,0x486e6174,0x00000000,0x00030005,0x00000042,0x00000073,0x00040005,0x00000068,
0x72756f53,0x00006563,0x00030005,0x00000077,0x00000052,0x00030005,0x00000094,0x00000064,
0x00030005,0x0000009f,0x0000006f,0x00030005,0x000000b3,0x00000074,0x00030005,0x000000bd,
0x00746968,0x00040005,0x000000c3,0x666c6168,0x00000057,0x00040005,0x000000c6,0x666c6168,
0x00000048,0x00030005,0x000000cb,0x00000073,0x00040005,0x000000ed,0x6f6e6f6d,0x00000000,
0x00030005,0x000000f2,0x00657965,0x00030005,0x000000f8,0x00007675,0x00040005,0x000000ff,
0x61636f6c,0x0000006c,0x00030005,0x00000110,0x00007863,0x00030005,0x00000124,0x00000070,
0x00040005,0x00000129,0x486e6174,0x00000000,0x00030005,0x0000012c,0x00000074,0x00030005,
0x0000013d,0x00000063,0x00040005,0x00000141,0x61726170,0x0000006d,0x00040005,0x00000143,
0x61726170,0x0000006d,0x00040005,0x00000145,0x61726170,0x0000006d,0x00040005,0x00000146,
0x61726170,0x0000006d,0x00040005,0x0000014b,0x61726170,0x0000006d,0x00040005,0x0000014d,
0x61726170,0x0000006d,0x00040005,0x0000014f,0x61726170,0x0000006d,0x00040005,0x00000150,
0x61726170,0x0000006d,0x00040005,0x00000158,0x61726170,0x0000006d,0x00040005,0x0000015a,
0x61726170,0x0000006d,0x00040005,0x0000015c,0x61726170,0x0000006d,0x00040005,0x0000015d,
0x61726170,0x0000006d,0x00040005,0x00000163,0x61726170,0x0000006d,0x00040005,0x00000165,
0x61726170,0x0000006d,0x00040005,0x00000167,0x61726170,0x0000006d,0x00040005,0x00000168,
0x61726170,0x0000006d,0x00030005,0x0000016b,0x00000065,0x00040005,0x00000171,0x65646166,
0x00000000,0x00050005,0x0000017d,0x4374756f,0x726f6c6f,0x00000000,0x00030047,0x0000001e,
0x00000002,0x00050048,0x0000001e,0x00000000,0x00000023,0x00000000,0x00050048,0x0000001e,
0x00000001,0x00000023,0x00000010,0x00050048,0x0000001e,0x00000002,0x00000023,0x00000020,
0x00050048,0x0000001e,0x00000003,0x00000023,0x00000030,0x00050048,0x0000001e,0x00000004,
0x00000023,0x00000040,0x00050048,0x0000001e,0x00000005,0x00000023,0x00000050,0x00050048,
0x0000001e,0x00000006,0x00000023,0x00000060,0x00040047,0x00000068,0x00000021,0x00000000,
0x00040047,0x00000068,0x00000022,0x00000000,0x00040047,0x000000f8,0x0000001e,0x00000000,
0x00040047,0x0000017d,0x0000001e,0x00000000,0x00020013,0x00000002,0x00030021,0x00000003,
0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000002,
0x00040020,0x00000008,0x00000007,0x00000007,0x00040020,0x00000009,0x00000007,0x00000006,
0x00050021,0x0000000a,0x00000007,0x00000008,0x00000009,0x00040017,0x0000000f,0x00000006,
0x00000003,0x00050021,0x00000010,0x0000000f,0x00000008,0x00000009,0x0004002b,0x00000006,
0x0000001c,0x3f800000,0x00040017,0x0000001d,0x00000006,0x00000004,0x0009001e,0x0000001e,
0x0000001d,0x0000001d,0x0000001d,0x0000001d,0x0000001d,0x0000001d,0x0000001d,0x00040020,
0x0000001f,0x00000009,0x0000001e,0x0004003b,0x0000001f,0x00000020,0x00000009,0x00040015,
0x00000021,0x00000020,0x00000001,0x0004002b,0x00000021,0x00000022,0x00000004,0x00040015,
0x00000023,0x00000020,0x00000000,0x0004002b,0x00000023,0x00000024,0x00000000,0x00040020,
0x00000025,0x00000009,0x00000006,0x0004002b,0x00000023,0x0000002b,0x00000001,0x0004002b,
0x00000021,0x00000036,0x00000005,0x0004002b,0x00000023,0x00000037,0x00000002,0x0004002b,
0x00000006,0x0000003a,0x3f000000,0x00020014,0x0000003b,0x0004002b,0x00000023,0x00000046,
0x00000003,0x0004002b,0x00000006,0x00000054,0x00000000,0x0005002c,0x00000007,0x00000055,
0x00000054,0x00000054,0x00040017,0x00000056,0x0000003b,0x00000002,0x0005002c,0x00000007,
0x0000005d,0x0000001c,0x0000001c,0x0006002c,0x0000000f,0x00000063,0x00000054,0x00000054,
0x00000054,0x00090019,0x00000065,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000,
0x00000001,0x00000000,0x0003001b,0x00000066,0x00000065,0x00040020,0x00000067,0x00000000,
0x00000066,0x0004003b,0x00000067,0x00000068,0x00000000,0x00040018,0x00000075,0x0000000f,
0x00000003,0x00040020,0x00000076,0x00000007,0x00000075,0x0004002b,0x00000021,0x00000078,
0x00000001,0x00040020,0x00000079,0x00000009,0x0000001d,0x0004002b,0x00000021,0x0000007d,
0x00000002,0x0004002b,0x00000021,0x00000081,0x00000003,0x00040020,0x00000093,0x00000007,
0x0000000f,0x0004002b,0x00000006,0x0000009b,0xbf800000,0x0004002b,0x00000006,0x000000ab,
0xb8d1b717,0x0004002b,0x00000006,0x000000af,0x3c75c28f,0x0004002b,0x00000006,0x000000b0,
0x3ca3d70a,0x0006002c,0x0000000f,0x000000b1,0x000000af,0x000000af,0x000000b0,0x00040020,
0x000000ec,0x00000007,0x0000003b,0x0004002b,0x00000021,0x000000ee,0x00000006,0x00040020,
0x000000f7,0x00000001,0x00000007,0x0004003b,0x000000f7,0x000000f8,0x00000001,0x00040020,
0x000000f9,0x00000001,0x00000006,0x0004002b,0x00000006,0x00000108,0x40000000,0x0004002b,
0x00000006,0x00000172,0x3cf5c28f,0x00040020,0x0000017c,0x00000003,0x0000001d,0x0004003b,
0x0000017c,0x0000017d,0x00000003,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,
0x000200f8,0x00000005,0x0004003b,0x000000ec,0x000000ed,0x00000007,0x0004003b,0x00000009,
0x000000f2,0x00000007,0x0004003b,0x00000008,0x000000ff,0x00000007,0x0004003b,0x00000008,
0x00000101,0x00000007,0x0004003b,0x00000009,0x00000110,0x00000007,0x0004003b,0x00000009,
0x00000112,0x00000007,0x0004003b,0x00000009,0x00000118,0x00000007,0x0004003b,0x00000008,
0x00000124,0x00000007,0x0004003b,0x00000009,0x00000129,0x00000007,0x0004003b,0x00000008,
0x0000012c,0x00000007,0x0004003b,0x00000093,0x0000013d,0x00000007,0x0004003b,0x00000008,
0x00000141,0x00000007,0x0004003b,0x00000009,0x00000143,0x00000007,0x0004003b,0x00000008,
0x00000145,0x00000007,0x0004003b,0x00000009,0x00000146,0x00000007,0x0004003b,0x00000008,
0x0000014b,0x00000007,0x0004003b,0x00000009,0x0000014d,0x00000007,0x0004003b,0x00000008,
0x0000014f,0x00000007,0x0004003b,0x00000009,0x00000150,0x00000007,0x0004003b,0x00000008,
0x00000158,0x00000007,0x0004003b,0x00000009,0x0000015a,0x00000007,0x0004003b,0x00000008,
0x0000015c,0x00000007,0x0004003b,0x00000009,0x0000015d,0x00000007,0x0004003b,0x00000008,
0x00000163,0x00000007,0x0004003b,0x00000009,0x00000165,0x00000007,0x0004003b,0x00000008,
0x00000167,0x00000007,0x0004003b,0x00000009,0x00000168,0x00000007,0x0004003b,0x00000008,
0x0000016b,0x00000007,0x0004003b,0x00000009,0x00000171,0x00000007,0x00060041,0x00000025,
0x000000ef,0x00000020,0x000000ee,0x00000024,0x0004003d,0x00000006,0x000000f0,0x000000ef,
0x000500ba,0x0000003b,0x000000f1,0x000000f0,0x0000003a,0x0003003e,0x000000ed,0x000000f1,
0x0004003d,0x0000003b,0x000000f3,0x000000ed,0x000400a8,0x0000003b,0x000000f4,0x000000f3,
0x000300f7,0x000000f6,0x00000000,0x000400fa,0x000000f4,0x000000f5,0x000000f6,0x000200f8,
0x000000f5,0x00050041,0x000000f9,0x000000fa,0x000000f8,0x00000024,0x0004003d,0x00000006,
0x000000fb,0x000000fa,0x000500b8,0x0000003b,0x000000fc,0x000000fb,0x0000003a,0x000200f9,
0x000000f6,0x000200f8,0x000000f6,0x000700f5,0x0000003b,0x000000fd,0x000000f3,0x00000005,
0x000000fc,0x000000f5,0x000600a9,0x00000006,0x000000fe,0x000000fd,0x00000054,0x0000001c,
0x0003003e,0x000000f2,0x000000fe,0x0004003d,0x0000003b,0x00000100,0x000000ed,0x000300f7,
0x00000103,0x00000000,0x000400fa,0x00000100,0x00000102,0x00000105,0x000200f8,0x00000102,
0x0004003d,0x00000007,0x00000104,0x000000f8,0x0003003e,0x00000101,0x00000104,0x000200f9,
0x00000103,0x000200f8,0x00000105,0x00050041,0x000000f9,0x00000106,0x000000f8,0x00000024,
0x0004003d,0x00000006,0x00000107,0x00000106,0x00050085,0x00000006,0x00000109,0x00000107,
0x00000108,0x0004003d,0x00000006,0x0000010a,0x000000f2,0x00050083,0x00000006,0x0000010b,
0x00000109,0x0000010a,0x00050041,0x000000f9,0x0000010c,0x000000f8,0x0000002b,0x0004003d,
0x00000006,0x0000010d,0x0000010c,0x00050050,0x00000007,0x0000010e,0x0000010b,0x0000010d,
0x0003003e,0x00000101,0x0000010e,0x000200f9,0x00000103,0x000200f8,0x00000103,0x0004003d,
0x00000007,0x0000010f,0x00000101,0x0003003e,0x000000ff,0x0000010f,0x0004003d,0x0000003b,
0x00000111,0x000000ed,0x000300f7,0x00000114,0x00000000,0x000400fa,0x00000111,0x00000113,
0x00000115,0x000200f8,0x00000113,0x0003003e,0x00000112,0x0000003a,0x000200f9,0x00000114,
0x000200f8,0x00000115,0x0004003d,0x00000006,0x00000116,0x000000f2,0x000500b8,0x0000003b,
0x00000117,0x00000116,0x0000003a,0x000300f7,0x0000011a,0x00000000,0x000400fa,0x00000117,
0x00000119,0x0000011d,0x000200f8,0x00000119,0x00060041,0x00000025,0x0000011b,0x00000020,
0x00000036,0x00000024,0x0004003d,0x00000006,0x0000011c,0x0000011b,0x0003003e,0x00000118,
0x0000011c,0x000200f9,0x0000011a,0x000200f8,0x0000011d,0x00060041,0x00000025,0x0000011e,
0x00000020,0x00000036,0x00000024,0x0004003d,0x00000006,0x0000011f,0x0000011e,0x0004007f,
0x00000006,0x00000120,0x0000011f,0x0003003e,0x00000118,0x00000120,0x000200f9,0x0000011a,
0x000200f8,0x0000011a,0x0004003d,0x00000006,0x00000121,0x00000118,0x00050081,0x00000006,
0x00000122,0x0000003a,0x00000121,0x0003003e,0x00000112,0x00000122,0x000200f9,0x00000114,
0x000200f8,0x00000114,0x0004003d,0x00000006,0x00000123,0x00000112,0x0003003e,0x00000110,
0x00000123,0x0004003d,0x00000007,0x00000125,0x000000ff,0x0004003d,0x00000006,0x00000126,
0x00000110,0x00050050,0x00000007,0x00000127,0x00000126,0x0000003a,0x00050083,0x00000007,
0x00000128,0x00000125,0x00000127,0x0003003e,0x00000124,0x00000128,0x00060041,0x00000025,
0x0000012a,0x00000020,0x00000022,0x00000037,0x0004003d,0x00000006,0x0000012b,0x0000012a,
0x0003003e,0x00000129,0x0000012b,0x00050041,0x00000009,0x0000012d,0x00000124,0x00000024,
0x0004003d,0x00000006,0x0000012e,0x0000012d,0x00060041,0x00000025,0x0000012f,0x00000020,
0x00000036,0x00000046,0x0004003d,0x00000006,0x00000130,0x0000012f,0x00050085,0x00000006,
0x00000131,0x0000012e,0x00000130,0x00050041,0x00000009,0x00000132,0x00000124,0x0000002b,
0x0004003d,0x00000006,0x00000133,0x00000132,0x00050050,0x00000007,0x00000134,0x00000131,
0x00000133,0x0005008e,0x00000007,0x00000135,0x00000134,0x00000108,0x0004003d,0x00000006,
0x00000136,0x00000129,0x0005008e,0x00000007,0x00000137,0x00000135,0x00000136,0x0003003e,
0x0000012c,0x00000137,0x00060041,0x00000025,0x00000138,0x00000020,0x00000022,0x00000046,
0x0004003d,0x00000006,0x00000139,0x00000138,0x000500ba,0x0000003b,0x0000013a,0x00000139,
0x00000054,0x000300f7,0x0000013c,0x00000000,0x000400fa,0x0000013a,0x0000013b,0x00000162,
0x000200f8,0x0000013b,0x00060041,0x00000025,0x0000013e,0x00000020,0x00000022,0x00000046,
0x0004003d,0x00000006,0x0000013f,0x0000013e,0x00050083,0x00000006,0x00000140,0x0000001c,
0x0000013f,0x0004003d,0x00000007,0x00000142,0x0000012c,0x0003003e,0x00000141,0x00000142,
0x0003003e,0x00000143,0x00000140,0x00060039,0x00000007,0x00000144,0x0000000d,0x00000141,
0x00000143,0x0003003e,0x00000145,0x00000144,0x0004003d,0x00000006,0x00000147,0x000000f2,
0x0003003e,0x00000146,0x00000147,0x00060039,0x0000000f,0x00000148,0x00000013,0x00000145,
0x00000146,0x00050051,0x00000006,0x00000149,0x00000148,0x00000000,0x00050041,0x00000009,
0x0000014a,0x0000013d,0x00000024,0x0003003e,0x0000014a,0x00000149,0x0004003d,0x00000007,
0x0000014c,0x0000012c,0x0003003e,0x0000014b,0x0000014c,0x0003003e,0x0000014d,0x0000001c,
0x00060039,0x00000007,0x0000014e,0x0000000d,0x0000014b,0x0000014d,0x0003003e,0x0000014f,
0x0000014e,0x0004003d,0x00000006,0x00000151,0x000000f2,0x0003003e,0x00000150,0x00000151,
0x00060039,0x0000000f,0x00000152,0x00000013,0x0000014f,0x00000150,0x00050051,0x00000006,
0x00000153,0x00000152,0x00000001,0x00050041,0x00000009,0x00000154,0x0000013d,0x0000002b,
0x0003003e,0x00000154,0x00000153,0x00060041,0x00000025,0x00000155,0x00000020,0x00000022,
0x00000046,0x0004003d,0x00000006,0x00000156,0x00000155,0x00050081,0x00000006,0x00000157,
0x0000001c,0x00000156,0x0004003d,0x00000007,0x00000159,0x0000012c,0x0003003e,0x00000158,
0x00000159,0x0003003e,0x0000015a,0x00000157,0x00060039,0x00000007,0x0000015b,0x0000000d,
0x00000158,0x0000015a,0x0003003e,0x0000015c,0x0000015b,0x0004003d,0x00000006,0x0000015e,
0x000000f2,0x0003003e,0x0000015d,0x0000015e,0x00060039,0x0000000f,0x0000015f,0x00000013,
0x0000015c,0x0000015d,0x00050051,0x00000006,0x00000160,0x0000015f,0x00000002,0x00050041,
0x00000009,0x00000161,0x0000013d,0x00000037,0x0003003e,0x00000161,0x00000160,0x000200f9,
0x0000013c,0x000200f8,0x00000162,0x0004003d,0x00000007,0x00000164,0x0000012c,0x0003003e,
0x00000163,0x00000164,0x0003003e,0x00000165,0x0000001c,0x00060039,0x00000007,0x00000166,
0x0000000d,0x00000163,0x00000165,0x0003003e,0x00000167,0x00000166,0x0004003d,0x00000006,
0x00000169,0x000000f2,0x0003003e,0x00000168,0x00000169,0x00060039,0x0000000f,0x0000016a,
0x00000013,0x00000167,0x00000168,0x0003003e,0x0000013d,0x0000016a,0x000200f9,0x0000013c,
0x000200f8,0x0000013c,0x0004003d,0x00000007,0x0000016c,0x000000ff,0x0004003d,0x00000007,
0x0000016d,0x000000ff,0x00050050,0x00000007,0x0000016e,0x0000001c,0x0000001c,0x00050083,
0x00000007,0x0000016f,0x0000016e,0x0000016d,0x0007000c,0x00000007,0x00000170,0x00000001,
0x00000025,0x0000016c,0x0000016f,0x0003003e,0x0000016b,0x00000170,0x00050041,0x00000009,
0x00000173,0x0000016b,0x00000024,0x0004003d,0x00000006,0x00000174,0x00000173,0x00060041,
0x00000025,0x00000175,0x00000020,0x00000036,0x00000046,0x0004003d,0x00000006,0x00000176,
0x00000175,0x00050085,0x00000006,0x00000177,0x00000174,0x00000176,0x00050041,0x00000009,
0x00000178,0x0000016b,0x0000002b,0x0004003d,0x00000006,0x00000179,0x00000178,0x0007000c,
0x00000006,0x0000017a,0x00000001,0x00000025,0x00000177,0x00000179,0x0008000c,0x00000006,
0x0000017b,0x00000001,0x00000031,0x00000054,0x00000172,0x0000017a,0x0003003e,0x00000171,
0x0000017b,0x0004003d,0x0000000f,0x0000017e,0x0000013d,0x0004003d,0x00000006,0x0000017f,
0x00000171,0x0005008e,0x0000000f,0x00000180,0x0000017e,0x0000017f,0x00050051,0x00000006,
0x00000181,0x00000180,0x00000000,0x00050051,0x00000006,0x00000182,0x00000180,0x00000001,
0x00050051,0x00000006,0x00000183,0x00000180,0x00000002,0x00070050,0x0000001d,0x00000184,
0x00000181,0x00000182,0x00000183,0x0000001c,0x0003003e,0x0000017d,0x00000184,0x000100fd,
0x00010038,0x00050036,0x00000007,0x0000000d,0x00000000,0x0000000a,0x00030037,0x00000008,
0x0000000b,0x00030037,0x00000009,0x0000000c,0x000200f8,0x0000000e,0x0004003b,0x00000009,
0x00000015,0x00000007,0x0004003d,0x00000007,0x00000016,0x0000000b,0x0004003d,0x00000007,
0x00000017,0x0000000b,0x00050094,0x00000006,0x00000018,0x00000016,0x00000017,0x0003003e,
0x00000015,0x00000018,0x0004003d,0x00000007,0x00000019,0x0000000b,0x0004003d,0x00000006,
0x0000001a,0x0000000c,0x0005008e,0x00000007,0x0000001b,0x00000019,0x0000001a,0x00060041,
0x00000025,0x00000026,0x00000020,0x00000022,0x00000024,0x0004003d,0x00000006,0x00000027,
0x00000026,0x0004003d,0x00000006,0x00000028,0x00000015,0x00050085,0x00000006,0x00000029,
0x00000027,0x00000028,0x00050081,0x00000006,0x0000002a,0x0000001c,0x00000029,0x00060041,
0x00000025,0x0000002c,0x00000020,0x00000022,0x0000002b,0x0004003d,0x00000006,0x0000002d,
0x0000002c,0x0004003d,0x00000006,0x0000002e,0x00000015,0x00050085,0x00000006,0x0000002f,
0x0000002d,0x0000002e,0x0004003d,0x00000006,0x00000030,0x00000015,0x00050085,0x00000006,
0x00000031,0x0000002f,0x00000030,0x00050081,0x00000006,0x00000032,0x0000002a,0x00000031,
0x0005008e,0x00000007,0x00000033,0x0000001b,0x00000032,0x000200fe,0x00000033,0x00010038,
0x00050036,0x0000000f,0x00000013,0x00000000,0x00000010,0x00030037,0x00000008,0x00000011,
0x00030037,0x00000009,0x00000012,0x000200f8,0x00000014,0x0004003b,0x00000009,0x0000003f,
0x00000007,0x0004003b,0x00000008,0x00000042,0x00000007,0x0004003b,0x00000076,0x00000077,
0x00000007,0x0004003b,0x00000093,0x00000094,0x00000007,0x0004003b,0x00000093,0x0000009f,
0x00000007,0x0004003b,0x00000009,0x000000b3,0x00000007,0x0004003b,0x00000093,0x000000bd,
0x00000007,0x0004003b,0x00000009,0x000000c3,0x00000007,0x0004003b,0x00000009,0x000000c6,
0x00000007,0x0004003b,0x00000008,0x000000cb,0x00000007,0x00060041,0x00000025,0x00000038,
0x00000020,0x00000036,0x00000037,0x0004003d,0x00000006,0x00000039,0x00000038,0x000500ba,
0x0000003b,0x0000003c,0x00000039,0x0000003a,0x000300f7,0x0000003e,0x00000000,0x000400fa,
0x0000003c,0x0000003d,0x0000003e,0x000200f8,0x0000003d,0x00060041,0x00000025,0x00000040,
0x00000020,0x00000022,0x00000037,0x0004003d,0x00000006,0x00000041,0x00000040,0x0003003e,
0x0000003f,0x00000041,0x00050041,0x00000009,0x00000043,0x00000011,0x00000024,0x0004003d,
0x00000006,0x00000044,0x00000043,0x0004003d,0x00000006,0x00000045,0x0000003f,0x00060041,
0x00000025,0x00000047,0x00000020,0x00000036,0x00000046,0x0004003d,0x00000006,0x00000048,
0x00000047,0x00050085,0x00000006,0x00000049,0x00000045,0x00000048,0x00050088,0x00000006,
0x0000004a,0x00000044,0x00000049,0x00050041,0x00000009,0x0000004b,0x00000011,0x0000002b,
0x0004003d,0x00000006,0x0000004c,0x0000004b,0x0004003d,0x00000006,0x0000004d,0x0000003f,
0x00050088,0x00000006,0x0000004e,0x0000004c,0x0000004d,0x00050050,0x00000007,0x0000004f,
0x0000004a,0x0000004e,0x0005008e,0x00000007,0x00000050,0x0000004f,0x0000003a,0x00050050,
0x00000007,0x00000051,0x0000003a,0x0000003a,0x00050081,0x00000007,0x00000052,0x00000050,
0x00000051,0x0003003e,0x00000042,0x00000052,0x0004003d,0x00000007,0x00000053,0x00000042,
0x000500b8,0x00000056,0x00000057,0x00000053,0x00000055,0x0004009a,0x0000003b,0x00000058,
0x00000057,0x000400a8,0x0000003b,0x00000059,0x00000058,0x000300f7,0x0000005b,0x00000000,
0x000400fa,0x00000059,0x0000005a,0x0000005b,0x000200f8,0x0000005a,0x0004003d,0x00000007,
0x0000005c,0x00000042,0x000500ba,0x00000056,0x0000005e,0x0000005c,0x0000005d,0x0004009a,
0x0000003b,0x0000005f,0x0000005e,0x000200f9,0x0000005b,0x000200f8,0x0000005b,0x000700f5,
0x0000003b,0x00000060,0x00000058,0x0000003d,0x0000005f,0x0000005a,0x000300f7,0x00000062,
0x00000000,0x000400fa,0x00000060,0x00000061,0x00000062,0x000200f8,0x00000061,0x000200fe,
0x00000063,0x000200f8,0x00000062,0x0004003d,0x00000066,0x00000069,0x00000068,0x00050041,
0x00000009,0x0000006a,0x00000042,0x00000024,0x0004003d,0x00000006,0x0000006b,0x0000006a,
0x0004003d,0x00000006,0x0000006c,0x00000012,0x00050081,0x00000006,0x0000006d,0x0000006b,
0x0000006c,0x00050085,0x00000006,0x0000006e,0x0000006d,0x0000003a,0x00050041,0x00000009,
0x0000006f,0x00000042,0x0000002b,0x0004003d,0x00000006,0x00000070,0x0000006f,0x00050050,
0x00000007,0x00000071,0x0000006e,0x00000070,0x00050057,0x0000001d,0x00000072,0x00000069,
0x00000071,0x0008004f,0x0000000f,0x00000073,0x00000072,0x00000072,0x00000000,0x00000001,
0x00000002,0x000200fe,0x00000073,0x000200f8,0x0000003e,0x00050041,0x00000079,0x0000007a,
0x00000020,0x00000078,0x0004003d,0x0000001d,0x0000007b,0x0000007a,0x0008004f,0x0000000f,
0x0000007c,0x0000007b,0x0000007b,0x00000000,0x00000001,0x00000002,0x00050041,0x00000079,
0x0000007e,0x00000020,0x0000007d,0x0004003d,0x0000001d,0x0000007f,0x0000007e,0x0008004f,
0x0000000f,0x00000080,0x0000007f,0x0000007f,0x00000000,0x00000001,0x00000002,0x00050041,
0x00000079,0x00000082,0x00000020,0x00000081,0x0004003d,0x0000001d,0x00000083,0x00000082,
0x0008004f,0x0000000f,0x00000084,0x00000083,0x00000083,0x00000000,0x00000001,0x00000002,
0x00050051,0x00000006,0x00000085,0x0000007c,0x00000000,0x00050051,0x00000006,0x00000086,
0x0000007c,0x00000001,0x00050051,0x00000006,0x00000087,0x0000007c,0x00000002,0x00050051,
0x00000006,0x00000088,0x00000080,0x00000000,0x00050051,0x00000006,0x00000089,0x00000080,
0x00000001,0x00050051,0x00000006,0x0000008a,0x00000080,0x00000002,0x00050051,0x00000006,
0x0000008b,0x00000084,0x00000000,0x00050051,0x00000006,0x0000008c,0x00000084,0x00000001,
0x00050051,0x00000006,0x0000008d,0x00000084,0x00000002,0x00060050,0x0000000f,0x0000008e,
0x00000085,0x00000086,0x00000087,0x00060050,0x0000000f,0x0000008f,0x00000088,0x00000089,
0x0000008a,0x00060050,0x0000000f,0x00000090,0x0000008b,0x0000008c,0x0000008d,0x00060050,
0x00000075,0x00000091,0x0000008e,0x0000008f,0x00000090,0x00040054,0x00000075,0x00000092,
0x00000091,0x0003003e,0x00000077,0x00000092,0x0004003d,0x00000075,0x00000095,0x00000077,
0x00050041,0x00000009,0x00000096,0x00000011,0x00000024,0x0004003d,0x00000006,0x00000097,
0x00000096,0x00050041,0x00000009,0x00000098,0x00000011,0x0000002b,0x0004003d,0x00000006,
0x00000099,0x00000098,0x0004007f,0x00000006,0x0000009a,0x00000099,0x00060050,0x0000000f,
0x0000009c,0x00000097,0x0000009a,0x0000009b,0x0006000c,0x0000000f,0x0000009d,0x00000001,
0x00000045,0x0000009c,0x00050091,0x0000000f,0x0000009e,0x00000095,0x0000009d,0x0003003e,
0x00000094,0x0000009e,0x0004003d,0x00000075,0x000000a0,0x00000077,0x0004003d,0x00000006,
0x000000a1,0x00000012,0x000500b8,0x0000003b,0x000000a2,0x000000a1,0x0000003a,0x000600a9,
0x00000006,0x000000a3,0x000000a2,0x0000009b,0x0000001c,0x00060041,0x00000025,0x000000a4,
0x00000020,0x00000078,0x00000046,0x0004003d,0x00000006,0x000000a5,0x000000a4,0x00050085,
0x00000006,0x000000a6,0x000000a3,0x000000a5,0x00060050,0x0000000f,0x000000a7,0x000000a6,
0x00000054,0x00000054,0x00050091,0x0000000f,0x000000a8,0x000000a0,0x000000a7,0x0003003e,
0x0000009f,0x000000a8,0x00050041,0x00000009,0x000000a9,0x00000094,0x00000037,0x0004003d,
0x00000006,0x000000aa,0x000000a9,0x000500ba,0x0000003b,0x000000ac,0x000000aa,0x000000ab,
0x000300f7,0x000000ae,0x00000000,0x000400fa,0x000000ac,0x000000ad,0x000000ae,0x000200f8,
0x000000ad,0x000200fe,0x000000b1,0x000200f8,0x000000ae,0x00060041,0x00000025,0x000000b4,
0x00000020,0x0000007d,0x00000046,0x0004003d,0x00000006,0x000000b5,0x000000b4,0x0004007f,
0x00000006,0x000000b6,0x000000b5,0x00050041,0x00000009,0x000000b7,0x0000009f,0x00000037,
0x0004003d,0x00000006,0x000000b8,0x000000b7,0x00050083,0x00000006,0x000000b9,0x000000b6,
0x000000b8,0x00050041,0x00000009,0x000000ba,0x00000094,0x00000037,0x0004003d,0x00000006,
0x000000bb,0x000000ba,0x00050088,0x00000006,0x000000bc,0x000000b9,0x000000bb,0x0003003e,
0x000000b3,0x000000bc,0x0004003d,0x0000000f,0x000000be,0x0000009f,0x0004003d,0x0000000f,
0x000000bf,0x00000094,0x0004003d,0x00000006,0x000000c0,0x000000b3,0x0005008e,0x0000000f,
0x000000c1,0x000000bf,0x000000c0,0x00050081,0x0000000f,0x000000c2,0x000000be,0x000000c1,
0x0003003e,0x000000bd,0x000000c2,0x00060041,0x00000025,0x000000c4,0x00000020,0x00000081,
0x00000046,0x0004003d,0x00000006,0x000000c5,0x000000c4,0x0003003e,0x000000c3,0x000000c5,
0x0004003d,0x00000006,0x000000c7,0x000000c3,0x00060041,0x00000025,0x000000c8,0x00000020,
0x00000036,0x0000002b,0x0004003d,0x00000006,0x000000c9,0x000000c8,0x00050088,0x00000006,
0x000000ca,0x000000c7,0x000000c9,0x0003003e,0x000000c6,0x000000ca,0x00050041,0x00000009,
0x000000cc,0x000000bd,0x00000024,0x0004003d,0x00000006,0x000000cd,0x000000cc,0x0004003d,
0x00000006,0x000000ce,0x000000c3,0x00050088,0x00000006,0x000000cf,0x000000cd,0x000000ce,
0x00050041,0x00000009,0x000000d0,0x000000bd,0x0000002b,0x0004003d,0x00000006,0x000000d1,
0x000000d0,0x0004007f,0x00000006,0x000000d2,0x000000d1,0x0004003d,0x00000006,0x000000d3,
0x000000c6,0x00050088,0x00000006,0x000000d4,0x000000d2,0x000000d3,0x00050050,0x00000007,
0x000000d5,0x000000cf,0x000000d4,0x0005008e,0x00000007,0x000000d6,0x000000d5,0x0000003a,
0x00050050,0x00000007,0x000000d7,0x0000003a,0x0000003a,0x00050081,0x00000007,0x000000d8,
0x000000d6,0x000000d7,0x0003003e,0x000000cb,0x000000d8,0x0004003d,0x00000007,0x000000d9,
0x000000cb,0x000500b8,0x00000056,0x000000da,0x000000d9,0x00000055,0x0004009a,0x0000003b,
0x000000db,0x000000da,0x000400a8,0x0000003b,0x000000dc,0x000000db,0x000300f7,0x000000de,
0x00000000,0x000400fa,0x000000dc,0x000000dd,0x000000de,0x000200f8,0x000000dd,0x0004003d,
0x00000007,0x000000df,0x000000cb,0x000500ba,0x00000056,0x000000e0,0x000000df,0x0000005d,
0x0004009a,0x0000003b,0x000000e1,0x000000e0,0x000200f9,0x000000de,0x000200f8,0x000000de,
0x000700f5,0x0000003b,0x000000e2,0x000000db,0x000000ae,0x000000e1,0x000000dd,0x000300f7,
0x000000e4,0x00000000,0x000400fa,0x000000e2,0x000000e3,0x000000e4,0x000200f8,0x000000e3,
0x000200fe,0x000000b1,0x000200f8,0x000000e4,0x0004003d,0x00000066,0x000000e6,0x00000068,
0x0004003d,0x00000007,0x000000e7,0x000000cb,0x00050057,0x0000001d,0x000000e8,0x000000e6,
0x000000e7,0x0008004f,0x0000000f,0x000000e9,0x000000e8,0x000000e8,0x00000000,0x00000001,
0x00000002,0x000200fe,0x000000e9,0x00010038
0x0000003f,0x486e6174,0x00000000,0x00030005,0x00000042,0x00000073,0x00030005,0x00000059,
0x00746966,0x00040005,0x00000082,0x72756f53,0x00006563,0x00030005,0x000000a3,0x00000052,
0x00030005,0x000000c0,0x00000064,0x00030005,0x000000cb,0x0000006f,0x00030005,0x000000e4,
0x00000074,0x00030005,0x000000f3,0x00746968,0x00040005,0x000000f9,0x666c6168,0x00000057,
0x00040005,0x000000fc,0x666c6168,0x00000048,0x00030005,0x00000101,0x00000073,0x00040005,
0x00000123,0x6f6e6f6d,0x00000000,0x00030005,0x00000127,0x00657965,0x00030005,0x0000012d,
0x00007675,0x00040005,0x00000134,0x61636f6c,0x0000006c,0x00030005,0x00000145,0x00007863,
0x00030005,0x00000159,0x00000070,0x00040005,0x0000015e,0x486e6174,0x00000000,0x00030005,
0x00000161,0x00000074,0x00030005,0x00000172,0x00000063,0x00040005,0x00000176,0x61726170,
0x0000006d,0x00040005,0x00000178,0x61726170,0x0000006d,0x00040005,0x0000017a,0x61726170,
0x0000006d,0x00040005,0x0000017b,0x61726170,0x0000006d,0x00040005,0x00000180,0x61726170,
0x0000006d,0x00040005,0x00000182,0x61726170,0x0000006d,0x00040005,0x00000184,0x61726170,
0x0000006d,0x00040005,0x00000185,0x61726170,0x0000006d,0x00040005,0x0000018d,0x61726170,
0x0000006d,0x00040005,0x0000018f,0x61726170,0x0000006d,0x00040005,0x00000191,0x61726170,
0x0000006d,0x00040005,0x00000192,0x61726170,0x0000006d,0x00040005,0x00000198,0x61726170,
0x0000006d,0x00040005,0x0000019a,0x61726170,0x0000006d,0x00040005,0x0000019c,0x61726170,
0x0000006d,0x00040005,0x0000019d,0x61726170,0x0000006d,0x00030005,0x000001a0,0x00000065,
0x00040005,0x000001a6,0x65646166,0x00000000,0x00050005,0x000001b2,0x4374756f,0x726f6c6f,
0x00000000,0x00030047,0x0000001e,0x00000002,0x00050048,0x0000001e,0x00000000,0x00000023,
0x00000000,0x00050048,0x0000001e,0x00000001,0x00000023,0x00000010,0x00050048,0x0000001e,
0x00000002,0x00000023,0x00000020,0x00050048,0x0000001e,0x00000003,0x00000023,0x00000030,
0x00050048,0x0000001e,0x00000004,0x00000023,0x00000040,0x00050048,0x0000001e,0x00000005,
0x00000023,0x00000050,0x00050048,0x0000001e,0x00000006,0x00000023,0x00000060,0x00040047,
0x00000082,0x00000021,0x00000000,0x00040047,0x00000082,0x00000022,0x00000000,0x00040047,
0x0000012d,0x0000001e,0x00000000,0x00040047,0x000001b2,0x0000001e,0x00000000,0x00020013,
0x00000002,0x00030021,0x00000003,0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,
0x00000007,0x00000006,0x00000002,0x00040020,0x00000008,0x00000007,0x00000007,0x00040020,
0x00000009,0x00000007,0x00000006,0x00050021,0x0000000a,0x00000007,0x00000008,0x00000009,
0x00040017,0x0000000f,0x00000006,0x00000003,0x00050021,0x00000010,0x0000000f,0x00000008,
0x00000009,0x0004002b,0x00000006,0x0000001c,0x3f800000,0x00040017,0x0000001d,0x00000006,
0x00000004,0x0009001e,0x0000001e,0x0000001d,0x0000001d,0x0000001d,0x0000001d,0x0000001d,
0x0000001d,0x0000001d,0x00040020,0x0000001f,0x00000009,0x0000001e,0x0004003b,0x0000001f,
0x00000020,0x00000009,0x00040015,0x00000021,0x00000020,0x00000001,0x0004002b,0x00000021,
0x00000022,0x00000004,0x00040015,0x00000023,0x00000020,0x00000000,0x0004002b,0x00000023,
0x00000024,0x00000000,0x00040020,0x00000025,0x00000009,0x00000006,0x0004002b,0x00000023,
0x0000002b,0x00000001,0x0004002b,0x00000021,0x00000036,0x00000005,0x0004002b,0x00000023,
0x00000037,0x00000002,0x0004002b,0x00000006,0x0000003a,0x3f000000,0x00020014,0x0000003b,
0x0004002b,0x00000023,0x00000046,0x00000003,0x0004002b,0x00000006,0x00000055,0x3fc00000,
0x0005002c,0x00000007,0x00000065,0x0000001c,0x0000001c,0x0004002b,0x00000006,0x0000006f,
0x00000000,0x0005002c,0x00000007,0x00000070,0x0000006f,0x0000006f,0x00040017,0x00000071,
0x0000003b,0x00000002,0x0006002c,0x0000000f,0x0000007d,0x0000006f,0x0000006f,0x0000006f,
0x00090019,0x0000007f,0x00000006,0x00000001,0x00000000,0x00000000,0x00000000,0x00000001,
0x00000000,0x0003001b,0x00000080,0x0000007f,0x00040020,0x00000081,0x00000000,0x00000080,
0x0004003b,0x00000081,0x00000082,0x00000000,0x00040018,0x000000a1,0x0000000f,0x00000003,
0x00040020,0x000000a2,0x00000007,0x000000a1,0x0004002b,0x00000021,0x000000a4,0x00000001,
0x00040020,0x000000a5,0x00000009,0x0000001d,0x0004002b,0x00000021,0x000000a9,0x00000002,
0x0004002b,0x00000021,0x000000ad,0x00000003,0x00040020,0x000000bf,0x00000007,0x0000000f,
0x0004002b,0x00000006,0x000000c7,0xbf800000,0x0004002b,0x00000021,0x000000cc,0x00000006,
0x0004002b,0x00000006,0x000000dc,0xb8d1b717,0x0004002b,0x00000006,0x000000e0,0x3c75c28f,
0x0004002b,0x00000006,0x000000e1,0x3ca3d70a,0x0006002c,0x0000000f,0x000000e2,0x000000e0,
0x000000e0,0x000000e1,0x00040020,0x00000122,0x00000007,0x0000003b,0x00040020,0x0000012c,
0x00000001,0x00000007,0x0004003b,0x0000012c,0x0000012d,0x00000001,0x00040020,0x0000012e,
0x00000001,0x00000006,0x0004002b,0x00000006,0x0000013d,0x40000000,0x0004002b,0x00000006,
0x000001a7,0x3cf5c28f,0x00040020,0x000001b1,0x00000003,0x0000001d,0x0004003b,0x000001b1,
0x000001b2,0x00000003,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003,0x000200f8,
0x00000005,0x0004003b,0x00000122,0x00000123,0x00000007,0x0004003b,0x00000009,0x00000127,
0x00000007,0x0004003b,0x00000008,0x00000134,0x00000007,0x0004003b,0x00000008,0x00000136,
0x00000007,0x0004003b,0x00000009,0x00000145,0x00000007,0x0004003b,0x00000009,0x00000147,
0x00000007,0x0004003b,0x00000009,0x0000014d,0x00000007,0x0004003b,0x00000008,0x00000159,
0x00000007,0x0004003b,0x00000009,0x0000015e,0x00000007,0x0004003b,0x00000008,0x00000161,
0x00000007,0x0004003b,0x000000bf,0x00000172,0x00000007,0x0004003b,0x00000008,0x00000176,
0x00000007,0x0004003b,0x00000009,0x00000178,0x00000007,0x0004003b,0x00000008,0x0000017a,
0x00000007,0x0004003b,0x00000009,0x0000017b,0x00000007,0x0004003b,0x00000008,0x00000180,
0x00000007,0x0004003b,0x00000009,0x00000182,0x00000007,0x0004003b,0x00000008,0x00000184,
0x00000007,0x0004003b,0x00000009,0x00000185,0x00000007,0x0004003b,0x00000008,0x0000018d,
0x00000007,0x0004003b,0x00000009,0x0000018f,0x00000007,0x0004003b,0x00000008,0x00000191,
0x00000007,0x0004003b,0x00000009,0x00000192,0x00000007,0x0004003b,0x00000008,0x00000198,
0x00000007,0x0004003b,0x00000009,0x0000019a,0x00000007,0x0004003b,0x00000008,0x0000019c,
0x00000007,0x0004003b,0x00000009,0x0000019d,0x00000007,0x0004003b,0x00000008,0x000001a0,
0x00000007,0x0004003b,0x00000009,0x000001a6,0x00000007,0x00060041,0x00000025,0x00000124,
0x00000020,0x000000cc,0x00000024,0x0004003d,0x00000006,0x00000125,0x00000124,0x000500ba,
0x0000003b,0x00000126,0x00000125,0x0000003a,0x0003003e,0x00000123,0x00000126,0x0004003d,
0x0000003b,0x00000128,0x00000123,0x000400a8,0x0000003b,0x00000129,0x00000128,0x000300f7,
0x0000012b,0x00000000,0x000400fa,0x00000129,0x0000012a,0x0000012b,0x000200f8,0x0000012a,
0x00050041,0x0000012e,0x0000012f,0x0000012d,0x00000024,0x0004003d,0x00000006,0x00000130,
0x0000012f,0x000500b8,0x0000003b,0x00000131,0x00000130,0x0000003a,0x000200f9,0x0000012b,
0x000200f8,0x0000012b,0x000700f5,0x0000003b,0x00000132,0x00000128,0x00000005,0x00000131,
0x0000012a,0x000600a9,0x00000006,0x00000133,0x00000132,0x0000006f,0x0000001c,0x0003003e,
0x00000127,0x00000133,0x0004003d,0x0000003b,0x00000135,0x00000123,0x000300f7,0x00000138,
0x00000000,0x000400fa,0x00000135,0x00000137,0x0000013a,0x000200f8,0x00000137,0x0004003d,
0x00000007,0x00000139,0x0000012d,0x0003003e,0x00000136,0x00000139,0x000200f9,0x00000138,
0x000200f8,0x0000013a,0x00050041,0x0000012e,0x0000013b,0x0000012d,0x00000024,0x0004003d,
0x00000006,0x0000013c,0x0000013b,0x00050085,0x00000006,0x0000013e,0x0000013c,0x0000013d,
0x0004003d,0x00000006,0x0000013f,0x00000127,0x00050083,0x00000006,0x00000140,0x0000013e,
0x0000013f,0x00050041,0x0000012e,0x00000141,0x0000012d,0x0000002b,0x0004003d,0x00000006,
0x00000142,0x00000141,0x00050050,0x00000007,0x00000143,0x00000140,0x00000142,0x0003003e,
0x00000136,0x00000143,0x000200f9,0x00000138,0x000200f8,0x00000138,0x0004003d,0x00000007,
0x00000144,0x00000136,0x0003003e,0x00000134,0x00000144,0x0004003d,0x0000003b,0x00000146,
0x00000123,0x000300f7,0x00000149,0x00000000,0x000400fa,0x00000146,0x00000148,0x0000014a,
0x000200f8,0x00000148,0x0003003e,0x00000147,0x0000003a,0x000200f9,0x00000149,0x000200f8,
0x0000014a,0x0004003d,0x00000006,0x0000014b,0x00000127,0x000500b8,0x0000003b,0x0000014c,
0x0000014b,0x0000003a,0x000300f7,0x0000014f,0x00000000,0x000400fa,0x0000014c,0x0000014e,
0x00000152,0x000200f8,0x0000014e,0x00060041,0x00000025,0x00000150,0x00000020,0x00000036,
0x00000024,0x0004003d,0x00000006,0x00000151,0x00000150,0x0003003e,0x0000014d,0x00000151,
0x000200f9,0x0000014f,0x000200f8,0x00000152,0x00060041,0x00000025,0x00000153,0x00000020,
0x00000036,0x00000024,0x0004003d,0x00000006,0x00000154,0x00000153,0x0004007f,0x00000006,
0x00000155,0x00000154,0x0003003e,0x0000014d,0x00000155,0x000200f9,0x0000014f,0x000200f8,
0x0000014f,0x0004003d,0x00000006,0x00000156,0x0000014d,0x00050081,0x00000006,0x00000157,
0x0000003a,0x00000156,0x0003003e,0x00000147,0x00000157,0x000200f9,0x00000149,0x000200f8,
0x00000149,0x0004003d,0x00000006,0x00000158,0x00000147,0x0003003e,0x00000145,0x00000158,
0x0004003d,0x00000007,0x0000015a,0x00000134,0x0004003d,0x00000006,0x0000015b,0x00000145,
0x00050050,0x00000007,0x0000015c,0x0000015b,0x0000003a,0x00050083,0x00000007,0x0000015d,
0x0000015a,0x0000015c,0x0003003e,0x00000159,0x0000015d,0x00060041,0x00000025,0x0000015f,
0x00000020,0x00000022,0x00000037,0x0004003d,0x00000006,0x00000160,0x0000015f,0x0003003e,
0x0000015e,0x00000160,0x00050041,0x00000009,0x00000162,0x00000159,0x00000024,0x0004003d,
0x00000006,0x00000163,0x00000162,0x00060041,0x00000025,0x00000164,0x00000020,0x00000036,
0x00000046,0x0004003d,0x00000006,0x00000165,0x00000164,0x00050085,0x00000006,0x00000166,
0x00000163,0x00000165,0x00050041,0x00000009,0x00000167,0x00000159,0x0000002b,0x0004003d,
0x00000006,0x00000168,0x00000167,0x00050050,0x00000007,0x00000169,0x00000166,0x00000168,
0x0005008e,0x00000007,0x0000016a,0x00000169,0x0000013d,0x0004003d,0x00000006,0x0000016b,
0x0000015e,0x0005008e,0x00000007,0x0000016c,0x0000016a,0x0000016b,0x0003003e,0x00000161,
0x0000016c,0x00060041,0x00000025,0x0000016d,0x00000020,0x00000022,0x00000046,0x0004003d,
0x00000006,0x0000016e,0x0000016d,0x000500ba,0x0000003b,0x0000016f,0x0000016e,0x0000006f,
0x000300f7,0x00000171,0x00000000,0x000400fa,0x0000016f,0x00000170,0x00000197,0x000200f8,
0x00000170,0x00060041,0x00000025,0x00000173,0x00000020,0x00000022,0x00000046,0x0004003d,
0x00000006,0x00000174,0x00000173,0x00050083,0x00000006,0x00000175,0x0000001c,0x00000174,
0x0004003d,0x00000007,0x00000177,0x00000161,0x0003003e,0x00000176,0x00000177,0x0003003e,
0x00000178,0x00000175,0x00060039,0x00000007,0x00000179,0x0000000d,0x00000176,0x00000178,
0x0003003e,0x0000017a,0x00000179,0x0004003d,0x00000006,0x0000017c,0x00000127,0x0003003e,
0x0000017b,0x0000017c,0x00060039,0x0000000f,0x0000017d,0x00000013,0x0000017a,0x0000017b,
0x00050051,0x00000006,0x0000017e,0x0000017d,0x00000000,0x00050041,0x00000009,0x0000017f,
0x00000172,0x00000024,0x0003003e,0x0000017f,0x0000017e,0x0004003d,0x00000007,0x00000181,
0x00000161,0x0003003e,0x00000180,0x00000181,0x0003003e,0x00000182,0x0000001c,0x00060039,
0x00000007,0x00000183,0x0000000d,0x00000180,0x00000182,0x0003003e,0x00000184,0x00000183,
0x0004003d,0x00000006,0x00000186,0x00000127,0x0003003e,0x00000185,0x00000186,0x00060039,
0x0000000f,0x00000187,0x00000013,0x00000184,0x00000185,0x00050051,0x00000006,0x00000188,
0x00000187,0x00000001,0x00050041,0x00000009,0x00000189,0x00000172,0x0000002b,0x0003003e,
0x00000189,0x00000188,0x00060041,0x00000025,0x0000018a,0x00000020,0x00000022,0x00000046,
0x0004003d,0x00000006,0x0000018b,0x0000018a,0x00050081,0x00000006,0x0000018c,0x0000001c,
0x0000018b,0x0004003d,0x00000007,0x0000018e,0x00000161,0x0003003e,0x0000018d,0x0000018e,
0x0003003e,0x0000018f,0x0000018c,0x00060039,0x00000007,0x00000190,0x0000000d,0x0000018d,
0x0000018f,0x0003003e,0x00000191,0x00000190,0x0004003d,0x00000006,0x00000193,0x00000127,
0x0003003e,0x00000192,0x00000193,0x00060039,0x0000000f,0x00000194,0x00000013,0x00000191,
0x00000192,0x00050051,0x00000006,0x00000195,0x00000194,0x00000002,0x00050041,0x00000009,
0x00000196,0x00000172,0x00000037,0x0003003e,0x00000196,0x00000195,0x000200f9,0x00000171,
0x000200f8,0x00000197,0x0004003d,0x00000007,0x00000199,0x00000161,0x0003003e,0x00000198,
0x00000199,0x0003003e,0x0000019a,0x0000001c,0x00060039,0x00000007,0x0000019b,0x0000000d,
0x00000198,0x0000019a,0x0003003e,0x0000019c,0x0000019b,0x0004003d,0x00000006,0x0000019e,
0x00000127,0x0003003e,0x0000019d,0x0000019e,0x00060039,0x0000000f,0x0000019f,0x00000013,
0x0000019c,0x0000019d,0x0003003e,0x00000172,0x0000019f,0x000200f9,0x00000171,0x000200f8,
0x00000171,0x0004003d,0x00000007,0x000001a1,0x00000134,0x0004003d,0x00000007,0x000001a2,
0x00000134,0x00050050,0x00000007,0x000001a3,0x0000001c,0x0000001c,0x00050083,0x00000007,
0x000001a4,0x000001a3,0x000001a2,0x0007000c,0x00000007,0x000001a5,0x00000001,0x00000025,
0x000001a1,0x000001a4,0x0003003e,0x000001a0,0x000001a5,0x00050041,0x00000009,0x000001a8,
0x000001a0,0x00000024,0x0004003d,0x00000006,0x000001a9,0x000001a8,0x00060041,0x00000025,
0x000001aa,0x00000020,0x00000036,0x00000046,0x0004003d,0x00000006,0x000001ab,0x000001aa,
0x00050085,0x00000006,0x000001ac,0x000001a9,0x000001ab,0x00050041,0x00000009,0x000001ad,
0x000001a0,0x0000002b,0x0004003d,0x00000006,0x000001ae,0x000001ad,0x0007000c,0x00000006,
0x000001af,0x00000001,0x00000025,0x000001ac,0x000001ae,0x0008000c,0x00000006,0x000001b0,
0x00000001,0x00000031,0x0000006f,0x000001a7,0x000001af,0x0003003e,0x000001a6,0x000001b0,
0x0004003d,0x0000000f,0x000001b3,0x00000172,0x0004003d,0x00000006,0x000001b4,0x000001a6,
0x0005008e,0x0000000f,0x000001b5,0x000001b3,0x000001b4,0x00050051,0x00000006,0x000001b6,
0x000001b5,0x00000000,0x00050051,0x00000006,0x000001b7,0x000001b5,0x00000001,0x00050051,
0x00000006,0x000001b8,0x000001b5,0x00000002,0x00070050,0x0000001d,0x000001b9,0x000001b6,
0x000001b7,0x000001b8,0x0000001c,0x0003003e,0x000001b2,0x000001b9,0x000100fd,0x00010038,
0x00050036,0x00000007,0x0000000d,0x00000000,0x0000000a,0x00030037,0x00000008,0x0000000b,
0x00030037,0x00000009,0x0000000c,0x000200f8,0x0000000e,0x0004003b,0x00000009,0x00000015,
0x00000007,0x0004003d,0x00000007,0x00000016,0x0000000b,0x0004003d,0x00000007,0x00000017,
0x0000000b,0x00050094,0x00000006,0x00000018,0x00000016,0x00000017,0x0003003e,0x00000015,
0x00000018,0x0004003d,0x00000007,0x00000019,0x0000000b,0x0004003d,0x00000006,0x0000001a,
0x0000000c,0x0005008e,0x00000007,0x0000001b,0x00000019,0x0000001a,0x00060041,0x00000025,
0x00000026,0x00000020,0x00000022,0x00000024,0x0004003d,0x00000006,0x00000027,0x00000026,
0x0004003d,0x00000006,0x00000028,0x00000015,0x00050085,0x00000006,0x00000029,0x00000027,
0x00000028,0x00050081,0x00000006,0x0000002a,0x0000001c,0x00000029,0x00060041,0x00000025,
0x0000002c,0x00000020,0x00000022,0x0000002b,0x0004003d,0x00000006,0x0000002d,0x0000002c,
0x0004003d,0x00000006,0x0000002e,0x00000015,0x00050085,0x00000006,0x0000002f,0x0000002d,
0x0000002e,0x0004003d,0x00000006,0x00000030,0x00000015,0x00050085,0x00000006,0x00000031,
0x0000002f,0x00000030,0x00050081,0x00000006,0x00000032,0x0000002a,0x00000031,0x0005008e,
0x00000007,0x00000033,0x0000001b,0x00000032,0x000200fe,0x00000033,0x00010038,0x00050036,
0x0000000f,0x00000013,0x00000000,0x00000010,0x00030037,0x00000008,0x00000011,0x00030037,
0x00000009,0x00000012,0x000200f8,0x00000014,0x0004003b,0x00000009,0x0000003f,0x00000007,
0x0004003b,0x00000008,0x00000042,0x00000007,0x0004003b,0x00000008,0x00000059,0x00000007,
0x0004003b,0x000000a2,0x000000a3,0x00000007,0x0004003b,0x000000bf,0x000000c0,0x00000007,
0x0004003b,0x000000bf,0x000000cb,0x00000007,0x0004003b,0x00000009,0x000000e4,0x00000007,
0x0004003b,0x000000bf,0x000000f3,0x00000007,0x0004003b,0x00000009,0x000000f9,0x00000007,
0x0004003b,0x00000009,0x000000fc,0x00000007,0x0004003b,0x00000008,0x00000101,0x00000007,
0x00060041,0x00000025,0x00000038,0x00000020,0x00000036,0x00000037,0x0004003d,0x00000006,
0x00000039,0x00000038,0x000500ba,0x0000003b,0x0000003c,0x00000039,0x0000003a,0x000300f7,
0x0000003e,0x00000000,0x000400fa,0x0000003c,0x0000003d,0x0000003e,0x000200f8,0x0000003d,
0x00060041,0x00000025,0x00000040,0x00000020,0x00000022,0x00000037,0x0004003d,0x00000006,
0x00000041,0x00000040,0x0003003e,0x0000003f,0x00000041,0x00050041,0x00000009,0x00000043,
0x00000011,0x00000024,0x0004003d,0x00000006,0x00000044,0x00000043,0x0004003d,0x00000006,
0x00000045,0x0000003f,0x00060041,0x00000025,0x00000047,0x00000020,0x00000036,0x00000046,
0x0004003d,0x00000006,0x00000048,0x00000047,0x00050085,0x00000006,0x00000049,0x00000045,
0x00000048,0x00050088,0x00000006,0x0000004a,0x00000044,0x00000049,0x00050041,0x00000009,
0x0000004b,0x00000011,0x0000002b,0x0004003d,0x00000006,0x0000004c,0x0000004b,0x0004003d,
0x00000006,0x0000004d,0x0000003f,0x00050088,0x00000006,0x0000004e,0x0000004c,0x0000004d,
0x00050050,0x00000007,0x0000004f,0x0000004a,0x0000004e,0x0005008e,0x00000007,0x00000050,
0x0000004f,0x0000003a,0x00050050,0x00000007,0x00000051,0x0000003a,0x0000003a,0x00050081,
0x00000007,0x00000052,0x00000050,0x00000051,0x0003003e,0x00000042,0x00000052,0x00060041,
0x00000025,0x00000053,0x00000020,0x00000036,0x00000037,0x0004003d,0x00000006,0x00000054,
0x00000053,0x000500ba,0x0000003b,0x00000056,0x00000054,0x00000055,0x000300f7,0x00000058,
0x00000000,0x000400fa,0x00000056,0x00000057,0x00000058,0x000200f8,0x00000057,0x00060041,
0x00000025,0x0000005a,0x00000020,0x00000036,0x00000046,0x0004003d,0x00000006,0x0000005b,
0x0000005a,0x00060041,0x00000025,0x0000005c,0x00000020,0x00000036,0x0000002b,0x0004003d,
0x00000006,0x0000005d,0x0000005c,0x00050088,0x00000006,0x0000005e,0x0000005b,0x0000005d,
0x00060041,0x00000025,0x0000005f,0x00000020,0x00000036,0x0000002b,0x0004003d,0x00000006,
0x00000060,0x0000005f,0x00060041,0x00000025,0x00000061,0x00000020,0x00000036,0x00000046,
0x0004003d,0x00000006,0x00000062,0x00000061,0x00050088,0x00000006,0x00000063,0x00000060,
0x00000062,0x00050050,0x00000007,0x00000064,0x0000005e,0x00000063,0x0007000c,0x00000007,
0x00000066,0x00000001,0x00000028,0x00000064,0x00000065,0x0003003e,0x00000059,0x00000066,
0x0004003d,0x00000007,0x00000067,0x00000042,0x00050050,0x00000007,0x00000068,0x0000003a,
0x0000003a,0x00050083,0x00000007,0x00000069,0x00000067,0x00000068,0x0004003d,0x00000007,
0x0000006a,0x00000059,0x00050085,0x00000007,0x0000006b,0x00000069,0x0000006a,0x00050050,
0x00000007,0x0000006c,0x0000003a,0x0000003a,0x00050081,0x00000007,0x0000006d,0x0000006b,
0x0000006c,0x0003003e,0x00000042,0x0000006d,0x0004003d,0x00000007,0x0000006e,0x00000042,
0x000500b8,0x00000071,0x00000072,0x0000006e,0x00000070,0x0004009a,0x0000003b,0x00000073,
0x00000072,0x000400a8,0x0000003b,0x00000074,0x00000073,0x000300f7,0x00000076,0x00000000,
0x000400fa,0x00000074,0x00000075,0x00000076,0x000200f8,0x00000075,0x0004003d,0x00000007,
0x00000077,0x00000042,0x000500ba,0x00000071,0x00000078,0x00000077,0x00000065,0x0004009a,
0x0000003b,0x00000079,0x00000078,0x000200f9,0x00000076,0x000200f8,0x00000076,0x000700f5,
0x0000003b,0x0000007a,0x00000073,0x00000057,0x00000079,0x00000075,0x000300f7,0x0000007c,
0x00000000,0x000400fa,0x0000007a,0x0000007b,0x0000007c,0x000200f8,0x0000007b,0x000200fe,
0x0000007d,0x000200f8,0x0000007c,0x0004003d,0x00000080,0x00000083,0x00000082,0x0004003d,
0x00000007,0x00000084,0x00000042,0x00050057,0x0000001d,0x00000085,0x00000083,0x00000084,
0x0008004f,0x0000000f,0x00000086,0x00000085,0x00000085,0x00000000,0x00000001,0x00000002,
0x000200fe,0x00000086,0x000200f8,0x00000058,0x0004003d,0x00000007,0x00000088,0x00000042,
0x000500b8,0x00000071,0x00000089,0x00000088,0x00000070,0x0004009a,0x0000003b,0x0000008a,
0x00000089,0x000400a8,0x0000003b,0x0000008b,0x0000008a,0x000300f7,0x0000008d,0x00000000,
0x000400fa,0x0000008b,0x0000008c,0x0000008d,0x000200f8,0x0000008c,0x0004003d,0x00000007,
0x0000008e,0x00000042,0x000500ba,0x00000071,0x0000008f,0x0000008e,0x00000065,0x0004009a,
0x0000003b,0x00000090,0x0000008f,0x000200f9,0x0000008d,0x000200f8,0x0000008d,0x000700f5,
0x0000003b,0x00000091,0x0000008a,0x00000058,0x00000090,0x0000008c,0x000300f7,0x00000093,
0x00000000,0x000400fa,0x00000091,0x00000092,0x00000093,0x000200f8,0x00000092,0x000200fe,
0x0000007d,0x000200f8,0x00000093,0x0004003d,0x00000080,0x00000095,0x00000082,0x00050041,
0x00000009,0x00000096,0x00000042,0x00000024,0x0004003d,0x00000006,0x00000097,0x00000096,
0x0004003d,0x00000006,0x00000098,0x00000012,0x00050081,0x00000006,0x00000099,0x00000097,
0x00000098,0x00050085,0x00000006,0x0000009a,0x00000099,0x0000003a,0x00050041,0x00000009,
0x0000009b,0x00000042,0x0000002b,0x0004003d,0x00000006,0x0000009c,0x0000009b,0x00050050,
0x00000007,0x0000009d,0x0000009a,0x0000009c,0x00050057,0x0000001d,0x0000009e,0x00000095,
0x0000009d,0x0008004f,0x0000000f,0x0000009f,0x0000009e,0x0000009e,0x00000000,0x00000001,
0x00000002,0x000200fe,0x0000009f,0x000200f8,0x0000003e,0x00050041,0x000000a5,0x000000a6,
0x00000020,0x000000a4,0x0004003d,0x0000001d,0x000000a7,0x000000a6,0x0008004f,0x0000000f,
0x000000a8,0x000000a7,0x000000a7,0x00000000,0x00000001,0x00000002,0x00050041,0x000000a5,
0x000000aa,0x00000020,0x000000a9,0x0004003d,0x0000001d,0x000000ab,0x000000aa,0x0008004f,
0x0000000f,0x000000ac,0x000000ab,0x000000ab,0x00000000,0x00000001,0x00000002,0x00050041,
0x000000a5,0x000000ae,0x00000020,0x000000ad,0x0004003d,0x0000001d,0x000000af,0x000000ae,
0x0008004f,0x0000000f,0x000000b0,0x000000af,0x000000af,0x00000000,0x00000001,0x00000002,
0x00050051,0x00000006,0x000000b1,0x000000a8,0x00000000,0x00050051,0x00000006,0x000000b2,
0x000000a8,0x00000001,0x00050051,0x00000006,0x000000b3,0x000000a8,0x00000002,0x00050051,
0x00000006,0x000000b4,0x000000ac,0x00000000,0x00050051,0x00000006,0x000000b5,0x000000ac,
0x00000001,0x00050051,0x00000006,0x000000b6,0x000000ac,0x00000002,0x00050051,0x00000006,
0x000000b7,0x000000b0,0x00000000,0x00050051,0x00000006,0x000000b8,0x000000b0,0x00000001,
0x00050051,0x00000006,0x000000b9,0x000000b0,0x00000002,0x00060050,0x0000000f,0x000000ba,
0x000000b1,0x000000b2,0x000000b3,0x00060050,0x0000000f,0x000000bb,0x000000b4,0x000000b5,
0x000000b6,0x00060050,0x0000000f,0x000000bc,0x000000b7,0x000000b8,0x000000b9,0x00060050,
0x000000a1,0x000000bd,0x000000ba,0x000000bb,0x000000bc,0x00040054,0x000000a1,0x000000be,
0x000000bd,0x0003003e,0x000000a3,0x000000be,0x0004003d,0x000000a1,0x000000c1,0x000000a3,
0x00050041,0x00000009,0x000000c2,0x00000011,0x00000024,0x0004003d,0x00000006,0x000000c3,
0x000000c2,0x00050041,0x00000009,0x000000c4,0x00000011,0x0000002b,0x0004003d,0x00000006,
0x000000c5,0x000000c4,0x0004007f,0x00000006,0x000000c6,0x000000c5,0x00060050,0x0000000f,
0x000000c8,0x000000c3,0x000000c6,0x000000c7,0x0006000c,0x0000000f,0x000000c9,0x00000001,
0x00000045,0x000000c8,0x00050091,0x0000000f,0x000000ca,0x000000c1,0x000000c9,0x0003003e,
0x000000c0,0x000000ca,0x00050041,0x000000a5,0x000000cd,0x00000020,0x000000cc,0x0004003d,
0x0000001d,0x000000ce,0x000000cd,0x0008004f,0x0000000f,0x000000cf,0x000000ce,0x000000ce,
0x00000001,0x00000002,0x00000003,0x0004003d,0x000000a1,0x000000d0,0x000000a3,0x0004003d,
0x00000006,0x000000d1,0x00000012,0x000500b8,0x0000003b,0x000000d2,0x000000d1,0x0000003a,
0x000600a9,0x00000006,0x000000d3,0x000000d2,0x000000c7,0x0000001c,0x00060041,0x00000025,
0x000000d4,0x00000020,0x000000a4,0x00000046,0x0004003d,0x00000006,0x000000d5,0x000000d4,
0x00050085,0x00000006,0x000000d6,0x000000d3,0x000000d5,0x00060050,0x0000000f,0x000000d7,
0x000000d6,0x0000006f,0x0000006f,0x00050091,0x0000000f,0x000000d8,0x000000d0,0x000000d7,
0x00050081,0x0000000f,0x000000d9,0x000000cf,0x000000d8,0x0003003e,0x000000cb,0x000000d9,
0x00050041,0x00000009,0x000000da,0x000000c0,0x00000037,0x0004003d,0x00000006,0x000000db,
0x000000da,0x000500ba,0x0000003b,0x000000dd,0x000000db,0x000000dc,0x000300f7,0x000000df,
0x00000000,0x000400fa,0x000000dd,0x000000de,0x000000df,0x000200f8,0x000000de,0x000200fe,
0x000000e2,0x000200f8,0x000000df,0x00060041,0x00000025,0x000000e5,0x00000020,0x000000a9,
0x00000046,0x0004003d,0x00000006,0x000000e6,0x000000e5,0x0004007f,0x00000006,0x000000e7,
0x000000e6,0x00050041,0x00000009,0x000000e8,0x000000cb,0x00000037,0x0004003d,0x00000006,
0x000000e9,0x000000e8,0x00050083,0x00000006,0x000000ea,0x000000e7,0x000000e9,0x00050041,
0x00000009,0x000000eb,0x000000c0,0x00000037,0x0004003d,0x00000006,0x000000ec,0x000000eb,
0x00050088,0x00000006,0x000000ed,0x000000ea,0x000000ec,0x0003003e,0x000000e4,0x000000ed,
0x0004003d,0x00000006,0x000000ee,0x000000e4,0x000500bc,0x0000003b,0x000000ef,0x000000ee,
0x0000006f,0x000300f7,0x000000f1,0x00000000,0x000400fa,0x000000ef,0x000000f0,0x000000f1,
0x000200f8,0x000000f0,0x000200fe,0x000000e2,0x000200f8,0x000000f1,0x0004003d,0x0000000f,
0x000000f4,0x000000cb,0x0004003d,0x0000000f,0x000000f5,0x000000c0,0x0004003d,0x00000006,
0x000000f6,0x000000e4,0x0005008e,0x0000000f,0x000000f7,0x000000f5,0x000000f6,0x00050081,
0x0000000f,0x000000f8,0x000000f4,0x000000f7,0x0003003e,0x000000f3,0x000000f8,0x00060041,
0x00000025,0x000000fa,0x00000020,0x000000ad,0x00000046,0x0004003d,0x00000006,0x000000fb,
0x000000fa,0x0003003e,0x000000f9,0x000000fb,0x0004003d,0x00000006,0x000000fd,0x000000f9,
0x00060041,0x00000025,0x000000fe,0x00000020,0x00000036,0x0000002b,0x0004003d,0x00000006,
0x000000ff,0x000000fe,0x00050088,0x00000006,0x00000100,0x000000fd,0x000000ff,0x0003003e,
0x000000fc,0x00000100,0x00050041,0x00000009,0x00000102,0x000000f3,0x00000024,0x0004003d,
0x00000006,0x00000103,0x00000102,0x0004003d,0x00000006,0x00000104,0x000000f9,0x00050088,
0x00000006,0x00000105,0x00000103,0x00000104,0x00050041,0x00000009,0x00000106,0x000000f3,
0x0000002b,0x0004003d,0x00000006,0x00000107,0x00000106,0x0004007f,0x00000006,0x00000108,
0x00000107,0x0004003d,0x00000006,0x00000109,0x000000fc,0x00050088,0x00000006,0x0000010a,
0x00000108,0x00000109,0x00050050,0x00000007,0x0000010b,0x00000105,0x0000010a,0x0005008e,
0x00000007,0x0000010c,0x0000010b,0x0000003a,0x00050050,0x00000007,0x0000010d,0x0000003a,
0x0000003a,0x00050081,0x00000007,0x0000010e,0x0000010c,0x0000010d,0x0003003e,0x00000101,
0x0000010e,0x0004003d,0x00000007,0x0000010f,0x00000101,0x000500b8,0x00000071,0x00000110,
0x0000010f,0x00000070,0x0004009a,0x0000003b,0x00000111,0x00000110,0x000400a8,0x0000003b,
0x00000112,0x00000111,0x000300f7,0x00000114,0x00000000,0x000400fa,0x00000112,0x00000113,
0x00000114,0x000200f8,0x00000113,0x0004003d,0x00000007,0x00000115,0x00000101,0x000500ba,
0x00000071,0x00000116,0x00000115,0x00000065,0x0004009a,0x0000003b,0x00000117,0x00000116,
0x000200f9,0x00000114,0x000200f8,0x00000114,0x000700f5,0x0000003b,0x00000118,0x00000111,
0x000000f1,0x00000117,0x00000113,0x000300f7,0x0000011a,0x00000000,0x000400fa,0x00000118,
0x00000119,0x0000011a,0x000200f8,0x00000119,0x000200fe,0x000000e2,0x000200f8,0x0000011a,
0x0004003d,0x00000080,0x0000011c,0x00000082,0x0004003d,0x00000007,0x0000011d,0x00000101,
0x00050057,0x0000001d,0x0000011e,0x0000011c,0x0000011d,0x0008004f,0x0000000f,0x0000011f,
0x0000011e,0x0000011e,0x00000000,0x00000001,0x00000002,0x000200fe,0x0000011f,0x00010038
};
@@ -0,0 +1,59 @@
#define _POSIX_C_SOURCE 200809L
#include "spatialpose_shm.h"
#include <jni.h>
#include <fcntl.h>
#include <pthread.h>
#include <stdatomic.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
static pthread_mutex_t spatial_lock = PTHREAD_MUTEX_INITIALIZER;
static struct droiddeck_spatialpose *shared;
JNIEXPORT jboolean JNICALL Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeSetSpatialFile(
JNIEnv *env, jclass clazz, jstring path) {
(void)clazz;
pthread_mutex_lock(&spatial_lock);
if (!shared && path) {
const char *name = (*env)->GetStringUTFChars(env, path, NULL);
if (name) {
int fd = open(name, O_RDWR | O_CREAT | O_CLOEXEC, 0600);
(*env)->ReleaseStringUTFChars(env, path, name);
if (fd >= 0) {
if (ftruncate(fd, sizeof(*shared)) == 0) {
void *p = mmap(NULL, sizeof(*shared), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (p != MAP_FAILED) {
shared = p;
memset(shared, 0, sizeof(*shared));
shared->magic = DROIDDECK_SPATIALPOSE_MAGIC;
shared->version = DROIDDECK_SPATIALPOSE_VERSION;
shared->size = sizeof(*shared);
}
}
close(fd);
}
}
}
jboolean ready = shared != NULL;
pthread_mutex_unlock(&spatial_lock);
return ready;
}
JNIEXPORT void JNICALL Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativePublishSpatial(
JNIEnv *env, jclass clazz, jobject buffer) {
(void)clazz;
if (!buffer || (*env)->GetDirectBufferCapacity(env, buffer) != sizeof(*shared)) return;
const struct droiddeck_spatialpose *src = (*env)->GetDirectBufferAddress(env, buffer);
if (!src || src->magic != DROIDDECK_SPATIALPOSE_MAGIC || src->version != DROIDDECK_SPATIALPOSE_VERSION ||
src->size != sizeof(*shared)) return;
pthread_mutex_lock(&spatial_lock);
if (shared) {
unsigned seq = __atomic_load_n(&shared->seq, __ATOMIC_RELAXED);
__atomic_store_n(&shared->seq, seq | 1u, __ATOMIC_RELAXED);
__atomic_thread_fence(__ATOMIC_RELEASE);
memcpy((char *)shared + 16, (const char *)src + 16, sizeof(*shared) - 16);
__atomic_store_n(&shared->seq, (seq | 1u) + 1u, __ATOMIC_RELEASE);
}
pthread_mutex_unlock(&spatial_lock);
}
@@ -0,0 +1,33 @@
#ifndef DROIDDECK_SPATIALPOSE_SHM_H
#define DROIDDECK_SPATIALPOSE_SHM_H
#include <stdint.h>
#define DROIDDECK_SPATIALPOSE_MAGIC 0x53504444u
#define DROIDDECK_SPATIALPOSE_VERSION 4u
#define DROIDDECK_SPATIALPOSE_FILE "droiddeck-spatialpose"
enum { DD_SPATIAL_ACTIVE = 1u, DD_SPATIAL_HANDS = 2u, DD_SPATIAL_JOYCONS = 4u, DD_SPATIAL_STEADY_AIM = 8u, DD_SPATIAL_VALID = 1u };
enum { DD_SPATIAL_TRIGGER_VALID = 2u, DD_SPATIAL_GRIP_VALID = 4u };
struct dd_spatial_pose {
int64_t time_ns;
float position[3];
float rotation[4];
uint32_t flags;
};
struct dd_spatial_hand {
struct dd_spatial_pose pose;
float trigger;
uint32_t grip;
};
struct droiddeck_spatialpose {
uint32_t magic, version, size, seq;
uint64_t connection, origin;
uint32_t flags, input_epochs;
struct dd_spatial_pose head;
struct dd_spatial_hand hands[2];
};
#ifdef __cplusplus
static_assert(sizeof(dd_spatial_pose) == 40, "spatial pose ABI");
static_assert(sizeof(droiddeck_spatialpose) == 176, "spatial snapshot ABI");
#else
_Static_assert(sizeof(struct droiddeck_spatialpose) == 176, "spatial snapshot ABI");
#endif
#endif
@@ -649,3 +649,14 @@ Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeRaisePriority(JNIEnv
int after = getpriority(PRIO_PROCESS, (id_t)tid);
return after == -1 && errno != 0 ? 100 : after;
}
JNIEXPORT void JNICALL
Java_com_droiddeck_launcher_wayland_WaylandCompositor_nativeSetCardboardSpatialPose(JNIEnv *env, jclass clazz,
jfloatArray rot, jfloatArray pos, jboolean live) {
(void)clazz;
if (!rot || !pos || (*env)->GetArrayLength(env, rot) != 9 || (*env)->GetArrayLength(env, pos) != 3) return;
float m[9], p[3];
(*env)->GetFloatArrayRegion(env, rot, 0, 9, m);
(*env)->GetFloatArrayRegion(env, pos, 0, 3, p);
vkp_cardboard_set_spatial_pose(m, p, live ? 1 : 0);
}
@@ -16,10 +16,11 @@ class App : Application() {
override fun onCreate() {
super.onCreate()
// The restart screen must not initialize USB, guest processes, or another graphics context.
val process = (getSystemService(ACTIVITY_SERVICE) as android.app.ActivityManager)
.runningAppProcesses?.firstOrNull { it.pid == android.os.Process.myPid() }?.processName
if (process == "$packageName:vr_recovery") return
// Helper processes must not initialize the main USB provider or guest graphics context.
val process = if (android.os.Build.VERSION.SDK_INT >= 28) Application.getProcessName()
else (getSystemService(ACTIVITY_SERVICE) as android.app.ActivityManager)
.runningAppProcesses?.firstOrNull { it.pid == android.os.Process.myPid() }?.processName
if (process == "$packageName:vr_recovery" || process == "$packageName:rgb_diagnostic") return
CrashHandler.install(this)
com.droiddeck.launcher.session.QuestExperimental.initialize(this)
com.droiddeck.launcher.session.SessionPrefs.settleGpuDriverMode(this)
@@ -27,6 +28,7 @@ class App : Application() {
com.droiddeck.launcher.core.DownloadSource.init(this)
GpuClockPin.clearLeftover(this)
com.droiddeck.launcher.glasses.Glasses.init(this)
com.droiddeck.launcher.glasses.GlassesMenuMirror.initialize(this)
BwrapSpawner.start(this)
com.droiddeck.launcher.frontend.GameFileSync.start(this)
com.droiddeck.launcher.stores.StoresState.init(this)
@@ -1192,7 +1192,9 @@ class MainActivity : ComponentActivity() {
}
private fun playSteamVr() {
if (com.droiddeck.launcher.handtracking.HandTracking.wanted(this)) placementCheck = true
val tracking = com.droiddeck.launcher.glasses.Glasses.settings.value
if (!com.droiddeck.launcher.glasses.Glasses.state.value.usesSpatialControllers(tracking) &&
com.droiddeck.launcher.handtracking.HandTracking.wanted(this)) placementCheck = true
else startSteamSession(steamVr = true)
}
@@ -56,6 +56,8 @@ import com.droiddeck.launcher.gpu.HeadTracker
import com.droiddeck.launcher.glasses.Glasses
import com.droiddeck.launcher.glasses.GlassesScreen
import com.droiddeck.launcher.glasses.GlassesRendering
import com.droiddeck.launcher.glasses.SpatialTracking
import com.droiddeck.launcher.glasses.TrackingRecovery
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import com.droiddeck.launcher.gpu.SteamVr
@@ -306,11 +308,13 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
private var glassesSettings by mutableStateOf(Glasses.settings.value)
private var glassesScreen: GlassesScreen? = null
private var glassesOutputActive by mutableStateOf(false)
private var trackingRecovery by mutableStateOf<TrackingRecovery?>(null)
private var glasses3dForVr = false
private var ended = false
private var glassesPresent = false
private var steamVrAutoTried = false
private var steamVrInstallTried = false
@Volatile private var steamVrRequestGeneration = 0L
private var headTracking by mutableStateOf(false)
private var glassesSlot by mutableStateOf(0)
private var glassesKey: List<Any?> = emptyList()
@@ -412,6 +416,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
} else if (intent.getBooleanExtra(EXTRA_STEAMVR, false) && SessionState.mode == SessionService.MODE_STEAM) {
SessionState.steamVr = true
}
if (SessionState.steamVr) Glasses.prepareSteamVrStart()
if (intent.action == SessionService.ACTION_AGENT_START) {
SessionEvents.record("agent.start_requested", mapOf("mode" to SessionState.mode))
}
@@ -519,9 +524,25 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
sessionOverlay = createSessionOverlay()
root.addView(sessionOverlay)
setContentView(root)
glassesScope.launch { Glasses.state.collect { glasses = it; onGlassesChanged() } }
glassesScope.launch { Glasses.settings.collect { glassesSettings = it; onGlassesChanged() } }
glassesScope.launch { Glasses.state.collect {
glasses = it
onGlassesChanged()
updateJoyConTracking()
updateHandCamera()
} }
glassesScope.launch { Glasses.settings.collect {
glassesSettings = it
onGlassesChanged()
updateJoyConTracking()
updateHandCamera()
} }
glassesScope.launch { com.droiddeck.launcher.handtracking.HandTracking.settings.collect { updateHandCamera() } }
glassesScope.launch {
while (true) {
updateTrackingRecovery()
kotlinx.coroutines.delay(100)
}
}
glassesScope.launch {
while (true) {
if (SessionState.running && SessionState.mode == SessionService.MODE_STEAM &&
@@ -564,6 +585,13 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
isFocusableInTouchMode = true
defaultFocusHighlightEnabled = false
setContent {
androidx.compose.runtime.SideEffect {
com.droiddeck.launcher.glasses.GlassesMenuMirror.setSessionMenuVisible(this@SessionActivity,
(drawerOpen || achievementsOpen || pcKeyboardOpen) && !pipUi && steamVrStatus != SteamVr.Status.RUNNING)
}
androidx.compose.runtime.DisposableEffect(Unit) {
onDispose { com.droiddeck.launcher.glasses.GlassesMenuMirror.setSessionMenuVisible(this@SessionActivity, false) }
}
androidx.compose.runtime.LaunchedEffect(drawerOpen, pcKeyboardOpen, achievementsOpen, pipUi) { updatePointerCapture() }
androidx.compose.runtime.LaunchedEffect(picturePosition, SessionState.running, pipUi, cardboard.enabled) { applyGameAlignment() }
DroidDeckTheme {
@@ -581,9 +609,20 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
cursorImage, cursorHotX, cursorHotY, cursorImageScale)
if (hud.text.isNotEmpty()) HudText(hud.text)
if (glassesOutputActive && !loading.visible) com.droiddeck.launcher.ui.GlassesPlayingHint(glasses.model)
if (trackingRecovery != null && !drawerOpen && !pcKeyboardOpen && !loading.visible) {
androidx.compose.ui.viewinterop.AndroidView(
factory = { com.droiddeck.launcher.ui.TrackingRecoveryView(it) },
modifier = Modifier.fillMaxSize(),
update = { it.update(trackingRecovery, !glassesOutputActive && glasses.usable && glasses.sbs) },
)
}
if (GlassesRendering.usesGlasses(glasses, glassesOutputActive, steamVrStatus == SteamVr.Status.RUNNING) &&
headTracking && !drawerOpen && !pcKeyboardOpen && !loading.visible) com.droiddeck.launcher.ui.GlassesRecenterButton(
controllerSettings?.tint ?: android.graphics.Color.rgb(90, 160, 255), controllerSettings?.opacity ?: 100) { headTracker?.recenter() }
controllerSettings?.tint ?: android.graphics.Color.rgb(90, 160, 255), controllerSettings?.opacity ?: 100,
onRecenterJoyCons = if (steamVrStatus == SteamVr.Status.RUNNING &&
glasses.usesSpatialControllers(glassesSettings) && glassesSettings.joyConTracking)
joyConVr?.let { { it.recenter() } } else null,
) { headTracker?.recenter() }
if (glasses.present && !drawerOpen && !pcKeyboardOpen && !loading.visible) com.droiddeck.launcher.ui.GlassesCornerButton(glassesSlot,
controllerSettings?.tint ?: android.graphics.Color.rgb(90, 160, 255), controllerSettings?.opacity ?: 100) {
drawerPage = com.droiddeck.launcher.ui.SessionDrawerPage.GLASSES
@@ -621,10 +660,12 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
steamVrSwitchedPicture, glassesVr, steamVrRequested) || (drawerOpen && drawerPage == SessionDrawerPage.VR)
LaunchedEffect(pollSteamVr) {
while (pollSteamVr) {
val request = steamVrRequestGeneration
val session = SessionState.sessionId
val now = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
SteamVr.status(this@SessionActivity)
}
onSteamVrStatus(now)
if (request == steamVrRequestGeneration && session == SessionState.sessionId) onSteamVrStatus(now)
kotlinx.coroutines.delay(3000)
}
}
@@ -661,7 +702,10 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
onCardboardRecenter = { headTracker?.recenter() },
glassesPresent = glasses.present,
glassesOutput = glassesOutputActive,
onGlassesRecenter = { headTracker?.recenter() },
onGlassesRecenter = {
headTracker?.recenter()
com.droiddeck.launcher.glasses.GlassesMenuMirror.recenter()
},
steamVr = steamVrStatus,
immersiveSteamVr = com.droiddeck.launcher.session.QuestExperimental.isSupported(),
onSteamVr = { action -> steamVrAction(action) },
@@ -1426,6 +1470,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
activeCardboard = effectiveCardboard(SessionPrefs.cardboard(this))
pushCardboard(activeCardboard)
WaylandCompositor.nativeSetCardboardPoseFile(File(filesDir, ".wayland-rt/droiddeck-headpose").path)
com.droiddeck.launcher.glasses.SpatialTracking.attachPublisher(File(filesDir, ".wayland-rt/droiddeck-spatialpose").path)
if (!SessionState.running) {
val waylandLog = File(SessionPaths.beginOrCurrent(this), "wayland.log")
WaylandCompositor.setSessionLogFile(waylandLog)
@@ -1646,6 +1691,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
private fun onSessionEnded(status: Int) {
ended = true
++steamVrRequestGeneration
if (isInPictureInPictureMode) {
runOnUiThread { finish() }
return
@@ -2677,19 +2723,27 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
startActivity(android.content.Intent(this, QuestXrActivity::class.java))
return
}
val request = ++steamVrRequestGeneration
val session = SessionState.sessionId
fun currentRequest() = request == steamVrRequestGeneration && session == SessionState.sessionId && !isDestroyed && !ended
if (action == "start") {
SessionState.steamVr = true
steamVrAutoTried = true
}
if (action == "stop") SessionState.steamVr = false
if (action == "stop") {
SessionState.steamVr = false
Glasses.cancelSteamVrStart()
}
if (glasses.present) {
// Optional stereo starts only after RUNNING is observed. READY can
// arrive repeatedly while the launch worker is still starting VR.
if (action == "install") steamVrStatus = SteamVr.Status.INSTALLING
steamVrWorker.execute {
runSteamVrAction(action, ::currentRequest) {
val ok = SteamVr.run(this, action)
val now = SteamVr.status(this)
uiHandler.post {
if (!currentRequest()) return@post
if (!ok && now != SteamVr.Status.RUNNING) Glasses.cancelSteamVrStart()
onSteamVrStatus(now)
if (!ok) Toast.makeText(this, getString(R.string.cardboard_steamvr_failed), Toast.LENGTH_LONG).show()
}
@@ -2702,10 +2756,12 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
}
if (action == "stop") restoreSteamVrPicture()
if (action == "install") steamVrStatus = SteamVr.Status.INSTALLING
steamVrWorker.execute {
runSteamVrAction(action, ::currentRequest) {
val ok = SteamVr.run(this, action)
val now = SteamVr.status(this)
uiHandler.post {
if (!currentRequest()) return@post
if (!ok && now != SteamVr.Status.RUNNING) Glasses.cancelSteamVrStart()
if (!ok && action == "start" && now != SteamVr.Status.RUNNING) restoreSteamVrPicture()
onSteamVrStatus(now)
if (!ok) Toast.makeText(this, getString(R.string.cardboard_steamvr_failed), Toast.LENGTH_LONG).show()
@@ -2713,6 +2769,20 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
}
}
private fun runSteamVrAction(action: String, currentRequest: () -> Boolean, run: () -> Unit) {
if (action != "start") {
steamVrWorker.execute(run)
return
}
Glasses.prepareSteamVrStart {
uiHandler.post {
if (SessionState.steamVr && currentRequest()) steamVrWorker.execute {
if (SessionState.steamVr && currentRequest()) run()
}
}
}
}
private fun switchPictureForSteamVr() {
if (cardboard.source != Cardboard.SOURCE_SBS) {
SessionPrefs.setCardboardSourceBeforeSteamVr(this, cardboard.source)
@@ -2723,10 +2793,21 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
steamVrSwitchedPicture = SessionPrefs.cardboardSourceBeforeSteamVr(this) >= 0
}
private fun usesSpatialControllers() =
GlassesRendering.usesGlasses(glasses, glassesScreen != null, steamVrStatus == SteamVr.Status.RUNNING) &&
glasses.usesSpatialControllers(glassesSettings)
private fun updateJoyConTracking() {
joyConVr?.setActive(steamVrStatus == SteamVr.Status.RUNNING &&
(ControllerPrefs.joyConVr(this) ||
(usesSpatialControllers() && glassesSettings.joyConTracking)))
}
private fun updateHandCamera() {
val joyCons = joyConVr?.holding == true
val bare = !joyCons && !controllerConnected()
val want = started && steamVrStatus == SteamVr.Status.RUNNING && (joyCons || bare) &&
!usesSpatialControllers() &&
com.droiddeck.launcher.handtracking.HandTracking.wanted(this)
handCamera?.bareHands = bare
if (want == (handCamera != null)) return
@@ -2748,12 +2829,14 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
}
private fun onSteamVrStatus(now: SteamVr.Status) {
if (ended || isDestroyed) return
val was = steamVrStatus
steamVrStatus = now
joyConVr?.setActive(now == SteamVr.Status.RUNNING && ControllerPrefs.joyConVr(this))
updateJoyConTracking()
updateHandCamera()
autoStartSteamVr(now)
if ((was == SteamVr.Status.RUNNING) != (now == SteamVr.Status.RUNNING)) applyCardboard(cardboard)
if (now != SteamVr.Status.UNAVAILABLE) Glasses.setVrActive(now == SteamVr.Status.RUNNING)
if (glasses.present) onGlassesSteamVr(now)
if (GlassesRendering.usesGlasses(glasses, glassesScreen != null, now == SteamVr.Status.RUNNING)) {
return
@@ -2809,15 +2892,17 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
private fun updateHeadTracker() {
val onGlasses = GlassesRendering.usesGlasses(glasses, glassesScreen != null,
steamVrStatus == SteamVr.Status.RUNNING)
SpatialTracking.setOutputUsesGlasses(onGlasses && !ended)
val c = activeCardboard
val centerView = onGlasses && c.source == Cardboard.SOURCE_SCREEN
val want = !ended && (resumed || (glassesScreen != null && started)) && c.enabled && c.headTracking
val current = headTracker
if (current != null && (!want || current.usesGlasses != onGlasses)) {
if (current != null && (!want || current.usesGlasses != onGlasses || current.centerView != centerView)) {
current.stop()
headTracker = null
}
if (want) {
val t = headTracker ?: HeadTracker(this, glasses = onGlasses) {
val t = headTracker ?: HeadTracker(this, glasses = onGlasses, centerView = centerView) {
@Suppress("DEPRECATION")
(if (Build.VERSION.SDK_INT >= 30) display else windowManager.defaultDisplay)?.rotation ?: android.view.Surface.ROTATION_0
}.also { headTracker = it }
@@ -2826,6 +2911,18 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
headTracking = headTracker != null
}
private fun updateTrackingRecovery() {
val onGlasses = GlassesRendering.usesGlasses(glasses, glassesScreen != null,
steamVrStatus == SteamVr.Status.RUNNING)
val spatialView = onGlasses && SpatialTracking.selected && glassesSettings.positionalTracking &&
(SessionState.steamVr || steamVrStatus == SteamVr.Status.RUNNING ||
(glassesOutputActive && glassesSettings.headTracking && glassesSettings.picture3d == com.droiddeck.launcher.glasses.GlassesSettings.PICTURE_SCREEN))
val active = started && !ended && SessionState.running && !loading.visible && !pipUi && spatialView
if (active) SpatialTracking.refreshStatus()
trackingRecovery = TrackingRecovery.forSession(active, glasses.canControl, SpatialTracking.status.value)
glassesScreen?.trackingRecovery?.update(trackingRecovery, glasses.sbs)
}
private fun outputHolder(): SurfaceHolder = glassesScreen?.holder ?: surfaceView.holder
private fun onOutputAttached(holder: SurfaceHolder) {
@@ -2864,7 +2961,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
private fun onGlassesChanged() {
val g = glasses
val s = glassesSettings
val key = listOf(g.present, g.usable, g.displayId, g.sbs, g.sbs90, g.switching3d, g.canControl, g.width, g.height, g.hz, s)
val key = listOf(g.present, g.usable, g.displayId, g.sbs, g.sbs90, g.supports30Hz, g.switching3d, g.canControl, g.width, g.height, g.hz, g.resolutions, g.vrActive, s)
if (key == glassesKey) return
glassesKey = key
updateGlassesScreen()
@@ -2898,7 +2995,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
val target = if (want) Glasses.glassesDisplay()?.takeIf { it.isValid } else null
val current = glassesScreen
if (current != null && current.display.displayId == target?.displayId) {
current.prefer(Glasses.outputHz())
current.prefer(Glasses.outputHz(), Glasses.outputResolution())
applyOutputPacing()
return
}
@@ -2907,7 +3004,7 @@ class SessionActivity : ComponentActivity(), SurfaceHolder.Callback {
runCatching { current.dismiss() }
}
if (target != null) {
val screen = GlassesScreen(this, target, Glasses.outputHz(), this)
val screen = GlassesScreen(this, target, Glasses.outputHz(), this, Glasses.outputResolution())
// show() can create the surface synchronously. Select its holder
// first, otherwise surfaceCreated() rejects the glasses callback.
glassesScreen = screen
@@ -108,15 +108,19 @@ class AgentBridgeProvider : ContentProvider() {
val value = request.optString("value")
fun on() = value == "1" || value.equals("true", true) || value.equals("on", true)
val number = value.toIntOrNull()
val numeric = setOf("refreshHz", "sbsHz", "brightness", "volume", "picture3d", "screenWidthDm", "screenDistanceDm", "vrFovDeg", "sbsSupport")
val numeric = setOf("refreshHz", "sbsHz", "brightness", "volume", "picture3d", "screenWidthDm", "screenDistanceDm", "vrFovDeg", "sbsSupport", "eyeHeightCm")
if (key in numeric && number == null) return error("INVALID_VALUE", "'$key' takes a number")
when (key) {
"refreshHz" -> g.setRefreshHz(number!!)
"sbsHz" -> g.setSbsHz(number!!)
"resolution2d" -> g.setResolution(com.droiddeck.launcher.glasses.GlassesDisplayProfile.TWO_D, value)
"resolution3d" -> g.setResolution(com.droiddeck.launcher.glasses.GlassesDisplayProfile.THREE_D, value)
"resolutionVr" -> g.setResolution(com.droiddeck.launcher.glasses.GlassesDisplayProfile.VR, value)
"brightness" -> g.setBrightness(number!!)
"volume" -> g.setVolume(number!!)
"sunblock" -> g.setSunblock(on())
"3d" -> g.set3d(on())
"3dof" -> g.set3dof(on())
"gameOnGlasses" -> g.setGameOnGlasses(on())
"headTracking" -> g.setHeadTracking(on())
"auto3d" -> g.setAuto3d(on())
@@ -125,6 +129,10 @@ class AgentBridgeProvider : ContentProvider() {
"screenDistanceDm" -> g.setScreenDistanceDm(number!!)
"vrFovDeg" -> g.setVrFovDeg(number!!)
"sbsSupport" -> g.setSbsSupport(number!!)
"positionalTracking" -> g.setPositionalTracking(on())
"handControllers" -> g.setHandControllers(on())
"joyConTracking" -> g.setJoyConTracking(on())
"eyeHeightCm" -> g.setEyeHeight(number!!)
else -> return error("INVALID_KEY", "Unknown glasses setting '$key'")
}
}
@@ -136,6 +144,34 @@ class AgentBridgeProvider : ContentProvider() {
"calibrate" -> g.startCalibration()
"cancel-calibration" -> g.cancelCalibration()
"reset-calibration" -> g.resetCalibration()
"spatial-diagnostics" -> com.droiddeck.launcher.glasses.SpatialTracking.diagnostics(request.optBoolean("enabled",true))
"spatial-recenter" -> com.droiddeck.launcher.glasses.SpatialTracking.recenter(request.optBoolean("screen",false))
"spatial-floor" -> g.setSpatialFloor()
"spatial-restart" -> g.restartSpatialTracking()
"spatial-hand-options" -> {
AgentAccess.requireCommands(context)
if(request.has("lite")) com.droiddeck.launcher.glasses.SpatialTracking.setHandModel(request.getBoolean("lite"))
if(request.has("parallel")) com.droiddeck.launcher.glasses.SpatialTracking.setHandCpuParallel(request.getBoolean("parallel"))
if(request.has("halfRate")) com.droiddeck.launcher.glasses.SpatialTracking.setHandHalfRate(request.getBoolean("halfRate"))
if(request.has("adaptive")) com.droiddeck.launcher.glasses.SpatialTracking.setHandAdaptiveInput(request.getBoolean("adaptive"))
if(request.has("scheduled")) com.droiddeck.launcher.glasses.SpatialTracking.setHandScheduledTracking(request.getBoolean("scheduled"))
}
"spatial-hand-benchmark" -> {
AgentAccess.requireCommands(context)
com.droiddeck.launcher.glasses.SpatialTracking.benchmarkSavedCapture(request.getString("capture"))
}
"spatial-hand-capture" -> {
AgentAccess.requireCommands(context)
com.droiddeck.launcher.glasses.SpatialTracking.captureDiagnostic()
}
"spatial-rgb-capture", "spatial-rgb-status" -> {
AgentAccess.requireCommands(context)
return (if (request.getString("op") == "spatial-rgb-capture")
com.droiddeck.launcher.glasses.LumaRgbDiagnostic.capture(context!!,request.optString("transport","sdk"),
request.optInt("width",640),request.optInt("height",480),request.optInt("fps",30),
request.optInt("durationMs",5000))
else com.droiddeck.launcher.glasses.LumaRgbDiagnostic.status(context!!)).put("ok", true)
}
"query" -> { g.query(); Thread.sleep(600) }
"mode" -> {
val mode = hex(request.optString("mode"), 0xFF) ?: return error("INVALID_VALUE", "mode must be one hex byte, e.g. 0x31")
@@ -0,0 +1,24 @@
package com.droiddeck.launcher.glasses
import android.content.Context
import com.droiddeck.launcher.R
import kotlin.math.abs
internal data class CalibrationDistanceResult(val accepted: Boolean, val message: String)
internal object CalibrationDistanceCheck {
fun evaluate(context: Context, expected: Float, corners: Int, points: List<SpatialVector>?): CalibrationDistanceResult {
require(expected.isFinite() && expected in 0.15f..1.5f)
if (corners != 24) return CalibrationDistanceResult(false,
context.getString(R.string.spatial_distance_corners, corners))
if (points?.size != 24 || points.any { !it.finite() }) return CalibrationDistanceResult(false,
context.getString(R.string.spatial_distance_depth))
val measured=(points.reduce { a,b -> a+b }*(1f/points.size)).length()
if (!measured.isFinite()) return CalibrationDistanceResult(false,context.getString(R.string.spatial_distance_invalid))
val accepted=abs(measured-expected)<0.05f
return CalibrationDistanceResult(accepted,
context.getString(R.string.spatial_distance_result,
SpatialMeasurements.validation(measured), SpatialMeasurements.validation(expected),
context.getString(if (accepted) R.string.spatial_distance_passed else R.string.spatial_distance_retry)))
}
}
@@ -0,0 +1,27 @@
package com.droiddeck.launcher.glasses
import org.json.JSONObject
internal data class CalibrationObservationData(
val objects: List<DoubleArray>, val left: List<DoubleArray>, val right: List<DoubleArray>, val head: SpatialPose,
) {
companion object {
fun parse(view: JSONObject): CalibrationObservationData {
fun points(name: String, dimensions: Int): List<DoubleArray> {
val rows=view.getJSONArray(name)
require(rows.length() in 12..24) { "Invalid saved board corner count" }
return (0 until rows.length()).map { row ->
val values=rows.getJSONArray(row)
require(values.length()==dimensions)
DoubleArray(dimensions) { values.getDouble(it).also { value -> require(value.isFinite()) } }
}
}
val objects=points("objects",3); val left=points("left",2); val right=points("right",2)
require(objects.size==left.size && objects.size==right.size)
val pose=view.getJSONArray("head")
require(pose.length()==7)
val head=SpatialPose.fromSdk(FloatArray(7) { pose.getDouble(it).toFloat() }) ?: error("Invalid saved head pose")
return CalibrationObservationData(objects,left,right,head)
}
}
}
@@ -51,10 +51,18 @@ data class GlassesState(
val sbsSupport: Int = SBS_UNKNOWN,
val sbs90: Boolean = false,
val experimental: Boolean = false,
val supportsScreen3dof: Boolean = false,
val supports30Hz: Boolean = false,
val supportsSpatial: Boolean = false,
val resolutions: List<GlassesResolution> = emptyList(),
val vrActive: Boolean = false,
) {
val hasDisplay: Boolean get() = displayId != Display.INVALID_DISPLAY
val usable: Boolean get() = hasDisplay && !noSignal
val canControl: Boolean get() = connected && controllable
val spatialConnected: Boolean get() = canControl && supportsSpatial
fun usesSpatialControllers(settings: GlassesSettings) = spatialConnected && settings.positionalTracking &&
(settings.handControllers || settings.joyConTracking)
val can3d: Boolean get() = canControl && !noSignal && sbsSupport != SBS_FAILS
val verticalFovDeg: Float get() {
val half = Math.toRadians(diagonalFovDeg / 2.0)
@@ -74,6 +82,9 @@ data class GlassesState(
data class GlassesSettings(
val refreshHz: Int = 120,
val sbsHz: Int = 60,
val resolution2d: String = "",
val resolution3d: String = "",
val resolutionVr: String = "",
val sunblock: Boolean? = null,
val gameOnGlasses: Boolean = true,
val headTracking: Boolean = true,
@@ -82,7 +93,14 @@ data class GlassesSettings(
val screenWidthDm: Int = 20,
val screenDistanceDm: Int = 30,
val vrFovDeg: Int = VR_FOV_DEFAULT,
val positionalTracking: Boolean = false,
val handControllers: Boolean = false,
val joyConTracking: Boolean = false,
val steadyHandAim: Boolean = false,
val eyeHeightCm: Int = 160,
) {
val screen3dof: Boolean get() = picture3d == PICTURE_SCREEN && headTracking
companion object {
const val VR_FOV_MIN = 24
const val VR_FOV_MAX = 90
@@ -129,10 +147,13 @@ object Glasses {
private var lastModeSentAt = 0L
private val modeState = GlassesModeState(SBS_CONFIRM_MS)
private val confirmMode = Runnable { refresh() }
private val steamVrSpatialGate = SteamVrSpatialGate()
private val resumeSteamVrSpatial = Runnable { updateSpatialMode() }
private var glassesMode = 0
private var autoModeSent = false
@Volatile private var sink: PoseSink? = null
private val poseObservers = java.util.concurrent.CopyOnWriteArraySet<PoseSink>()
@Volatile private var probe: ((ByteArray?, FloatArray?) -> Unit)? = null
@Volatile private var latest: FloatArray? = null
@Volatile private var imuPackets = 0L
@@ -142,6 +163,7 @@ object Glasses {
fun init(context: Context) {
if (::handler.isInitialized) return
app = context.applicationContext
SpatialTracking.initialize(app)
usb = app.getSystemService(Context.USB_SERVICE) as UsbManager
displays = app.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
_settings.value = load()
@@ -200,13 +222,84 @@ object Glasses {
}
fun setRefreshHz(hz: Int) = post {
update { it.copy(refreshHz = hz.coerceIn(60, 120)) }
if (hz == 30 && (_state.value.sbs || !GlassesRendering.native30HzAvailable(_settings.value, _state.value))) return@post
update { it.copy(refreshHz = if (hz == 30) 30 else hz.coerceIn(60, 120)) }
if (!_state.value.sbs) applyMode(force = true)
}
fun setSbsHz(hz: Int) = post {
update { it.copy(sbsHz = if (hz >= 90) 90 else 60) }
if (_state.value.sbs && _state.value.can3d) sendMode(true)
if (hz == 30 && (!_state.value.sbs || !GlassesRendering.native30HzAvailable(_settings.value, _state.value))) return@post
update { it.copy(sbsHz = when { hz == 30 -> 30; hz >= 90 -> 90; else -> 60 }) }
if (hz != 30 && _state.value.sbs && _state.value.can3d) sendMode(true)
}
fun setResolution(profile: GlassesDisplayProfile, value: String) = post {
val s = _state.value
val choice = if (value == "auto") "" else value
if (choice.isNotEmpty() && s.resolutions.none { it.key == choice }) return@post
if (GlassesDisplayModes.choice(_settings.value, profile) == choice) return@post
update { when (profile) {
GlassesDisplayProfile.TWO_D -> it.copy(resolution2d = choice)
GlassesDisplayProfile.THREE_D -> it.copy(resolution3d = choice)
GlassesDisplayProfile.VR -> it.copy(resolutionVr = choice)
} }
val active = GlassesDisplayModes.profile(s.sbs, s.vrActive)
val inheritedByVr = s.vrActive && _settings.value.resolutionVr.isEmpty() &&
profile == GlassesDisplayModes.profile(s.sbs, false)
if ((profile == active || inheritedByVr) && s.canControl && !s.switching3d &&
model?.resolutions?.isNotEmpty() == true) sendMode(s.sbs)
}
fun prepareSteamVrStart(onPrepared: () -> Unit = {}) {
if (!::handler.isInitialized) {
steamVrSpatialGate.prepare()
onPrepared()
return
}
post {
if (steamVrSpatialGate.prepare()) {
handler.removeCallbacks(resumeSteamVrSpatial)
updateSpatialMode()
}
onPrepared()
}
}
fun cancelSteamVrStart() {
if (!::handler.isInitialized) {
steamVrSpatialGate.cancel()
return
}
post {
steamVrSpatialGate.cancel()
handler.removeCallbacks(resumeSteamVrSpatial)
updateSpatialMode()
}
}
fun setVrActive(on: Boolean) = post {
val s = _state.value
val trackingChanged = steamVrSpatialGate.setRunning(on, SystemClock.uptimeMillis())
if (trackingChanged) {
handler.removeCallbacks(resumeSteamVrSpatial)
steamVrSpatialGate.deadline?.let {
Log.i(TAG, "SteamVR RUNNING; spatial resume scheduled at uptime ${it}ms")
handler.postAtTime(resumeSteamVrSpatial, it)
}
}
if (s.vrActive == on) {
if (trackingChanged) updateSpatialMode()
return@post
}
val previous = GlassesDisplayModes.selected(_settings.value, s)
publish { it.copy(vrActive = on) }
val restore2d = !on && modeState.stopVr()
val stereo = (s.sbs || modeState.pendingStereo) && !restore2d
val selected = GlassesDisplayModes.selected(_settings.value, _state.value, stereo)
if (s.canControl && s.usable && (restore2d ||
(previous != selected && model?.resolutions?.isNotEmpty() == true)) &&
!(on && _settings.value.auto3d && !s.sbs)) sendMode(stereo)
updateSpatialMode()
}
fun set3d(on: Boolean) = post {
@@ -255,7 +348,59 @@ object Glasses {
markSbsFailed(linkDevice?.productId, s == GlassesState.SBS_FAILS)
rememberSbs(s)
}
fun setGameOnGlasses(on: Boolean) = post { update { it.copy(gameOnGlasses = on) } }
fun setGameOnGlasses(on: Boolean) = post {
val s = _state.value
if (!on && s.canControl && (s.sbs || modeState.pendingStereo)) {
modeState.manual()
sendMode(false)
}
update {
it.copy(
gameOnGlasses = on,
picture3d = if (!on && it.picture3d == GlassesSettings.PICTURE_SCREEN)
GlassesSettings.PICTURE_AUTO else it.picture3d,
)
}
}
fun set3dof(on: Boolean) = post {
if (!_state.value.supportsScreen3dof) return@post
update {
it.copy(
gameOnGlasses = on || it.gameOnGlasses,
headTracking = on || it.headTracking,
picture3d = if (on) GlassesSettings.PICTURE_SCREEN
else if (it.picture3d == GlassesSettings.PICTURE_SCREEN) GlassesSettings.PICTURE_AUTO else it.picture3d,
)
}
}
fun setPositionalTracking(on: Boolean) = post {
if (!_state.value.spatialConnected || _settings.value.positionalTracking == on) return@post
update { it.copy(positionalTracking = on, handControllers = it.handControllers && on,
joyConTracking = it.joyConTracking && on) }
if (!on) SpatialTracking.setControllers(false, false)
closeLink()
refresh()
}
fun setHandControllers(on: Boolean) = post {
val wanted = on && _settings.value.positionalTracking && _state.value.spatialConnected
update { it.copy(handControllers = wanted, joyConTracking = it.joyConTracking && !wanted) }
SpatialTracking.setControllers(wanted, _settings.value.joyConTracking)
}
fun setSteadyHandAim(on: Boolean) = post {
update { it.copy(steadyHandAim = on) }
SpatialTracking.setSteadyHandAim(on)
}
fun setJoyConTracking(on: Boolean) = post {
val wanted = on && _settings.value.positionalTracking && _state.value.spatialConnected
update { it.copy(joyConTracking = wanted, handControllers = it.handControllers && !wanted) }
SpatialTracking.setControllers(_settings.value.handControllers, wanted)
}
fun setEyeHeight(cm: Int) = post { update { it.copy(eyeHeightCm = cm.coerceIn(50, 250)) } }
fun setSpatialFloor() = SpatialTracking.setFloor(_settings.value.eyeHeightCm / 100f)
fun restartSpatialTracking() = post {
if (_state.value.spatialConnected && _settings.value.positionalTracking) { closeLink(); refresh() }
}
fun setHeadTracking(on: Boolean) = post { update { it.copy(headTracking = on) } }
fun setAuto3d(on: Boolean) = post { update { it.copy(auto3d = on) } }
fun setPicture3d(picture: Int) = post { update { it.copy(picture3d = picture.coerceIn(0, 2)) } }
@@ -264,6 +409,7 @@ object Glasses {
fun setVrFovDeg(deg: Int) = post { update { it.copy(vrFovDeg = deg.coerceIn(GlassesSettings.VR_FOV_MIN, GlassesSettings.VR_FOV_MAX)) } }
fun outputHz(): Int = GlassesRendering.outputHz(_settings.value, _state.value)
fun outputResolution(): GlassesResolution? = GlassesDisplayModes.selected(_settings.value, _state.value)
fun glassesDisplay(): Display? = _state.value.displayId.takeIf { it != Display.INVALID_DISPLAY }?.let { displays.getDisplay(it) }
@@ -277,6 +423,16 @@ object Glasses {
post { updateImu() }
}
internal fun addPoseObserver(observer: PoseSink) {
poseObservers.add(observer)
post { updateImu() }
}
internal fun removePoseObserver(observer: PoseSink) {
poseObservers.remove(observer)
post { updateImu() }
}
fun simulate(seconds: Int, holdYaw: Double? = null, holdPitch: Double? = null): Boolean {
val hold = holdYaw != null || holdPitch != null
val t = Thread({
@@ -312,6 +468,15 @@ object Glasses {
.put("permission", s.permission.name).put("connected", s.connected)
.put("controlError", s.controlError ?: JSONObject.NULL)
.put("experimental", s.experimental)
.put("supportsScreen3dof", s.supportsScreen3dof)
.put("supportsSpatial", s.supportsSpatial)
.put("firmware", (link as? VitureSdkLink)?.firmware ?: JSONObject.NULL)
.put("spatialSdk", (link as? VitureSdkLink)?.spatialDiagnostics()?.let {
JSONObject().put("status",it[0]).put("callbackAgeMs",it[1]).put("cameraPoseDeltaMs",it[2])
} ?: JSONObject.NULL)
.put("spatial", SpatialTracking.diagnosticStatus())
.put("supports30Hz", s.supports30Hz).put("vrActive", s.vrActive)
.put("resolutions", JSONArray(s.resolutions.map { it.key }))
.put("display", if (s.hasDisplay) JSONObject().put("id", s.displayId).put("width", s.width).put("height", s.height).put("hz", s.hz.toDouble()) else JSONObject.NULL)
.put("sbs", s.sbs).put("deviceMode", glassesMode).put("switching3d", s.switching3d)
.put("brightness", s.brightness).put("volume", s.volume)
@@ -325,6 +490,11 @@ object Glasses {
.put("fovDeg", s.diagonalFovDeg.toDouble()).put("noSignal", s.noSignal).put("sbsSupport", s.sbsSupport)
.put("settings", JSONObject().put("refreshHz", g.refreshHz).put("sbsHz", g.sbsHz).put("sunblock", g.sunblock ?: JSONObject.NULL)
.put("gameOnGlasses", g.gameOnGlasses).put("headTracking", g.headTracking).put("auto3d", g.auto3d)
.put("screen3dof", g.screen3dof)
.put("resolution2d", g.resolution2d).put("resolution3d", g.resolution3d).put("resolutionVr", g.resolutionVr)
.put("positionalTracking", g.positionalTracking).put("handControllers", g.handControllers)
.put("joyConTracking", g.joyConTracking).put("eyeHeightCm", g.eyeHeightCm)
.put("steadyHandAim", g.steadyHandAim)
.put("picture3d", g.picture3d).put("screenWidthDm", g.screenWidthDm).put("screenDistanceDm", g.screenDistanceDm)
.put("vrFovDeg", g.vrFovDeg))
}
@@ -450,6 +620,14 @@ object Glasses {
noSignal = noSignal,
sbs90 = m?.sbs90 == true,
experimental = m === XrealModel,
supportsScreen3dof = link?.hasImu == true,
supports30Hz = display?.let(::supports30Hz) == true,
supportsSpatial = (link as? VitureSdkLink)?.isCarina == true,
resolutions = m?.resolutions?.takeIf { modes -> modes.isNotEmpty() }
?: display?.supportedModes.orEmpty().filter { mode ->
mode.physicalWidth >= 1280 && (mode.physicalWidth >= 3000) == sbsByDisplay
}.map { mode -> GlassesResolution(mode.physicalWidth / (if (sbsByDisplay) 2 else 1), mode.physicalHeight) }
.distinct().sortedWith(compareBy({ size -> size.height }, { size -> size.width })),
)
}
if (link != null) applyMode(force = false)
@@ -458,6 +636,14 @@ object Glasses {
internal fun isGlassesVendor(vid: Int) = vid == VitureProtocol.VENDOR_ID || RayNeoModel.isVendor(vid) || vid == XrealModel.VENDOR_ID
internal fun supports30Hz(display: Display): Boolean {
val current = display.mode
return display.supportedModes.any {
it.physicalWidth == current.physicalWidth && it.physicalHeight == current.physicalHeight &&
abs(it.refreshRate - 30f) < 0.5f
}
}
private fun canOpenModel(m: GlassesModel): Boolean = m.controllable &&
(m !is VitureProtocol.Model || !m.needsSdk || VitureSdk.available)
@@ -511,14 +697,29 @@ object Glasses {
runCatching { usb.requestPermission(device, pi) }.onFailure { Log.w(TAG, "requestPermission", it) }
}
private fun updateSpatialMode() {
val current = link as? VitureSdkLink ?: return
if (!current.ready || !current.isCarina || current.spatialEnabled ==
steamVrSpatialGate.spatialEnabled(_settings.value.positionalTracking, SystemClock.uptimeMillis())) return
val device = linkDevice
closeLink()
refresh()
val resumed = link as? VitureSdkLink
if (!current.spatialEnabled && resumed?.isCarina == true && resumed.spatialEnabled &&
device != null && linkDevice?.deviceName == device.deviceName) SpatialTracking.recenter(false)
}
private fun openLink(device: UsbDevice) {
val generation = ++linkGeneration
val spatial = steamVrSpatialGate.spatialEnabled(_settings.value.positionalTracking, SystemClock.uptimeMillis())
calibrationKey = calibrationStore.key(device.vendorId, device.productId,
runCatching { device.serialNumber }.getOrNull(), model?.name.orEmpty())
val l = if (device.vendorId == VitureProtocol.VENDOR_ID) {
if ((model as? VitureProtocol.Model)?.needsSdk == true) {
VitureSdkLink.open(usb, device, File(app.cacheDir, "viture"), sdkListener(generation),
(model as? VitureProtocol.Model)?.family)
// Android can evict cacheDir while the SDK is running.
Log.i(TAG, "opening VITURE SDK pid=0x%04X spatial=%s".format(device.productId, spatial))
VitureSdkLink.open(usb, device, File(app.noBackupFilesDir, "viture"), sdkListener(generation),
(model as? VitureProtocol.Model)?.family, spatial)
} else VitureLink.open(usb, device, linkListener(generation))
} else if (model === XrealModel) {
XrealLink.open(app, xrealListener(generation))
@@ -536,6 +737,12 @@ object Glasses {
return
}
link = opened
if (opened is VitureSdkLink && opened.isCarina) SpatialTracking.connect(opened.sdkToken, generation,
opened.spatialIdentity ?: runCatching { device.serialNumber }.getOrNull()?.takeIf { it.isNotBlank() }
?.let { "${device.vendorId}:${device.productId}:$it" },
spatial, _settings.value.eyeHeightCm / 100f, _settings.value.handControllers,
_settings.value.joyConTracking, _settings.value.steadyHandAim)
else SpatialTracking.useLegacyTracking()
_calibration.value = GlassesCalibrationState(supported = opened is RayNeoLink,
saved = opened is RayNeoLink && calibrationKey?.let(calibrationStore::load) != null)
linkDevice = device
@@ -584,6 +791,9 @@ object Glasses {
}
private fun sdkListener(generation: Long) = object : VitureSdkLink.Listener {
override fun onSpatial(pose: FloatArray, timeNs: Long, valid: Boolean) {
if (generation == linkGeneration) SpatialTracking.accept(pose, timeNs, generation, valid)
}
override fun onValue(msgId: Int, value: Int) = post {
if (generation != linkGeneration) return@post
when (msgId) {
@@ -652,13 +862,14 @@ object Glasses {
private fun closeLink() {
val l = link ?: return
linkGeneration++
SpatialTracking.disconnect()
link = null
linkDevice = null
calibrationKey = null
_calibration.value = GlassesCalibrationState(phase = if (_calibration.value.running) CalibrationPhase.FAILED else CalibrationPhase.IDLE)
glassesMode = 0
l.close()
publish { it.copy(connected = false, tracking = false) }
publish { it.copy(connected = false, tracking = false, supportsScreen3dof = false, supportsSpatial = false) }
Log.i(TAG, "link closed")
}
@@ -688,10 +899,14 @@ object Glasses {
}
if (!s.usable || s.sbs || modeState.pendingStereo) return
val hz = _settings.value.refreshHz
if (glassesMode == m.mode(hz, false)) return
// Native 30 Hz is selected by the Android presentation, without a vendor mode command.
if (hz == 30 && GlassesRendering.native30HzAvailable(_settings.value, s)) return
if (glassesMode == m.mode(hz, false, outputResolution())) return
if (!force) {
if (autoModeSent || SystemClock.uptimeMillis() - lastModeSentAt < 8_000) return
if (glassesMode == 0 && abs(s.hz - hz) < 1.5f) return
val resolution = outputResolution()
if (glassesMode == 0 && abs(s.hz - hz) < 1.5f &&
(resolution == null || (s.width == resolution.width && s.height == resolution.height))) return
autoModeSent = true
}
sendMode(false)
@@ -699,8 +914,10 @@ object Glasses {
private fun sendMode(sbs: Boolean): Boolean {
val m = model ?: return false
if (sbs && !_settings.value.gameOnGlasses) update { it.copy(gameOnGlasses = true) }
val g = _settings.value
val mode = m.mode(if (sbs) g.sbsHz else g.refreshHz, sbs)
val resolution = GlassesDisplayModes.selected(g, _state.value, sbs)
val mode = m.mode(if (sbs) g.sbsHz else g.refreshHz, sbs, resolution)
if (link?.send(VitureProtocol.MSG_DISPLAY, byteArrayOf(mode.toByte())) == true) {
if (link is RayNeoLink) glassesMode = mode
lastModeSentAt = SystemClock.uptimeMillis()
@@ -728,7 +945,7 @@ object Glasses {
private fun updateImu() {
val l = link ?: return
if (!l.ready) return
val want = (sink != null || probe != null || _calibration.value.running) && l.hasImu
val want = (sink != null || poseObservers.isNotEmpty() || probe != null || _calibration.value.running || SpatialTracking.enabled) && l.hasImu
if (want == _state.value.tracking) return
val accepted = l.send(VitureProtocol.MSG_IMU, byteArrayOf(if (want) 1 else 0))
if (want && !accepted) return
@@ -769,6 +986,7 @@ object Glasses {
private fun deliver(q: FloatArray, timeNs: Long) {
latest = q
sink?.onPose(q[0], q[1], q[2], q[3], timeNs)
poseObservers.forEach { it.onPose(q[0], q[1], q[2], q[3], timeNs) }
}
private fun onReport(frame: VitureProtocol.Frame) {
@@ -832,8 +1050,16 @@ object Glasses {
private fun load(): GlassesSettings {
val p = app.getSharedPreferences(PREFS, Context.MODE_PRIVATE)
return GlassesSettings(
positionalTracking = p.getBoolean("positionalTracking", false),
handControllers = p.getBoolean("handControllers", false),
joyConTracking = p.getBoolean("joyConTracking", false),
steadyHandAim = p.getBoolean("steadyHandAim", false),
eyeHeightCm = p.getInt("eyeHeightCm", 160).coerceIn(50, 250),
refreshHz = p.getInt("refreshHz", 120),
sbsHz = p.getInt("sbsHz", 60),
resolution2d = p.getString("resolution2d", "").orEmpty(),
resolution3d = p.getString("resolution3d", "").orEmpty(),
resolutionVr = p.getString("resolutionVr", "").orEmpty(),
sunblock = if (p.contains("sunblock")) p.getBoolean("sunblock", false) else null,
gameOnGlasses = p.getBoolean("gameOnGlasses", true),
headTracking = p.getBoolean("headTracking", true),
@@ -847,8 +1073,16 @@ object Glasses {
private fun save(s: GlassesSettings) {
app.getSharedPreferences(PREFS, Context.MODE_PRIVATE).edit().apply {
putBoolean("positionalTracking", s.positionalTracking)
putBoolean("handControllers", s.handControllers)
putBoolean("joyConTracking", s.joyConTracking)
putBoolean("steadyHandAim", s.steadyHandAim)
putInt("eyeHeightCm", s.eyeHeightCm)
putInt("refreshHz", s.refreshHz)
putInt("sbsHz", s.sbsHz)
putString("resolution2d", s.resolution2d)
putString("resolution3d", s.resolution3d)
putString("resolutionVr", s.resolutionVr)
s.sunblock?.let { putBoolean("sunblock", it) } ?: remove("sunblock")
putBoolean("gameOnGlasses", s.gameOnGlasses)
putBoolean("headTracking", s.headTracking)
@@ -0,0 +1,29 @@
package com.droiddeck.launcher.glasses
data class GlassesResolution(val width: Int, val height: Int) {
val key: String get() = "${width}x$height"
}
enum class GlassesDisplayProfile { TWO_D, THREE_D, VR }
internal object GlassesDisplayModes {
fun profile(stereo: Boolean, vr: Boolean): GlassesDisplayProfile = when {
vr -> GlassesDisplayProfile.VR
stereo -> GlassesDisplayProfile.THREE_D
else -> GlassesDisplayProfile.TWO_D
}
fun choice(settings: GlassesSettings, profile: GlassesDisplayProfile): String = when (profile) {
GlassesDisplayProfile.TWO_D -> settings.resolution2d
GlassesDisplayProfile.THREE_D -> settings.resolution3d
GlassesDisplayProfile.VR -> settings.resolutionVr
}
fun selected(settings: GlassesSettings, state: GlassesState,
stereo: Boolean = state.sbs, vr: Boolean = state.vrActive): GlassesResolution? {
val selected = choice(settings, profile(stereo, vr)).ifEmpty {
if (vr) choice(settings, profile(stereo, false)) else ""
}
return state.resolutions.firstOrNull { it.key == selected }
}
}
@@ -9,6 +9,8 @@ interface GlassesModel {
val diagonalFovDeg: Float
val sbs90: Boolean
val hasSunblock: Boolean get() = true
val resolutions: List<GlassesResolution> get() = emptyList()
fun mode(hz: Int, sbs: Boolean, resolution: GlassesResolution?): Int = mode(hz, sbs)
fun mode(hz: Int, sbs: Boolean): Int
fun modeForStep(step: Int): Int?
}
@@ -0,0 +1,175 @@
package com.droiddeck.launcher.glasses
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.graphics.Color
import android.graphics.PorterDuff
import android.graphics.SurfaceTexture
import android.os.Build
import android.os.Looper
import android.view.Choreographer
import android.view.Surface
import android.view.View
import android.view.WindowManager
import android.view.inspector.WindowInspector
import kotlin.math.min
import kotlin.math.roundToInt
class GlassesMenuCapture(private val activity: Activity, private val onSize: (Int, Int) -> Unit,
private val onFailure: (Throwable) -> Unit = {}) : Choreographer.FrameCallback {
private var texture: SurfaceTexture? = null
private var surface: Surface? = null
private var running = false
private var framePending = false
private var lastFrameNs = Long.MIN_VALUE
private var failures = 0
private var bufferWidth = 0
private var bufferHeight = 0
private val basePosition = IntArray(2)
private val windowPosition = IntArray(2)
fun attach(texture: SurfaceTexture, surface: Surface) {
checkMainThread()
this.texture = texture
this.surface = surface
bufferWidth = 0
bufferHeight = 0
failures = 0
lastFrameNs = Long.MIN_VALUE
}
fun start() {
checkMainThread()
if (running || activity.isDestroyed || activity.isFinishing) return
running = true
failures = 0
lastFrameNs = Long.MIN_VALUE
schedule()
}
fun stop() {
checkMainThread()
running = false
if (framePending) Choreographer.getInstance().removeFrameCallback(this)
framePending = false
}
override fun doFrame(frameTimeNanos: Long) {
framePending = false
if (!running) return
if (activity.isDestroyed || activity.isFinishing) {
stop()
return
}
if (lastFrameNs == Long.MIN_VALUE || frameTimeNanos - lastFrameNs >= FRAME_INTERVAL_NS) {
lastFrameNs = frameTimeNanos
try {
capture()
failures = 0
} catch (error: Exception) {
if (++failures >= MAX_FAILURES) {
stop()
onFailure(error)
}
}
}
if (running) schedule()
}
private fun schedule() {
if (framePending) return
framePending = true
Choreographer.getInstance().postFrameCallback(this)
}
private fun capture() {
val target = surface ?: return
val input = texture ?: return
val decor = activity.window.decorView
if (!decor.isAttachedToWindow || !decor.isShown) return
val size = captureSize(decor.width, decor.height) ?: return
check(target.isValid)
if (bufferWidth != size.first || bufferHeight != size.second) {
input.setDefaultBufferSize(size.first, size.second)
onSize(size.first, size.second)
bufferWidth = size.first
bufferHeight = size.second
}
val candidates = if (Build.VERSION.SDK_INT >= 29) WindowInspector.getGlobalWindowViews() else emptyList()
val roots = captureRoots(activity, candidates)
val canvas = target.lockHardwareCanvas()
val initialSave = canvas.save()
try {
canvas.drawColor(Color.BLACK, PorterDuff.Mode.SRC)
val scale = min(canvas.width.toFloat() / decor.width, canvas.height.toFloat() / decor.height)
canvas.translate((canvas.width - decor.width * scale) / 2f, (canvas.height - decor.height * scale) / 2f)
canvas.scale(scale, scale)
canvas.clipRect(0, 0, decor.width, decor.height)
decor.getLocationOnScreen(basePosition)
for (root in roots) {
val params = root.layoutParams as? WindowManager.LayoutParams
if (root !== decor && params != null && params.flags and WindowManager.LayoutParams.FLAG_DIM_BEHIND != 0) {
canvas.drawColor(Color.argb((params.dimAmount.coerceIn(0f, 1f) * 255).roundToInt(), 0, 0, 0))
}
root.getLocationOnScreen(windowPosition)
val saved = canvas.save()
try {
canvas.translate((windowPosition[0] - basePosition[0]).toFloat(), (windowPosition[1] - basePosition[1]).toFloat())
root.draw(canvas)
} finally {
canvas.restoreToCount(saved)
}
}
} finally {
try {
canvas.restoreToCount(initialSave)
} finally {
target.unlockCanvasAndPost(canvas)
}
}
}
private fun checkMainThread() {
check(Looper.myLooper() == Looper.getMainLooper())
}
internal companion object {
private const val FRAME_INTERVAL_NS = 33_333_334L
private const val MAX_FAILURES = 3
private const val MAX_EDGE = 1920
internal fun captureSize(width: Int, height: Int): Pair<Int, Int>? {
if (width <= 0 || height <= 0) return null
val scale = min(1.0, MAX_EDGE.toDouble() / maxOf(width, height))
return (width * scale).roundToInt().coerceAtLeast(1) to (height * scale).roundToInt().coerceAtLeast(1)
}
internal fun captureRoots(activity: Activity, candidates: List<View>): List<View> {
val decor = activity.window.decorView
if (activity.window.attributes.flags and WindowManager.LayoutParams.FLAG_SECURE != 0 ||
!decor.isAttachedToWindow || !decor.isShown) return emptyList()
if (Build.VERSION.SDK_INT < 29) return listOf(decor)
val display = decor.display?.displayId ?: return listOf(decor)
val token = decor.applicationWindowToken
return listOf(decor) + candidates.filter { root ->
if (root === decor || !root.isAttachedToWindow || !root.isShown || root.display?.displayId != display) return@filter false
val params = root.layoutParams as? WindowManager.LayoutParams ?: return@filter false
if (params.flags and WindowManager.LayoutParams.FLAG_SECURE != 0 ||
params.type !in WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW..WindowManager.LayoutParams.LAST_SUB_WINDOW) return@filter false
ownsContext(root.context, activity) || (token != null && root.applicationWindowToken == token)
}.distinct()
}
private fun ownsContext(context: Context, activity: Activity): Boolean {
var current = context
repeat(16) {
if (current === activity) return true
val next = (current as? ContextWrapper)?.baseContext ?: return false
if (next === current) return false
current = next
}
return false
}
}
}
@@ -0,0 +1,219 @@
package com.droiddeck.launcher.glasses
import android.app.Activity
import android.app.Application
import android.app.Presentation
import android.os.Bundle
import android.util.Log
import android.view.Display
import android.view.WindowManager
import com.droiddeck.launcher.SessionActivity
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
object GlassesMenuMirror {
private var controller: GlassesMenuController? = null
fun initialize(app: Application) {
if (controller != null) return
val pose = GlassesMenuPose()
val menus = GlassesMenuController(pose,
display = Glasses::glassesDisplay,
observe = { active -> if (active) Glasses.addPoseObserver(pose) else Glasses.removePoseObserver(pose) },
create = { activity, display, failure -> MenuPresentation(activity, display, pose, failure) })
controller = menus
app.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks {
override fun onActivityResumed(activity: Activity) = menus.resume(activity)
override fun onActivityPaused(activity: Activity) = menus.pause(activity)
override fun onActivityDestroyed(activity: Activity) = menus.pause(activity)
override fun onActivityCreated(activity: Activity, state: Bundle?) = Unit
override fun onActivityStarted(activity: Activity) = Unit
override fun onActivityStopped(activity: Activity) = Unit
override fun onActivitySaveInstanceState(activity: Activity, state: Bundle) = Unit
})
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate).launch {
combine(Glasses.state, Glasses.settings) { state, settings -> state to settings }
.collect { (state, settings) -> menus.update(state, settings) }
}
}
fun setSessionMenuVisible(activity: Activity, visible: Boolean) {
controller?.setSessionMenuVisible(activity, visible)
}
fun recenter() { controller?.recenter() }
}
internal class GlassesMenuController(
private val pose: GlassesMenuPose,
private val display: () -> Display?,
private val observe: (Boolean) -> Unit,
private val create: (Activity, Display, (Throwable) -> Unit) -> Output,
private val schedule: (() -> Unit) -> Unit = { android.os.Handler(android.os.Looper.getMainLooper()).post(it) },
) {
interface Output {
fun configure(state: GlassesState, settings: GlassesSettings)
fun show()
fun close()
}
private var activity: Activity? = null
private val sessionMenus = java.util.WeakHashMap<Activity, Boolean>()
private var state = GlassesState()
private var settings = GlassesSettings()
private var output: Output? = null
private var outputDisplay = Display.INVALID_DISPLAY
private var failed = false
private var dismissalRetried = false
private var generation = 0
fun resume(owner: Activity) {
if (activity !== owner) close()
activity = owner
failed = false
dismissalRetried = false
reconcile()
}
fun pause(owner: Activity) {
if (activity !== owner) return
close()
activity = null
}
fun setSessionMenuVisible(owner: Activity, visible: Boolean) {
if (visible) sessionMenus[owner] = true else sessionMenus.remove(owner)
if (activity === owner) reconcile()
}
fun update(next: GlassesState, preferences: GlassesSettings) {
val deviceChanged = state.canControl != next.canControl || state.model != next.model
if (deviceChanged) pose.reset()
if (deviceChanged || state.displayId != next.displayId || state.width != next.width ||
state.height != next.height || state.sbs != next.sbs || settings != preferences) {
failed = false
dismissalRetried = false
}
state = next
settings = preferences
pose.configure(preferences)
reconcile()
}
fun recenter() = pose.recenter()
private fun reconcile() {
val owner = activity
val eligible = owner != null && !owner.isFinishing && !owner.isDestroyed && !owner.isInPictureInPictureMode &&
owner.window.attributes.flags and WindowManager.LayoutParams.FLAG_SECURE == 0 &&
owner !is com.droiddeck.launcher.QuestXrActivity &&
(owner !is SessionActivity || sessionMenus[owner] == true) &&
settings.gameOnGlasses && settings.screen3dof && state.usable && state.canControl && state.supportsScreen3dof
val target = if (eligible && !failed) display() else null
if (target == null || owner == null || owner.windowManager.defaultDisplay.displayId == target.displayId) {
close()
return
}
if (outputDisplay != target.displayId) close()
try {
output?.let { it.configure(state, settings); return }
val token = ++generation
val next = create(owner, target) { error ->
if (generation == token) {
close()
if (error is GlassesMenuDisplayStopped && !dismissalRetried) {
dismissalRetried = true
val retry = generation
schedule { if (generation == retry && activity === owner) reconcile() }
} else {
Log.w("GlassesMenu", "Tracked menus are unavailable for this display", error)
failed = true
}
}
}
if (generation != token) {
next.close()
return
}
output = next
outputDisplay = target.displayId
next.configure(state, settings)
if (generation != token) return
observe(true)
next.show()
} catch (error: RuntimeException) {
Log.w("GlassesMenu", "Could not show tracked menus", error)
failed = true
close()
}
}
private fun close() {
++generation
val previous = output
output = null
outputDisplay = Display.INVALID_DISPLAY
if (previous != null) {
observe(false)
try { previous.close() }
catch (error: RuntimeException) { Log.w("GlassesMenu", "Could not close tracked menus", error) }
}
}
}
internal class GlassesMenuDisplayStopped : IllegalStateException("The glasses menu display stopped")
private class MenuPresentation(
private val owner: Activity,
display: Display,
pose: GlassesMenuPose,
private val onFailure: (Throwable) -> Unit,
) : Presentation(owner, display), GlassesMenuController.Output {
private var closed = false
private val capture: GlassesMenuCapture = GlassesMenuCapture(owner,
onSize = { width, height -> surface.setSourceSize(width, height) }, onFailure = onFailure)
private val surface: GlassesMenuSurface = GlassesMenuSurface(context, pose, onFailure = onFailure) { texture, input ->
if (!closed) {
capture.attach(texture, input)
capture.start()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
setContentView(surface)
}
override fun configure(state: GlassesState, settings: GlassesSettings) {
surface.configure(settings, state)
val mode = display.mode
val hz = if (state.sbs) settings.sbsHz else settings.refreshHz
val best = display.supportedModes.filter {
it.physicalWidth == mode.physicalWidth && it.physicalHeight == mode.physicalHeight
}.minByOrNull { kotlin.math.abs(it.refreshRate - hz) }
if (best != null) window?.let { it.attributes = it.attributes.apply { preferredDisplayModeId = best.modeId } }
}
override fun close() {
if (closed) return
closed = true
capture.stop()
surface.close()
dismiss()
}
override fun onStop() {
capture.stop()
surface.close()
super.onStop()
if (!closed) {
closed = true
onFailure(GlassesMenuDisplayStopped())
}
}
}
@@ -0,0 +1,86 @@
package com.droiddeck.launcher.glasses
import com.droiddeck.launcher.gpu.HeadPose
class GlassesMenuPose internal constructor(
private val spatialEnabled: () -> Boolean = { SpatialTracking.enabled },
private val spatialView: (Long) -> SpatialPose? = { now ->
synchronized(SpatialTracking.session) {
if (SpatialTracking.session.valid(now)) SpatialTracking.session.view(true) else null
}
},
private val recenterSpatial: () -> Unit = { SpatialTracking.recenter(true) },
) : Glasses.PoseSink {
data class Frame(
val rotation: FloatArray,
val position: FloatArray,
val valid: Boolean,
val spatial: Boolean,
)
private var tracking = true
private var positional = false
private var reference: HeadPose.Reference? = null
private var latest: FloatArray? = null
private var latestTime = 0L
private var safe = identity()
@Synchronized fun configure(settings: GlassesSettings) {
if (tracking != settings.headTracking) {
reference = null
safe = identity()
}
tracking = settings.headTracking
positional = settings.positionalTracking
}
@Synchronized override fun onPose(x: Float, y: Float, z: Float, w: Float, timeNs: Long) {
val norm = x*x + y*y + z*z + w*w
if (!norm.isFinite() || norm !in 0.25f..2.25f || timeNs <= latestTime) return
val matrix = HeadPose.fromGlasses(x, y, z, w)
if (matrix.any { !it.isFinite() }) return
latest = matrix
latestTime = timeNs
}
@Synchronized fun snapshot(now: Long = System.nanoTime()): Frame {
if (!tracking) return identity()
val spatial = positional && spatialEnabled()
if (spatial) {
val pose = spatialView(now)
val normalized = pose?.rotation?.normalized()
if (pose != null && pose.position.finite() && normalized != null) {
safe = Frame(normalized.matrix(), pose.position.array(), true, true)
return copy(safe)
}
} else {
val matrix = latest
if (matrix != null && latestTime > 0 && now-latestTime in 0..MAX_AGE_NS) {
val anchor = reference ?: HeadPose.recenter(matrix, centerView = true).also { reference = it }
safe = Frame(HeadPose.relative(anchor, matrix), FloatArray(3), true, false)
return copy(safe)
}
}
return copy(safe).copy(valid = false, spatial = spatial)
}
@Synchronized fun recenter() {
if (positional && spatialEnabled()) recenterSpatial()
else reference = null
}
@Synchronized fun reset() {
reference = null
latest = null
latestTime = 0L
safe = identity()
}
private fun copy(frame: Frame) = frame.copy(rotation = frame.rotation.copyOf(), position = frame.position.copyOf())
private fun identity() = Frame(HeadPose.IDENTITY.copyOf(), FloatArray(3), false, false)
companion object {
internal const val MAX_AGE_NS = 100_000_000L
}
}
@@ -0,0 +1,291 @@
package com.droiddeck.launcher.glasses
import android.content.Context
import android.graphics.SurfaceTexture
import android.opengl.GLES11Ext
import android.opengl.GLES20
import android.opengl.GLSurfaceView
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.Surface
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
import kotlin.math.tan
class GlassesMenuSurface(
context: Context,
private val pose: GlassesMenuPose,
private val onFailure: (Throwable) -> Unit = {},
private val onInput: (SurfaceTexture, Surface) -> Unit,
) : GLSurfaceView(context) {
private data class Scene(val settings: GlassesSettings, val state: GlassesState)
private val main = Handler(Looper.getMainLooper())
private val closed = AtomicBoolean()
private val failed = AtomicBoolean()
private val generation = AtomicInteger()
private val pendingFrame = AtomicBoolean()
private val inputLock = Any()
private var inputTexture: SurfaceTexture? = null
private var inputSurface: Surface? = null
@Volatile private var scene = Scene(GlassesSettings(), GlassesState())
@Volatile private var sourceAspect = 16f / 9f
private val renderer = MenuRenderer()
init {
setEGLContextClientVersion(2)
setEGLConfigChooser(8, 8, 8, 8, 0, 0)
preserveEGLContextOnPause = true
setRenderer(renderer)
renderMode = RENDERMODE_CONTINUOUSLY
}
fun configure(settings: GlassesSettings, state: GlassesState) {
pose.configure(settings)
scene = Scene(settings, state)
}
fun setSourceSize(width: Int, height: Int) {
if (width > 0 && height > 0) sourceAspect = width.toFloat() / height
}
fun close() {
if (!closed.compareAndSet(false, true)) return
generation.incrementAndGet()
queueEvent { renderer.release() }
}
override fun onDetachedFromWindow() {
close()
super.onDetachedFromWindow()
// GLSurfaceView may exit its GL thread before queued cleanup is consumed.
releaseInput()
}
private fun releaseInput() = synchronized(inputLock) {
inputTexture?.setOnFrameAvailableListener(null)
inputSurface?.release()
inputTexture?.release()
inputSurface = null
inputTexture = null
pendingFrame.set(false)
}
private fun fail(error: Throwable) {
if (!failed.compareAndSet(false, true)) return
generation.incrementAndGet()
Log.e("GlassesMenu", "Menu rendering is unavailable", error)
main.post { if (!closed.get()) onFailure(error) }
}
private inner class MenuRenderer : Renderer {
private val vertices = ByteBuffer.allocateDirect(8 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().apply {
put(floatArrayOf(-1f, -1f, 1f, -1f, -1f, 1f, 1f, 1f)); position(0)
}
private val textureMatrix = FloatArray(16)
private var texture = 0
private var program = 0
private var width = 1
private var height = 1
private var hasFrame = false
private var positionLocation = 0
private var textureLocation = 0
private var transformLocation = 0
private var rotationLocation = IntArray(3)
private var headLocation = 0
private var screenLocation = 0
private var opticsLocation = 0
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
releaseInput()
texture = 0
program = 0
hasFrame = false
if (closed.get() || failed.get()) return
val token = generation.incrementAndGet()
try {
program = linkProgram()
positionLocation = GLES20.glGetAttribLocation(program, "aPosition")
textureLocation = GLES20.glGetUniformLocation(program, "uTexture")
transformLocation = GLES20.glGetUniformLocation(program, "uTextureMatrix")
rotationLocation = IntArray(3) { GLES20.glGetUniformLocation(program, "uRotation$it") }
headLocation = GLES20.glGetUniformLocation(program, "uHead")
screenLocation = GLES20.glGetUniformLocation(program, "uScreen")
opticsLocation = GLES20.glGetUniformLocation(program, "uOptics")
val names = IntArray(1)
GLES20.glGenTextures(1, names, 0)
texture = names[0]
check(texture != 0) { "Could not allocate the menu texture" }
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture)
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR)
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR)
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE)
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE)
checkGl("initialize menu texture")
val incoming = SurfaceTexture(texture)
val surface = synchronized(inputLock) {
inputTexture = incoming
Surface(incoming).also { inputSurface = it }
}
incoming.setOnFrameAvailableListener({
if (!closed.get() && generation.get() == token) pendingFrame.set(true)
}, main)
main.post {
if (!closed.get() && !failed.get() && generation.get() == token) onInput(incoming, surface)
}
} catch (e: RuntimeException) {
fail(e)
release()
}
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
this.width = width.coerceAtLeast(1)
this.height = height.coerceAtLeast(1)
GLES20.glViewport(0, 0, this.width, this.height)
}
override fun onDrawFrame(gl: GL10?) {
if (closed.get() || failed.get()) return
GLES20.glClearColor(0.015f, 0.015f, 0.02f, 1f)
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
if (program == 0) return
if (pendingFrame.getAndSet(false)) synchronized(inputLock) {
inputTexture?.let {
try {
it.updateTexImage()
it.getTransformMatrix(textureMatrix)
checkGl("update menu texture")
hasFrame = true
} catch (e: RuntimeException) {
fail(e)
release()
return
}
}
}
if (!hasFrame) return
val current = scene
val frame = pose.snapshot()
val halfWidth = current.settings.screenWidthDm.coerceIn(5, 200) / 20f
val distance = current.settings.screenDistanceDm.coerceIn(5, 200) / 10f
val mono = !current.state.sbs
val fov = current.state.verticalFovDeg.takeIf { it.isFinite() }?.coerceIn(20f, 140f) ?: 23.3f
val tangent = tan(Math.toRadians(fov.toDouble()) / 2).toFloat()
GLES20.glUseProgram(program)
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture)
GLES20.glUniform1i(textureLocation, 0)
GLES20.glUniformMatrix4fv(transformLocation, 1, false, textureMatrix, 0)
for (row in 0..2) GLES20.glUniform3fv(rotationLocation[row], 1, frame.rotation, row * 3)
GLES20.glUniform3fv(headLocation, 1, frame.position, 0)
GLES20.glUniform3f(screenLocation, halfWidth, distance, sourceAspect)
GLES20.glUniform4f(opticsLocation, tangent, width.toFloat() / height / (if (mono) 1f else 2f),
if (mono) 0f else IPD_METERS / 2f, if (mono) 1f else 0f)
vertices.position(0)
GLES20.glEnableVertexAttribArray(positionLocation)
GLES20.glVertexAttribPointer(positionLocation, 2, GLES20.GL_FLOAT, false, 0, vertices)
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
GLES20.glDisableVertexAttribArray(positionLocation)
}
fun release() {
releaseInput()
if (texture != 0) GLES20.glDeleteTextures(1, intArrayOf(texture), 0)
if (program != 0) GLES20.glDeleteProgram(program)
texture = 0
program = 0
hasFrame = false
}
private fun checkGl(operation: String) {
val error = GLES20.glGetError()
check(error == GLES20.GL_NO_ERROR) { "Could not $operation: GL error 0x${error.toString(16)}" }
}
private fun shader(type: Int, source: String): Int {
val shader = GLES20.glCreateShader(type)
GLES20.glShaderSource(shader, source)
GLES20.glCompileShader(shader)
val status = IntArray(1)
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, status, 0)
if (status[0] == 0) {
val error = GLES20.glGetShaderInfoLog(shader)
GLES20.glDeleteShader(shader)
throw IllegalStateException(error)
}
return shader
}
private fun linkProgram(): Int {
val vertex = shader(GLES20.GL_VERTEX_SHADER, VERTEX)
var fragment = 0
var linked = 0
try {
fragment = shader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT)
linked = GLES20.glCreateProgram()
GLES20.glAttachShader(linked, vertex)
GLES20.glAttachShader(linked, fragment)
GLES20.glLinkProgram(linked)
val status = IntArray(1)
GLES20.glGetProgramiv(linked, GLES20.GL_LINK_STATUS, status, 0)
check(status[0] != 0) { GLES20.glGetProgramInfoLog(linked) }
return linked
} catch (e: RuntimeException) {
if (linked != 0) GLES20.glDeleteProgram(linked)
throw e
} finally {
GLES20.glDeleteShader(vertex)
if (fragment != 0) GLES20.glDeleteShader(fragment)
}
}
}
companion object {
internal const val IPD_METERS = 0.064f
private const val VERTEX = """
attribute vec2 aPosition;
varying vec2 vUv;
void main() {
gl_Position = vec4(aPosition, 0.0, 1.0);
vUv = aPosition * 0.5 + 0.5;
}
"""
private const val FRAGMENT = """
#extension GL_OES_EGL_image_external : require
precision highp float;
uniform samplerExternalOES uTexture;
uniform mat4 uTextureMatrix;
uniform vec3 uRotation0, uRotation1, uRotation2, uHead, uScreen;
uniform vec4 uOptics;
varying vec2 vUv;
vec3 rotate(vec3 v) {
return vec3(dot(uRotation0, v), dot(uRotation1, v), dot(uRotation2, v));
}
void main() {
bool mono = uOptics.w > 0.5;
float eye = mono || vUv.x < 0.5 ? 0.0 : 1.0;
vec2 local = vec2(mono ? vUv.x : vUv.x * 2.0 - eye, 1.0 - vUv.y);
vec2 ray = (local - 0.5) * vec2(uOptics.y, 1.0) * (2.0 * uOptics.x);
vec3 direction = rotate(vec3(ray.x, -ray.y, -1.0));
vec3 origin = uHead + rotate(vec3((eye < 0.5 ? -1.0 : 1.0) * uOptics.z, 0.0, 0.0));
vec3 color = vec3(0.015, 0.015, 0.02);
if (direction.z < -0.0001) {
float distance = (-uScreen.y - origin.z) / direction.z;
vec3 hit = origin + direction * distance;
vec2 source = vec2(hit.x / uScreen.x, -hit.y * uScreen.z / uScreen.x) * 0.5 + 0.5;
if (distance > 0.0 && all(greaterThanEqual(source, vec2(0.0))) && all(lessThanEqual(source, vec2(1.0)))) {
vec2 uv = (uTextureMatrix * vec4(source.x, 1.0 - source.y, 0.0, 1.0)).xy;
color = texture2D(uTexture, uv).rgb;
}
}
gl_FragColor = vec4(color, 1.0);
}
"""
}
}
@@ -4,13 +4,32 @@ import com.droiddeck.launcher.gpu.Cardboard
/** Share the actual picture mode with settings so controls match the rendered view. */
internal object GlassesRendering {
enum class Picture { FLAT, VIRTUAL_SCREEN, STEREO }
enum class Picture { FLAT, DUPLICATED, VIRTUAL_SCREEN, STEREO }
fun outputHz(settings: GlassesSettings, state: GlassesState): Int =
if (state.sbs) if (state.sbs90) settings.sbsHz else 60 else settings.refreshHz
fun native30HzAvailable(settings: GlassesSettings, state: GlassesState): Boolean =
state.usable && state.supports30Hz && settings.gameOnGlasses
fun mirrorRefreshLimit(settings: GlassesSettings, state: GlassesState, outputActive: Boolean): Int =
if (state.usable && !outputActive) outputHz(settings, state) else 0
fun refreshRates(settings: GlassesSettings, state: GlassesState): List<Int> = buildList {
if (native30HzAvailable(settings, state)) add(30)
add(60)
if (!state.sbs || state.sbs90) add(90)
if (!state.sbs) add(120)
}
fun outputHz(settings: GlassesSettings, state: GlassesState): Int {
val requested = if (state.sbs) settings.sbsHz else settings.refreshHz
return when {
requested == 30 -> if (native30HzAvailable(settings, state)) 30 else 60
state.sbs && !state.sbs90 -> 60
else -> requested
}
}
fun mirrorRefreshLimit(settings: GlassesSettings, state: GlassesState, outputActive: Boolean): Int {
if (!state.usable || outputActive) return 0
val hz = outputHz(settings, state)
return if (hz == 30 && kotlin.math.abs(state.hz - 30f) >= 0.5f) 60 else hz
}
fun fpsLimit(requested: Int, mirrorRefreshLimit: Int): Int = when {
mirrorRefreshLimit <= 0 -> requested
@@ -22,14 +41,12 @@ internal object GlassesRendering {
outputActive || (steamVrRunning && state.usable)
fun picture(settings: GlassesSettings, stereoDisplay: Boolean, steamVrRunning: Boolean): Picture =
when (settings.picture3d) {
GlassesSettings.PICTURE_SCREEN -> Picture.VIRTUAL_SCREEN
GlassesSettings.PICTURE_SBS -> Picture.STEREO
else -> when {
steamVrRunning -> Picture.STEREO
stereoDisplay -> Picture.VIRTUAL_SCREEN
else -> Picture.FLAT
}
when {
steamVrRunning -> Picture.STEREO
settings.picture3d == GlassesSettings.PICTURE_SCREEN -> Picture.VIRTUAL_SCREEN
settings.picture3d == GlassesSettings.PICTURE_SBS -> Picture.STEREO
stereoDisplay -> Picture.DUPLICATED
else -> Picture.FLAT
}
fun cardboard(
@@ -55,12 +72,17 @@ internal object GlassesRendering {
if (picture == Picture.FLAT) return Cardboard.OFF
return Cardboard(
enabled = true,
source = if (picture == Picture.STEREO) Cardboard.SOURCE_SBS else Cardboard.SOURCE_SCREEN,
source = when (picture) {
Picture.STEREO -> Cardboard.SOURCE_SBS
Picture.DUPLICATED -> Cardboard.SOURCE_FLAT
else -> Cardboard.SOURCE_SCREEN
},
viewer = Cardboard.Viewer.VITURE.id,
ipdMm = base.ipdMm,
screenWidthDm = settings.screenWidthDm,
screenDistanceDm = settings.screenDistanceDm,
headTracking = settings.headTracking && state.canControl,
headTracking = settings.headTracking && state.canControl &&
(picture == Picture.VIRTUAL_SCREEN || steamVrRunning),
mono = !state.sbs,
)
}
@@ -13,10 +13,12 @@ import android.view.WindowManager
import android.widget.FrameLayout
import kotlin.math.abs
class GlassesScreen(context: Context, display: Display, refreshHz: Int, private val callback: SurfaceHolder.Callback) : Presentation(context, display) {
class GlassesScreen(context: Context, display: Display, refreshHz: Int, private val callback: SurfaceHolder.Callback, resolution: GlassesResolution? = null) : Presentation(context, display) {
val surfaceView = SurfaceView(getContext())
internal val trackingRecovery = com.droiddeck.launcher.ui.TrackingRecoveryView(getContext())
val holder: SurfaceHolder get() = surfaceView.holder
private var wantHz = refreshHz
private var wantResolution = resolution
val hz: Float get() = display.mode.refreshRate
@@ -24,14 +26,16 @@ class GlassesScreen(context: Context, display: Display, refreshHz: Int, private
super.onCreate(savedInstanceState)
val root = FrameLayout(context).apply { setBackgroundColor(Color.BLACK) }
root.addView(surfaceView, FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
root.addView(trackingRecovery, FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT))
setContentView(root)
window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
applyMode()
surfaceView.holder.addCallback(callback)
}
fun prefer(hz: Int) {
fun prefer(hz: Int, resolution: GlassesResolution? = null) {
wantHz = hz
wantResolution = resolution
// A 2D/SBS transition can replace the active mode without changing Hz.
applyMode()
}
@@ -39,9 +43,14 @@ class GlassesScreen(context: Context, display: Display, refreshHz: Int, private
private fun applyMode() {
val w = window ?: return
val current = display.mode
val best = display.supportedModes.filter {
val target = wantResolution
val width = target?.width?.times(if (current.physicalWidth >= 3000) 2 else 1) ?: current.physicalWidth
val height = target?.height ?: current.physicalHeight
val requested = display.supportedModes.filter { it.physicalWidth == width && it.physicalHeight == height }
val candidates = requested.ifEmpty { display.supportedModes.filter {
it.physicalWidth == current.physicalWidth && it.physicalHeight == current.physicalHeight
}.minByOrNull { abs(it.refreshRate - wantHz) } ?: return
} }
val best = candidates.minByOrNull { abs(it.refreshRate - wantHz) } ?: return
if (w.attributes.preferredDisplayModeId != best.modeId) {
Log.i("GlassesScreen", "display ${display.displayId}: prefer mode ${best.modeId} " +
"${best.physicalWidth}x${best.physicalHeight}@${best.refreshRate} (requested $wantHz Hz)")
@@ -0,0 +1,48 @@
package com.droiddeck.launcher.glasses
import kotlin.math.*
internal class HandGestures(private val rightHand: Boolean) {
private var grip = false
private var last: SpatialHand? = null
var lastOutcome = HandObservationOutcome.LANDMARK_COUNT_INVALID; private set
fun reset() { grip = false; last = null }
fun resetAfterRejection(outcome: HandObservationOutcome) {
if (outcome != HandObservationOutcome.PALM_MOTION_REJECTED) reset()
}
private fun reject(outcome: HandObservationOutcome): SpatialHand? { lastOutcome = outcome; return null }
fun estimate(points: List<SpatialVector?>, worldFromCamera: SpatialPose, time: Long,
leftImage: List<SpatialVector>? = null, rightImage: List<SpatialVector>? = null): SpatialHand? {
if (points.size != 21) return reject(HandObservationOutcome.LANDMARK_COUNT_INVALID)
val p = points.map { it?.takeIf(SpatialVector::finite) }
val wrist = p[0] ?: return reject(HandObservationOutcome.PALM_LANDMARK_MISSING)
val index = p[5] ?: return reject(HandObservationOutcome.PALM_LANDMARK_MISSING)
val middle = p[9] ?: return reject(HandObservationOutcome.PALM_LANDMARK_MISSING)
val ring = p[13] ?: return reject(HandObservationOutcome.PALM_LANDMARK_MISSING)
val little = p[17] ?: return reject(HandObservationOutcome.PALM_LANDMARK_MISSING)
val width = (index-little).length()
if (width !in 0.035f..0.13f) return reject(HandObservationOutcome.PALM_WIDTH_INVALID)
val forward = (middle-wrist).normalized() ?: return reject(HandObservationOutcome.PALM_BASIS_INVALID)
val lateral = (if (rightHand) little-index else index-little).normalized() ?: return reject(HandObservationOutcome.PALM_BASIS_INVALID)
val up = lateral.cross(forward).normalized() ?: return reject(HandObservationOutcome.PALM_BASIS_INVALID)
val right = forward.cross(up).normalized() ?: return reject(HandObservationOutcome.PALM_BASIS_INVALID)
val palm = (wrist+index+middle+ring+little) * 0.2f
val pose = worldFromCamera * SpatialPose(palm,SpatialRotation.fromBasis(right,up,forward * -1f))
last?.let { old ->
val dt=(time-old.timeNs)/1e9f
if (dt>0 && dt<0.2f && (pose.position-old.pose.position).length()>0.08f+dt*3f)
return reject(HandObservationOutcome.PALM_MOTION_REJECTED)
}
val stereo = if (leftImage != null || rightImage != null)
StereoFingerGestures.estimate(leftImage,rightImage,grip) else null
val triggerValid = stereo?.triggerValid ?: (p[4] != null && p[8] != null)
val trigger = stereo?.trigger ?: if (triggerValid) ((0.55f-(p[4]!!-p[8]!!).length()/width)/0.35f).coerceIn(0f,1f) else 0f
val gripValid = stereo?.gripValid ?: listOf(12,16,20).all { p[it] != null }
val closed = if (stereo == null && gripValid) listOf(12 to 9,16 to 13,20 to 17).count { (tip,base) ->
(p[tip]!!-wrist).length() < (p[base]!!-wrist).length()*1.35f
} else 0
grip=stereo?.grip ?: (gripValid && if(grip) closed>=2 else closed==3)
lastOutcome = HandObservationOutcome.ACCEPTED
return SpatialHand(pose,time,true,trigger,grip,triggerValid,gripValid).also { last=it }
}
}
@@ -0,0 +1,57 @@
package com.droiddeck.launcher.glasses
import android.graphics.Bitmap
import com.google.mediapipe.framework.image.ByteBufferImageBuilder
import com.google.mediapipe.framework.image.MPImage
import java.nio.ByteBuffer
internal class HandImageBuffer {
companion object {
const val PREPROCESSING = "raw-grayscale-rgb"
}
private var pixels = ByteBuffer.allocateDirect(0)
private var source = IntArray(0)
private var rgb = ByteArray(0)
private fun allocateRgb(count: Int) {
if (rgb.size != count * 3) {
rgb = ByteArray(count * 3)
pixels = ByteBuffer.allocateDirect(rgb.size)
}
}
fun image(grayscale: ByteArray, width: Int, height: Int): MPImage {
require(width > 0 && height > 0 && width.toLong() * height == grayscale.size.toLong())
allocateRgb(grayscale.size)
var index = 0
for (value in grayscale) {
rgb[index++] = value
rgb[index++] = value
rgb[index++] = value
}
return image(width, height)
}
fun image(grayscale: Bitmap): MPImage {
val count = grayscale.width * grayscale.height
if (source.size != count) source = IntArray(count)
allocateRgb(count)
grayscale.getPixels(source, 0, grayscale.width, 0, 0, grayscale.width, grayscale.height)
var index = 0
for (pixel in source) {
val value = (pixel ushr 16).toByte()
rgb[index++] = value
rgb[index++] = value
rgb[index++] = value
}
return image(grayscale.width, grayscale.height)
}
private fun image(width: Int, height: Int): MPImage {
pixels.clear()
pixels.put(rgb)
pixels.rewind()
// MediaPipe applies the model metadata's float normalization to these RGB bytes.
return ByteBufferImageBuilder(pixels, width, height, MPImage.IMAGE_FORMAT_RGB).build()
}
}
@@ -0,0 +1,72 @@
package com.droiddeck.launcher.glasses
internal class HandInferenceSchedule(
private val stableFrames: Int = 3,
private val discoveryIntervalMs: Long = 240,
private val maximumGapMs: Long = 400
) {
init {
require(stableFrames > 0)
require(discoveryIntervalMs > 0 && maximumGapMs >= discoveryIntervalMs)
}
private var lastFrameMs = -1L
private var selectedAtMs = -1L
private var selectedMaximum = 2
private var lastSearchMs = -1L
private var candidateSide = -1
private var consistentFrames = 0
private var trackingSide = -1
fun select(timestampMs: Long): Int {
if (timestampMs < 0) {
reset()
return 2
}
if (selectedAtMs >= 0 || lastFrameMs >= 0 &&
(timestampMs <= lastFrameMs || timestampMs - lastFrameMs > maximumGapMs)) reset()
val maximum = if (trackingSide >= 0 && lastSearchMs >= 0 &&
timestampMs - lastSearchMs < discoveryIntervalMs) 1 else 2
lastFrameMs = timestampMs
selectedAtMs = timestampMs
selectedMaximum = maximum
if (maximum == 2) lastSearchMs = timestampMs
return maximum
}
fun observe(timestampMs: Long, leftSides: List<Int>, rightSides: List<Int>,
leftRawCount: Int = leftSides.size, rightRawCount: Int = rightSides.size) {
if (timestampMs < 0 || selectedAtMs != timestampMs) {
reset()
return
}
selectedAtMs = -1L
val side = leftSides.singleOrNull() ?: -1
val consistent = leftRawCount == 1 && rightRawCount == 1 && side in 0..1 &&
rightSides.singleOrNull() == side
if (!consistent || selectedMaximum == 1 && side != trackingSide) {
forgetHand()
return
}
if (candidateSide != side) {
candidateSide = side
consistentFrames = 0
}
consistentFrames = minOf(stableFrames, consistentFrames + 1)
if (consistentFrames >= stableFrames) trackingSide = side
}
fun reset() {
lastFrameMs = -1L
selectedAtMs = -1L
selectedMaximum = 2
lastSearchMs = -1L
forgetHand()
}
private fun forgetHand() {
candidateSide = -1
consistentFrames = 0
trackingSide = -1
}
}
@@ -0,0 +1,10 @@
package com.droiddeck.launcher.glasses
internal enum class HandModel(val asset: String, val sha256: String) {
FULL("models/hand_landmarker.task", "fbc2a30080c3c557093b5ddfc334698132eb341044ccee322ccf8bcf3607cde1"),
LITE("models/hand_landmarker_lite.task", "173c0f47ecee7e935bd4a1e93aa580756b4180c1307c07d709313e82c28672c8");
companion object {
val DEFAULT = FULL
}
}
@@ -0,0 +1,48 @@
package com.droiddeck.launcher.glasses
internal class HandProcessingRate {
var permille = 500
private set
private var processingMs = Double.NaN
private var headroomSamples = 0
fun observe(processingNs: Long, observationAgeNs: Long, trackingHealthy: Boolean): Int {
if (!trackingHealthy || processingNs <= 0 || observationAgeNs < 0) return reset()
val measuredMs = processingNs / 1_000_000.0
val ageMs = observationAgeNs / 1_000_000.0
processingMs = if (processingMs.isNaN()) measuredMs else processingMs + 0.2 * (measuredMs - processingMs)
if (measuredMs >= 80 || ageMs >= 140) {
permille = 500
headroomSamples = 0
return permille
}
val reduced = when {
permille > 500 && processingMs > 1000.0 / 15 -> 500
permille == 800 && processingMs > 50 -> 600
else -> permille
}
if (reduced != permille) {
permille = reduced
headroomSamples = 0
return permille
}
val enoughHeadroom = when (permille) {
500 -> processingMs <= 55 && ageMs <= 110
600 -> processingMs <= 40 && ageMs <= 90
else -> false
}
headroomSamples = if (enoughHeadroom) headroomSamples + 1 else 0
if (headroomSamples >= 8) {
permille = if (permille == 500) 600 else 800
headroomSamples = 0
}
return permille
}
fun reset(): Int {
permille = 500
processingMs = Double.NaN
headroomSamples = 0
return permille
}
}
@@ -0,0 +1,107 @@
package com.droiddeck.launcher.glasses
internal enum class HandObservationOutcome {
ACCEPTED, DISABLED, CALIBRATION_REQUIRED, CLOCK_NOT_READY, HEAD_TRACKING_INVALID,
CAMERA_NOT_SYNCHRONIZED, STALE_OBSERVATION, INFERENCE_UNAVAILABLE,
NO_MATCHING_DETECTION, AMBIGUOUS_HANDEDNESS, ONE_CAMERA_ONLY, STEREO_REJECTED,
LANDMARK_COUNT_INVALID, PALM_LANDMARK_MISSING, PALM_WIDTH_INVALID, PALM_BASIS_INVALID,
PALM_MOTION_REJECTED, WRIST_LANDMARK_MISSING, WRIST_MOTION_REJECTED
}
internal data class HandObservationDiagnostic(
val outcome: HandObservationOutcome,
val reconstructedPalmLandmarks: List<Int> = emptyList(),
val triggerEstimate: Float = 0f,
val triggerGeometryValid: Boolean = false,
val gripEstimate: Boolean = false,
val gripGeometryValid: Boolean = false,
val positionSource: String = "none"
) {
val accepted get() = outcome == HandObservationOutcome.ACCEPTED
val triggerValid get() = accepted && triggerGeometryValid
val gripValid get() = accepted && gripGeometryValid
}
internal data class HandSideMetrics(
val observations: Long,
val accepted: Long,
val triggerValid: Long,
val gripValid: Long,
val triggerAboveClickThreshold: Long,
val triggerReleased: Long,
val gripPressed: Long,
val trackingLosses: Long,
val triggerLosses: Long,
val gripLosses: Long,
val maximumConsecutiveRejected: Long,
val outcomes: Map<HandObservationOutcome, Long>
)
internal data class HandTrackingMetricSnapshot(
val processedPairs: Long,
val inferencePairs: Long,
val left: HandSideMetrics,
val right: HandSideMetrics
)
internal class HandTrackingMetrics {
private class Side {
val outcomes = LongArray(HandObservationOutcome.entries.size)
var triggerValid = 0L
var gripValid = 0L
var triggerAboveClickThreshold = 0L
var triggerReleased = 0L
var gripPressed = 0L
var trackingLosses = 0L
var triggerLosses = 0L
var gripLosses = 0L
var consecutiveRejected = 0L
var maximumConsecutiveRejected = 0L
var wasAccepted = false
var wasTriggerValid = false
var wasGripValid = false
fun observe(sample: HandObservationDiagnostic) {
outcomes[sample.outcome.ordinal]++
if (sample.triggerValid) {
triggerValid++
if (sample.triggerEstimate > 0.65f) triggerAboveClickThreshold++
if (sample.triggerEstimate < 0.25f) triggerReleased++
}
if (sample.gripValid) {
gripValid++
if (sample.gripEstimate) gripPressed++
}
if (wasAccepted && !sample.accepted) trackingLosses++
if (wasTriggerValid && !sample.triggerValid) triggerLosses++
if (wasGripValid && !sample.gripValid) gripLosses++
consecutiveRejected = if (sample.accepted) 0 else consecutiveRejected + 1
maximumConsecutiveRejected = maxOf(maximumConsecutiveRejected, consecutiveRejected)
wasAccepted = sample.accepted
wasTriggerValid = sample.triggerValid
wasGripValid = sample.gripValid
}
fun snapshot() = HandSideMetrics(outcomes.sum(), outcomes[HandObservationOutcome.ACCEPTED.ordinal],
triggerValid, gripValid, triggerAboveClickThreshold, triggerReleased, gripPressed, trackingLosses, triggerLosses, gripLosses,
maximumConsecutiveRejected, HandObservationOutcome.entries.filter { outcomes[it.ordinal] > 0 }
.associateWith { outcomes[it.ordinal] })
}
private val sides = Array(2) { Side() }
private var lastSequence = 0L
private var processedPairs = 0L
private var inferencePairs = 0L
fun observe(sequence: Long, inferred: Boolean, left: HandObservationDiagnostic,
right: HandObservationDiagnostic) {
if (sequence <= lastSequence) return
lastSequence = sequence
processedPairs++
if (inferred) inferencePairs++
sides[0].observe(left)
sides[1].observe(right)
}
fun snapshot() = HandTrackingMetricSnapshot(processedPairs, inferencePairs, sides[0].snapshot(), sides[1].snapshot())
}
@@ -0,0 +1,16 @@
package com.droiddeck.launcher.glasses
import kotlin.math.hypot
internal object JoyConFlowGate {
const val MAX_SEED_AGE_NS = 750_000_000L
fun fresh(seed: Long, previous: Long, now: Long) = seed > 0 && previous > 0 &&
now > previous && now - previous <= 200_000_000L && now - seed in 0..MAX_SEED_AGE_NS
fun point(x: Double, y: Double, backX: Double, backY: Double, oldX: Double, oldY: Double,
error: Float, forward: Boolean, backward: Boolean, width: Int, height: Int): Boolean =
forward && backward && error.isFinite() && error in 0f..20f &&
x.isFinite() && y.isFinite() && backX.isFinite() && backY.isFinite() &&
x >= 3 && y >= 3 && x < width - 3 && y < height - 3 &&
hypot(backX - oldX, backY - oldY) <= 1.5 && hypot(x - oldX, y - oldY) <= 100
}
@@ -0,0 +1,26 @@
package com.droiddeck.launcher.glasses
internal class JoyConWristPosition {
private var last: SpatialHand? = null
var lastOutcome = HandObservationOutcome.WRIST_LANDMARK_MISSING; private set
fun reset() { last = null }
private fun reject(outcome: HandObservationOutcome): SpatialHand? {
lastOutcome = outcome
return null
}
fun estimate(points: List<SpatialVector?>, worldFromCamera: SpatialPose, time: Long): SpatialHand? {
val wrist = points.firstOrNull()?.takeIf(SpatialVector::finite)
?: return reject(HandObservationOutcome.WRIST_LANDMARK_MISSING)
val pose = worldFromCamera * SpatialPose(wrist)
last?.let { old ->
val dt = (time - old.timeNs) / 1e9f
if (dt <= 0 || (dt < .2f && (pose.position - old.pose.position).length() > .08f + dt * 3f))
return reject(HandObservationOutcome.WRIST_MOTION_REJECTED)
}
lastOutcome = HandObservationOutcome.ACCEPTED
return SpatialHand(pose, time, true, triggerValid = false, gripValid = false).also { last = it }
}
}
@@ -0,0 +1,160 @@
package com.droiddeck.launcher.glasses
import android.Manifest
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.hardware.usb.UsbManager
import android.os.IBinder
import android.util.AtomicFile
import org.json.JSONObject
import java.io.File
internal object LumaRgbDiagnostic {
private fun file(context: Context) = AtomicFile(File(context.filesDir, "spatial/rgb-diagnostic.json"))
private fun save(context: Context, value: JSONObject) {
val target = file(context)
target.baseFile.parentFile?.mkdirs()
val output = target.startWrite()
try {
output.write(value.toString().toByteArray(Charsets.UTF_8))
target.finishWrite(output)
} catch (e: Exception) { target.failWrite(output); throw e }
}
private fun expired(value: JSONObject, nowMs: Long) = nowMs-value.optLong("startedMs") !in
0..value.optLong("timeoutMs",15_000).coerceIn(15_000,45_000)
private fun workerAlive(context: Context, pid: Int): Boolean = pid > 0 && pid != android.os.Process.myPid() &&
runCatching {
File("/proc/$pid/cmdline").readText().substringBefore('\u0000') == "${context.packageName}:rgb_diagnostic"
}.getOrDefault(false)
internal fun resolveStatus(value: JSONObject, nowMs: Long, workerAlive: Boolean): JSONObject {
val pending = value.optString("state") == "capturing"
if (workerAlive && !expired(value,nowMs)) {
if (!pending) value.put("resultState",value.optString("state")).put("phase","finishing")
return value.put("state","capturing").put("busy",true)
}
if (pending && (expired(value,nowMs) || value.optInt("workerPid") > 0 && !workerAlive))
value.put("state","failed").put("message","RGB capture worker stopped or timed out")
return value.put("busy",workerAlive || value.optString("state") == "capturing")
}
fun status(context: Context): JSONObject = runCatching {
val value = JSONObject(file(context).readFully().toString(Charsets.UTF_8))
val now = android.os.SystemClock.elapsedRealtime()
val pid = value.optInt("workerPid")
if (expired(value,now) && workerAlive(context,pid)) android.os.Process.killProcess(pid)
resolveStatus(value,now,workerAlive(context,pid))
}.getOrElse { JSONObject().put("state", "idle") }
@Synchronized fun capture(context: Context, transport: String = "sdk", width: Int = 640, height: Int = 480,
fps: Int = 30, durationMs: Int = 5000): JSONObject {
require(transport in listOf("sdk","uvc") && width in 160..1920 && height in 120..1080 &&
fps in 5..30 && durationMs in 1000..30000) { "Invalid RGB capture options" }
val app = context.applicationContext
val old = status(app)
if (old.optBoolean("busy")) return old
val usb = app.getSystemService(Context.USB_SERVICE) as UsbManager
val device = usb.deviceList.values.singleOrNull { it.vendorId == 0x0c45 && it.productId == 0x636b }
fun state(value: String, message: String): JSONObject {
val result = JSONObject().put("state", value).put("message", message)
save(app, result)
return result
}
if (!Glasses.state.value.spatialConnected || device == null)
return state("unavailable", "Connect the Luma Ultra glasses and RGB camera")
if (app.checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED)
return state("permission_required", "Grant Android camera permission to capture the RGB camera")
if (!usb.hasPermission(device)) {
val intent = Intent("${app.packageName}.RGB_DIAGNOSTIC_PERMISSION").setPackage(app.packageName)
val pending = PendingIntent.getBroadcast(app, 3, intent, PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT)
usb.requestPermission(device, pending)
return state("permission_required", "Approve the USB camera permission, then request the capture again")
}
val result = JSONObject().put("state", "capturing").put("startedMs", android.os.SystemClock.elapsedRealtime())
.put("timeoutMs",durationMs+15000).put("transport",transport)
save(app, result)
try { app.startService(Intent(app, LumaRgbCaptureService::class.java).putExtra("device", device.deviceName)
.putExtra("transport",transport).putExtra("width",width).putExtra("height",height)
.putExtra("fps",fps).putExtra("durationMs",durationMs)) }
catch (e: Exception) { return state("failed", e.message ?: e.toString()) }
return result
}
internal fun perform(context: Context, deviceName: String, transport: String, width: Int, height: Int,
fps: Int, durationMs: Int) {
val folder=File(context.filesDir,"spatial/rgb-captures/${System.nanoTime()}").apply { mkdirs() }
val startedMs=android.os.SystemClock.elapsedRealtime()
val workerPid=android.os.Process.myPid()
save(context,JSONObject().put("state","capturing").put("path",folder.path)
.put("startedMs",startedMs).put("timeoutMs",durationMs+15000)
.put("transport",transport).put("workerPid",workerPid))
val result = try {
val direct=transport=="uvc"
check(if(direct) UvcRgbDiagnostic.available else VitureSdk.available) { "Requested RGB transport is unavailable" }
val usb = context.getSystemService(Context.USB_SERVICE) as UsbManager
val device = checkNotNull(usb.deviceList[deviceName]) { "RGB camera disconnected" }
check(device.vendorId == 0x0c45 && device.productId == 0x636b && usb.hasPermission(device))
val connection = checkNotNull(usb.openDevice(device)) { "Could not open RGB camera" }
val metadata=LongArray(if(direct) 14 else 7)
val jpeg=try {
File(folder,"usb-descriptors.bin").writeBytes(connection.rawDescriptors)
android.os.ParcelFileDescriptor.open(File(folder,"usb.log"),
android.os.ParcelFileDescriptor.MODE_CREATE or android.os.ParcelFileDescriptor.MODE_TRUNCATE or
android.os.ParcelFileDescriptor.MODE_WRITE_ONLY).use { log ->
if(direct) UvcRgbDiagnostic.nativeSnapshot(connection.fileDescriptor,width,height,fps,durationMs,metadata,log.fd)
else VitureSdk.nativeRgbSnapshot(device.vendorId,device.productId,connection.fileDescriptor,metadata,log.fd)
}
} finally { connection.close() }
check(jpeg != null) { "RGB camera returned no frame ($transport status ${metadata[4]}, callbacks ${metadata[5]})" }
File(folder,"rgb.jpg").writeBytes(jpeg)
val info=JSONObject().put("state","completed").put("path",folder.path)
.put("width",metadata[0]).put("height",metadata[1]).put("timestampNs",metadata[2])
.put("sequence",metadata[3]).put("callbacks",metadata[5]).put("source","Luma Ultra UVC RGB")
.put("receiptNs",metadata[6]).put("timingBasis","Host callback receipt; not exposure time")
.put("snapshotAcquired",true)
if(direct) {
val health=UvcStreamHealth.assess(metadata,fps,durationMs)
info.put("firstReceiptNs",metadata[7]).put("maxFrameGapMs",health.maximumGapMs)
.put("completeFrames",metadata[9]).put("invalidFrames",metadata[10]).put("bytes",metadata[11])
.put("observationEndNs",metadata[12]).put("tailFrameAgeMs",metadata[13]/1e6)
.put("observationMs",health.observationMs).put("observedFps",health.observedFps)
.put("streamHealthy",health.healthy)
}
info
} catch (e: Exception) { JSONObject().put("state","failed").put("snapshotAcquired",false)
.put("message",e.message ?: e.toString()) }
result.put("path",folder.path).put("transport",transport).put("requestedWidth",width).put("requestedHeight",height)
.put("requestedFps",fps).put("durationMs",durationMs).put("workerPid",workerPid)
.put("startedMs",startedMs).put("timeoutMs",durationMs+15000)
if (transport == "uvc" && !result.has("streamHealthy")) result.put("streamHealthy",false)
File(folder,"frame.json").writeText(result.toString())
save(context,result)
}
}
class LumaRgbCaptureService : Service() {
private var running = false
override fun onBind(intent: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (!running) {
running = true
Thread({
try { LumaRgbDiagnostic.perform(this,intent?.getStringExtra("device") ?: "",
intent?.getStringExtra("transport") ?: "sdk",intent?.getIntExtra("width",640) ?: 640,
intent?.getIntExtra("height",480) ?: 480,intent?.getIntExtra("fps",30) ?: 30,
intent?.getIntExtra("durationMs",5000) ?: 5000) }
finally {
stopSelf()
// SDK 2.4.0 can crash while cancelling failed USB transfers; isolate each diagnostic.
android.os.Process.killProcess(android.os.Process.myPid())
}
}, "luma-rgb-diagnostic").start()
}
return START_NOT_STICKY
}
}
@@ -0,0 +1,36 @@
package com.droiddeck.launcher.glasses
import com.droiddeck.launcher.R
import java.util.Locale
import kotlin.math.roundToInt
internal object SpatialMeasurements {
fun height(centimeters: Int): String {
val tenths = (centimeters / 2.54 * 10).roundToInt()
return String.format(Locale.getDefault(), "%d cm (%d ft %.1f in)", centimeters, tenths / 120, tenths % 120 / 10.0)
}
fun distance(meters: Float): String = String.format(
Locale.getDefault(), "%.1f m (%.1f ft)", meters, meters / 0.3048f)
fun validation(meters: Float): String = String.format(
Locale.getDefault(), "%.1f cm (%.2f in)", meters * 100, meters / 0.0254f)
}
internal enum class CalibrationDistance(val meters: Float, val labelRes: Int) {
INCHES_12(0.3048f, R.string.spatial_distance_12in),
CENTIMETERS_30(0.3f, R.string.spatial_distance_30cm),
INCHES_20(0.508f, R.string.spatial_distance_20in),
CENTIMETERS_50(0.5f, R.string.spatial_distance_50cm),
}
internal data class SpatialCalibrationProgress(
val collecting: Boolean = false,
val views: Int = 0,
val fitted: Boolean = false,
val solving: Boolean = false,
val capturing: Boolean = false,
val captureMessage: String? = null,
val validating: Boolean = false,
val validationMessage: String? = null,
)
@@ -0,0 +1,278 @@
package com.droiddeck.launcher.glasses
import kotlin.math.*
data class SpatialVector(val x: Float = 0f, val y: Float = 0f, val z: Float = 0f) {
operator fun plus(b: SpatialVector) = SpatialVector(x + b.x, y + b.y, z + b.z)
operator fun minus(b: SpatialVector) = SpatialVector(x - b.x, y - b.y, z - b.z)
operator fun times(s: Float) = SpatialVector(x * s, y * s, z * s)
fun dot(b: SpatialVector) = x * b.x + y * b.y + z * b.z
fun cross(b: SpatialVector) = SpatialVector(y * b.z - z * b.y, z * b.x - x * b.z, x * b.y - y * b.x)
fun length() = sqrt(dot(this))
fun normalized() = length().takeIf { it > 1e-6f }?.let { this * (1f / it) }
fun finite() = x.isFinite() && y.isFinite() && z.isFinite()
fun array() = floatArrayOf(x, y, z)
}
data class SpatialRotation(val x: Float = 0f, val y: Float = 0f, val z: Float = 0f, val w: Float = 1f) {
fun normalized(): SpatialRotation? {
val n = sqrt(x*x + y*y + z*z + w*w)
return if (n.isFinite() && n in 0.5f..1.5f) SpatialRotation(x/n, y/n, z/n, w/n) else null
}
fun inverse() = SpatialRotation(-x, -y, -z, w)
operator fun times(b: SpatialRotation) = SpatialRotation(
w*b.x + x*b.w + y*b.z - z*b.y, w*b.y - x*b.z + y*b.w + z*b.x,
w*b.z + x*b.y - y*b.x + z*b.w, w*b.w - x*b.x - y*b.y - z*b.z)
fun apply(v: SpatialVector): SpatialVector {
val q = SpatialVector(x, y, z)
val t = q.cross(v) * 2f
return v + t * w + q.cross(t)
}
fun matrix() = floatArrayOf(
1-2*(y*y+z*z), 2*(x*y-z*w), 2*(x*z+y*w),
2*(x*y+z*w), 1-2*(x*x+z*z), 2*(y*z-x*w),
2*(x*z-y*w), 2*(y*z+x*w), 1-2*(x*x+y*y))
fun yaw(): SpatialRotation {
val forward = apply(SpatialVector(0f, 0f, -1f))
val a = if (forward.x*forward.x + forward.z*forward.z < 1e-8f) {
val right = apply(SpatialVector(x = 1f))
atan2(-right.z, right.x) / 2
} else atan2(-forward.x, -forward.z) / 2
return SpatialRotation(y = sin(a), w = cos(a))
}
companion object {
fun fromBasis(right: SpatialVector, up: SpatialVector, back: SpatialVector): SpatialRotation {
val m = floatArrayOf(right.x, up.x, back.x, right.y, up.y, back.y, right.z, up.z, back.z)
val trace = m[0] + m[4] + m[8]
val q = if (trace > 0) {
val s = sqrt(trace + 1) * 2
SpatialRotation((m[7]-m[5])/s, (m[2]-m[6])/s, (m[3]-m[1])/s, s/4)
} else {
val i = if (m[0] > m[4] && m[0] > m[8]) 0 else if (m[4] > m[8]) 1 else 2
val j = (i+1)%3; val k = (i+2)%3
val s = sqrt(1 + m[i*3+i] - m[j*3+j] - m[k*3+k]) * 2
val v = FloatArray(3)
v[i] = s/4; v[j] = (m[j*3+i]+m[i*3+j])/s; v[k] = (m[k*3+i]+m[i*3+k])/s
SpatialRotation(v[0], v[1], v[2], (m[k*3+j]-m[j*3+k])/s)
}
return q.normalized() ?: SpatialRotation()
}
}
}
data class SpatialPose(val position: SpatialVector = SpatialVector(), val rotation: SpatialRotation = SpatialRotation()) {
fun inverse(): SpatialPose {
val r = rotation.inverse()
return SpatialPose(r.apply(position * -1f), r)
}
operator fun times(b: SpatialPose) = SpatialPose(position + rotation.apply(b.position), rotation * b.rotation)
companion object {
fun fromSdk(v: FloatArray): SpatialPose? {
if (v.size != 7 || v.any { !it.isFinite() }) return null
val q = SpatialRotation(v[4], v[5], v[6], v[3]).normalized() ?: return null
return SpatialPose(SpatialVector(v[0], v[1], v[2]), q)
}
}
}
data class SpatialSample(val pose: SpatialPose, val timeNs: Long, val generation: Long, val valid: Boolean)
data class SpatialHand(val pose: SpatialPose = SpatialPose(), val timeNs: Long = 0, val valid: Boolean = false,
val trigger: Float = 0f, val grip: Boolean = false,
val triggerValid: Boolean = valid, val gripValid: Boolean = valid) {
fun flags() = if (!valid) 0 else 1 or (if (triggerValid) 2 else 0) or (if (gripValid) 4 else 0)
}
class SpatialSession {
var generation: Long = 0; private set
var originGeneration: Long = 0; private set
var trackingFrameGeneration: Long = 0; private set
var needsRecenter = false; private set
val recenterPending: Boolean get() = pendingRecenter != null
private var pendingRecenter: Boolean? = null
private var recoveryAnchor: SpatialSample? = null
private var head: SpatialSample? = null
private var lastStable: SpatialSample? = null
private var vrOrigin: SpatialPose? = null
private var screenOrigin: SpatialPose? = null
private var hands = arrayOf(SpatialHand(), SpatialHand())
private val gestureEpochs = IntArray(4)
private val gestureEligible = BooleanArray(4)
private var inputsAfterNs = 0L
private var height = 1.6f
private val history = ArrayDeque<SpatialSample>()
@Synchronized fun reset(connection: Long, eyeHeight: Float, requireSetup: Boolean = false) {
generation = connection; height = eyeHeight.takeIf { it.isFinite() }?.coerceIn(0.5f, 2.5f) ?: 1.6f
originGeneration++; trackingFrameGeneration++; head = null; lastStable = null; vrOrigin = null; screenOrigin = null
history.clear()
pendingRecenter = null; recoveryAnchor = null
hands = arrayOf(SpatialHand(), SpatialHand()); needsRecenter = requireSetup
gestureEpochs.fill(0); gestureEligible.fill(false)
inputsAfterNs = 0L
}
@Synchronized fun accept(sample: SpatialSample): Boolean {
if (sample.generation != generation || sample.timeNs <= (head?.timeNs ?: 0)) return false
val previous = head
if (previous != null && sample.timeNs-previous.timeNs > 100_000_000L) invalidateHeadInputs(sample.timeNs)
val rotation = sample.pose.rotation.normalized()
val current = sample.copy(pose = sample.pose.copy(rotation = rotation ?: SpatialRotation()),
valid = sample.valid && sample.pose.position.finite() && rotation != null)
head = current
if (!current.valid) {
recoveryAnchor = null; invalidateHeadInputs(current.timeNs); return false
}
lastStable?.let {
val d = (current.pose.position - it.pose.position).length()
val a = current.pose.rotation; val b = it.pose.rotation
if (!needsRecenter && (d > 0.5f || abs(a.x*b.x+a.y*b.y+a.z*b.z+a.w*b.w) < 0.9238795f)) {
needsRecenter = true; trackingFrameGeneration++
}
}
pendingRecenter?.let { screen ->
val anchor = recoveryAnchor
val a = current.pose.rotation; val b = anchor?.pose?.rotation ?: a
if (anchor == null || previous?.valid != true || current.timeNs-previous.timeNs > 100_000_000L ||
(current.pose.position-anchor.pose.position).length() > 0.05f ||
abs(a.x*b.x+a.y*b.y+a.z*b.z+a.w*b.w) < 0.9961947f) {
recoveryAnchor = current
} else if (current.timeNs-anchor.timeNs >= 250_000_000L) {
applyRecenter(screen, current)
}
}
if (needsRecenter || recenterPending) { invalidateHeadInputs(current.timeNs); return false }
lastStable = current
history.addLast(current)
while (history.size > 512) history.removeFirst()
if (vrOrigin == null) vrOrigin = SpatialPose(current.pose.position - SpatialVector(y = height), current.pose.rotation.yaw())
if (screenOrigin == null) screenOrigin = current.pose
return true
}
@Synchronized fun setFloor(eyeHeight: Float, now: Long = System.nanoTime()) {
if (!eyeHeight.isFinite()) return
height = eyeHeight.coerceIn(0.5f, 2.5f)
val p = head?.takeIf { it.valid && now-it.timeNs in 0..100_000_000L }?.pose ?: return
vrOrigin = SpatialPose(p.position - SpatialVector(y = height), p.rotation.yaw())
if (needsRecenter) { history.clear(); screenOrigin = p }
lastStable = head; needsRecenter = false; pendingRecenter = null; recoveryAnchor = null
originGeneration++; invalidateHeadInputs(now)
}
@Synchronized fun recenter(screen: Boolean, now: Long = System.nanoTime()) {
val current = head?.takeIf { it.valid && now-it.timeNs in 0..100_000_000L }
if (current == null || needsRecenter || recenterPending) {
if (pendingRecenter == null) recoveryAnchor = null
pendingRecenter = screen
invalidateHeadInputs(now)
return
}
applyRecenter(screen, current)
}
private fun applyRecenter(screen: Boolean, current: SpatialSample) {
val p = current.pose
if (vrOrigin == null) vrOrigin = SpatialPose(p.position - SpatialVector(y = height), p.rotation.yaw())
if (screenOrigin == null) screenOrigin = p
if (needsRecenter) {
vrOrigin = SpatialPose(p.position - SpatialVector(y = height), p.rotation.yaw())
screenOrigin = p; originGeneration++; history.clear()
}
if (screen) screenOrigin = p else {
val floor = vrOrigin?.position?.y ?: (p.position.y-height)
vrOrigin = SpatialPose(SpatialVector(p.position.x, floor, p.position.z), p.rotation.yaw())
originGeneration++
}
lastStable = current; needsRecenter = false; pendingRecenter = null; recoveryAnchor = null
invalidateHeadInputs(current.timeNs)
}
@Synchronized fun view(screen: Boolean): SpatialPose? = lastStable?.pose?.let {
(if (screen) screenOrigin else vrOrigin)?.inverse()?.times(it)
}
@Synchronized fun latest() = head
@Synchronized fun poseAt(time: Long, connection: Long): SpatialPose? {
if (connection != generation || needsRecenter || recenterPending) return null
return history.minByOrNull { abs(it.timeNs-time) }?.takeIf { abs(it.timeNs-time) <= 12_000_000L }?.pose
}
@Synchronized fun valid(now: Long) = !needsRecenter && !recenterPending && head?.let { it.valid && now-it.timeNs in 0..100_000_000L } == true
@Synchronized internal fun isCurrentTrackingFrame(connection: Long, frame: Long, clock: () -> Long = System::nanoTime): Boolean =
connection == generation && frame == trackingFrameGeneration && valid(clock())
@Synchronized fun setHands(connection: Long, left: SpatialHand, right: SpatialHand) {
if (connection != generation || needsRecenter || recenterPending) return
val next = arrayOf(left, right)
for (index in 0..1) {
val oldTime = hands[index].timeNs
val hand = next[index]
if (oldTime > 0 && (hand.timeNs < oldTime || hand.timeNs-oldTime > HandObservation.MAX_AGE_NS))
invalidateInputs(index)
updateInputs(index, hand, head?.valid == true && usableHand(hand) && hand.timeNs > inputsAfterNs)
}
hands = next
}
@Synchronized fun hand(index: Int, now: Long): SpatialHand {
val h = hands[index]
val origin = vrOrigin
val current = origin != null && usableHand(h) && valid(now) && now-h.timeNs in 0..HandObservation.MAX_AGE_NS
updateInputs(index, h, current && h.timeNs > inputsAfterNs)
return if (origin == null) SpatialHand() else h.copy(pose = origin.inverse() * h.pose, valid = current,
triggerValid = h.triggerValid && gestureEligible[index*2], gripValid = h.gripValid && gestureEligible[index*2+1])
}
@Synchronized fun inputEpochs(): Int {
var packed = 0
for (index in gestureEpochs.indices) packed = packed or (gestureEpochs[index] shl (index*8))
return packed
}
private fun usableHand(hand: SpatialHand): Boolean {
val q = hand.pose.rotation
val norm = q.x*q.x+q.y*q.y+q.z*q.z+q.w*q.w
return hand.valid && hand.timeNs > 0 && hand.pose.position.finite() && norm > 0.99f && norm < 1.01f
}
private fun updateInputs(index: Int, hand: SpatialHand, valid: Boolean) {
updateInput(index*2, valid && hand.triggerValid && hand.trigger.isFinite() && hand.trigger in 0f..1f)
updateInput(index*2+1, valid && hand.gripValid)
}
private fun updateInput(index: Int, eligible: Boolean) {
if (gestureEligible[index] && !eligible) gestureEpochs[index] = (gestureEpochs[index]+1) and 0xff
gestureEligible[index] = eligible
}
private fun invalidateInputs(index: Int? = null) {
if (index == null) for (gesture in gestureEligible.indices) updateInput(gesture, false)
else { updateInput(index*2, false); updateInput(index*2+1, false) }
}
private fun invalidateHeadInputs(time: Long) {
inputsAfterNs = maxOf(inputsAfterNs, time)
hands = arrayOf(SpatialHand(), SpatialHand())
invalidateInputs()
}
}
internal data class HandObservation(val timeNs: Long, val connection: Long, val trackingFrame: Long) {
fun headPose(session: SpatialSession, cameraSynchronized: Boolean, now: Long? = null): SpatialPose? = synchronized(session) {
val checkedAt=now ?: System.nanoTime()
if (!cameraSynchronized || timeNs <= 0 || checkedAt-timeNs !in 0..MAX_AGE_NS ||
!session.isCurrentTrackingFrame(connection, trackingFrame) { checkedAt }) null
else session.poseAt(timeNs, connection)
}
companion object {
const val MAX_AGE_NS = 200_000_000L
}
}
internal class CameraClock {
private val offsets = ArrayDeque<Long>()
private var lastSource = 0L
private var lastReceipt = 0L
fun map(source: Long, receipt: Long): Long? {
if (source <= 0 || receipt <= 0) {
offsets.clear();lastSource=0;lastReceipt=0
return null
}
val offset = receipt-source
val skipped = lastSource > 0 && source-lastSource > 200_000_000L
val inconsistent = skipped && offsets.minOrNull()?.let { abs(offset-it) > 20_000_000L } == true
if (source <= lastSource || receipt <= lastReceipt || inconsistent) offsets.clear()
lastSource = source;lastReceipt = receipt
offsets.addLast(offset)
while (offsets.size > 64) offsets.removeFirst()
if (offsets.size < 16) return null
val sorted = offsets.sorted()
if (sorted[sorted.size*9/10]-sorted.first() > 20_000_000L) return null
return source+sorted.first()
}
}
@@ -0,0 +1,208 @@
package com.droiddeck.launcher.glasses
import android.content.Context
import com.droiddeck.launcher.R
import com.droiddeck.launcher.wayland.WaylandCompositor
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import java.nio.ByteBuffer
import java.nio.ByteOrder
data class SpatialTrackingStatus(val message: String = "", val calibration: Boolean = false,
val frames: Long = 0, val dropped: Long = 0, val processingMs: Float = 0f,
val frameAgeMs: Float = -1f, val cameraHz: Float = 0f, val synchronized: Boolean = false,
val trackingValid: Boolean = false, val needsRecenter: Boolean = false, val recenterPending: Boolean = false,
val handStatus: String = "", val headFresh: Boolean = false,
val cameraFrameDivisor: Int = 1, val rateLimitedFrames: Long = 0, val cameraCopyPermille: Int = 1000,
val calibrationSource: String = "none", val calibrationVerified: Boolean = false)
object SpatialTracking {
val session = SpatialSession()
private val mutableStatus = MutableStateFlow(SpatialTrackingStatus())
val status = mutableStatus.asStateFlow()
@Volatile var enabled = false; private set
@Volatile var handsEnabled = false; private set
private var context: Context? = null
private var engine: StereoHandEngine? = null
private var diagnosticsEnabled = false
private val buffer = ByteBuffer.allocateDirect(176).order(ByteOrder.LITTLE_ENDIAN)
private var lastStatus = 0L
@Volatile private var publisherAttached = false
@Volatile private var spatialSelected = false
private var outputUsesGlasses = true
private var handsSelected = false
private var steadyAimSelected = false
@Volatile private var joyConsSelected = false
internal val joyConsEnabled get() = enabled && joyConsSelected
private var hadSpatialSession = false
internal val selected: Boolean get() = spatialSelected
private fun text(id: Int) = context?.getString(id).orEmpty()
fun initialize(app: Context) {
context = app.applicationContext
mutableStatus.update { it.copy(
message = it.message.ifEmpty { text(R.string.spatial_disabled) },
handStatus = it.handStatus.ifEmpty { text(R.string.spatial_hands_off) },
) }
}
fun attachPublisher(path: String) {
publisherAttached = WaylandCompositor.nativeSetSpatialFile(path)
publish(System.nanoTime())
}
internal fun connect(token: Long, generation: Long, identity: String?, spatial: Boolean, eyeHeight: Float, hands: Boolean,
joyCons: Boolean = false, steadyAim: Boolean = false) {
engine?.close(); engine = null
enabled = spatial; handsEnabled = spatial && (hands || joyCons)
spatialSelected = spatial; handsSelected = spatial && hands && !joyCons
joyConsSelected = spatial && joyCons
steadyAimSelected = steadyAim
session.reset(generation, eyeHeight, spatial && hadSpatialSession)
hadSpatialSession = hadSpatialSession || spatial
if (spatial) context?.let { engine = StereoHandEngine(it, token, generation, identity) }
mutableStatus.value = SpatialTrackingStatus(if (spatial) text(R.string.spatial_wait_stable) else text(R.string.spatial_disabled))
engine?.setEnabled(handsEnabled)
engine?.diagnostics(diagnosticsEnabled)
publish(System.nanoTime())
}
internal fun disconnect() {
enabled = false; handsEnabled = false
session.reset(session.generation + 1, 1.6f)
publish(System.nanoTime())
engine?.close(); engine = null
mutableStatus.value = SpatialTrackingStatus(text(R.string.spatial_disconnected))
publish(System.nanoTime())
}
@Synchronized internal fun useLegacyTracking() {
spatialSelected = false; handsSelected = false; joyConsSelected = false
publish(System.nanoTime())
}
@Synchronized internal fun setOutputUsesGlasses(on: Boolean) {
if (outputUsesGlasses == on) return
outputUsesGlasses = on
publish(System.nanoTime())
}
@Synchronized internal fun accept(values: FloatArray, time: Long, generation: Long, valid: Boolean) {
if (!enabled || generation != session.generation) return
val pose = SpatialPose.fromSdk(values)
session.accept(SpatialSample(pose ?: SpatialPose(), time, generation, valid && pose != null))
publish(time)
if (time-lastStatus > 250_000_000L) {
lastStatus = time
refreshStatus(time)
}
}
@Synchronized fun refreshStatus(now: Long? = null) {
val checkedAt = now ?: System.nanoTime()
val valid = session.valid(checkedAt)
val pending = session.recenterPending
val recenter = session.needsRecenter
val fresh = session.latest()?.let { it.valid && checkedAt-it.timeNs in 0..100_000_000L } == true
mutableStatus.update { it.copy(trackingValid = valid, needsRecenter = recenter, recenterPending = pending, headFresh = fresh,
handStatus = engine?.handStatus?.message ?: text(R.string.spatial_hands_off),
message = when {
!enabled -> text(R.string.spatial_tracking_off)
pending -> text(R.string.spatial_recenter_pending)
!fresh -> text(R.string.spatial_tracking_lost)
recenter -> text(R.string.spatial_origin_changed)
else -> text(R.string.spatial_active)
}) }
}
@Synchronized fun setControllers(hands: Boolean, joyCons: Boolean) {
handsSelected = hands && !joyCons
joyConsSelected = joyCons
handsEnabled = enabled && (hands || joyCons)
session.setHands(session.generation, SpatialHand(), SpatialHand())
engine?.setEnabled(handsEnabled)
publish(System.nanoTime())
}
@Synchronized fun setSteadyHandAim(on: Boolean) {
steadyAimSelected = on
publish(System.nanoTime())
}
fun setFloor(height: Float) { session.setFloor(height); refreshStatus(); publish(System.nanoTime()) }
fun recenter(screen: Boolean) { session.recenter(screen); refreshStatus(); publish(System.nanoTime()) }
fun diagnostics(on: Boolean) { diagnosticsEnabled = on; engine?.diagnostics(on) }
fun calibrationStart() { engine?.startCalibration() }
fun calibrationCapture() { engine?.captureCalibration() }
fun calibrationSolve() { engine?.solveCalibration() }
fun calibrationValidate(distanceMeters: Float) { engine?.validateCalibration(distanceMeters) }
fun captureDiagnostic() { engine?.recordDiagnostic() }
fun setGpu(gpu: Boolean) { engine?.setGpu(gpu) }
fun setHandModel(lite: Boolean) { engine?.setModel(lite) }
fun setHandHalfRate(halfRate: Boolean) { engine?.setHalfRate(halfRate) }
fun setHandCpuParallel(parallel: Boolean) { engine?.setCpuParallel(parallel) }
fun setHandAdaptiveInput(adaptive: Boolean) { engine?.setAdaptiveInput(adaptive) }
fun setHandScheduledTracking(scheduled: Boolean) { engine?.setScheduledTracking(scheduled) }
internal fun handLiteEnabled() = (engine?.model ?: HandModel.DEFAULT) == HandModel.LITE
internal fun handHalfRateEnabled() = engine?.halfRateInput ?: true
internal fun handAdaptiveInputEnabled() = engine?.adaptiveInput ?: true
fun benchmarkSavedCapture(id: String) { checkNotNull(engine) { "Connect glasses and enable positional tracking" }.benchmarkSavedCapture(id) }
internal fun diagnosticFrame() = engine?.preview
internal fun diagnosticMessage() = engine?.message ?: text(R.string.spatial_connect_hint)
internal fun calibrationProgress() = engine?.calibrationProgress ?: SpatialCalibrationProgress()
internal fun gpuEnabled() = engine?.gpu ?: false
fun diagnosticStatus(): org.json.JSONObject {
val now=System.nanoTime();val sample=session.latest();val state=status.value;val progress=calibrationProgress()
return org.json.JSONObject().put("enabled",enabled).put("valid",session.valid(now)).put("needsRecenter",session.needsRecenter)
.put("joyConTracking",joyConsSelected)
.put("steadyHandAim",handsSelected && steadyAimSelected)
.put("positionEstimator",if (joyConsSelected) "joycon-wrist" else "full-palm")
.put("recenterPending",session.recenterPending)
.put("handMaxAgeMs",HandObservation.MAX_AGE_NS / 1_000_000)
.put("handGpuEnabled",gpuEnabled()).put("handInferenceBackend",engine?.inferenceBackend ?: "not initialized")
.put("handModel",(engine?.model ?: HandModel.DEFAULT).name).put("handHalfRate",handHalfRateEnabled())
.put("handImagePreprocessing",HandImageBuffer.PREPROCESSING)
.put("handLensCorrection",engine?.lensCorrection ?: "none")
.put("handCpuParallel",engine?.cpuParallel ?: true)
.put("handAdaptiveInput",handAdaptiveInputEnabled()).put("handScheduledTracking",engine?.scheduledTracking ?: true)
.put("handInputRatePermille",engine?.inputRatePermille ?: 500)
.put("handBenchmark",engine?.benchmark)
.put("calibrationViews",progress.views).put("calibrationCollecting",progress.collecting)
.put("capturePending",progress.capturing).put("captureMessage",progress.captureMessage)
.put("calibrationFitted",progress.fitted).put("validationPending",progress.validating).put("validationMessage",progress.validationMessage)
.put("calibrationIdentityAvailable",engine?.hasStableIdentity == true).put("handTracking",engine?.handStatus?.json())
.put("calibrationSource",engine?.calibrationSource ?: "none").put("calibrationVerified",engine?.calibrationVerified == true)
.put("generation",session.generation).put("origin",session.originGeneration)
.put("ageMs",sample?.let { (now-it.timeNs)/1e6 } ?: -1.0)
.put("position",sample?.pose?.position?.array()?.let { org.json.JSONArray(it.toList()) })
.put("message",state.message).put("camera",diagnosticMessage()).put("frames",state.frames).put("dropped",state.dropped)
.put("processingMs",state.processingMs.toDouble()).put("calibrated",state.calibration)
.put("frameAgeMs",state.frameAgeMs.toDouble()).put("cameraHz",state.cameraHz.toDouble()).put("synchronized",state.synchronized)
.put("cameraFrameDivisor",state.cameraFrameDivisor).put("rateLimitedFrames",state.rateLimitedFrames)
.put("cameraCopyPermille",state.cameraCopyPermille)
.put("leftValid",session.hand(0,now).valid).put("rightValid",session.hand(1,now).valid)
}
internal fun statistics(calibrated: Boolean, frames: Long, dropped: Long, ms: Float, age: Float, hz: Float, synchronized: Boolean,
frameDivisor: Int, rateLimited: Long, copyPermille: Int = 1000) {
mutableStatus.update { it.copy(calibration = calibrated, frames = frames, dropped = dropped, processingMs = ms,
frameAgeMs=age,cameraHz=hz,synchronized=synchronized,cameraFrameDivisor=frameDivisor,
rateLimitedFrames=rateLimited,cameraCopyPermille=copyPermille,
calibrationSource=engine?.calibrationSource ?: "none",calibrationVerified=engine?.calibrationVerified == true) }
}
@Synchronized internal fun publish(now: Long) {
synchronized(session) {
val hands = Array(2) { session.hand(it, now) }
buffer.clear()
buffer.putInt(0x53504444).putInt(4).putInt(176).putInt(0)
buffer.putLong(session.generation).putLong(session.originGeneration)
val modes = (if (spatialSelected) 1 else 0) or (if (handsSelected) 2 else 0) or
(if (joyConsSelected) 4 else 0) or (if (handsSelected && steadyAimSelected) 8 else 0)
buffer.putInt(if (outputUsesGlasses) modes else 0).putInt(session.inputEpochs())
putPose(session.view(false) ?: SpatialPose(), session.latest()?.timeNs ?: 0, if(session.valid(now)) 1 else 0)
for (i in 0..1) {
val hand = hands[i]
putPose(hand.pose, hand.timeNs, hand.flags())
buffer.putFloat(if (hand.valid && hand.triggerValid) hand.trigger else 0f)
.putInt(if (hand.valid && hand.gripValid && hand.grip) 1 else 0)
}
buffer.flip()
if (publisherAttached) WaylandCompositor.nativePublishSpatial(buffer)
}
}
private fun putPose(p: SpatialPose, time: Long, flags: Int) {
buffer.putLong(time).putFloat(p.position.x).putFloat(p.position.y).putFloat(p.position.z)
buffer.putFloat(p.rotation.x).putFloat(p.rotation.y).putFloat(p.rotation.z).putFloat(p.rotation.w)
buffer.putInt(flags)
}
}
@@ -0,0 +1,32 @@
package com.droiddeck.launcher.glasses
internal class SteamVrSpatialGate(private val settleMs: Long = 2_000) {
private var waiting = false
private var running = false
var deadline: Long? = null
private set
fun prepare(): Boolean {
if (running || waiting) return false
waiting = true
deadline = null
return true
}
fun setRunning(on: Boolean, now: Long): Boolean {
if (running == on) return false
running = on
waiting = false
deadline = if (on) now + settleMs else null
return true
}
fun cancel() {
waiting = false
running = false
deadline = null
}
fun spatialEnabled(requested: Boolean, now: Long) =
requested && !waiting && (deadline?.let { now >= it } ?: true)
}
@@ -0,0 +1,300 @@
package com.droiddeck.launcher.glasses
import android.content.Context
import com.droiddeck.launcher.R
import org.json.JSONArray
import org.json.JSONObject
import org.opencv.calib3d.Calib3d
import org.opencv.core.*
import org.opencv.objdetect.CharucoBoard
import org.opencv.objdetect.CharucoDetector
import org.opencv.objdetect.Objdetect
import org.opencv.imgproc.Imgproc
import java.io.File
import kotlin.math.*
internal data class BoardObservation(val objects: Mat, val left: Mat, val right: Mat, val head: SpatialPose)
internal class CalibrationAlignmentException(context: Context, val deviation: Float) : IllegalArgumentException(
context.getString(R.string.spatial_alignment_error, SpatialMeasurements.validation(deviation)))
internal class StereoCalibration(val identity: String, val width: Int, val height: Int, val fisheye: Boolean,
val k1: Mat, val d1: Mat, val k2: Mat, val d2: Mat, val r: Mat, val t: Mat,
val cameraToHead: SpatialPose, val error: Double, var verified: Boolean = false,
val bundledBase: Boolean = false) : AutoCloseable {
val readyForTracking get() = verified || bundledBase
val source get() = if (bundledBase && !verified) "luma-ultra-base" else "device"
private val p1 = Mat.zeros(3, 4, CvType.CV_64F)
private val p2 = Mat.zeros(3, 4, CvType.CV_64F)
private val undistortionMaps = Array(2) { arrayOf(Mat(),Mat()) }
init {
val a = Mat.eye(3, 4, CvType.CV_64F)
val b = Mat.zeros(3, 4, CvType.CV_64F)
r.copyTo(b.colRange(0, 3)); t.copyTo(b.col(3))
val empty = Mat()
Core.gemm(k1, a, 1.0, empty, 0.0, p1); Core.gemm(k2, b, 1.0, empty, 0.0, p2)
a.release(); b.release(); empty.release()
}
fun undistort(src: Mat, dst: Mat, right: Boolean) {
val k = if (right) k2 else k1; val d = if (right) d2 else d1
val maps=undistortionMaps[if(right) 1 else 0]
if(maps[0].empty()) {
val identity=Mat.eye(3,3,CvType.CV_64F)
try {
if(fisheye) Calib3d.fisheye_initUndistortRectifyMap(k,d,identity,k,src.size(),CvType.CV_16SC2,maps[0],maps[1])
else Calib3d.initUndistortRectifyMap(k,d,identity,k,src.size(),CvType.CV_16SC2,maps[0],maps[1])
} finally { identity.release() }
}
Imgproc.remap(src,dst,maps[0],maps[1],Imgproc.INTER_LINEAR)
}
fun triangulateRaw(left: List<Point>, right: List<Point>): List<SpatialVector>? {
val points = triangulateLandmarksRaw(left,right) ?: return null
return if(points.any { it == null }) null else points.filterNotNull()
}
fun triangulateLandmarksRaw(left: List<Point>, right: List<Point>): List<SpatialVector?>? {
if (left.size != right.size || left.isEmpty()) return null
val l=MatOfPoint2f(*left.toTypedArray()); val r=MatOfPoint2f(*right.toTypedArray())
try { return triangulateRawPoints(l,r) } finally { l.release();r.release() }
}
fun triangulateRaw(left: Mat, right: Mat): List<SpatialVector>? {
val points = triangulateRawPoints(left,right) ?: return null
return if(points.any { it == null }) null else points.filterNotNull()
}
private fun triangulateRawPoints(left: Mat, right: Mat): List<SpatialVector?>? {
val l=MatOfPoint2f(); val r=MatOfPoint2f(); val identity=Mat.eye(3,3,CvType.CV_64F)
try {
if (fisheye) {
Calib3d.fisheye_undistortPoints(left,l,k1,d1,identity,k1)
Calib3d.fisheye_undistortPoints(right,r,k2,d2,identity,k2)
} else {
val criteria=TermCriteria(TermCriteria.COUNT+TermCriteria.EPS,50,1e-8)
Calib3d.undistortPointsIter(left,l,k1,d1,identity,k1,criteria)
Calib3d.undistortPointsIter(right,r,k2,d2,identity,k2,criteria)
}
return triangulatePoints(l.toList(),r.toList())
} finally { l.release(); r.release(); identity.release() }
}
fun triangulate(left: List<Point>, right: List<Point>): List<SpatialVector>? {
val points = triangulatePoints(left,right) ?: return null
return if(points.any { it == null }) null else points.filterNotNull()
}
private fun triangulatePoints(left: List<Point>, right: List<Point>): List<SpatialVector?>? {
if (left.size != right.size || left.isEmpty()) return null
val a = Mat(2, left.size, CvType.CV_64F); val b = Mat(2, right.size, CvType.CV_64F); val out = Mat()
try {
a.put(0, 0, *left.map { it.x }.toDoubleArray()); a.put(1, 0, *left.map { it.y }.toDoubleArray())
b.put(0, 0, *right.map { it.x }.toDoubleArray()); b.put(1, 0, *right.map { it.y }.toDoubleArray())
Calib3d.triangulatePoints(p1, p2, a, b, out)
val projection1 = DoubleArray(12); val projection2 = DoubleArray(12)
p1.get(0,0,projection1); p2.get(0,0,projection2)
return left.indices.map { i ->
StereoPointValidation.reconstruct(DoubleArray(4) { out.get(it,i)[0] },
doubleArrayOf(left[i].x,left[i].y),doubleArrayOf(right[i].x,right[i].y),projection1,projection2)
}
} finally { a.release(); b.release(); out.release() }
}
fun save(file: File) {
fun mat(m: Mat) = JSONArray((0 until m.rows()).flatMap { row -> (0 until m.cols()).map { m.get(row,it)[0] } })
val j = JSONObject().put("version",1).put("identity",identity).put("width",width).put("height",height)
.put("fisheye",fisheye).put("error",error).put("verified",verified)
for ((name,value) in listOf("k1" to k1,"d1" to d1,"k2" to k2,"d2" to d2,"r" to r,"t" to t))
j.put(name, JSONObject().put("rows",value.rows()).put("cols",value.cols()).put("data",mat(value)))
val p = cameraToHead.position; val q = cameraToHead.rotation
j.put("cameraToHead",JSONArray(listOf(p.x,p.y,p.z,q.w,q.x,q.y,q.z)))
file.parentFile?.mkdirs()
val tmp = File(file.parentFile,file.name+".tmp"); tmp.writeText(j.toString())
java.nio.file.Files.move(tmp.toPath(),file.toPath(),java.nio.file.StandardCopyOption.REPLACE_EXISTING)
}
override fun close() { (listOf(k1,d1,k2,d2,r,t,p1,p2)+undistortionMaps.flatMap { it.toList() }).forEach { it.release() } }
companion object {
fun load(file: File, identity: String, width: Int, height: Int): StereoCalibration? =
runCatching { StereoCalibrationProfile.parse(file.readText(), identity, width, height)?.let(::fromProfile) }.getOrNull()
fun fromProfile(profile: StereoCalibrationProfile): StereoCalibration {
val matrices = mutableListOf<Mat>()
try {
profile.matrices.forEach { data ->
val matrix = Mat(data.rows, data.columns, CvType.CV_64F)
matrices.add(matrix)
matrix.put(0, 0, *data.values)
}
return StereoCalibration(profile.identity, profile.width, profile.height, profile.fisheye,
matrices[0], matrices[1], matrices[2], matrices[3], matrices[4], matrices[5],
profile.cameraToHead, profile.error, profile.verified, profile.bundledBase)
} catch (e: Exception) {
matrices.forEach(Mat::release)
throw e
}
}
}
}
internal class StereoCalibrator(private val context: Context) : AutoCloseable {
val board = CharucoBoard(Size(5.0,7.0),0.03f,0.022f,Objdetect.getPredefinedDictionary(Objdetect.DICT_4X4_50))
private val detector = CharucoDetector(board)
val observations = mutableListOf<BoardObservation>()
var commonCorners: Int = 0; private set
fun detect(left: Mat, right: Mat, head: SpatialPose): BoardObservation? {
val lc=Mat(); val li=Mat(); val rc=Mat(); val ri=Mat()
try {
detector.detectBoard(left,lc,li); detector.detectBoard(right,rc,ri)
val l=(0 until li.rows()).associate { li.get(it,0)[0].toInt() to lc.get(it,0) }
val r=(0 until ri.rows()).associate { ri.get(it,0)[0].toInt() to rc.get(it,0) }
val ids=l.keys.intersect(r.keys).sorted()
commonCorners = ids.size
if (ids.size<12) return null
val objects=MatOfPoint3f(*ids.map { Point3((it%4+1)*0.03,(it/4+1)*0.03,0.0) }.toTypedArray())
return BoardObservation(objects,MatOfPoint2f(*ids.map { Point(l[it]!![0],l[it]!![1]) }.toTypedArray()),
MatOfPoint2f(*ids.map { Point(r[it]!![0],r[it]!![1]) }.toTypedArray()),head)
} finally { listOf(lc,li,rc,ri).forEach { it.release() } }
}
fun solve(identity: String,width: Int,height: Int): StereoCalibration {
require(observations.size>=24) { context.getString(R.string.spatial_need_views) }
val initial=observations.first().head.rotation
val axes=observations.map { initial.inverse()*it.head.rotation }.map { SpatialVector(it.x,it.y,it.z) }
require(axes.any { a -> axes.any { b -> a.cross(b).length()>0.02f } }) { context.getString(R.string.spatial_need_rotation) }
val training=observations.filterIndexed { i,_ -> i%5!=0 }; val held=observations.filterIndexed { i,_ -> i%5==0 }
val failures = mutableListOf<Pair<String, Exception>>()
val candidates=listOf(false,true).mapNotNull { fish ->
try { fit(identity,width,height,fish,training,held) }
catch (e: Exception) {
val model = if (fish) "Fisheye" else "Pinhole"
android.util.Log.w("StereoCalibration", "$model calibration failed", e)
failures.add(model to e)
null
}
}
val best=candidates.minByOrNull { it.error } ?: run {
val alignment=failures.map { it.second }.filterIsInstance<CalibrationAlignmentException>().minByOrNull { it.deviation }
val reason=alignment?.message ?: failures.joinToString(" ") { (model,e) ->
"$model: ${e.message?.substringBefore('\n') ?: e.javaClass.simpleName}"
}
error(context.getString(R.string.spatial_calibration_error, reason))
}
candidates.filter { it !== best }.forEach { it.close() }
if(best.error>2.0) { best.close(); error(context.getString(R.string.spatial_reprojection_error, best.error)) }
return best
}
private fun fit(identity: String,w: Int,h: Int,fish: Boolean,train: List<BoardObservation>,held: List<BoardObservation>): StereoCalibration {
val k1=Mat.eye(3,3,CvType.CV_64F); val k2=Mat.eye(3,3,CvType.CV_64F)
val d1=Mat.zeros(if(fish) 4 else 5,1,CvType.CV_64F); val d2=d1.clone()
val r=Mat(); val t=Mat(); val objects=train.map { it.objects }; val size=Size(w.toDouble(),h.toDouble())
fun intrinsic(k: Mat,d: Mat,images: List<Mat>) {
val rv=mutableListOf<Mat>(); val tv=mutableListOf<Mat>()
try {
if(fish) Calib3d.fisheye_calibrate(objects,images,size,k,d,rv,tv,Calib3d.fisheye_CALIB_RECOMPUTE_EXTRINSIC or Calib3d.fisheye_CALIB_FIX_SKEW)
else Calib3d.calibrateCamera(objects,images,size,k,d,rv,tv)
} finally { (rv+tv).forEach { it.release() } }
}
try {
intrinsic(k1,d1,train.map { it.left }); intrinsic(k2,d2,train.map { it.right })
stereoGeometry(objects,train.map{it.left},train.map{it.right},k1,d1,k2,d2,size,fish,r,t)
require(Core.norm(t) in 0.02..0.3) { context.getString(R.string.spatial_bad_baseline) }
val hr=mutableListOf<Mat>(); val ht=mutableListOf<Mat>(); val br=mutableListOf<Mat>(); val bt=mutableListOf<Mat>()
val camR=Mat(); val camT=Mat()
try {
for(o in train) {
val rv=Mat(); val tv=Mat(); val rotation=Mat()
boardPose(o.objects,o.left,k1,d1,fish,rv,tv)
Calib3d.Rodrigues(rv,rotation); rv.release(); br.add(rotation); bt.add(tv)
hr.add(matrix(o.head.rotation)); ht.add(vector(o.head.position))
}
Calib3d.calibrateHandEye(hr,ht,br,bt,camR,camT,Calib3d.CALIB_HAND_EYE_PARK)
require(Core.checkRange(camR) && Core.checkRange(camT) && Core.norm(camT)<0.3) { context.getString(R.string.spatial_need_orientations) }
val cameraToHead=pose(camR,camT)
require(abs(Core.determinant(camR)-1.0)<0.01) { context.getString(R.string.spatial_bad_rotation) }
val boardWorld=held.map { o ->
val rv=Mat();val tv=Mat();val rotation=Mat()
try {
boardPose(o.objects,o.left,k1,d1,fish,rv,tv);Calib3d.Rodrigues(rv,rotation)
o.head*cameraToHead*pose(rotation,tv)
} finally { rv.release();tv.release();rotation.release() }
}
val center=boardWorld.map { it.position }.reduce { a,b -> a+b }*(1f/boardWorld.size)
val drift = boardWorld.maxOf { (it.position-center).length() }
if (drift>=0.05f) throw CalibrationAlignmentException(context,drift)
val orientation=boardWorld.first().rotation
require(boardWorld.all { val q=orientation.inverse()*it.rotation;abs(q.w)>cos(Math.toRadians(8.0)/2) }) { context.getString(R.string.spatial_bad_alignment) }
val error=held.map { reprojection(it,k1,d1,k2,d2,r,t,fish) }.average()
require(error.isFinite())
return StereoCalibration(identity,w,h,fish,k1,d1,k2,d2,r,t,cameraToHead,error)
} finally { (hr+ht+br+bt+listOf(camR,camT)).forEach { it.release() } }
} catch(e: Exception) { listOf(k1,k2,d1,d2,r,t).forEach { it.release() }; throw e }
}
private fun boardPose(objects: Mat,pixels: Mat,k: Mat,d: Mat,fish: Boolean,rv: Mat,tv: Mat) {
val ok=if(fish) Calib3d.fisheye_solvePnP(objects,pixels,k,d,rv,tv)
else {
val obj=MatOfPoint3f(objects); val img=MatOfPoint2f(pixels); val dist=MatOfDouble(d)
try { Calib3d.solvePnP(obj,img,k,dist,rv,tv) } finally { obj.release(); img.release(); dist.release() }
}
require(ok)
}
private fun reprojection(o: BoardObservation,k1: Mat,d1: Mat,k2: Mat,d2: Mat,r: Mat,t: Mat,fish: Boolean): Double {
val rv=Mat(); val tv=Mat(); val rot=Mat(); val rightR=Mat(); val rightT=Mat(); val rightV=Mat(); val empty=Mat()
val predicted=MatOfPoint2f(); val objectPoints=MatOfPoint3f(o.objects); val dist=MatOfDouble(d2)
try {
boardPose(o.objects,o.left,k1,d1,fish,rv,tv); Calib3d.Rodrigues(rv,rot)
Core.gemm(r,rot,1.0,empty,0.0,rightR); Core.gemm(r,tv,1.0,t,1.0,rightT); Calib3d.Rodrigues(rightR,rightV)
if(fish) Calib3d.fisheye_projectPoints(o.objects,predicted,rightV,rightT,k2,d2)
else Calib3d.projectPoints(objectPoints,rightV,rightT,k2,dist,predicted)
return Core.norm(predicted,o.right,Core.NORM_L2)/sqrt(o.right.rows().toDouble())
} finally { listOf(rv,tv,rot,rightR,rightT,rightV,empty,predicted,objectPoints,dist).forEach { it.release() } }
}
override fun close() { observations.forEach { listOf(it.objects,it.left,it.right).forEach(Mat::release) }; observations.clear(); detector.clear() }
fun save(file: File, identity: String, width: Int, height: Int) {
fun points(m: Mat) = JSONArray((0 until m.rows()).map { row -> JSONArray(m.get(row,0).toList()) })
val views = JSONArray(observations.map { o ->
val p=o.head.position; val q=o.head.rotation
JSONObject().put("objects",points(o.objects)).put("left",points(o.left)).put("right",points(o.right))
.put("head",JSONArray(listOf(p.x,p.y,p.z,q.w,q.x,q.y,q.z)))
})
val data=JSONObject().put("version",1).put("identity",identity).put("width",width).put("height",height).put("views",views)
file.parentFile?.mkdirs()
val temporary=File(file.parentFile,file.name+".tmp")
temporary.writeText(data.toString())
java.nio.file.Files.move(temporary.toPath(),file.toPath(),java.nio.file.StandardCopyOption.REPLACE_EXISTING)
}
companion object {
fun load(context: Context, file: File, identity: String, width: Int, height: Int): StereoCalibrator? {
if (!file.isFile) return null
val restored=StereoCalibrator(context)
try {
val data=JSONObject(file.readText())
require(data.getInt("version")==1 && data.getString("identity")==identity && data.getInt("width")==width && data.getInt("height")==height)
val views=data.getJSONArray("views")
require(views.length() in 1..1000)
for (i in 0 until views.length()) {
val view=views.getJSONObject(i)
val points=CalibrationObservationData.parse(view)
restored.observations.add(BoardObservation(
MatOfPoint3f(*points.objects.map { Point3(it[0],it[1],it[2]) }.toTypedArray()),
MatOfPoint2f(*points.left.map { Point(it[0],it[1]) }.toTypedArray()),
MatOfPoint2f(*points.right.map { Point(it[0],it[1]) }.toTypedArray()),points.head))
}
return restored
} catch (e: Exception) {
restored.close()
android.util.Log.w("StereoCalibration", "Saved captures could not be restored", e)
return null
}
}
internal fun stereoGeometry(objects: List<Mat>, left: List<Mat>, right: List<Mat>, k1: Mat,d1: Mat,k2: Mat,d2: Mat,
size: Size,fish: Boolean,r: Mat,t: Mat) {
val e=Mat(); val f=Mat(); val identity=Mat.eye(3,3,CvType.CV_64F); val zero=Mat.zeros(5,1,CvType.CV_64F)
val corrected=mutableListOf<Mat>()
fun images(input: List<Mat>, k: Mat,d: Mat) = if (!fish) input else input.map { pixels ->
Mat().also { output -> corrected.add(output); Calib3d.fisheye_undistortPoints(pixels,output,k,d,identity,k) }
}
try {
// OpenCV 4.13 fisheye stereo fitting assumes equal corner counts in every view.
Calib3d.stereoCalibrate(objects,images(left,k1,d1),images(right,k2,d2),k1,if(fish)zero else d1,k2,if(fish)zero else d2,
size,r,t,e,f,Calib3d.CALIB_FIX_INTRINSIC)
} finally { (corrected+listOf(e,f,identity,zero)).forEach(Mat::release) }
}
fun matrix(q: SpatialRotation)=Mat(3,3,CvType.CV_64F).also { it.put(0,0,*q.matrix().map(Float::toDouble).toDoubleArray()) }
fun vector(p: SpatialVector)=Mat(3,1,CvType.CV_64F).also { it.put(0,0,p.x.toDouble(),p.y.toDouble(),p.z.toDouble()) }
fun pose(r: Mat,t: Mat)=SpatialPose(SpatialVector(t.get(0,0)[0].toFloat(),t.get(1,0)[0].toFloat(),t.get(2,0)[0].toFloat()),
SpatialRotation.fromBasis(column(r,0),column(r,1),column(r,2)))
private fun column(r: Mat,c: Int)=SpatialVector(r.get(0,c)[0].toFloat(),r.get(1,c)[0].toFloat(),r.get(2,c)[0].toFloat())
}
}
@@ -0,0 +1,51 @@
package com.droiddeck.launcher.glasses
import org.json.JSONObject
internal data class CalibrationMatrix(val rows: Int, val columns: Int, val values: DoubleArray)
internal data class StereoCalibrationProfile(
val identity: String, val width: Int, val height: Int, val fisheye: Boolean,
val matrices: List<CalibrationMatrix>, val cameraToHead: SpatialPose, val error: Double,
val verified: Boolean, val bundledBase: Boolean = false,
) {
companion object {
const val BASE_ASSET = "calibration/luma-ultra-640x480.json"
const val BASE_IDENTITY = "luma-ultra-base-v1"
fun select(saved: String?, base: () -> String?, identity: String?, width: Int, height: Int): StereoCalibrationProfile? {
if (identity != null && saved != null) parse(saved, identity, width, height)?.let { return it }
return base()?.let { parse(it, identity ?: BASE_IDENTITY, width, height, bundledBase = true) }
}
fun parse(text: String, identity: String, width: Int, height: Int,
bundledBase: Boolean = false): StereoCalibrationProfile? = runCatching {
val data = JSONObject(text)
require(data.getInt("version") == 1 && data.getInt("width") == width && data.getInt("height") == height)
if (bundledBase) {
require(data.getString("identity") == BASE_IDENTITY && data.getString("deviceModel") == "VITURE Luma Ultra")
require(data.getString("cameraPair") == "left0-right0" && width == 640 && height == 480)
} else require(data.getString("identity") == identity)
val fisheye = data.getBoolean("fisheye")
val matrices = listOf("k1", "d1", "k2", "d2", "r", "t").map { name ->
val matrix = data.getJSONObject(name)
val rows = matrix.getInt("rows"); val columns = matrix.getInt("cols")
val values = matrix.getJSONArray("data")
require(rows in 1..14 && columns in 1..14 && values.length() == rows * columns)
CalibrationMatrix(rows, columns, DoubleArray(values.length()) { values.getDouble(it).also { n -> require(n.isFinite()) } })
}
require(listOf(0, 2, 4).all { matrices[it].rows == 3 && matrices[it].columns == 3 })
require(matrices[5].rows == 3 && matrices[5].columns == 1)
require(listOf(1, 3).all {
val matrix = matrices[it]
(matrix.rows == 1 || matrix.columns == 1) && matrix.values.size in if (fisheye) listOf(4) else listOf(4, 5, 8, 12, 14)
})
require(listOf(0, 2).all { matrices[it].values[0] > 0 && matrices[it].values[4] > 0 })
val pose = data.getJSONArray("cameraToHead"); require(pose.length() == 7)
val cameraToHead = SpatialPose.fromSdk(FloatArray(7) { pose.getDouble(it).toFloat() }) ?: error("Invalid camera transform")
val error = data.getDouble("error"); require(error.isFinite() && error in 0.0..2.0)
StereoCalibrationProfile(identity, width, height, fisheye, matrices, cameraToHead, error,
verified = !bundledBase && data.getBoolean("verified"), bundledBase = bundledBase)
}.getOrNull()
}
}
@@ -0,0 +1,67 @@
package com.droiddeck.launcher.glasses
import kotlin.math.abs
import kotlin.math.min
internal object StereoFingerGestures {
data class Result(val trigger: Float = 0f, val triggerValid: Boolean = false,
val grip: Boolean = false, val gripValid: Boolean = false)
fun estimate(left: List<SpatialVector>?, right: List<SpatialVector>?, gripping: Boolean): Result {
val a = View.create(left) ?: return Result()
val b = View.create(right) ?: return Result()
val triggerA = a.pinch()
val triggerB = b.pinch()
val triggerValid = triggerA != null && triggerB != null && abs(triggerA-triggerB) <= 0.30f
val curls = listOf(9,13,17).map { base ->
val ca = a.curl(base)
val cb = b.curl(base)
if (ca != null && cb != null && abs(ca-cb) <= 0.45f) min(ca,cb) else null
}
val gripValid = curls.all { it != null }
val closed = curls.count { it != null && it >= (if (gripping) 0.40f else 0.65f) }
return Result(if (triggerValid) min(triggerA!!,triggerB!!) else 0f,triggerValid,
gripValid && closed >= (if (gripping) 2 else 3),gripValid)
}
private class View(val points: List<SpatialVector>,val width: Float) {
private fun point(index: Int) = points[index].takeIf(SpatialVector::finite)
?.let { SpatialVector(it.x,it.y) }
fun pinch(): Float? {
val thumb = point(4) ?: return null
val index = point(8) ?: return null
val wrist = point(0) ?: return null
if ((thumb-wrist).length() > width*3.5f || (index-wrist).length() > width*3.5f) return null
return ((0.55f-(thumb-index).length()/width)/0.35f).coerceIn(0f,1f)
}
fun curl(base: Int): Float? {
val wrist = point(0) ?: return null
val joints = (base..base+3).map { point(it) ?: return null }
val lengths = joints.zipWithNext { a,b -> (b-a).length() }
if (lengths.any { it < width*0.005f }) return null
val path = lengths.sum()
val reach = (joints[0]-wrist).length()
if (path !in width*0.15f..width*3f || reach < width*0.20f) return null
val shortness = ((1.7f-(joints[3]-wrist).length()/reach)/0.55f).coerceIn(0f,1f)
val bend = ((1f-(joints[3]-joints[0]).length()/path)/0.35f).coerceIn(0f,1f)
return min(shortness,bend)
}
companion object {
fun create(points: List<SpatialVector>?): View? {
if (points?.size != 21) return null
if (listOf(0,5,9,13,17).any { !points[it].finite() }) return null
fun xy(index: Int) = SpatialVector(points[index].x,points[index].y)
val across = xy(5)-xy(17)
val along = xy(9)-xy(0)
val width = across.length()
val length = along.length()
if (!width.isFinite() || !length.isFinite() || width < 1e-3f || length < 1e-3f) return null
if (width/length !in 0.25f..3f || abs(across.cross(along).z)/(width*length) < 0.15f) return null
return View(points,width)
}
}
}
}
@@ -0,0 +1,650 @@
package com.droiddeck.launcher.glasses
import android.content.Context
import com.droiddeck.launcher.R
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import com.google.mediapipe.framework.image.MPImage
import com.google.mediapipe.tasks.core.BaseOptions
import com.google.mediapipe.tasks.core.Delegate
import com.google.mediapipe.tasks.vision.core.RunningMode
import com.google.mediapipe.tasks.vision.handlandmarker.HandLandmarker
import org.opencv.android.OpenCVLoader
import org.opencv.android.Utils
import org.opencv.core.*
import org.opencv.imgproc.Imgproc
import org.opencv.imgcodecs.Imgcodecs
import org.json.JSONObject
import java.io.File
import java.nio.ByteBuffer
import java.security.MessageDigest
import java.util.concurrent.ConcurrentLinkedQueue
internal data class HandTrackingDiagnostics(val message: String, val leftCameraHands: Int = 0,
val rightCameraHands: Int = 0, val matched: Int = 0, val reconstructed: Int = 0, val accepted: Int = 0,
val preprocessMs: Float = 0f, val inferenceMs: Float = 0f,
val triggerValid: Int = 0, val gripValid: Int = 0,
val leftRawHands: Int = 0, val rightRawHands: Int = 0,
val previousLeftRawHands: Int = -1, val previousRightRawHands: Int = -1,
val leftInferenceMs: Float = 0f, val rightInferenceMs: Float = 0f,
val inferenceHandLimit: Int = 2,
val observationTimeNs: Long = 0, val observationAgeMs: Float = -1f,
val left: HandObservationDiagnostic? = null, val right: HandObservationDiagnostic? = null,
val observations: HandTrackingMetricSnapshot? = null) {
fun json() = JSONObject().put("message",message).put("experimental",true).put("leftCameraHands",leftCameraHands)
.put("rightCameraHands",rightCameraHands).put("matched",matched).put("reconstructed",reconstructed).put("accepted",accepted)
.put("preprocessMs",preprocessMs.toDouble()).put("inferenceMs",inferenceMs.toDouble())
.put("triggerValid",triggerValid).put("gripValid",gripValid)
.put("leftRawHands",leftRawHands).put("rightRawHands",rightRawHands)
.put("previousLeftRawHands",previousLeftRawHands).put("previousRightRawHands",previousRightRawHands)
.put("leftInferenceMs",leftInferenceMs.toDouble()).put("rightInferenceMs",rightInferenceMs.toDouble())
.put("inferenceHandLimit",inferenceHandLimit)
.put("observationTimeNs",observationTimeNs).put("observationAgeMs",observationAgeMs.toDouble())
.put("left",left?.json()).put("right",right?.json()).put("observations",observations?.json())
}
private fun HandObservationDiagnostic.json() = JSONObject().put("outcome",outcome.name.lowercase())
.put("positionSource",positionSource)
.put("reconstructedPalmLandmarks",org.json.JSONArray(reconstructedPalmLandmarks))
.put("accepted",accepted).put("triggerEstimate",triggerEstimate.toDouble())
.put("triggerGeometryValid",triggerGeometryValid).put("triggerValid",triggerValid)
.put("gripEstimate",gripEstimate).put("gripGeometryValid",gripGeometryValid).put("gripValid",gripValid)
private fun HandSideMetrics.json() = JSONObject().put("observations",observations).put("accepted",accepted)
.put("triggerValid",triggerValid).put("gripValid",gripValid)
.put("triggerAboveClickThreshold",triggerAboveClickThreshold).put("triggerReleased",triggerReleased)
.put("gripPressed",gripPressed).put("trackingLosses",trackingLosses)
.put("triggerLosses",triggerLosses).put("gripLosses",gripLosses)
.put("maximumConsecutiveRejected",maximumConsecutiveRejected)
.put("outcomes",JSONObject().also { json -> outcomes.forEach { (outcome,count) -> json.put(outcome.name.lowercase(),count) } })
private fun HandTrackingMetricSnapshot.json() = JSONObject().put("processedPairs",processedPairs).put("inferencePairs",inferencePairs)
.put("scope","Processed camera pairs in this tracking connection, including absent hands and setup; not detection accuracy")
.put("left",left.json()).put("right",right.json())
internal class StereoHandEngine(private val context: Context, private val token: Long,
private val generation: Long, private val identity: String?) : AutoCloseable {
@Volatile private var running=true
@Volatile private var enabled=false
@Volatile private var debug=false
@Volatile var preview: Bitmap? = null; private set
@Volatile var message=context.getString(R.string.spatial_wait_cameras); private set
@Volatile var calibrationProgress = SpatialCalibrationProgress(); private set
@Volatile var handStatus = HandTrackingDiagnostics(context.getString(R.string.spatial_wait_hands)); private set
val hasStableIdentity get() = identity != null
private val commands=ConcurrentLinkedQueue<() -> Unit>()
private var calibrator: StereoCalibrator?=null
private var calibrationFrame = 0L
private var calibration: StereoCalibration?=null
val calibrationSource get() = calibration?.source ?: "none"
val calibrationVerified get() = calibration?.verified == true
val lensCorrection get() = calibration?.let { if(it.fisheye) "fisheye-points-only" else "pinhole-points-only" } ?: "none"
private var capture=false
private var record=false
private var verify: Float?=null
private var validationRequestedAt=0L
@Volatile var gpu=false; private set
@Volatile var model=HandModel.DEFAULT; private set
@Volatile var halfRateInput=true; private set
@Volatile var adaptiveInput=true; private set
@Volatile var scheduledTracking=true; private set
@Volatile var inputRatePermille=500; private set
@Volatile var cpuParallel=true; private set
@Volatile var benchmark: JSONObject?=null; private set
@Volatile var inferenceBackend: String? = null; private set
private var detectors: DetectorBank?=null
private var fastDetectors: DetectorBank?=null
private var rightWorker: StereoInferenceRunner?=null
private val inferenceSchedule=HandInferenceSchedule()
private val processingRate=HandProcessingRate()
private val handMetrics=HandTrackingMetrics()
private val gestures=Array(2) { HandGestures(rightHand=it==1) }
private val joyConPositions=Array(2) { JoyConWristPosition() }
private var joyConFlow: StereoJoyConFlow? = null
private val profile: File get() {
val hash=MessageDigest.getInstance("SHA-256").digest((identity ?: "session:$generation").toByteArray()).joinToString("") { "%02x".format(it) }
return File(context.filesDir,"spatial/calibration-$hash.json")
}
private val captures: File get() = File(profile.parentFile,profile.nameWithoutExtension+"-captures.json")
private fun saveCaptures() {
try { calibrator?.save(captures,identity ?: "session:$generation",width,height) }
catch (e: Exception) { android.util.Log.w("StereoCalibration", "Could not save captured views", e) }
}
fun setEnabled(on: Boolean) { enabled=on }
fun diagnostics(on: Boolean) { debug=on }
fun setGpu(on: Boolean) { commands.add { clearHands();closeDetectors();gpu=on } }
fun setModel(lite: Boolean) { commands.add {
clearHands();closeDetectors();model=if(lite) HandModel.LITE else HandModel.FULL
} }
fun setHalfRate(on: Boolean) { commands.add { adaptiveInput=false;halfRateInput=on;processingRate.reset() } }
fun setAdaptiveInput(on: Boolean) { commands.add { adaptiveInput=on;processingRate.reset() } }
fun setScheduledTracking(on: Boolean) { commands.add { clearHands();closeDetectors();scheduledTracking=on } }
fun setCpuParallel(on: Boolean) { commands.add { clearHands();closeDetectors();cpuParallel=on } }
fun benchmarkSavedCapture(id: String) {
require(id.matches(Regex("[0-9]{1,20}")))
benchmark=JSONObject().put("state","pending").put("capture",id)
commands.add {
try { benchmark=benchmarkCapture(id) }
catch(e: Exception) { benchmark=JSONObject().put("state","failed").put("error",e.message) }
}
}
fun startCalibration() { debug=true; commands.add {
check(width > 0 && height > 0) { context.getString(R.string.spatial_wait_previews) }
calibrator?.close();calibrator=StereoCalibrator(context)
calibrationFrame = SpatialTracking.session.trackingFrameGeneration
calibrationProgress = SpatialCalibrationProgress(collecting = true)
saveCaptures()
message=context.getString(R.string.spatial_collect_instructions)
} }
fun captureCalibration() { commands.add {
check(calibrationProgress.collecting && calibrationFrame==SpatialTracking.session.trackingFrameGeneration) {
context.getString(R.string.spatial_saved_views_hint)
}
capture=true
calibrationProgress = calibrationProgress.copy(capturing = true, captureMessage = context.getString(R.string.spatial_capture_pending))
} }
fun recordDiagnostic() { commands.add { record=true } }
fun validateCalibration(distance: Float) { commands.add {
require(distance.isFinite() && distance in 0.15f..1.5f)
verify=distance;validationRequestedAt=System.nanoTime()
calibrationProgress=calibrationProgress.copy(validating=true,
validationMessage=context.getString(R.string.spatial_validation_pending))
} }
private var width=0; private var height=0
private val imageBuffer=HandImageBuffer()
private val rightImageBuffer=HandImageBuffer()
private val worker=Thread({ loop() },"viture-stereo-hands").apply { isDaemon=true; start() }
fun solveCalibration() { commands.add {
val c=calibrator ?: error(context.getString(R.string.spatial_start_first))
saveCaptures()
calibrationProgress = calibrationProgress.copy(solving = true, validationMessage = null)
try {
val fitted=c.solve(identity ?: "session:$generation",width,height)
clearHands();closeDetectors()
calibration?.close();calibration=fitted
calibrationProgress = calibrationProgress.copy(fitted = true)
if(identity!=null) fitted.save(profile)
message=context.getString(R.string.spatial_fit_ready, fitted.error)
} finally { calibrationProgress = calibrationProgress.copy(solving = false) }
} }
private fun detector(selected: HandModel=model, useGpu: Boolean=gpu, maxHands: Int=2): HandLandmarker {
val bytes=context.assets.open(selected.asset).use { it.readBytes() }
val hash=MessageDigest.getInstance("SHA-256").digest(bytes).joinToString("") { "%02x".format(it) }
check(hash==selected.sha256) { context.getString(R.string.spatial_model_checksum_error) }
return HandLandmarker.createFromOptions(context,HandLandmarker.HandLandmarkerOptions.builder()
.setBaseOptions(BaseOptions.builder().setModelAssetPath(selected.asset)
.setDelegate(if(useGpu) Delegate.GPU else Delegate.CPU).build())
.setRunningMode(RunningMode.VIDEO).setNumHands(maxHands).setMinHandDetectionConfidence(0.6f)
.setMinHandPresenceConfidence(0.6f).setMinTrackingConfidence(0.6f).build())
}
private data class Detection(val side: Int,val points: List<Point>)
private data class Detections(val hands: List<Detection>,val rawCount: Int,val inferenceMs: Float)
private class DetectorBank(val left: HandLandmarker, val right: HandLandmarker, val maxHands: Int) {
var previousLeftRaw=-1
var previousRightRaw=-1
}
private fun createBank(selected: HandModel, useGpu: Boolean, maxHands: Int,
worker: StereoInferenceRunner?): DetectorBank {
val left=detector(selected,useGpu,maxHands)
try {
val right=worker?.onRight { detector(selected,useGpu,maxHands) } ?: detector(selected,useGpu,maxHands)
return DetectorBank(left,right,maxHands)
} catch(e: Throwable) { left.close();throw e }
}
private fun closeBank(bank: DetectorBank?, worker: StereoInferenceRunner?) {
if(bank==null) return
try { bank.left.close() } finally {
if(worker!=null) worker.onRight { bank.right.close() } else bank.right.close()
}
}
private fun imagePoints(detection: Detection?) = detection?.points?.map {
SpatialVector(it.x.toFloat(),it.y.toFloat(),0f)
}
private fun detect(detector: HandLandmarker, bitmap: Bitmap, stamp: Long, buffer: HandImageBuffer,
wristOnly: Boolean): Detections =
detect(detector,stamp,wristOnly) { buffer.image(bitmap) }
private fun detect(detector: HandLandmarker, stamp: Long, wristOnly: Boolean,
prepareImage: () -> MPImage): Detections {
val image=prepareImage()
try {
val start=System.nanoTime()
val result=detector.detectForVideo(image,stamp)
val elapsed=(System.nanoTime()-start)/1e6f
val hands=result.landmarks().mapIndexedNotNull { i, hand ->
val category=result.handedness()[i].firstOrNull() ?: return@mapIndexedNotNull null
if(category.score()<0.6f) return@mapIndexedNotNull null
val points=if(wristOnly) hand.take(1) else hand
Detection(if(category.categoryName()=="Left") 0 else 1,points.map { Point(it.x()*image.width.toDouble(),it.y()*image.height.toDouble()) })
}
return Detections(hands,result.landmarks().size,elapsed)
} finally { image.close() }
}
private fun closeDetectors() {
inferenceBackend=null
inferenceSchedule.reset();processingRate.reset()
val search=detectors;val fast=fastDetectors;val worker=rightWorker
detectors=null;fastDetectors=null;rightWorker=null
try { closeBank(search,worker) } finally {
try { closeBank(fast,worker) } finally { worker?.close() }
}
}
private fun detectPair(left: HandLandmarker, right: HandLandmarker, leftImage: Bitmap,
rightImage: Bitmap, stamp: Long, worker: StereoInferenceRunner?, wristOnly: Boolean): Pair<Detections,Detections> {
val l={ detect(left,leftImage,stamp,imageBuffer,wristOnly) };val r={ detect(right,rightImage,stamp,rightImageBuffer,wristOnly) }
return worker?.pair(l,r) ?: (l() to r())
}
private fun detectRawPair(left: HandLandmarker, right: HandLandmarker, leftGray: ByteArray,
rightGray: ByteArray, stamp: Long, worker: StereoInferenceRunner?, wristOnly: Boolean): Pair<Detections,Detections> {
val l={ detect(left,stamp,wristOnly) { imageBuffer.image(leftGray,width,height) } }
val r={ detect(right,stamp,wristOnly) { rightImageBuffer.image(rightGray,width,height) } }
return worker?.pair(l,r) ?: (l() to r())
}
private fun benchmarkCapture(id: String): JSONObject {
val folder=File(context.filesDir,"spatial/captures/$id")
val c=calibration ?: error("Load the saved calibration before benchmarking")
val wristOnly=SpatialTracking.joyConsEnabled
val gray=ArrayList<Mat>();val images=ArrayList<Bitmap>();val rows=org.json.JSONArray()
clearHands();closeDetectors()
try {
for(name in listOf("left","right")) {
val source=Imgcodecs.imread(File(folder,"$name.png").path,Imgcodecs.IMREAD_GRAYSCALE)
gray.add(source)
check(source.cols()==width && source.rows()==height) { "Saved pair has incompatible dimensions" }
val rgba=Mat()
try {
Imgproc.cvtColor(source,rgba,Imgproc.COLOR_GRAY2RGBA)
val bitmap=Bitmap.createBitmap(width,height,Bitmap.Config.ARGB_8888)
images.add(bitmap);Utils.matToBitmap(rgba,bitmap)
} finally { rgba.release() }
}
for((selected,parallel,scheduled) in listOf(Triple(HandModel.FULL,true,false),Triple(HandModel.FULL,true,true),
Triple(HandModel.LITE,true,true),Triple(HandModel.FULL,true,false))) {
var search: DetectorBank?=null;var fast: DetectorBank?=null
val schedule=HandInferenceSchedule()
val worker=if(parallel) StereoInferenceRunner() else null
val times=ArrayList<Double>();val samples=org.json.JSONArray()
try {
search=createBank(selected,false,2,worker)
if(scheduled) fast=createBank(selected,false,1,worker)
repeat(24) { frame ->
check(running) { "Tracking stopped during benchmark" }
val start=System.nanoTime();val stamp=1000L+frame*80L
val bank=if(scheduled && schedule.select(stamp)==1) fast!! else search
val (leftResult,rightResult)=detectPair(bank.left,bank.right,images[0],images[1],stamp,worker,wristOnly)
val l=leftResult.hands;val r=rightResult.hands
if(scheduled) schedule.observe(stamp,l.map { it.side },r.map { it.side },leftResult.rawCount,rightResult.rawCount)
val ms=(System.nanoTime()-start)/1e6
val hands=org.json.JSONArray()
val joyCons=org.json.JSONArray()
val wrists=org.json.JSONArray()
for(side in 0..1) {
val a=l.singleOrNull { it.side==side };val b=r.singleOrNull { it.side==side }
val points=if(a!=null && b!=null) c.triangulateLandmarksRaw(a.points,b.points) else null
points?.get(0)?.takeIf { it.finite() }?.let { wrist ->
wrists.put(JSONObject().put("side",side).put("position",org.json.JSONArray(wrist.array().toList())))
}
val hand=if(wristOnly) null else points?.let { HandGestures(rightHand=side==1).estimate(it,SpatialPose(),stamp*1_000_000,imagePoints(a),imagePoints(b)) }
val joyCon=if(wristOnly) points?.let { JoyConWristPosition().estimate(it,SpatialPose(),stamp*1_000_000) } else null
if(joyCon!=null) joyCons.put(JSONObject().put("side",side)
.put("position",org.json.JSONArray(joyCon.pose.position.array().toList())))
if(hand!=null) hands.put(JSONObject().put("side",side).put("flags",hand.flags())
.put("position",org.json.JSONArray(hand.pose.position.array().toList()))
.put("trigger",hand.trigger.toDouble()).put("grip",hand.grip))
}
if(frame>=4) {
times.add(ms);samples.put(JSONObject().put("left",l.size).put("right",r.size).put("hands",hands)
.put("joyCons",joyCons).put("wrists",wrists)
.put("inferenceHandLimit",bank.maxHands)
.put("leftRawHands",leftResult.rawCount).put("rightRawHands",rightResult.rawCount)
.put("leftInferenceMs",leftResult.inferenceMs.toDouble()).put("rightInferenceMs",rightResult.inferenceMs.toDouble()))
}
}
} finally {
try { closeBank(search,worker) } finally {
try { closeBank(fast,worker) } finally { worker?.close() }
}
}
val sorted=times.sorted()
rows.put(JSONObject().put("model",selected.name).put("parallel",parallel).put("medianMs",(sorted[9]+sorted[10])/2)
.put("scheduled",scheduled)
.put("p95Ms",sorted[18]).put("samples",samples))
}
return JSONObject().put("state","completed").put("capture",id).put("backend","CPU")
.put("wristOnly",wristOnly).put("imagePreprocessing",HandImageBuffer.PREPROCESSING)
.put("lensCorrection",lensCorrection)
.put("scope","Saved stereo pair replay; not live tracking or external latency").put("results",rows)
} finally { gray.forEach { it.release() };images.forEach { it.recycle() };clearHands() }
}
private fun clearHands() {
gestures.forEach { it.reset() }
joyConPositions.forEach { it.reset() }
joyConFlow?.reset()
inferenceSchedule.reset();processingRate.reset()
SpatialTracking.session.setHands(generation,SpatialHand(),SpatialHand())
}
private fun loop() {
try { check(OpenCVLoader.initLocal()) } catch(e: Throwable) { message=context.getString(R.string.spatial_opencv_error, e.message.orEmpty());return }
var grayL=Mat();var grayR=Mat();val rgba=Mat()
var bmpL: Bitmap?=null;var bmpR: Bitmap?=null
var leftGray=ByteArray(0);var rightGray=ByteArray(0)
val left=ByteBuffer.allocateDirect(2048*2048);val right=ByteBuffer.allocateDirect(2048*2048)
val metadata=DoubleArray(9);val headData=FloatArray(7)
var lastSeq=0L;var dropped=0L;var frames=0L;var previewTime=0L
val clock=CameraClock()
var lastSource=0L;var lastStamp=0L;var captureOn=false;var captureRate=1000;var lastInputSeq=0L
var rate=0f
try {
check(OpenCVLoader.initLocal()) { context.getString(R.string.spatial_opencv_load_error) }
joyConFlow=StereoJoyConFlow()
while(running) {
while(true) {
val command=commands.poll() ?: break
try { command() } catch(e: Exception) { message=e.message ?: e.toString() }
}
if (verify!=null && System.nanoTime()-validationRequestedAt>2_000_000_000L) {
verify=null
calibrationProgress=calibrationProgress.copy(validating=false,
validationMessage=context.getString(R.string.spatial_no_camera_frame))
}
val want=enabled || debug
val copyPermille=if(enabled && !calibrationProgress.collecting && !capture && verify==null && !record)
if(adaptiveInput && !gpu) processingRate.permille else if(halfRateInput) 500 else 1000
else 1000
inputRatePermille=copyPermille
if(want!=captureOn || copyPermille!=captureRate) {
VitureSdk.nativeCapture(token,want,copyPermille);captureOn=want;captureRate=copyPermille
if(!want) clearHands()
}
if(!want) { Thread.sleep(20);continue }
val seq=VitureSdk.nativeStereo(token,left,right,metadata,headData,!enabled || capture)
if(seq<=0) { Thread.sleep(5);continue }
val sequenceDelta=if(lastSeq>0) (seq-lastSeq).coerceAtLeast(1) else 1
if(lastSeq>0) dropped+=(sequenceDelta-1)
lastSeq=seq;frames++
val start=System.nanoTime();val source=(metadata[2]*1e9).toLong();val receipt=metadata[3].toLong()
val inputSequence=metadata[7].toLong()
if(source<=lastSource) { clearHands();message=context.getString(R.string.spatial_clock_reset);closeDetectors();lastStamp=0 }
if(lastSource>0 && source>lastSource) {
val hz=1e9f*(inputSequence-lastInputSeq).coerceAtLeast(1)/(source-lastSource)
rate=if(rate==0f)hz else rate*.9f+hz*.1f
}
lastSource=source;lastInputSeq=inputSequence
val time=clock.map(source,receipt) ?: 0L
val trackingFrame = SpatialTracking.session.trackingFrameGeneration
if (calibrator != null && calibrationProgress.collecting && calibrationFrame != trackingFrame) {
capture=false
message=context.getString(R.string.spatial_frame_changed)
calibrationProgress = calibrationProgress.copy(collecting = false, capturing = false, captureMessage = message)
}
// The callback pose's basis is undocumented; use canonical GL pose history.
val observation=HandObservation(time,generation,trackingFrame)
var captureHead=observation.headPose(SpatialTracking.session,metadata[6]>0)
val w=metadata[0].toInt();val h=metadata[1].toInt()
if(w!=width || h!=height) {
clearHands();closeDetectors()
width=w;height=h;leftGray=ByteArray(w*h);rightGray=ByteArray(w*h)
grayL.release();grayR.release();grayL=Mat(h,w,CvType.CV_8UC1);grayR=Mat(h,w,CvType.CV_8UC1)
bmpL?.recycle();bmpR?.recycle();bmpL=null;bmpR=null
calibration?.close()
val selected=StereoCalibrationProfile.select(
if(identity!=null) runCatching { profile.readText() }.getOrNull() else null,
{ runCatching { context.assets.open(StereoCalibrationProfile.BASE_ASSET).bufferedReader().use { it.readText() } }.getOrNull() },
identity,w,h)
calibration=selected?.let(StereoCalibration::fromProfile)
calibrator?.close();calibrator=null
calibrationProgress = SpatialCalibrationProgress(fitted = calibration != null)
message=context.getString(R.string.spatial_stereo_loaded, w, h, when {
calibration?.verified==true -> context.getString(R.string.spatial_device_loaded)
calibration?.bundledBase==true -> context.getString(R.string.spatial_base_loaded)
else -> context.getString(R.string.spatial_calibration_needed)
})
if (identity!=null) calibrator=StereoCalibrator.load(context,captures,identity,w,h)
calibrator?.let {
calibrationFrame=-1
val restored=context.getString(R.string.spatial_restored, it.observations.size)
calibrationProgress=calibrationProgress.copy(views=it.observations.size,captureMessage=restored)
message=restored
}
}
left.position(0);left.get(leftGray)
right.position(0);right.get(rightGray)
val joyConMode=SpatialTracking.joyConsEnabled
val drawPreview=debug && start-previewTime>150_000_000L
if(capture || verify!=null || record || drawPreview || joyConMode) {
grayL.put(0,0,leftGray);grayR.put(0,0,rightGray)
}
if(capture) {
capture=false
val observation=captureHead?.let { calibrator?.detect(grayL,grayR,it) }
val currentTracking = SpatialTracking.session.isCurrentTrackingFrame(generation, trackingFrame)
if(observation!=null && currentTracking) {
calibrator!!.observations.add(observation)
calibrationProgress = calibrationProgress.copy(views = calibrator!!.observations.size)
saveCaptures()
message=context.getString(R.string.spatial_captured, calibrator!!.observations.size)
} else {
observation?.let { listOf(it.objects,it.left,it.right).forEach(Mat::release) }
message = when {
calibrator == null -> context.getString(R.string.spatial_tap_start)
SpatialTracking.session.recenterPending -> context.getString(R.string.spatial_capture_recenter_pending)
SpatialTracking.session.needsRecenter -> context.getString(R.string.spatial_capture_recenter)
captureHead == null || !currentTracking -> context.getString(R.string.spatial_capture_unsynchronized)
else -> context.getString(R.string.spatial_capture_corners, calibrator?.commonCorners ?: 0)
}
}
calibrationProgress = calibrationProgress.copy(capturing = false, captureMessage = message)
}
val c=calibration
val distance=verify
if(distance!=null) {
verify=null
var temporary: StereoCalibrator?=null
var observed: BoardObservation?=null
try {
check(c!=null) { context.getString(R.string.spatial_align_first) }
temporary=StereoCalibrator(context)
observed=temporary.detect(grayL,grayR,SpatialPose())
val points=observed?.let { c.triangulateRaw(it.left,it.right) }
val result=CalibrationDistanceCheck.evaluate(context,distance,temporary.commonCorners,points)
if(result.accepted) {
c.verified=true;if(identity!=null)c.save(profile)
calibrationProgress=calibrationProgress.copy(validationMessage=result.message+
if(identity==null) " " + context.getString(R.string.spatial_validation_session) else " " + context.getString(R.string.spatial_validation_saved))
} else calibrationProgress=calibrationProgress.copy(validationMessage=result.message)
} catch (e: Exception) {
android.util.Log.w("StereoCalibration","Distance validation failed",e)
calibrationProgress=calibrationProgress.copy(validationMessage=context.getString(R.string.spatial_validation_error, e.message?.substringBefore('\n') ?: context.getString(R.string.spatial_processing_failed)))
} finally {
calibrationProgress=calibrationProgress.copy(validating=false)
observed?.let { listOf(it.objects,it.left,it.right).forEach(Mat::release) };temporary?.close()
}
}
var dl=emptyList<Detection>();var dr=emptyList<Detection>()
var trackingHealthy=false
var inferred=false
val sideDiagnostics=Array(2) { HandObservationDiagnostic(HandObservationOutcome.NO_MATCHING_DETECTION) }
val nextStatus: HandTrackingDiagnostics
if(enabled && c?.readyForTracking==true && time>0 && SpatialTracking.session.isCurrentTrackingFrame(generation, trackingFrame) && System.nanoTime()-time in 0..HandObservation.MAX_AGE_NS) {
val preprocessMs=(System.nanoTime()-start)/1e6f
if(detectors==null) {
try {
rightWorker=if(cpuParallel && !gpu) StereoInferenceRunner() else null
detectors=createBank(model,gpu,2,rightWorker)
if(scheduledTracking && !gpu) fastDetectors=createBank(model,false,1,rightWorker)
inferenceBackend=if(gpu) "GPU" else "CPU"
}
catch(e: Exception) {
closeDetectors()
enabled=false;clearHands();message=context.getString(R.string.spatial_inference_error, e.message.orEmpty())
val unavailable=HandObservationDiagnostic(HandObservationOutcome.INFERENCE_UNAVAILABLE)
handMetrics.observe(seq,false,unavailable,unavailable)
handStatus=HandTrackingDiagnostics(message,observationTimeNs=time,
observationAgeMs=(System.nanoTime()-time)/1e6f,left=unavailable,right=unavailable,observations=handMetrics.snapshot())
continue
}
}
val stamp=maxOf(time/1_000_000,lastStamp+1);lastStamp=stamp
val inferenceStart=System.nanoTime()
val scheduled=scheduledTracking && !gpu
val bank=if(scheduled && inferenceSchedule.select(stamp)==1) fastDetectors!! else detectors!!
val pair=detectRawPair(bank.left,bank.right,leftGray,rightGray,stamp,rightWorker,joyConMode)
inferred=true
dl=pair.first.hands;dr=pair.second.hands
if(scheduled) inferenceSchedule.observe(stamp,dl.map { it.side },dr.map { it.side },pair.first.rawCount,pair.second.rawCount)
val inferenceMs=(System.nanoTime()-inferenceStart)/1e6f
// VIO callbacks can arrive during inference; verify this exact camera stamp before publishing.
captureHead=observation.headPose(SpatialTracking.session,
metadata[6]>0 || VitureSdk.nativeCameraSynchronized(token,metadata[2]))
val hands=Array(2) { SpatialHand() }
if (joyConMode) joyConFlow?.begin(grayL,grayR,time) else joyConFlow?.reset()
var matched=0;var reconstructed=0
for(side in 0..1) {
val leftCandidates=dl.filter { it.side==side };val rightCandidates=dr.filter { it.side==side }
val l=leftCandidates.singleOrNull();val r=rightCandidates.singleOrNull()
val detectedPoints=if(l!=null && r!=null) {
matched++
c.triangulateLandmarksRaw(l.points,r.points)
} else null
var points=detectedPoints
if(points?.any { it != null }==true) reconstructed++
var hand=points?.let { p -> captureHead?.let { head ->
if (joyConMode) joyConPositions[side].estimate(p,head*c.cameraToHead,time)
else gestures[side].estimate(p,head*c.cameraToHead,time,imagePoints(l),imagePoints(r))
} }
var positionSource=if(hand!=null) "landmarks" else "none"
if (joyConMode && captureHead!=null) {
if (hand!=null && l!=null && r!=null && detectedPoints!=null)
joyConFlow?.seed(side,l.points.first(),r.points.first(),time)
else if (leftCandidates.size<=1 && rightCandidates.size<=1) {
joyConFlow?.points(side)?.let { tracked ->
val flowed=c.triangulateLandmarksRaw(tracked.left,tracked.right)
val position=flowed?.let { joyConPositions[side].estimate(it,captureHead!!*c.cameraToHead,time) }
if(position!=null) { hand=position;points=flowed;positionSource="stereo_flow" }
}
}
}
val outcome=when {
hand!=null -> HandObservationOutcome.ACCEPTED
leftCandidates.size>1 || rightCandidates.size>1 -> HandObservationOutcome.AMBIGUOUS_HANDEDNESS
l==null && r==null -> HandObservationOutcome.NO_MATCHING_DETECTION
l==null || r==null -> HandObservationOutcome.ONE_CAMERA_ONLY
points?.any { it != null }!=true -> HandObservationOutcome.STEREO_REJECTED
captureHead==null -> HandObservationOutcome.CAMERA_NOT_SYNCHRONIZED
else -> if (joyConMode) joyConPositions[side].lastOutcome else gestures[side].lastOutcome
}
sideDiagnostics[side]=HandObservationDiagnostic(outcome,
listOf(0,5,9,13,17).filter { points?.getOrNull(it)?.finite()==true },
hand?.trigger ?: 0f,hand?.triggerValid ?: false,hand?.grip ?: false,hand?.gripValid ?: false,positionSource)
if (hand!=null) hands[side]=hand
else if (joyConMode) gestures[side].reset()
else gestures[side].resetAfterRejection(outcome)
}
if (joyConMode) joyConFlow?.end(grayL,grayR,time)
val current=SpatialTracking.session.isCurrentTrackingFrame(generation, trackingFrame) &&
joyConMode==SpatialTracking.joyConsEnabled
val fresh=System.nanoTime()-time in 0..HandObservation.MAX_AGE_NS
if (current && fresh)
SpatialTracking.session.setHands(generation,hands[0],hands[1])
else {
clearHands()
for(side in 0..1) sideDiagnostics[side]=sideDiagnostics[side].copy(outcome=
if(!current) HandObservationOutcome.HEAD_TRACKING_INVALID else HandObservationOutcome.STALE_OBSERVATION)
}
val accepted=if(current && fresh) hands.count { it.valid } else 0
trackingHealthy=accepted>0
nextStatus=HandTrackingDiagnostics(when {
!current -> context.getString(R.string.spatial_hand_head_changed)
!fresh -> context.getString(R.string.spatial_hand_timeout, HandObservation.MAX_AGE_NS / 1_000_000)
captureHead==null -> context.getString(R.string.spatial_hand_wait_sync)
accepted>0 && joyConMode -> context.getString(R.string.spatial_wrists_tracked, accepted)
accepted>0 -> context.getString(R.string.spatial_palms_tracked, accepted, hands.count { it.valid && it.triggerValid }, hands.count { it.valid && it.gripValid })
dl.isEmpty() && dr.isEmpty() && joyConMode -> context.getString(R.string.spatial_no_wrists)
dl.isEmpty() && dr.isEmpty() -> context.getString(R.string.spatial_no_hands)
(dl.isEmpty() || dr.isEmpty()) && joyConMode -> context.getString(R.string.spatial_one_camera_wrists)
dl.isEmpty() || dr.isEmpty() -> context.getString(R.string.spatial_one_camera_hands)
matched==0 -> context.getString(R.string.spatial_ambiguous_hands)
reconstructed==0 -> context.getString(R.string.spatial_inconsistent_depth)
joyConMode -> context.getString(R.string.spatial_rejected_wrist)
else -> context.getString(R.string.spatial_inconsistent_palm)
},dl.size,dr.size,matched,reconstructed,accepted,preprocessMs,inferenceMs,
if(current && fresh) hands.count { it.valid && it.triggerValid } else 0,
if(current && fresh) hands.count { it.valid && it.gripValid } else 0,
pair.first.rawCount,pair.second.rawCount,bank.previousLeftRaw,bank.previousRightRaw,
pair.first.inferenceMs,pair.second.inferenceMs,bank.maxHands)
bank.previousLeftRaw=pair.first.rawCount;bank.previousRightRaw=pair.second.rawCount
} else {
clearHands()
val outcome=when {
!enabled -> HandObservationOutcome.DISABLED
c?.readyForTracking!=true -> HandObservationOutcome.CALIBRATION_REQUIRED
time<=0 -> HandObservationOutcome.CLOCK_NOT_READY
!SpatialTracking.session.isCurrentTrackingFrame(generation,trackingFrame) -> HandObservationOutcome.HEAD_TRACKING_INVALID
else -> HandObservationOutcome.STALE_OBSERVATION
}
for(side in 0..1) sideDiagnostics[side]=HandObservationDiagnostic(outcome)
nextStatus=HandTrackingDiagnostics(when {
!enabled -> context.getString(R.string.spatial_hands_off)
c?.readyForTracking!=true -> context.getString(R.string.spatial_hands_need_calibration)
captureHead==null -> context.getString(R.string.spatial_hand_wait_sync)
!SpatialTracking.session.isCurrentTrackingFrame(generation,trackingFrame) -> context.getString(R.string.spatial_hands_wait_stable)
else -> context.getString(R.string.spatial_old_frame)
})
}
handMetrics.observe(seq,inferred,sideDiagnostics[0],sideDiagnostics[1])
handStatus=nextStatus.copy(observationTimeNs=time,
observationAgeMs=if(time>0) (System.nanoTime()-time)/1e6f else -1f,
left=sideDiagnostics[0],right=sideDiagnostics[1],observations=handMetrics.snapshot())
if(drawPreview) {
previewTime=start
if(bmpL==null) bmpL=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888)
if(bmpR==null) bmpR=Bitmap.createBitmap(w,h,Bitmap.Config.ARGB_8888)
Imgproc.cvtColor(grayL,rgba,Imgproc.COLOR_GRAY2RGBA);Utils.matToBitmap(rgba,bmpL)
Imgproc.cvtColor(grayR,rgba,Imgproc.COLOR_GRAY2RGBA);Utils.matToBitmap(rgba,bmpR)
val combined=Bitmap.createBitmap(w*2,h,Bitmap.Config.ARGB_8888);val canvas=Canvas(combined)
canvas.drawBitmap(bmpL!!,0f,0f,null);canvas.drawBitmap(bmpR!!,w.toFloat(),0f,null)
val paint=Paint().apply { color=Color.GREEN;strokeWidth=3f }
for((index,detections) in listOf(dl,dr).withIndex()) for(d in detections) for(p in d.points)
canvas.drawCircle(p.x.toFloat()+index*w,p.y.toFloat(),3f,paint)
preview=combined
}
if(record) {
record=false
val folder=File(context.filesDir,"spatial/captures/$receipt").apply { mkdirs() }
Imgcodecs.imwrite(File(folder,"left.png").path,grayL);Imgcodecs.imwrite(File(folder,"right.png").path,grayR)
File(folder,"frame.json").writeText(JSONObject().put("sourceSeconds",metadata[2]).put("receiptNs",receipt)
.put("connection",generation).put("width",w).put("height",h).put("pairedPose",captureHead!=null).toString())
message=context.getString(R.string.spatial_capture_saved, folder.path)
}
if(running && generation==SpatialTracking.session.generation) {
val finished=System.nanoTime()
if(adaptiveInput && !gpu) processingRate.observe(finished-start,if(time>0) finished-time else -1,trackingHealthy)
SpatialTracking.statistics(c?.readyForTracking==true,frames,dropped,(finished-start)/1e6f,
if(time>0)(finished-time)/1e6f else -1f,rate,captureHead!=null,
when(copyPermille) { 500 -> 2;1000 -> 1;else -> 0 },metadata[8].toLong(),copyPermille)
SpatialTracking.publish(System.nanoTime())
}
}
} catch(e: InterruptedException) { /* Shutdown interrupts the idle wait. */ }
catch(e: Throwable) {
message=context.getString(R.string.spatial_hand_error, e.message.orEmpty())
val unavailable=HandObservationDiagnostic(HandObservationOutcome.INFERENCE_UNAVAILABLE)
handStatus=HandTrackingDiagnostics(message,left=unavailable,right=unavailable,observations=handMetrics.snapshot())
calibrationProgress = calibrationProgress.copy(capturing = false, captureMessage = message,
validating = false, validationMessage = if(calibrationProgress.validating) message else calibrationProgress.validationMessage)
clearHands()
}
finally {
VitureSdk.nativeCapture(token,false,1000)
joyConFlow?.close();joyConFlow=null
closeDetectors()
calibration?.close();calibrator?.close()
listOf(grayL,grayR,rgba).forEach(Mat::release);bmpL?.recycle();bmpR?.recycle()
}
}
override fun close() {
running=false;worker.interrupt()
if(Thread.currentThread()!==worker) worker.join(2000)
}
}
@@ -0,0 +1,38 @@
package com.droiddeck.launcher.glasses
import java.util.concurrent.ExecutionException
import java.util.concurrent.Executors
import java.util.concurrent.Future
internal class StereoInferenceRunner : AutoCloseable {
private val worker = Executors.newSingleThreadExecutor { task ->
Thread(task, "viture-right-hand").apply { isDaemon = true }
}
fun <T> onRight(action: () -> T): T = worker.submit<T> { action() }.completed()
fun <T> pair(left: () -> T, right: () -> T): Pair<T, T> {
val pending = worker.submit<T> { right() }
val result = try { left() } catch (error: Throwable) {
try { pending.completed() } catch (other: Throwable) { if (other !== error) error.addSuppressed(other) }
throw error
}
return result to pending.completed()
}
override fun close() { worker.shutdown() }
private fun <T> Future<T>.completed(): T {
var interrupted = false
try {
while (true) {
try { return get() }
catch (e: InterruptedException) { interrupted = true }
catch (e: ExecutionException) { throw e.cause ?: e }
}
} finally {
// Native inference must finish before either camera's pixels or detector can be reused.
if (interrupted) Thread.currentThread().interrupt()
}
}
}
@@ -0,0 +1,72 @@
package com.droiddeck.launcher.glasses
import org.opencv.core.*
import org.opencv.video.Video
internal class StereoJoyConFlow : AutoCloseable {
data class Points(val left: List<Point>, val right: List<Point>, val seedNs: Long)
private val previousLeft = Mat()
private val previousRight = Mat()
private val tracks = arrayOfNulls<Points>(2)
private var previousTime = 0L
fun reset() {
previousTime = 0
tracks.fill(null)
previousLeft.release(); previousRight.release()
}
fun begin(left: Mat, right: Mat, time: Long) {
for (side in 0..1) {
val old = tracks[side] ?: continue
if (!JoyConFlowGate.fresh(old.seedNs, previousTime, time) ||
previousLeft.size() != left.size() || previousRight.size() != right.size()) {
tracks[side] = null
continue
}
tracks[side] = try {
Points(follow(previousLeft, left, old.left), follow(previousRight, right, old.right), old.seedNs)
} catch (e: CvException) {
android.util.Log.w("StereoJoyConFlow", "Discarding failed optical flow", e)
null
}
}
}
fun points(side: Int): Points? = tracks[side]
fun seed(side: Int, left: Point, right: Point, time: Long) {
tracks[side] = Points(listOf(Point(left.x, left.y)), listOf(Point(right.x, right.y)), time)
}
fun end(left: Mat, right: Mat, time: Long) {
left.copyTo(previousLeft); right.copyTo(previousRight); previousTime = time
}
private fun follow(previous: Mat, current: Mat, points: List<Point>): List<Point> {
val indices = points.indices.filter { points[it].x.isFinite() && points[it].y.isFinite() }
val result = MutableList(points.size) { missing() }
if (indices.isEmpty()) return result
val before = MatOfPoint2f(*indices.map { points[it] }.toTypedArray())
val after = MatOfPoint2f(); val back = MatOfPoint2f()
val forwardStatus = MatOfByte(); val backStatus = MatOfByte()
val error = MatOfFloat(); val backError = MatOfFloat()
try {
Video.calcOpticalFlowPyrLK(previous, current, before, after, forwardStatus, error)
Video.calcOpticalFlowPyrLK(current, previous, after, back, backStatus, backError)
val next = after.toArray(); val returned = back.toArray()
val valid = forwardStatus.toArray(); val validBack = backStatus.toArray(); val errors = error.toArray()
for ((i, index) in indices.withIndex()) {
val n = next[i]; val b = returned[i]; val p = points[index]
if (JoyConFlowGate.point(n.x,n.y,b.x,b.y,p.x,p.y,errors[i],valid[i].toInt()!=0,
validBack[i].toInt()!=0,current.cols(),current.rows())) result[index] = n
}
} finally {
listOf(before, after, back, forwardStatus, backStatus, error, backError).forEach(Mat::release)
}
return result
}
private fun missing() = Point(Double.NaN, Double.NaN)
override fun close() = reset()
}
@@ -0,0 +1,21 @@
package com.droiddeck.launcher.glasses
import kotlin.math.abs
import kotlin.math.hypot
internal object StereoPointValidation {
fun reconstruct(homogeneous: DoubleArray, left: DoubleArray, right: DoubleArray,
p1: DoubleArray, p2: DoubleArray): SpatialVector? {
if (homogeneous.size != 4 || left.size != 2 || right.size != 2 || p1.size != 12 || p2.size != 12) return null
val w = homogeneous[3]
if (!w.isFinite() || abs(w) < 1e-8) return null
val v = DoubleArray(3) { homogeneous[it]/w }
if (v.any { !it.isFinite() } || v[2] !in 0.15..1.5) return null
for ((projection,pixel) in listOf(p1 to left,p2 to right)) {
val q = DoubleArray(3) { row -> (0..2).sumOf { projection[row*4+it]*v[it] } + projection[row*4+3] }
if (q.any { !it.isFinite() } || pixel.any { !it.isFinite() } || q[2] <= 0 ||
hypot(q[0]/q[2]-pixel[0],q[1]/q[2]-pixel[1]) > 3.0) return null
}
return SpatialVector(v[0].toFloat(),v[1].toFloat(),v[2].toFloat())
}
}
@@ -0,0 +1,17 @@
package com.droiddeck.launcher.glasses
internal enum class TrackingRecovery {
DISCONNECTED, LOST, RECENTER, STABILIZING;
companion object {
fun forSession(active: Boolean, connected: Boolean, status: SpatialTrackingStatus): TrackingRecovery? = when {
!active -> null
!connected -> DISCONNECTED
status.trackingValid -> null
!status.headFresh -> LOST
status.recenterPending -> STABILIZING
status.needsRecenter -> RECENTER
else -> LOST
}
}
}
@@ -0,0 +1,32 @@
package com.droiddeck.launcher.glasses
internal object UvcRgbDiagnostic {
val available: Boolean by lazy {
runCatching { System.loadLibrary("droiddeck_uvc"); true }
.onFailure { android.util.Log.w("UvcRgbDiagnostic", "Direct UVC transport unavailable", it) }.getOrDefault(false)
}
external fun nativeSnapshot(fd: Int, width: Int, height: Int, fps: Int, durationMs: Int,
metadata: LongArray, logFd: Int): ByteArray?
}
internal data class UvcStreamHealth(val observationMs: Double, val observedFps: Double,
val maximumGapMs: Double, val healthy: Boolean) {
companion object {
// Health requires the full requested window, 80% of requested FPS, and no gap
// (including the final silent interval) above max(250 ms, three frame periods).
fun assess(metadata: LongArray, requestedFps: Int, durationMs: Int): UvcStreamHealth {
require(metadata.size == 14 && requestedFps > 0 && durationMs > 0)
val first = metadata[7]
val last = metadata[6]
val end = metadata[12]
val window = if (first > 0 && end > first) end-first else 0
val frames = metadata[9]
val fps = if (window > 0 && frames > 1) (frames-1)*1e9/window else 0.0
val maximumGapMs = maxOf(metadata[8],metadata[13],(end-last).coerceAtLeast(0))/1e6
val healthy = metadata[4] == 0L && first > 0 && last in first..end && frames >= 2 &&
window >= durationMs*1_000_000L && fps >= requestedFps*0.8 &&
maximumGapMs <= maxOf(250.0,3000.0/requestedFps)
return UvcStreamHealth(window/1e6,fps,maximumGapMs,healthy)
}
}
}
@@ -48,11 +48,21 @@ object VitureProtocol {
Family.BEAST -> 58f
}
override fun mode(hz: Int, sbs: Boolean): Int = when {
tall && sbs -> if (hz >= 90 && sbs90) 0x45 else 0x42
tall -> when { hz >= 120 -> 0x44; hz >= 90 -> 0x43; else -> 0x41 }
sbs -> if (hz >= 90 && sbs90) 0x35 else 0x32
else -> when { hz >= 120 -> 0x34; hz >= 90 -> 0x33; else -> 0x31 }
override val resolutions: List<GlassesResolution> get() = buildList {
add(GlassesResolution(1920, 1080))
if (tall) add(GlassesResolution(1920, 1200))
}
override fun mode(hz: Int, sbs: Boolean): Int = mode(hz, sbs, null)
override fun mode(hz: Int, sbs: Boolean, resolution: GlassesResolution?): Int {
val useTall = tall && (resolution == null || resolution !in resolutions || resolution.height == 1200)
return when {
useTall && sbs -> if (hz >= 90 && sbs90) 0x45 else 0x42
useTall -> when { hz >= 120 -> 0x44; hz >= 90 -> 0x43; else -> 0x41 }
sbs -> if (hz >= 90 && sbs90) 0x35 else 0x32
else -> when { hz >= 120 -> 0x34; hz >= 90 -> 0x33; else -> 0x31 }
}
}
override fun modeForStep(step: Int): Int? = if (tall) null else when (step) {
@@ -15,8 +15,16 @@ internal object VitureSdk {
fun marketName(pid: Int): String? = if (available) nativeMarketName(pid) else null
external fun nativeAvailable(): Boolean
external fun nativeMarketName(pid: Int): String?
external fun nativeOpen(pid: Int, fd: Int, cacheDirectory: String): Long
external fun nativeOpen(pid: Int, fd: Int, cacheDirectory: String, spatial: Boolean = false): Long
external fun nativeSpatialPose(token: Long, pose: FloatArray, state: IntArray): Long
external fun nativeCapture(token: Long, enabled: Boolean, ratePermille: Int)
external fun nativeStereo(token: Long, left: java.nio.ByteBuffer, right: java.nio.ByteBuffer, metadata: DoubleArray, head: FloatArray, waitForPose: Boolean): Long
external fun nativeCameraSynchronized(token: Long, sourceSeconds: Double): Boolean
external fun nativeRgbSnapshot(vid: Int, pid: Int, fd: Int, metadata: LongArray, logFd: Int): ByteArray?
external fun nativeType(token: Long): Int
external fun nativeFirmware(token: Long): String?
external fun nativeSerialHash(token: Long): ByteArray?
external fun nativeSpatialDiagnostics(token: Long): DoubleArray?
external fun nativeTracking(token: Long, enabled: Boolean): Int
external fun nativeCommand(token: Long, command: Int, value: Int): Int
/** Returns CLOCK_BOOTTIME receipt time, zero for no new pose, or a negative SDK error. */
@@ -34,6 +42,11 @@ internal object VitureSdk {
const val SET_FILM = 7
}
internal object VitureSpatialIdentity {
fun fromSerialHash(hash: ByteArray?): String? = hash?.takeIf { it.size == 32 && it.any { b -> b != 0.toByte() } }
?.joinToString("", prefix = "viture:sn-sha256:") { "%02x".format(it.toInt() and 0xff) }
}
internal object VitureSdkPose {
// Pro 2 hardware captures use OpenGL axes despite the generic Gen2 callback's
// NWU documentation. Keep that model-specific exception out of other Gen2 devices.
@@ -13,13 +13,21 @@ internal class VitureSdkLink private constructor(
private val token: Long,
private val listener: Listener,
family: VitureProtocol.Family?,
private val spatial: Boolean,
) : GlassesLink {
interface Listener {
fun onValue(msgId: Int, value: Int)
fun onPose(q: FloatArray, timeNs: Long)
fun onSpatial(pose: FloatArray, timeNs: Long, valid: Boolean) {}
fun onLost()
}
internal val sdkToken get() = token
internal val spatialEnabled get() = spatial
internal val firmware = VitureSdk.nativeFirmware(token)
internal fun spatialDiagnostics() = if (running && spatial) VitureSdk.nativeSpatialDiagnostics(token) else null
internal val isCarina = VitureSdk.nativeType(token) == VitureSdk.CARINA
internal val spatialIdentity = if (isCarina) VitureSpatialIdentity.fromSerialHash(VitureSdk.nativeSerialHash(token)) else null
private val lock = Any()
@Volatile private var running = true
@Volatile private var tracking = false
@@ -71,6 +79,8 @@ internal class VitureSdkLink private constructor(
private fun poll() {
val pose = FloatArray(4)
val spatialPose = FloatArray(7)
val spatialStatus = IntArray(1)
val event = IntArray(2)
var failures = 0
while (running) {
@@ -84,7 +94,14 @@ internal class VitureSdkLink private constructor(
3 -> listener.onValue(VitureProtocol.MSG_FILM, event[1])
}
}
if (!running || !tracking) 0L else VitureSdk.nativePose(token, pose)
if (!running || !tracking) 0L else if (spatial && isCarina) {
val time = VitureSdk.nativeSpatialPose(token, spatialPose, spatialStatus)
if (time > 0) listener.onSpatial(spatialPose, time, spatialStatus[0] == 0)
if (spatialStatus[0] == 0) {
spatialPose.copyInto(pose, 0, 3, 7)
time
} else if (spatialStatus[0] < 0) spatialStatus[0].toLong() else 0L
} else VitureSdk.nativePose(token, pose)
}
if (timestamp > 0) {
failures = 0
@@ -113,15 +130,15 @@ internal class VitureSdkLink private constructor(
companion object {
fun open(usb: UsbManager, device: UsbDevice, cacheDirectory: File, listener: Listener,
family: VitureProtocol.Family? = null): VitureSdkLink? {
family: VitureProtocol.Family? = null, spatial: Boolean = false): VitureSdkLink? {
if (!VitureSdk.available) return null
val conn = usb.openDevice(device) ?: return null
val token = runCatching {
cacheDirectory.mkdirs()
VitureSdk.nativeOpen(device.productId, conn.fileDescriptor, cacheDirectory.absolutePath)
VitureSdk.nativeOpen(device.productId, conn.fileDescriptor, cacheDirectory.absolutePath, spatial)
}.onFailure { Log.w("VitureSdkLink", "SDK initialization failed", it) }.getOrDefault(0)
if (token <= 0) { conn.close(); return null }
return runCatching { VitureSdkLink(conn, token, listener, family) }.getOrElse {
return runCatching { VitureSdkLink(conn, token, listener, family, spatial) }.getOrElse {
VitureSdk.nativeClose(token)
conn.close()
Log.w("VitureSdkLink", "SDK worker could not start", it)
@@ -42,12 +42,13 @@ data class Cardboard(
companion object {
const val SOURCE_SCREEN = 0
const val SOURCE_SBS = 1
const val SOURCE_FLAT = 2
val OFF = Cardboard()
fun decode(json: JSONObject): Cardboard = Cardboard(
enabled = json.optBoolean("enabled", false),
source = if (json.optInt("source", SOURCE_SCREEN) == SOURCE_SBS) SOURCE_SBS else SOURCE_SCREEN,
source = json.optInt("source", SOURCE_SCREEN).takeIf { it in SOURCE_SCREEN..SOURCE_FLAT } ?: SOURCE_SCREEN,
viewer = Viewer.byId(json.optString("viewer", Viewer.CARDBOARD_V2.id)).id,
ipdMm = json.optInt("ipdMm", 64).coerceIn(50, 80),
screenWidthDm = json.optInt("screenWidthDm", 40).coerceIn(5, 200),
@@ -102,7 +103,8 @@ object HeadPose {
class Reference(val basis: FloatArray, val tilt: FloatArray)
fun recenter(screenToWorld: FloatArray): Reference {
fun recenter(screenToWorld: FloatArray, centerView: Boolean = false): Reference {
if (centerView) return Reference(screenToWorld.copyOf(), IDENTITY)
val fz = -screenToWorld[8]
val flat = kotlin.math.abs(fz) > FLAT_SIN
var fx = if (flat) screenToWorld[1] else -screenToWorld[2]
@@ -8,9 +8,11 @@ import android.hardware.SensorManager
import android.os.Handler
import android.os.HandlerThread
import android.util.Log
import com.droiddeck.launcher.glasses.SpatialTracking
import com.droiddeck.launcher.wayland.WaylandCompositor
class HeadTracker(context: Context, private val glasses: Boolean = false, private val rotation: () -> Int) : SensorEventListener {
class HeadTracker(context: Context, private val glasses: Boolean = false, val centerView: Boolean = false,
private val rotation: () -> Int) : SensorEventListener {
private val sensors = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager
private val sensor: Sensor? = sensors.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR)
?: sensors.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR)
@@ -26,7 +28,12 @@ class HeadTracker(context: Context, private val glasses: Boolean = false, privat
synchronized(glassesLock) {
if (!started) return@PoseSink
latest = m
apply(m)
if (SpatialTracking.enabled) {
SpatialTracking.session.view(centerView)?.let { pose ->
WaylandCompositor.nativeSetCardboardSpatialPose(pose.rotation.matrix(), pose.position.array(),
SpatialTracking.session.valid(System.nanoTime()))
}
} else apply(m)
}
}
@@ -64,6 +71,7 @@ class HeadTracker(context: Context, private val glasses: Boolean = false, privat
synchronized(glassesLock) {
if (!started) return
started = false
SpatialTracking.publish(System.nanoTime())
WaylandCompositor.nativeSetCardboardPose(HeadPose.IDENTITY, false)
}
com.droiddeck.launcher.glasses.Glasses.clearPoseSink(glassesSink)
@@ -77,6 +85,7 @@ class HeadTracker(context: Context, private val glasses: Boolean = false, privat
}
fun recenter() {
if (glasses && SpatialTracking.enabled) { SpatialTracking.recenter(centerView); return }
recenterPending = true
if (glasses) synchronized(glassesLock) { if (started) latest?.let { apply(it) } }
else latest?.let { apply(it) }
@@ -90,7 +99,7 @@ class HeadTracker(context: Context, private val glasses: Boolean = false, privat
}
private fun apply(screenToWorld: FloatArray) {
val ref = reference.takeUnless { recenterPending } ?: HeadPose.recenter(screenToWorld).also {
val ref = reference.takeUnless { recenterPending } ?: HeadPose.recenter(screenToWorld, centerView).also {
reference = it
recenterPending = false
}
@@ -91,6 +91,10 @@ class JoyConVr(
worker.post { hands.forEach { it?.release() } }
}
fun recenter() {
worker.post { hands.forEach { it?.recenter() } }
}
fun close() {
worker.post {
closed = true
@@ -195,9 +199,7 @@ class JoyConVr(
private val hold = Runnable {
if (!systemDown || held) return@Runnable
held = true
recenter++
confirm()
publish()
recenter()
}
private val tapEnd = Runnable {
systemTap = false
@@ -281,6 +283,13 @@ class JoyConVr(
publish()
}
fun recenter() {
if (!live) return
recenter++
confirm()
publish()
}
fun stop() {
live = false
runCatching { sensors.unregisterListener(this) }
@@ -36,7 +36,7 @@ object BwrapSpawner {
return
}
server = socket
FileUtils.delete(File(app.cacheDir, "bwrap"))
FileUtils.delete(File(LinuxRuntime.liveDir(app), "bwrap"))
Thread({
while (true) {
val client = try { socket.accept() } catch (e: IOException) { Log.e(TAG, "accept", e); break }
@@ -47,7 +47,7 @@ object BwrapSpawner {
private fun serve(context: Context, client: LocalSocket) {
val id = seq.incrementAndGet()
val dir = File(context.cacheDir, "bwrap/$id")
val dir = File(LinuxRuntime.liveDir(context), "bwrap/$id")
client.use {
val out = DataOutputStream(client.outputStream)
try {
@@ -239,12 +239,12 @@ object BwrapSpawner {
}
private fun genericCpuinfo(context: Context): File? = try {
val out = File(context.cacheDir, "cpuinfo-generic")
val out = File(LinuxRuntime.liveDir(context), "cpuinfo-generic")
val text = File("/proc/cpuinfo").readLines()
.filterNot { it.startsWith("CPU implementer") || it.startsWith("CPU part") || it.startsWith("CPU variant") || it.startsWith("CPU revision") }
.joinToString("\n", postfix = "\n")
if (!out.isFile || out.readText() != text) {
val staged = File.createTempFile("cpuinfo", null, context.cacheDir)
val staged = File.createTempFile("cpuinfo", null, LinuxRuntime.liveDir(context))
staged.writeText(text)
if (!staged.renameTo(out)) staged.delete()
}
@@ -290,7 +290,7 @@ object BwrapSpawner {
builder.environment().apply {
clear()
put("PROOT_LOADER", LinuxRuntime.prootLoader(context).path)
put("PROOT_TMP_DIR", context.cacheDir.path)
put("PROOT_TMP_DIR", LinuxRuntime.prootTempDir(context).path)
if (SessionPrefs.prootNoSeccomp(context)) put("PROOT_NO_SECCOMP", "1")
LinuxRuntime.prootLibraryPath(context).takeIf { it.isNotEmpty() }?.let { put("LD_LIBRARY_PATH", it) }
}
@@ -46,7 +46,7 @@ object GuestCommand {
val builder = ProcessBuilder(cmd).directory(root).redirectErrorStream(true)
builder.environment().apply {
put("PROOT_LOADER", LinuxRuntime.prootLoader(context).path)
put("PROOT_TMP_DIR", context.cacheDir.path)
put("PROOT_TMP_DIR", LinuxRuntime.prootTempDir(context).path)
if (linkDir != null) put("PROOT_L2S_DIR", linkDir.path)
if (SessionPrefs.prootNoSeccomp(context)) put("PROOT_NO_SECCOMP", "1")
LinuxRuntime.prootLibraryPath(context).takeIf { it.isNotEmpty() }?.let { put("LD_LIBRARY_PATH", it) }
@@ -48,6 +48,19 @@ public final class LinuxRuntime {
return new File(context.getFilesDir(), "session");
}
public static File liveDir(Context context) {
// Android may evict cache files even while guest processes are using them.
File dir = new File(context.getNoBackupFilesDir(), "guest-runtime");
dir.mkdirs();
return dir;
}
public static File prootTempDir(Context context) {
File dir = new File(liveDir(context), "proot");
dir.mkdirs();
return dir;
}
private static final String HOST_DIR = "opt/android-host";
public static File prootBinary(Context context) {
@@ -151,6 +164,7 @@ public final class LinuxRuntime {
bind(cmd, new File(root, "etc/droiddeck/empty").getPath() + ":/sys/fs/selinux");
bind(cmd, context.getFilesDir().getPath());
bind(cmd, context.getCacheDir().getPath());
bind(cmd, liveDir(context).getPath());
if (runtimeDir != null) {
bind(cmd, runtimeDir.getPath());
bind(cmd, runtimeDir.getPath() + ":" + GUEST_RUNTIME_DIR);
@@ -159,7 +173,7 @@ public final class LinuxRuntime {
if (externalStorage != null && externalStorage.isDirectory()) {
bind(cmd, externalStorage.getPath());
}
File shm = new File(context.getCacheDir(), "shm");
File shm = new File(liveDir(context), "shm");
shm.mkdirs();
bind(cmd, shm.getPath() + ":/dev/shm");
bindSystemFonts(cmd, root);
@@ -222,7 +236,7 @@ public final class LinuxRuntime {
long major = ((dev >> 8) & 0xfff) | ((dev >> 32) & ~0xfffL);
long minor = (dev & 0xff) | ((dev >> 12) & ~0xffL);
String node = "renderD" + minor;
File base = new File(context.getCacheDir(), "drm");
File base = new File(liveDir(context), "drm");
File dri = new File(base, "dri");
File device = new File(base, "sys/" + major + ":" + minor + "/device");
File drm = new File(device, "drm/" + node);
@@ -367,12 +367,17 @@ public final class LinuxRuntimeInstaller {
}
public static Removal beginUninstall(Context context) {
if (com.droiddeck.launcher.session.SessionState.INSTANCE.getRunning()) return null;
com.droiddeck.launcher.session.SessionPhase phase = com.droiddeck.launcher.session.SessionState.INSTANCE.getPhase();
if (phase != com.droiddeck.launcher.session.SessionPhase.IDLE && phase != com.droiddeck.launcher.session.SessionPhase.FAILED) return null;
if (sessionInUse()) return null;
return beginUninstall(context.getApplicationContext(), LinuxRuntime.rootDir(context));
}
private static boolean sessionInUse() {
com.droiddeck.launcher.session.SessionState session = com.droiddeck.launcher.session.SessionState.INSTANCE;
com.droiddeck.launcher.session.SessionPhase phase = session.getPhase();
return session.getRunning() ||
(phase != com.droiddeck.launcher.session.SessionPhase.IDLE && phase != com.droiddeck.launcher.session.SessionPhase.FAILED);
}
static Removal beginUninstall(Context app, File root) {
synchronized (JOB_LOCK) {
if (running != null) return null;
@@ -397,6 +402,7 @@ public final class LinuxRuntimeInstaller {
if (!started.compareAndSet(false, true)) return join(job, listener);
if (listener != null) job.listeners.add(listener);
try {
requireStopped();
report(text(app, R.string.rtinst_removing), -1);
final long[] removed = {0};
final long[] lastShown = {0};
@@ -423,6 +429,8 @@ public final class LinuxRuntimeInstaller {
if (!discard(leftover, progress)) throw new IOException("Cannot remove " + leftover);
}
emptyTrash(root.getParentFile());
requireStopped();
if (app != null) RuntimeFileTree.delete(new File(app.getNoBackupFilesDir(), "guest-runtime"), null);
job.ok = true;
report(text(app, R.string.rtinst_removed), 100);
return true;
@@ -436,6 +444,11 @@ public final class LinuxRuntimeInstaller {
job.done.countDown();
}
}
private void requireStopped() throws IOException {
if (Thread.currentThread().isInterrupted()) throw new IOException("Runtime removal was interrupted");
if (app != null && sessionInUse()) throw new IOException("A session is still using the runtime");
}
private void report(String stage, int percent) {
job.stage = stage;
job.percent = percent;
@@ -225,7 +225,7 @@ object ProtonExtras {
.redirectErrorStream(true)
val hostEnv = process.environment()
hostEnv["PROOT_LOADER"] = LinuxRuntime.prootLoader(context).path
hostEnv["PROOT_TMP_DIR"] = context.cacheDir.path
hostEnv["PROOT_TMP_DIR"] = LinuxRuntime.prootTempDir(context).path
LinuxRuntime.prootLibraryPath(context).takeIf { it.isNotEmpty() }?.let { hostEnv["LD_LIBRARY_PATH"] = it }
val child = process.start()
var placed: String? = null
@@ -121,6 +121,7 @@ object SessionFiles {
"droiddeck/driver.vrdrivermanifest",
"droiddeck/resources/settings/default.vrsettings",
"layer/libVkLayer_droiddeck_vr.so",
"layer/libdroiddeck_vr_scheduling.so",
"layer/VK_LAYER_DROIDDECK_vr.json",
) + handModels).map { "usr/local/lib/droiddeck-vr/$it" to "usr/local/lib/droiddeck-vr/$it" } +
listOf("usr/local/lib/droiddeck-vr/layer/VK_LAYER_DROIDDECK_vr_implicit.json" to
@@ -414,7 +414,7 @@ class SessionService : Service() {
Log.i(TAG, "run: $program (${SessionState.programArgs.size} arguments) under gamescope")
}
FileUtils.clear(File(cacheDir, "shm"))
FileUtils.clear(File(LinuxRuntime.liveDir(this), "shm"))
val binds = sessionBinds(controllersOn, fakeInputDir, sessionDir, guest, sessionRoot, runtimeDir)
val fastPathKey = if (ProotFastPath.enabled(this)) {
@@ -439,7 +439,7 @@ class SessionService : Service() {
val hostEnv = HostEnvironment()
hostEnv["PROOT_LOADER"] = LinuxRuntime.prootLoader(this).path
hostEnv["PROOT_TMP_DIR"] = cacheDir.path
hostEnv["PROOT_TMP_DIR"] = LinuxRuntime.prootTempDir(this).path
if (SessionPrefs.prootNoSeccomp(this)) {
hostEnv["PROOT_NO_SECCOMP"] = "1"
Log.i(TAG, "proot: seccomp acceleration off by request")
@@ -1375,6 +1375,9 @@ class SessionService : Service() {
val failureStatus = SessionState.failureStatus ?: status
SessionEvents.fail(code, message, failureStatus)
}
SessionState.steamVr = false
com.droiddeck.launcher.glasses.Glasses.cancelSteamVrStart()
com.droiddeck.launcher.glasses.Glasses.setVrActive(false)
com.droiddeck.launcher.glasses.Glasses.set3dForVr(false)
SessionState.notifyEnded(status)
stopForeground(STOP_FOREGROUND_REMOVE)
@@ -155,7 +155,7 @@ object SteamDeckPad {
val listing = listingDir(sessionRoot)
for (name in listOf("sys/bus", "sys/class", "sys/devices", "class/hidraw", "bus")) File(listing, name).mkdirs()
link(File(context.cacheDir, "drm/sys/$MAJOR:$MINOR"), HIDRAW)
link(File(LinuxRuntime.liveDir(context), "drm/sys/$MAJOR:$MINOR"), HIDRAW)
val udevData = File(LinuxRuntime.rootDir(context), "run/udev/data").apply { mkdirs() }
File(udevData, "c$MAJOR:$MINOR").writeText("I:1\nE:ID_INPUT=1\nE:ID_INPUT_JOYSTICK=1\n")
@@ -183,7 +183,7 @@ object SteamDeckPad {
link(File(dir(TOUCH_HIDRAW), "subsystem"), "/sys/class/hidraw")
link(File(dir(TOUCH_HIDRAW), "device"), TOUCH_HID)
link(File(hidrawClass, TOUCH_NODE), TOUCH_HIDRAW)
link(File(context.cacheDir, "drm/sys/$MAJOR:$TOUCH_MINOR"), TOUCH_HIDRAW)
link(File(LinuxRuntime.liveDir(context), "drm/sys/$MAJOR:$TOUCH_MINOR"), TOUCH_HIDRAW)
File(udevData, "c$MAJOR:$TOUCH_MINOR").writeText("I:1\nE:ID_INPUT=1\nE:ID_INPUT_JOYSTICK=1\n")
Log.i(TAG, "deck pad: /dev/$TOUCH_NODE described as Steam's touch controller (0000:11fb)")
}
@@ -220,7 +220,7 @@ object SteamDeckPad {
link(File(dir(hidraw), "subsystem"), "/sys/class/hidraw")
link(File(dir(hidraw), "device"), hid)
if (wired) padNodes[minor] = hidraw else link(File(hidrawClass, node), hidraw)
link(File(context.cacheDir, "drm/sys/$MAJOR:$minor"), hidraw)
link(File(LinuxRuntime.liveDir(context), "drm/sys/$MAJOR:$minor"), hidraw)
File(udevData, "c$MAJOR:$minor").writeText("I:1\nE:ID_INPUT=1\nE:ID_INPUT_JOYSTICK=1\n")
Log.i(TAG, "steam controller: /dev/$node described as Triton (28de:$product)")
}
@@ -255,7 +255,7 @@ object SteamDeckPad {
link(File(dir(hidraw), "subsystem"), "/sys/class/hidraw")
link(File(dir(hidraw), "device"), hid)
padNodes[minor] = hidraw
link(File(context.cacheDir, "drm/sys/$MAJOR:$minor"), hidraw)
link(File(LinuxRuntime.liveDir(context), "drm/sys/$MAJOR:$minor"), hidraw)
File(udevData, "c$MAJOR:$minor").writeText("I:1\nE:ID_INPUT=1\nE:ID_INPUT_JOYSTICK=1\n")
Log.i(TAG, "playstation pad: /dev/$node described as 054c:$product")
}
@@ -122,7 +122,7 @@ object GogComet {
pb.environment().apply {
remove("LD_PRELOAD"); remove("LD_AUDIT"); remove("LD_LIBRARY_PATH")
put("PROOT_LOADER", LinuxRuntime.prootLoader(context).path)
put("PROOT_TMP_DIR", context.cacheDir.path)
put("PROOT_TMP_DIR", LinuxRuntime.prootTempDir(context).path)
if (SessionPrefs.prootNoSeccomp(context)) put("PROOT_NO_SECCOMP", "1")
LinuxRuntime.prootLibraryPath(context).takeIf { it.isNotEmpty() }?.let { put("LD_LIBRARY_PATH", it) }
}
@@ -86,6 +86,7 @@ import com.droiddeck.launcher.R
private enum class CheckState { OK, WARN, BUSY }
private const val STORAGE_TAB = 3
private const val GLASSES_TAB = 5
@Composable
private fun CheckRow(state: CheckState, title: String, detail: String?, divider: Boolean = true, action: (@Composable () -> Unit)? = null) {
@@ -175,7 +176,7 @@ internal fun SetupPanel(
onDismiss = { editDownloadPrefix = false },
onSave = { a.onDownloadPrefix(it); editDownloadPrefix = false },
)
val tabs = listOf(stringResource(R.string.setup_tab_overview), stringResource(R.string.setup_tab_controller), stringResource(R.string.setup_tab_launcher), stringResource(R.string.setup_tab_storage), stringResource(R.string.setup_tab_diagnostics))
val tabs = listOf(stringResource(R.string.setup_tab_overview), stringResource(R.string.setup_tab_controller), stringResource(R.string.setup_tab_launcher), stringResource(R.string.setup_tab_storage), stringResource(R.string.setup_tab_diagnostics), stringResource(R.string.setup_tab_glasses))
var tab by rememberSaveable { mutableStateOf(0) }
val chipFocus = remember { List(tabs.size) { FocusRequester() } }
var tabTurned by remember { mutableStateOf(false) }
@@ -445,6 +446,7 @@ internal fun SetupPanel(
stringResource(R.string.setup_reset_settings_button), a.onResetSettings)
}
}
GLASSES_TAB -> GlassesSetupPanel()
}
}
}
@@ -5,8 +5,10 @@ import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.displayCutout
@@ -42,8 +44,14 @@ import androidx.compose.runtime.collectAsState
import com.droiddeck.launcher.R
import com.droiddeck.launcher.glasses.Glasses
import com.droiddeck.launcher.glasses.GlassesRendering
import com.droiddeck.launcher.glasses.GlassesDisplayModes
import com.droiddeck.launcher.glasses.GlassesDisplayProfile
import com.droiddeck.launcher.glasses.GlassesSettings
import com.droiddeck.launcher.glasses.GlassesState
import android.content.Intent
import androidx.compose.ui.platform.LocalContext
import com.droiddeck.launcher.glasses.SpatialTracking
import com.droiddeck.launcher.glasses.SpatialMeasurements
import com.droiddeck.launcher.handtracking.HandTracking
import java.util.Locale
@@ -68,6 +76,7 @@ internal fun rememberGlasses(): Pair<GlassesState, GlassesSettings> {
internal object GlassesTab {
const val DISPLAY = "display"
const val VR = "vr"
const val HARDWARE = "hardware"
const val TRACKING = "tracking"
}
@@ -75,9 +84,10 @@ internal object GlassesTab {
@Composable
internal fun glassesTabs(g: GlassesState): List<Pair<String, String>> = buildList {
add(GlassesTab.DISPLAY to stringResource(R.string.glasses_display))
if (g.canControl || g.hasDisplay) add(GlassesTab.VR to stringResource(R.string.glasses_vr))
if (g.canControl) {
if (g.hardwareControls) add(GlassesTab.HARDWARE to stringResource(R.string.glasses_hardware))
add(GlassesTab.TRACKING to stringResource(R.string.glasses_tracking))
if (g.hardwareControls) add(GlassesTab.HARDWARE to stringResource(R.string.glasses_hardware))
}
}
@@ -95,10 +105,22 @@ internal fun GlassesGroups(
steamVr: @Composable () -> Unit = {},
) {
val display = tab == null || tab == GlassesTab.DISPLAY
val vr = tab == null || tab == GlassesTab.VR
val picture = GlassesRendering.picture(s, g.sbs, steamVrRunning)
val usesGlasses = GlassesRendering.usesGlasses(g, outputActive, steamVrRunning)
val spatialTracking = g.spatialConnected && s.positionalTracking
val spatialVr = spatialTracking && usesGlasses && steamVrRunning
val flatPicture = picture == GlassesRendering.Picture.FLAT || picture == GlassesRendering.Picture.DUPLICATED
val virtualScreen = usesGlasses && picture == GlassesRendering.Picture.VIRTUAL_SCREEN
val directView = usesGlasses && picture == GlassesRendering.Picture.STEREO
val menuScreen = !inSession && g.usable && s.gameOnGlasses && s.screen3dof
if (g.canControl && onRecenter != null && s.headTracking && (virtualScreen || menuScreen || (directView && steamVrRunning))) {
SettingsGroup(stringResource(R.string.glasses_quick_actions), compact = true) {
ActionRow(stringResource(R.string.glasses_recenter),
if (virtualScreen || menuScreen) stringResource(R.string.glasses_recenter_screen_hint) else null,
stringResource(R.string.cardboard_recenter_button), onRecenter)
}
}
if (display) SettingsGroup(g.model) {
if (g.experimental) SettingsRow(stringResource(R.string.glasses_control), stringResource(R.string.glasses_xreal_experimental)) {}
val status = if (g.hasDisplay) stringResource(R.string.glasses_status, g.width, g.height, kotlin.math.round(g.hz).toInt(),
@@ -119,35 +141,38 @@ internal fun GlassesGroups(
ToggleRow(host, "glasses-game", stringResource(R.string.glasses_game_on), stringResource(R.string.glasses_game_on_hint),
s.gameOnGlasses, chipModifier = track("glasses-game")) { Glasses.setGameOnGlasses(it) }
}
if (display && (g.canControl || g.hasDisplay)) SettingsGroup(stringResource(R.string.glasses_display)) {
if (g.canControl) {
if (!g.sbs) ChoiceRow(host, "glasses-hz", stringResource(R.string.glasses_refresh), null,
listOf(60, 90, 120).map { it to "$it Hz" }, s.refreshHz, chipModifier = track("glasses-hz")) { Glasses.setRefreshHz(it) }
if (display && g.canControl && (inSession || g.sbs || g.sbsSupport == GlassesState.SBS_FAILS)) {
SettingsGroup(stringResource(R.string.glasses_display_mode)) {
if (g.sbsSupport == GlassesState.SBS_FAILS && !g.sbs) {
ActionRow(stringResource(R.string.glasses_3d), stringResource(R.string.glasses_3d_failed),
stringResource(R.string.stores_dl_retry), { Glasses.set3d(true) })
} else {
if (inSession || g.sbs) ToggleRow(host, "glasses-3d", stringResource(R.string.glasses_3d), stringResource(R.string.glasses_3d_hint),
g.sbs, chipModifier = track("glasses-3d")) { Glasses.set3d(it) }
if (g.sbs) {
if (g.sbs90) ChoiceRow(host, "glasses-3d-hz", stringResource(R.string.glasses_3d_refresh), null,
listOf(60, 90).map { it to "$it Hz" }, s.sbsHz, chipModifier = track("glasses-3d-hz")) { Glasses.setSbsHz(it) }
else SettingsRow(stringResource(R.string.glasses_3d_refresh), "60 Hz") {}
}
}
} else ToggleRow(host, "glasses-3d", stringResource(R.string.glasses_3d), stringResource(R.string.glasses_3d_hint),
g.sbs, chipModifier = track("glasses-3d")) { Glasses.set3d(it) }
}
ToggleRow(host, "glasses-auto3d", stringResource(R.string.glasses_auto3d), stringResource(R.string.glasses_auto3d_hint),
s.auto3d, chipModifier = track("glasses-auto3d")) { Glasses.setAuto3d(it) }
}
if (display && g.canControl) {
val profiles = if (inSession) listOf(GlassesDisplayModes.profile(g.sbs, false))
else listOf(GlassesDisplayProfile.TWO_D, GlassesDisplayProfile.THREE_D)
for (profile in profiles) SettingsGroup(stringResource(if (profile == GlassesDisplayProfile.TWO_D)
R.string.glasses_output_2d else R.string.glasses_output_3d)) {
GlassesResolutionRow(host, g, s, profile, track)
if ((profile == GlassesDisplayProfile.THREE_D) == g.sbs) GlassesRefreshRow(host, g, s, track)
}
}
if (display && (g.canControl || g.hasDisplay)) SettingsGroup(stringResource(R.string.glasses_picture_group)) {
ChoiceRow(host, "glasses-picture", stringResource(R.string.glasses_picture), stringResource(R.string.glasses_picture_hint),
listOf(
GlassesSettings.PICTURE_AUTO to stringResource(R.string.glasses_picture_auto),
GlassesSettings.PICTURE_SCREEN to stringResource(R.string.glasses_picture_screen),
GlassesSettings.PICTURE_SBS to stringResource(R.string.glasses_picture_sbs),
), s.picture3d, chipModifier = track("glasses-picture"), hintLines = 2) { Glasses.setPicture3d(it) }
), s.picture3d, chipModifier = track("glasses-picture")) { Glasses.setPicture3d(it) }
if (g.canControl && g.supportsScreen3dof) ToggleRow(host, "glasses-3dof",
stringResource(if (g.spatialConnected && s.positionalTracking) R.string.glasses_6dof else R.string.glasses_3dof),
stringResource(R.string.glasses_3dof_hint), s.screen3dof, chipModifier = track("glasses-3dof")) { Glasses.set3dof(it) }
when {
!inSession -> SettingsRow(stringResource(R.string.glasses_picture_screen), stringResource(R.string.glasses_screen_next_session)) {}
!inSession -> SettingsRow(stringResource(R.string.glasses_picture_screen), stringResource(R.string.glasses_screen_app_wide)) {}
!usesGlasses -> SettingsRow(stringResource(R.string.glasses_picture_screen), stringResource(R.string.glasses_screen_no_output)) {}
picture == GlassesRendering.Picture.FLAT -> ActionRow(
flatPicture -> ActionRow(
stringResource(R.string.glasses_picture_screen), stringResource(R.string.glasses_screen_flat_hint),
stringResource(R.string.glasses_screen_use), { Glasses.setPicture3d(GlassesSettings.PICTURE_SCREEN) })
directView -> SettingsRow(stringResource(R.string.glasses_picture_screen),
@@ -155,15 +180,44 @@ internal fun GlassesGroups(
}
if (virtualScreen || (!inSession && s.picture3d != GlassesSettings.PICTURE_SBS)) {
SliderRow(stringResource(R.string.glasses_screen_size), null, s.screenWidthDm, 5..60, step = 1,
format = { String.format(Locale.US, "%.1f m", it / 10f) }, modifier = track("glasses-width")) { Glasses.setScreenWidthDm(it) }
format = { SpatialMeasurements.distance(it / 10f) }, modifier = track("glasses-width")) { Glasses.setScreenWidthDm(it) }
SliderRow(stringResource(R.string.glasses_screen_distance), null, s.screenDistanceDm, 5..100, step = 1,
format = { String.format(Locale.US, "%.1f m", it / 10f) }, modifier = track("glasses-distance")) { Glasses.setScreenDistanceDm(it) }
format = { SpatialMeasurements.distance(it / 10f) }, modifier = track("glasses-distance")) { Glasses.setScreenDistanceDm(it) }
}
}
if (vr && (g.canControl || g.hasDisplay)) SettingsGroup(stringResource(R.string.cardboard_steamvr)) {
steamVr()
if ((directView && steamVrRunning) || !inSession) SliderRow(stringResource(R.string.glasses_vr_fov), stringResource(R.string.glasses_vr_fov_hint), s.vrFovDeg,
ToggleRow(host, "glasses-auto3d", stringResource(R.string.glasses_auto3d), stringResource(R.string.glasses_auto3d_hint),
s.auto3d, chipModifier = track("glasses-auto3d")) { Glasses.setAuto3d(it) }
if (g.canControl) {
GlassesResolutionRow(host, g, s, GlassesDisplayProfile.VR, track)
if (steamVrRunning && tab != null) GlassesRefreshRow(host, g, s, track)
}
SliderRow(stringResource(R.string.glasses_vr_fov), stringResource(R.string.glasses_vr_fov_hint), s.vrFovDeg,
GlassesSettings.VR_FOV_MIN..GlassesSettings.VR_FOV_MAX, step = 1,
format = { "$it°" }, modifier = track("glasses-vr-fov")) { Glasses.setVrFovDeg(it) }
}
if ((tab == null || tab == GlassesTab.TRACKING) && g.canControl) {
SettingsGroup(stringResource(R.string.glasses_head_tracking)) {
val trackingHint = when {
inSession && !usesGlasses -> R.string.glasses_screen_no_output
spatialVr -> R.string.glasses_head_tracking_spatial_vr_hint
spatialTracking -> R.string.glasses_head_tracking_spatial_hint
inSession && flatPicture -> R.string.glasses_tracking_flat_hint
inSession && directView && !steamVrRunning -> R.string.glasses_tracking_sbs_hint
else -> R.string.glasses_head_tracking_hint
}
ToggleRow(host, "glasses-tracking", stringResource(R.string.glasses_head_tracking), stringResource(trackingHint),
s.headTracking || spatialVr, enabled = !spatialVr,
chipModifier = track("glasses-tracking")) { Glasses.setHeadTracking(it) }
}
val calibration by Glasses.calibration.collectAsState()
if (calibration.supported) SettingsGroup(stringResource(R.string.glasses_calibration)) {
GlassesCalibrationControls(calibration,
onStart = Glasses::startCalibration, onCancel = Glasses::cancelCalibration,
onReset = Glasses::resetCalibration, onDone = { onRecenter?.invoke() })
}
}
if ((tab == null || tab == GlassesTab.HARDWARE) && g.canControl && g.hardwareControls) SettingsGroup(stringResource(R.string.glasses_hardware)) {
if (g.brightnessMax > 0) SliderRow(stringResource(R.string.glasses_brightness), null, g.brightness.coerceAtLeast(0), 0..g.brightnessMax, step = 1,
modifier = track("glasses-brightness")) { Glasses.setBrightness(it) }
@@ -172,28 +226,71 @@ internal fun GlassesGroups(
if (g.hasSunblock) ToggleRow(host, "glasses-sunblock", stringResource(R.string.glasses_sunblock), stringResource(R.string.glasses_sunblock_hint),
g.sunblock, chipModifier = track("glasses-sunblock")) { Glasses.setSunblock(it) }
}
if ((tab == null || tab == GlassesTab.TRACKING) && g.canControl) SettingsGroup(stringResource(R.string.glasses_tracking)) {
val trackingHint = when {
inSession && !usesGlasses -> R.string.glasses_screen_no_output
inSession && picture == GlassesRendering.Picture.FLAT -> R.string.glasses_tracking_flat_hint
inSession && directView && !steamVrRunning -> R.string.glasses_tracking_sbs_hint
else -> R.string.glasses_head_tracking_hint
}
ToggleRow(host, "glasses-tracking", stringResource(R.string.glasses_head_tracking), stringResource(trackingHint),
s.headTracking, chipModifier = track("glasses-tracking")) { Glasses.setHeadTracking(it) }
if (onRecenter != null && s.headTracking && (virtualScreen || (directView && steamVrRunning))) ActionRow(stringResource(R.string.glasses_recenter), null,
stringResource(R.string.cardboard_recenter_button), onRecenter)
val calibration by Glasses.calibration.collectAsState()
if (calibration.supported) GlassesCalibrationControls(calibration,
onStart = Glasses::startCalibration, onCancel = Glasses::cancelCalibration,
onReset = Glasses::resetCalibration, onDone = { onRecenter?.invoke() })
}
val context = androidx.compose.ui.platform.LocalContext.current
val hands by HandTracking.settings.collectAsState()
androidx.compose.runtime.LaunchedEffect(Unit) { HandTracking.load(context) }
if ((tab == null || tab == GlassesTab.TRACKING) && g.canControl && hands.usable) SettingsGroup(stringResource(R.string.hand_camera_group)) {
HandCameraRows(host, track, inSession)
if ((tab == null || tab == GlassesTab.TRACKING) && g.canControl) {
if (g.spatialConnected) {
SettingsGroup(stringResource(R.string.glasses_experimental)) {
val spatial by SpatialTracking.status.collectAsState()
ToggleRow(host, "glasses-position", stringResource(R.string.glasses_position), stringResource(R.string.glasses_position_hint),
s.positionalTracking, chipModifier = track("glasses-position")) { Glasses.setPositionalTracking(it) }
if (s.positionalTracking) {
SettingsRow(stringResource(R.string.glasses_tracking_status), spatial.message) {}
SliderRow(stringResource(R.string.glasses_eye_height), null, s.eyeHeightCm, 50..250, step = 1,
format = SpatialMeasurements::height, modifier = track("glasses-eye-height")) { Glasses.setEyeHeight(it) }
ActionRow(stringResource(R.string.glasses_floor), null, stringResource(R.string.glasses_floor_set), Glasses::setSpatialFloor)
HandCalibrationStatus(spatial)
ActionRow(stringResource(R.string.glasses_spatial_setup), stringResource(R.string.glasses_spatial_setup_hint),
stringResource(R.string.glasses_spatial_open), { context.startActivity(Intent(context, SpatialTrackingActivity::class.java)) })
ToggleRow(host, "glasses-hands", stringResource(R.string.glasses_hands), stringResource(R.string.glasses_hands_hint),
s.handControllers, chipModifier = track("glasses-hands")) { Glasses.setHandControllers(it) }
if (s.handControllers) ToggleRow(host, "glasses-steady-hand-aim",
stringResource(R.string.glasses_steady_hand_aim), stringResource(R.string.glasses_steady_hand_aim_hint),
s.steadyHandAim, chipModifier = track("glasses-steady-hand-aim")) { Glasses.setSteadyHandAim(it) }
}
ToggleRow(host, "glasses-joycons", stringResource(R.string.glasses_joycons),
stringResource(if (s.positionalTracking) R.string.glasses_joycons_hint else R.string.glasses_joycons_requires_position),
s.joyConTracking, enabled = s.positionalTracking, chipModifier = track("glasses-joycons")) { Glasses.setJoyConTracking(it) }
}
}
if (hands.usable) SettingsGroup(stringResource(R.string.phone_camera_tracking_group)) {
HandCameraRows(host, track, inSession)
}
}
}
@Composable
private fun GlassesResolutionRow(host: MenuHost, g: GlassesState, s: GlassesSettings,
profile: GlassesDisplayProfile, track: (String) -> Modifier) {
val label = when (profile) {
GlassesDisplayProfile.TWO_D -> R.string.glasses_resolution_2d
GlassesDisplayProfile.THREE_D -> R.string.glasses_resolution_3d
GlassesDisplayProfile.VR -> R.string.glasses_resolution_vr
}
val choices = listOf("" to stringResource(if (profile == GlassesDisplayProfile.VR)
R.string.glasses_resolution_inherit else R.string.glasses_resolution_auto)) +
g.resolutions.map { it.key to "${it.width} × ${it.height}" }
val saved = GlassesDisplayModes.choice(s, profile)
val key = "glasses-resolution-${profile.name.lowercase()}"
ChoiceRow(host, key, stringResource(label), stringResource(R.string.glasses_resolution_hint), choices,
saved.takeIf { value -> choices.any { it.first == value } } ?: "",
enabled = !g.switching3d, chipModifier = track(key)) { Glasses.setResolution(profile, it) }
}
@Composable
private fun GlassesRefreshRow(host: MenuHost, g: GlassesState, s: GlassesSettings, track: (String) -> Modifier) {
val rates = GlassesRendering.refreshRates(s, g).map { it to "$it Hz" }
val hint = if (g.supports30Hz && !s.gameOnGlasses) stringResource(R.string.glasses_refresh_30_hint) else null
val label = stringResource(if (g.sbs) R.string.glasses_3d_refresh else R.string.glasses_refresh)
if (rates.size > 1) {
val key = if (g.sbs) "glasses-3d-hz" else "glasses-hz"
ChoiceRow(host, key, label, hint, rates, GlassesRendering.outputHz(s, g),
enabled = !g.switching3d, chipModifier = track(key)) {
if (g.sbs) Glasses.setSbsHz(it) else Glasses.setRefreshHz(it)
}
} else SettingsRow(label, hint ?: "${rates.single().first} Hz") {}
}
private fun padButtonColors(tint: Int, opacity: Int, pressed: Boolean): Pair<Color, Color> {
@@ -231,28 +328,37 @@ internal fun GlassesCornerButton(slot: Int, tint: Int, opacity: Int, onClick: ()
}
@Composable
internal fun GlassesRecenterButton(tint: Int, opacity: Int, onClick: () -> Unit) {
internal fun GlassesRecenterButton(tint: Int, opacity: Int, onRecenterJoyCons: (() -> Unit)? = null, onClick: () -> Unit) {
Box(
Modifier.fillMaxSize().windowInsetsPadding(WindowInsets.displayCutout).padding(top = 12.dp),
contentAlignment = Alignment.TopCenter,
) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
GlassesRecenterAction(stringResource(R.string.glasses_recenter_button), "glasses-recenter", tint, opacity, onClick)
if (onRecenterJoyCons != null) GlassesRecenterAction(
stringResource(R.string.glasses_recenter_joycons_button), "glasses-recenter-joycons", tint, opacity, onRecenterJoyCons)
}
}
}
@Composable
private fun GlassesRecenterAction(label: String, tag: String, tint: Int, opacity: Int, onClick: () -> Unit) {
val source = remember { MutableInteractionSource() }
val pressed by source.collectIsPressedAsState()
val (fill, line) = padButtonColors(tint, opacity, pressed)
val shape = RoundedCornerShape(50)
Box(
Modifier.fillMaxSize().windowInsetsPadding(WindowInsets.displayCutout).padding(top = 12.dp),
contentAlignment = Alignment.TopCenter,
contentAlignment = Alignment.Center,
modifier = Modifier
.height(36.dp)
.clip(shape)
.background(fill)
.border(1.5.dp, line, shape)
.clickable(interactionSource = source, indication = null, role = Role.Button, onClick = onClick)
.padding(horizontal = 18.dp)
.testTag(tag),
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.height(36.dp)
.clip(shape)
.background(fill)
.border(1.5.dp, line, shape)
.clickable(interactionSource = source, indication = null, role = Role.Button, onClick = onClick)
.padding(horizontal = 18.dp)
.testTag("glasses-recenter"),
) {
Text(stringResource(R.string.glasses_recenter_button), color = line, fontSize = 14.sp)
}
Text(label, color = line, fontSize = 14.sp)
}
}
@@ -0,0 +1,38 @@
package com.droiddeck.launcher.ui
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.droiddeck.launcher.R
import com.droiddeck.launcher.glasses.GlassesMenuMirror
@Composable
internal fun GlassesSetupPanel() {
val (glasses, settings) = rememberGlasses()
val host = rememberMenuHost()
val tabs = glassesTabs(glasses)
var selectedTab by rememberSaveable { mutableStateOf(GlassesTab.DISPLAY) }
val tab = selectedTab.takeIf { selected -> tabs.any { it.first == selected } } ?: GlassesTab.DISPLAY
LaunchedEffect(tab, glasses.present, glasses.canControl, glasses.model) { host.open = null }
Column(Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(12.dp)) {
Note(stringResource(R.string.setup_glasses_scope))
if (!glasses.present && !glasses.hasDisplay && !glasses.canControl) {
Note(stringResource(R.string.setup_glasses_connect))
} else {
SegmentedTabs(tabs, tab, modifier = Modifier.fillMaxWidth(),
tabModifier = { Modifier.testTag("setup-glasses-tab-$it") }) { selectedTab = it }
GlassesGroups(host, glasses, settings, onRecenter = GlassesMenuMirror::recenter, inSession = false, tab = tab)
}
}
}
@@ -79,11 +79,12 @@ import java.util.Locale
internal fun HandCameraHost(check: Boolean?, onAskPermission: () -> Unit, onPlay: () -> Unit, onClose: () -> Unit) {
if (!HandTracking.supported) return
val context = LocalContext.current
val (glasses, _) = rememberGlasses()
val (glasses, tracking) = rememberGlasses()
val settings by HandTracking.settings.collectAsState()
LaunchedEffect(Unit) { HandTracking.load(context) }
var prompted by rememberSaveable { mutableStateOf(false) }
if (!prompted && HandTracking.shouldAsk(settings, glasses.present && glasses.canControl)) {
if (!prompted && !glasses.usesSpatialControllers(tracking) &&
HandTracking.shouldAsk(settings, glasses.present && glasses.canControl)) {
HandCameraPrompt(settings.sides,
onAllow = { side ->
prompted = true
@@ -168,7 +169,7 @@ internal fun HandCameraRows(host: MenuHost, track: (String) -> Modifier, inSessi
HandTracking.refreshPermission(context)
denied = !granted
}
ToggleRow(host, "hand-camera", stringResource(R.string.hand_camera_setting), stringResource(R.string.hand_camera_setting_hint),
ToggleRow(host, "hand-camera", stringResource(R.string.phone_camera_tracking_setting), stringResource(R.string.hand_camera_setting_hint),
s.enabled, chipModifier = track("hand-camera")) { on ->
HandTracking.setEnabled(context, on)
if (on && !s.permitted) ask.launch(Manifest.permission.CAMERA)
@@ -306,6 +306,10 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val host = rememberMenuHost()
val (glasses, glassesSettings) = rememberGlasses()
val glassesPages = glassesTabs(glasses)
var selectedGlassesTab by rememberSaveable { mutableStateOf(GlassesTab.DISPLAY) }
val glassesTab = selectedGlassesTab.takeIf { key -> glassesPages.any { it.first == key } } ?: GlassesTab.DISPLAY
var androidAppsExpanded by rememberSaveable { mutableStateOf(false) }
var advancedEffectsExpanded by rememberSaveable { mutableStateOf(false) }
var appToChooseDisplay by remember { mutableStateOf<HomeApp.LaunchableApp?>(null) }
@@ -313,6 +317,10 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont
var sheetCoords by remember { mutableStateOf<LayoutCoordinates?>(null) }
var stopCoords by remember { mutableStateOf<LayoutCoordinates?>(null) }
val pageScroll = remember { SessionDrawerPage.entries.associateWith { ScrollState(0) } }
LaunchedEffect(glassesTab) {
if (page == SessionDrawerPage.GLASSES) host.open = null
pageScroll.getValue(SessionDrawerPage.GLASSES).scrollTo(0)
}
val veil by animateFloatAsState(if (open) 1f else 0f, Motion.tw(260), label = "veil")
val focus = remember { DrawerFocus() }
val inputModeManager = LocalInputModeManager.current
@@ -339,7 +347,7 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont
if (open && !controllerActive) focusManager.clearFocus(force = true)
}
var focusedPage by remember { mutableStateOf(page) }
LaunchedEffect(open, page, controllerActive, host.open, appToChooseDisplay, confirmStop) {
LaunchedEffect(open, page, glassesTab, controllerActive, host.open, appToChooseDisplay, confirmStop) {
if (!open) {
host.open = null
confirmStop = false
@@ -348,7 +356,11 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont
if (host.open != null || appToChooseDisplay != null || confirmStop) return@LaunchedEffect
val turned = page != focusedPage
focusedPage = page
val first = if (page == SessionDrawerPage.SESSION && a.isHomeApp) "apps" else page.firstControl
val first = when {
page == SessionDrawerPage.GLASSES -> "glasses-tab-$glassesTab"
page == SessionDrawerPage.SESSION && a.isHomeApp -> "apps"
else -> page.firstControl
}
if (turned) focus.forget(page)
val target = focus.target(page) ?: first
repeat(24) {
@@ -437,6 +449,10 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont
host.open = null; onPageChange(pages.step(page, 1))
}
}
if (page == SessionDrawerPage.GLASSES) SegmentedTabs(
glassesPages, glassesTab, modifier = Modifier.fillMaxWidth().padding(top = 8.dp),
tabModifier = { key -> focus.track(page, "glasses-tab-$key").testTag("glasses-tab-$key") },
) { selectedGlassesTab = it }
Column(
modifier = Modifier.weight(1f).fillMaxWidth().verticalScroll(pageScroll.getValue(page)),
) { CompositionLocalProvider(LocalChipMinWidth provides 120.dp) {
@@ -538,9 +554,8 @@ internal fun SessionDrawer(open: Boolean, requestedPage: SessionDrawerPage, cont
SecondScreenGroup(host, a) { key -> focus.track(page, key) }
}
SessionDrawerPage.GLASSES -> {
val (g, s) = rememberGlasses()
GlassesGroups(host, g, s, { key -> focus.track(page, key) }, onRecenter = a.onGlassesRecenter,
outputActive = a.glassesOutput, steamVrRunning = a.steamVr == SteamVr.Status.RUNNING,
GlassesGroups(host, glasses, glassesSettings, { key -> focus.track(page, key) }, onRecenter = a.onGlassesRecenter,
tab = glassesTab, outputActive = a.glassesOutput, steamVrRunning = a.steamVr == SteamVr.Status.RUNNING,
steamVr = { SteamVrRow(a) })
}
SessionDrawerPage.GAMES -> {
@@ -761,7 +761,8 @@ fun SettingsPage(
}
@Composable
fun <T> SegmentedTabs(options: List<Pair<T, String>>, selected: T?, modifier: Modifier = Modifier, onPick: (T) -> Unit) {
fun <T> SegmentedTabs(options: List<Pair<T, String>>, selected: T?, modifier: Modifier = Modifier,
tabModifier: (T) -> Modifier = { Modifier }, onPick: (T) -> Unit) {
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val index = options.indexOfFirst { it.first == selected }
@@ -798,7 +799,7 @@ fun <T> SegmentedTabs(options: List<Pair<T, String>>, selected: T?, modifier: Mo
val pick = { onPick(value) }
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.width(segment).fillMaxHeight()
modifier = tabModifier(value).width(segment).fillMaxHeight()
.clip(pillShape)
.glideBorder(hot, pillShape, if (chosen) pal.onSignal else pal.signal)
.semantics { role = Role.Tab; this.selected = chosen }
@@ -0,0 +1,258 @@
package com.droiddeck.launcher.ui
import android.os.Bundle
import android.os.CancellationSignal
import android.os.ParcelFileDescriptor
import android.print.*
import android.graphics.Bitmap
import android.graphics.pdf.PdfDocument
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Color
import android.graphics.Paint
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.droiddeck.launcher.R
import com.droiddeck.launcher.core.AppUiPrefs
import com.droiddeck.launcher.glasses.CalibrationDistance
import com.droiddeck.launcher.glasses.Glasses
import com.droiddeck.launcher.glasses.SpatialCalibrationProgress
import com.droiddeck.launcher.glasses.SpatialTracking
import com.droiddeck.launcher.glasses.SpatialTrackingStatus
import com.droiddeck.launcher.glasses.StereoCalibrator
import kotlinx.coroutines.delay
import org.opencv.android.OpenCVLoader
import org.opencv.android.Utils
import org.opencv.core.Mat
import org.opencv.core.Size
class SpatialTrackingActivity : ComponentActivity() {
override fun attachBaseContext(newBase: android.content.Context) {
super.attachBaseContext(newBase)
com.droiddeck.launcher.core.AppLanguage.applyTo(this, newBase)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (!Glasses.state.value.spatialConnected) { finish(); return }
requestedOrientation = com.droiddeck.launcher.session.SessionPrefs.orientation(this)
setContent {
DroidDeckTheme(appScale = AppUiPrefs.scale(this)) {
val glasses by Glasses.state.collectAsState()
if (!glasses.spatialConnected) {
LaunchedEffect(Unit) { SpatialTracking.diagnostics(false); finish() }
return@DroidDeckTheme
}
var preview by remember { mutableStateOf<Bitmap?>(null) }
var message by remember { mutableStateOf("") }
var progress by remember { mutableStateOf(SpatialCalibrationProgress()) }
var gpu by remember { mutableStateOf(SpatialTracking.gpuEnabled()) }
var lite by remember { mutableStateOf(SpatialTracking.handLiteEnabled()) }
var halfRate by remember { mutableStateOf(SpatialTracking.handHalfRateEnabled()) }
var adaptive by remember { mutableStateOf(SpatialTracking.handAdaptiveInputEnabled()) }
val status by SpatialTracking.status.collectAsState()
LaunchedEffect(Unit) {
while(true) {
SpatialTracking.refreshStatus()
preview = SpatialTracking.diagnosticFrame()
message = SpatialTracking.diagnosticMessage()
progress = SpatialTracking.calibrationProgress()
gpu = SpatialTracking.gpuEnabled()
lite = SpatialTracking.handLiteEnabled()
halfRate = SpatialTracking.handHalfRateEnabled()
adaptive = SpatialTracking.handAdaptiveInputEnabled()
delay(200)
}
}
SpatialSetupScreen(status, progress, preview, message, SpatialSetupActions(
recenter = { SpatialTracking.recenter(false) }, restart = Glasses::restartSpatialTracking,
print = ::printBoard, start = SpatialTracking::calibrationStart,
capture = SpatialTracking::calibrationCapture, solve = SpatialTracking::calibrationSolve,
validate = SpatialTracking::calibrationValidate, gpu = SpatialTracking::setGpu,
lite = SpatialTracking::setHandModel, halfRate = SpatialTracking::setHandHalfRate,
adaptive = SpatialTracking::setHandAdaptiveInput,
saveFrame = SpatialTracking::captureDiagnostic, done = { finish() }), gpu, lite, halfRate, adaptive)
}
}
}
override fun onResume() {
super.onResume()
if (Glasses.state.value.spatialConnected) SpatialTracking.diagnostics(true) else finish()
}
override fun onPause() { SpatialTracking.diagnostics(false);super.onPause() }
private fun printBoard() {
if(!OpenCVLoader.initLocal()) return
val c=StereoCalibrator(this);val mat=Mat()
c.board.generateImage(Size(1500.0,2100.0),mat,0,1)
val bitmap=Bitmap.createBitmap(1500,2100,Bitmap.Config.ARGB_8888)
Utils.matToBitmap(mat,bitmap);mat.release();c.close()
val manager=getSystemService(PRINT_SERVICE) as PrintManager
manager.print(getString(R.string.spatial_print_job),object:PrintDocumentAdapter() {
override fun onLayout(old: PrintAttributes?, new: PrintAttributes, signal: CancellationSignal, callback: LayoutResultCallback, extras: Bundle?) {
callback.onLayoutFinished(PrintDocumentInfo.Builder("droiddeck-charuco.pdf").setPageCount(1).setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT).build(),true)
}
override fun onWrite(pages: Array<out PageRange>, destination: ParcelFileDescriptor, signal: CancellationSignal, callback: WriteResultCallback) {
if(signal.isCanceled) { callback.onWriteCancelled();return }
try {
val pdf=PdfDocument()
try {
val page=pdf.startPage(PdfDocument.PageInfo.Builder(595,842,1).create())
val scale=72f/25.4f
page.canvas.drawBitmap(bitmap,Rect(0,0,1500,2100),RectF(30*scale,35*scale,180*scale,245*scale),null)
val ink = Paint(Paint.ANTI_ALIAS_FLAG).apply { color = Color.BLACK; textSize = 11f; textAlign = Paint.Align.CENTER }
printText(page.canvas, getString(R.string.spatial_print_title), 12*scale, ink)
ink.textSize = 9f
printText(page.canvas, getString(R.string.spatial_print_scale), 21*scale, ink)
printText(page.canvas, getString(R.string.spatial_print_stationary), 254*scale, ink)
page.canvas.drawLine(55*scale, 270*scale, 155*scale, 270*scale, ink)
page.canvas.drawLine(55*scale, 268*scale, 55*scale, 272*scale, ink)
page.canvas.drawLine(155*scale, 268*scale, 155*scale, 272*scale, ink)
printText(page.canvas, getString(R.string.spatial_print_check), 276*scale, ink)
pdf.finishPage(page)
java.io.FileOutputStream(destination.fileDescriptor).use { pdf.writeTo(it) }
} finally { pdf.close() }
callback.onWriteFinished(arrayOf(PageRange.ALL_PAGES))
} catch(e: Exception) { callback.onWriteFailed(e.message) }
}
override fun onFinish() { bitmap.recycle() }
},PrintAttributes.Builder().setMediaSize(PrintAttributes.MediaSize.ISO_A4).setColorMode(PrintAttributes.COLOR_MODE_MONOCHROME)
.setMinMargins(PrintAttributes.Margins.NO_MARGINS).build())
}
private fun printText(canvas: android.graphics.Canvas, text: String, y: Float, ink: Paint) {
val paint = TextPaint(ink).apply { textAlign = Paint.Align.LEFT }
val layout = StaticLayout.Builder.obtain(text, 0, text.length, paint, 535)
.setAlignment(Layout.Alignment.ALIGN_CENTER).setIncludePad(false).build()
val saved = canvas.save()
canvas.translate(30f, y)
layout.draw(canvas)
canvas.restoreToCount(saved)
}
}
internal data class SpatialSetupActions(
val recenter: () -> Unit = {}, val restart: () -> Unit = {}, val print: () -> Unit = {},
val start: () -> Unit = {}, val capture: () -> Unit = {}, val solve: () -> Unit = {},
val validate: (Float) -> Unit = {}, val gpu: (Boolean) -> Unit = {},
val lite: (Boolean) -> Unit = {}, val halfRate: (Boolean) -> Unit = {},
val adaptive: (Boolean) -> Unit = {},
val saveFrame: () -> Unit = {}, val done: () -> Unit = {},
)
@Composable
internal fun SpatialSetupScreen(
status: SpatialTrackingStatus, progress: SpatialCalibrationProgress, preview: Bitmap?, message: String,
actions: SpatialSetupActions, gpu: Boolean = false, lite: Boolean = false, halfRate: Boolean = true,
adaptive: Boolean = true,
) {
val host = rememberMenuHost()
var distance by rememberSaveable { mutableStateOf(CalibrationDistance.INCHES_12) }
var diagnostics by rememberSaveable { mutableStateOf(false) }
var confirmRestart by remember { mutableStateOf(false) }
val ready = status.trackingValid && status.synchronized && !progress.solving && !progress.validating
Surface(Modifier.fillMaxSize().systemBarsPadding(), color = MaterialTheme.colorScheme.background) {
BoxWithConstraints(Modifier.fillMaxSize()) {
CompositionLocalProvider(LocalNarrowPane provides (maxWidth < NarrowPaneWidth)) {
SettingsPage(host, null, actions.done, compactLayout = true,
modifier = Modifier.testTag("spatial-setup-page"), header = {
Text(stringResource(R.string.spatial_title), style = MaterialTheme.typography.titleLarge,
maxLines = 1, overflow = TextOverflow.Ellipsis)
}) {
SettingsGroup(stringResource(R.string.glasses_tracking)) {
SettingsRow(stringResource(R.string.glasses_head_tracking), status.message.ifEmpty { stringResource(R.string.spatial_disabled) }, stacked = true) {
SecondaryButton(if (status.recenterPending) stringResource(R.string.spatial_wait_tracking) else stringResource(R.string.cardboard_recenter_button),
enabled = !status.recenterPending, onClick = actions.recenter)
}
preview?.let {
Image(it.asImageBitmap(), stringResource(R.string.spatial_cameras_description),
Modifier.fillMaxWidth().padding(12.dp).aspectRatio(it.width.toFloat() / it.height))
}
SettingsRow(stringResource(R.string.spatial_camera_sync), if (status.synchronized) stringResource(R.string.spatial_ready) else
stringResource(R.string.spatial_sync_hint)) {}
}
SettingsGroup(stringResource(R.string.spatial_prepare)) {
ActionRow(stringResource(R.string.spatial_print_actual), stringResource(R.string.spatial_print_hint),
stringResource(R.string.spatial_print), actions.print)
}
SettingsGroup(stringResource(R.string.spatial_capture_group)) {
SettingsRow(stringResource(R.string.spatial_board_stationary), stringResource(R.string.spatial_capture_hint), stacked = true) {
SecondaryButton(stringResource(R.string.spatial_start), enabled = ready, onClick = actions.start)
}
SettingsRow(stringResource(R.string.spatial_views_count, progress.views), when {
!status.trackingValid -> stringResource(R.string.spatial_capture_paused, status.message)
!status.synchronized -> stringResource(R.string.spatial_wait_sync)
!progress.collecting -> progress.captureMessage ?: stringResource(R.string.spatial_start_hint)
else -> progress.captureMessage ?: stringResource(R.string.spatial_views_hint)
}, stacked = true) {
SecondaryButton(if (progress.capturing) stringResource(R.string.spatial_capturing) else stringResource(R.string.spatial_capture),
enabled = ready && progress.collecting && !progress.capturing, onClick = actions.capture)
}
SettingsRow(stringResource(R.string.spatial_alignment), message, stacked = true) {
SecondaryButton(if (progress.solving) stringResource(R.string.spatial_calculating) else stringResource(R.string.spatial_calculate),
enabled = !progress.solving && !progress.validating && progress.views >= 24, onClick = actions.solve)
}
}
SettingsGroup(stringResource(R.string.spatial_distance_group)) {
ChoiceRow(host, "calibration-distance", stringResource(R.string.spatial_distance), stringResource(R.string.spatial_distance_hint),
CalibrationDistance.entries.map { it to stringResource(it.labelRes) }, distance, onPick = { distance = it })
SettingsRow(stringResource(R.string.spatial_camera_board_distance), progress.validationMessage ?:
stringResource(R.string.spatial_measure_hint, stringResource(distance.labelRes)), stacked = true) {
SecondaryButton(if(progress.validating) stringResource(R.string.spatial_checking_distance) else stringResource(R.string.spatial_validate),
enabled = ready && progress.fitted, onClick = { actions.validate(distance.meters) })
}
HandCalibrationStatus(status)
SettingsRow(stringResource(R.string.glasses_hands_group), status.handStatus.ifEmpty { stringResource(R.string.spatial_hands_off) }) {}
}
SettingsGroup(stringResource(R.string.spatial_diagnostics)) {
ToggleRow(host, "spatial-diagnostics", stringResource(R.string.spatial_show_diagnostics), stringResource(R.string.spatial_diagnostics_hint), diagnostics) { diagnostics = it }
if (diagnostics) {
SettingsRow(stringResource(R.string.spatial_camera_timing), stringResource(R.string.spatial_timing_values, status.cameraHz, status.frameAgeMs, status.processingMs)) {}
SettingsRow(stringResource(R.string.spatial_hand_input), if (status.cameraHz > 0)
stringResource(R.string.spatial_input_values, status.cameraHz*status.cameraCopyPermille/1000f, status.cameraCopyPermille/10f)
else stringResource(R.string.spatial_wait_timing)) {}
SettingsRow(stringResource(R.string.spatial_frames), stringResource(R.string.spatial_frame_counts, status.frames, status.dropped, status.rateLimitedFrames)) {}
ToggleRow(host, "spatial-gpu", stringResource(R.string.spatial_gpu), stringResource(R.string.spatial_gpu_hint), gpu, onChange = actions.gpu)
ToggleRow(host, "spatial-lite", stringResource(R.string.spatial_lite), stringResource(R.string.spatial_lite_hint), lite, onChange = actions.lite)
ToggleRow(host, "spatial-adaptive-rate", stringResource(R.string.spatial_adaptive), stringResource(R.string.spatial_adaptive_hint), adaptive, onChange = actions.adaptive)
if (!adaptive) ToggleRow(host, "spatial-half-rate", stringResource(R.string.spatial_half_rate), stringResource(R.string.spatial_half_rate_hint), halfRate, onChange = actions.halfRate)
ActionRow(stringResource(R.string.spatial_diagnostic_capture), stringResource(R.string.spatial_diagnostic_capture_hint), stringResource(R.string.spatial_save_frame), actions.saveFrame)
ActionRow(stringResource(R.string.spatial_restart_tracking), stringResource(R.string.spatial_restart_hint), stringResource(R.string.spatial_restart), { confirmRestart = true })
}
}
Spacer(Modifier.height(16.dp))
SecondaryButton(stringResource(R.string.setup_check_done), onClick = actions.done)
}
}
}
}
if (confirmRestart) AlertDialog(
onDismissRequest = { confirmRestart = false }, title = { Text(stringResource(R.string.spatial_restart_title)) },
text = { Text(stringResource(R.string.spatial_restart_confirm)) },
confirmButton = { TextButton(onClick = { confirmRestart = false; actions.restart() }) { Text(stringResource(R.string.spatial_restart)) } },
dismissButton = { TextButton(onClick = { confirmRestart = false }) { Text(stringResource(R.string.common_cancel)) } },
)
}
@Composable
internal fun HandCalibrationStatus(status: SpatialTrackingStatus) {
val (label, hint) = when {
status.calibrationSource == "luma-ultra-base" -> R.string.glasses_hands_base to R.string.glasses_hands_base_hint
status.calibrationVerified -> R.string.glasses_hands_calibration_verified to R.string.glasses_hands_ready_hint
else -> R.string.glasses_hands_calibration_required to R.string.glasses_hands_calibration_required_hint
}
SettingsRow(stringResource(label), stringResource(hint)) {}
}
@@ -49,6 +49,7 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.unit.dp
import com.droiddeck.launcher.frontend.Library
import com.droiddeck.launcher.glasses.GlassesMenuMirror
import androidx.compose.foundation.layout.width
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.key
@@ -120,7 +121,8 @@ internal fun SteamHome(s: FrontEndState, a: FrontEndActions, modifier: Modifier)
modifier = Modifier.fillMaxWidth().testTag("play-steamvr")) { if (enabled) { drop.open = false; a.onPlaySteamVr() } }
},
) { tab ->
GlassesGroups(rememberMenuHost(), glasses, glassesSettings, inSession = false, tab = tab)
GlassesGroups(rememberMenuHost(), glasses, glassesSettings,
onRecenter = GlassesMenuMirror::recenter, inSession = false, tab = tab)
}
},
onOpen = a.onSteamSettings, onPrimary = a.onPlay,
@@ -0,0 +1,84 @@
package com.droiddeck.launcher.ui
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Typeface
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.view.View
import androidx.compose.ui.graphics.toArgb
import com.droiddeck.launcher.R
import com.droiddeck.launcher.glasses.TrackingRecovery
import com.droiddeck.launcher.session.SessionPrefs
internal class TrackingRecoveryView(context: Context) : View(context) {
private var recovery: TrackingRecovery? = null
private var stereo = false
private var palette = Themes.byId(SessionPrefs.theme(context))
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var blocks = emptyList<StaticLayout>()
init {
visibility = GONE
isClickable = true
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_YES
}
fun update(state: TrackingRecovery?, stereo: Boolean, palette: Palette = Themes.byId(SessionPrefs.theme(context))) {
if (recovery == state && this.stereo == stereo && this.palette == palette) return
recovery = state
this.stereo = stereo
this.palette = palette
visibility = if (state == null) GONE else VISIBLE
blocks = state?.let {
val (title, body, help) = when (it) {
TrackingRecovery.DISCONNECTED -> Triple(R.string.tracking_recovery_disconnected, R.string.tracking_recovery_connect, R.string.tracking_recovery_usb_help)
TrackingRecovery.LOST -> Triple(R.string.tracking_recovery_lost, R.string.tracking_recovery_look, R.string.tracking_recovery_restart_help)
TrackingRecovery.RECENTER -> Triple(R.string.tracking_recovery_recenter, R.string.tracking_recovery_recenter_steps, R.string.tracking_recovery_recenter_help)
TrackingRecovery.STABILIZING -> Triple(R.string.tracking_recovery_stabilizing, R.string.tracking_recovery_hold, R.string.tracking_recovery_automatic)
}
val text = listOf(context.getString(R.string.glasses_head_tracking), context.getString(title), context.getString(body), context.getString(help))
contentDescription = text.joinToString(". ")
text.mapIndexed { index, value ->
val ink = TextPaint(Paint.ANTI_ALIAS_FLAG).apply {
textSize = floatArrayOf(16f, 38f, 24f, 19f)[index]
color = (when (index) { 0 -> palette.primary; 3 -> palette.onSurfaceVariant; else -> palette.onBackground }).toArgb()
typeface = if (index < 2) Typeface.create("sans-serif-medium", Typeface.NORMAL) else Typeface.create("sans-serif", Typeface.NORMAL)
}
StaticLayout.Builder.obtain(value, 0, value.length, ink, 660)
.setAlignment(Layout.Alignment.ALIGN_NORMAL).setIncludePad(false).setLineSpacing(4f, 1f).build()
}
} ?: emptyList()
if (state == null) contentDescription = null
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
if (recovery == null) return
canvas.drawColor(palette.background.toArgb())
val eyes = if (stereo) 2 else 1
val eyeWidth = width.toFloat() / eyes
val gaps = floatArrayOf(14f, 24f, 32f, 0f)
val cardHeight = blocks.sumOf { it.height } + gaps.sum() + 80f
val scale = minOf(eyeWidth / 860f, height / (cardHeight + 120f))
repeat(eyes) { eye ->
val saved = canvas.save()
canvas.clipRect(eye * eyeWidth, 0f, (eye + 1) * eyeWidth, height.toFloat())
canvas.translate(eye * eyeWidth + (eyeWidth - 740f * scale) / 2f, (height - cardHeight * scale) / 2f)
canvas.scale(scale, scale)
paint.style = Paint.Style.FILL; paint.color = palette.surface.toArgb()
canvas.drawRoundRect(0f, 0f, 740f, cardHeight, 24f, 24f, paint)
paint.style = Paint.Style.STROKE; paint.strokeWidth = 1.5f; paint.color = palette.line.toArgb()
canvas.drawRoundRect(0f, 0f, 740f, cardHeight, 24f, 24f, paint)
canvas.translate(40f, 40f)
blocks.forEachIndexed { index, block ->
block.draw(canvas)
canvas.translate(0f, block.height + gaps[index])
}
canvas.restoreToCount(saved)
}
}
}
@@ -276,6 +276,9 @@ public final class WaylandCompositor {
float lensInset, float chroma, boolean mono, float displayHz);
public static native void nativeSetCardboardPose(float[] rotation, boolean live);
public static native void nativeSetCardboardSpatialPose(float[] rotation, float[] position, boolean live);
public static native boolean nativeSetSpatialFile(String path);
public static native void nativePublishSpatial(java.nio.ByteBuffer snapshot);
public static native void nativeSetCardboardPoseFile(String path);
+214 -10
View File
@@ -2466,14 +2466,14 @@
<string name="glasses_display">العرض</string>
<string name="glasses_refresh">معدل التحديث</string>
<string name="glasses_3d">3D (جنبًا إلى جنب)</string>
<string name="glasses_3d_hint">تحصل كل عين على نصفها الخاص من صورة بدقة 3840×1080. إذا انقطعت الصورة، فاضغط مطولًا على زر 2D/3D في النظارة.</string>
<string name="glasses_3d_hint">تحويل النظارات الذكية إلى وضع ثلاثي الأبعاد جنبًا إلى جنب. يتم التحكم في تتبع الرأس بشكل منفصل.</string>
<string name="glasses_3d_failed">لم يتم تأكيد إشارة 3D. حاول مجددًا بعد عودة صورة 2D.</string>
<string name="glasses_no_signal">لا توجد صورة: النظارة في وضع لا يستطيع هذا الهاتف إرساله. اضغط مطولًا على زر 2D/3D فيها، أو افصلها ثم أعد توصيلها.</string>
<string name="glasses_3d_refresh">معدل تحديث 3D</string>
<string name="glasses_auto3d">3D لألعاب الواقع الافتراضي</string>
<string name="glasses_auto3d_hint">يتحول إلى 3D أثناء تشغيل SteamVR، ثم يعود بعده.</string>
<string name="glasses_picture">صورة 3D</string>
<string name="glasses_picture_hint">تلقائي: يملأ SteamVR مجال الرؤية؛ والألعاب الأخرى تظهر كشاشة معلّقة في 3D وتبقى مسطّحة في 2D.</string>
<string name="glasses_picture">الصورة</string>
<string name="glasses_picture_hint">يعرض الوضع التلقائي المحتوى المسطح في كلتا العينين ويكتشف SteamVR. اختر وضع اللعب جنبًا إلى جنب لعرض محتوى SBS.</string>
<string name="glasses_picture_auto">تلقائي</string>
<string name="glasses_picture_screen">شاشة افتراضية</string>
<string name="glasses_picture_sbs">لعبة جنبًا إلى جنب</string>
@@ -2483,20 +2483,20 @@
<string name="glasses_screen_no_output">تُطبَّق هذه الإعدادات عند تفعيل «اللعبة على النظارة» وعرض اللعبة عليها.</string>
<string name="glasses_screen_flat_hint">يُبقي الوضع «تلقائي» لعبة 2D هذه بملء الشاشة. استخدم شاشة افتراضية لضبط عرضها وبُعدها وتتبّع الرأس.</string>
<string name="glasses_screen_use">استخدام شاشة افتراضية</string>
<string name="glasses_screen_direct_hint">يملأ SteamVR مجال الرؤية بالكامل. اضبط مجال رؤيته أدناه؛ ولا يُطبَّق عرض الشاشة وبُعدها إلا على الشاشة الافتراضية.</string>
<string name="glasses_screen_direct_hint">يملأ SteamVR مجال الرؤية. اضبط مجال رؤيته في الواقع الافتراضي؛ ينطبق العرض والمسافة على الشاشة الافتراضية فقط.</string>
<string name="glasses_screen_sbs_hint">تملأ ألعاب جنبًا إلى جنب رؤية كل عين. ولا يُطبَّق عرض الشاشة وبُعدها إلا على الشاشة الافتراضية.</string>
<string name="glasses_tracking_flat_hint">يُبقي الوضع «تلقائي» لعبة 2D هذه ثابتة أمامك. اختر «شاشة افتراضية» في «العرض» لاستخدام تتبّع الرأس.</string>
<string name="glasses_tracking_sbs_hint">يُطبَّق تتبّع الرأس على الشاشة الافتراضية أو على لعبة SteamVR قيد التشغيل.</string>
<string name="glasses_hardware">النظارة</string>
<string name="glasses_hardware">الجهاز</string>
<string name="glasses_brightness">السطوع</string>
<string name="glasses_volume">مستوى الصوت</string>
<string name="glasses_sunblock">حجب الشمس</string>
<string name="glasses_sunblock_hint">يعتّم العدسات الأمامية.</string>
<string name="glasses_tracking">تتبّع الرأس</string>
<string name="glasses_tracking">التتبع</string>
<string name="glasses_head_tracking">تتبّع الرأس</string>
<string name="glasses_head_tracking_hint">تُدير مستشعرات النظارة نفسها الشاشة الافتراضية وSteamVR مع حركة رأسك.</string>
<string name="glasses_recenter">إعادة توسيط العرض</string>
<string name="glasses_recenter_button">إعادة التوسيط</string>
<string name="glasses_recenter_button">إعادة توسيط الشاشة</string>
<string name="glasses_vr_fov">مجال رؤية SteamVR</string>
<string name="glasses_vr_fov_hint">القيمة الأقل تقرّب SteamVR وتملأ النظارة؛ والأعلى تُظهر المزيد مما حولك.</string>
<string name="glasses_playing">جارٍ اللعب على %1$s</string>
@@ -2517,8 +2517,8 @@
<string name="glasses_calibration_reset_hint">هل تريد إزالة المعايرة المحفوظة واستعادة الإعدادات الافتراضية للطراز؟ أعد توسيط العرض بعد ارتداء النظارة مجددًا.</string>
<string name="glasses_calibration_reset_button">إعادة ضبط</string>
<string name="hand_camera_group">تتبّع اليدين</string>
<string name="hand_camera_prompt_title">تتبّع يديك باستخدام الكاميرا؟</string>
<string name="hand_camera_prompt_body">يمكن للكاميرا رؤية يديك. عند عدم توصيل أي وحدة تحكم، تصبح يداك المجردتان هما يدا VR: اضغط الإبهام والسبابة معًا للنقر، وأغلق يدك للإمساك، ووجّه راحة يدك نحوك واضغط بإصبعيك لفتح القائمة. مع Joy-Con، تضعها الكاميرا حيث توجد يداك فعلًا. تُعالَج الصور على هذا الجهاز ولا تُحفظ أو تُرسَل أبدًا.</string>
<string name="hand_camera_prompt_title">تتبع اليدين أو وحدات تحكم Joy-Con باستخدام كاميرا الهاتف؟</string>
<string name="hand_camera_prompt_body">عند عدم توصيل وحدة تحكم، تتتبع كاميرا الهاتف يديك في الواقع الافتراضي: اضغط بإبهامك وسبابتك للنقر، وأغلق يدك للإمساك، وأدر راحة يدك نحوك واضغط لفتح القائمة. عند توصيل وحدات تحكم Joy-Con، تتتبع الكاميرا موضعها بينما توفر وحدات التحكم إمكانية الدوران وإدخال الأزرار. تتم معالجة الصور على هذا الجهاز ولا يتم حفظها أو إرسالها.</string>
<string name="hand_camera_rear">الكاميرا الخلفية</string>
<string name="hand_camera_rear_hint">مُوصى بها. عادةً أوضح وأوسع.</string>
<string name="hand_camera_front">الكاميرا الأمامية</string>
@@ -2526,7 +2526,7 @@
<string name="hand_camera_allow">استخدام الكاميرا</string>
<string name="hand_camera_not_now">ليس الآن</string>
<string name="hand_camera_setting">تتبّع اليدين بالكاميرا</string>
<string name="hand_camera_setting_hint">عند عدم توصيل أي وحدة تحكم، تصبح يداك هما يدا VR: اضغط بإصبعيك للنقر، ووجّه راحة يدك نحوك واضغط لفتح القائمة. مع Joy-Con، تضعها حيث تراها الكاميرا. تبقى الصور على هذا الجهاز.</string>
<string name="hand_camera_setting_hint">استخدم كاميرا الهاتف لإيماءات اليد المجردة أو لتحديد موضع وحدات تحكم Joy-Con المقترنة في الواقع الافتراضي. توفر وحدات تحكم Joy-Con التحكم في الدوران والأزرار. يتوقف التتبع مؤقتًا بينما تتعقب النظارات الذكية يديك أو وحدات تحكم Joy-Con. تتم معالجة الصور على هذا الجهاز فقط.</string>
<string name="hand_camera_choice">الكاميرا</string>
<string name="hand_camera_access">الوصول إلى الكاميرا</string>
<string name="hand_camera_access_hint">اسمح بالوصول إلى الكاميرا حتى يبدأ تتبّع اليدين.</string>
@@ -2780,4 +2780,208 @@
<string name="boot_movie_converting_started">جارٍ تحويل %1$s لـ Steam. أبقِ DroidDeck مفتوحًا.</string>
<string name="boot_movie_unsupported">لا يستطيع هذا الجهاز فك ترميز هذا الفيديو.</string>
<string name="boot_movie_import_failed">تعذّر استيراد الفيديو</string>
<string name="glasses_experimental">كاميرات Luma Ultra - تجريبية</string>
<string name="glasses_joycons">تتبع موضع Joy-Con</string>
<string name="glasses_joycons_hint">استخدم كاميرات النظارات الذكية لتحديد موضع وحدات تحكم Joy-Con المقترنة. يتم التحكم في الدوران والأزرار من خلال وحدات تحكم Joy-Con. يتم تقدير الموضع عندما تكون معصميك مخفية. يحل هذا محل وحدات التحكم التي يتم تشغيلها باليدين ويوقف تتبع كاميرا الهاتف مؤقتًا. وجّه وحدات تحكم Joy-Con للأمام عند توصيل مركز التتبع أو إعادة ضبطه.</string>
<string name="glasses_joycons_requires_position">فعّل تتبع الموضع أولًا. يتطلب ذلك وحدات تحكم Joy-Con تدعم الحركة على نظام Android 12 أو أحدث. مُعطّل افتراضيًا.</string>
<string name="setup_tab_glasses">النظارات الذكية</string>
<string name="setup_glasses_scope">تتم مشاركة إعدادات العرض وتتبع الرأس بين قوائم DroidDeck والألعاب وجلسات سطح المكتب.</string>
<string name="setup_glasses_connect">وصّل النظارات الذكية لتغيير إعدادات العرض والتتبع. يتم الاحتفاظ بإعداداتك المحفوظة عند فصلها.</string>
<string name="glasses_vr">الواقع الافتراضي</string>
<string name="glasses_display_mode">وضع العرض</string>
<string name="glasses_output_2d">إخراج ثنائي الأبعاد</string>
<string name="glasses_output_3d">إخراج ثلاثي الأبعاد</string>
<string name="glasses_picture_group">الصورة والشاشة الافتراضية</string>
<string name="glasses_position_group">الموضع ومستوى الأرضية</string>
<string name="glasses_hands_group">تتبع اليدين - تجريبي</string>
<string name="glasses_tracking_status">حالة التتبع</string>
<string name="glasses_resolution_2d">دقة ثنائية الأبعاد</string>
<string name="glasses_resolution_3d">دقة ثلاثية الأبعاد</string>
<string name="glasses_resolution_vr">دقة الواقع الافتراضي</string>
<string name="glasses_resolution_auto">تلقائي</string>
<string name="glasses_resolution_inherit">نفس دقة العرض ثنائي/ثلاثي الأبعاد</string>
<string name="glasses_resolution_hint">دقة الإخراج لكل عين.</string>
<string name="glasses_refresh_30_hint">تفعيل وضع الألعاب على النظارات الذكية لاختيار وضع 30 هرتز الأصلي للشاشة.</string>
<string name="glasses_3dof">شاشة افتراضية 3DoF</string>
<string name="glasses_3dof_hint">تثبيت الألعاب المسطحة أمامك لتتمكن من النظر حولك. يُمكّن هذا الخيار وضع الألعاب على النظارات الذكية دون الحاجة إلى التحويل إلى وضع ثلاثي الأبعاد.</string>
<string name="glasses_screen_app_wide">يتم تحديث العرض والمسافة للشاشة الافتراضية المُتتبعة لقوائم DroidDeck والألعاب وجلسات سطح المكتب فورًا.</string>
<string name="glasses_head_tracking_spatial_vr_hint">يشمل تتبع الحركة دوران الرأس في SteamVR. لتتبع الدوران فقط، أوقف تتبع الحركة.</string>
<string name="glasses_head_tracking_spatial_hint">يتحكم في حركة الرأس على الشاشة الافتراضية. يشمل تتبع الحركة دوران الرأس في SteamVR.</string>
<string name="glasses_quick_actions">إجراءات سريعة</string>
<string name="glasses_recenter_screen_hint">انظر إلى المكان الذي تريد وضع الشاشة فيه، ثم أعد ضبط مركز التتبع لتحديد ارتفاعه واتجاهه.</string>
<string name="glasses_recenter_joycons_button">إعادة توسيط Joy-Con</string>
<string name="phone_camera_tracking_group">كاميرا الهاتف - تجريبية</string>
<string name="phone_camera_tracking_setting">تتبع اليدين أو وحدات تحكم Joy-Con</string>
<string name="glasses_6dof">شاشة افتراضية 6DoF</string>
<string name="glasses_position">تتبع الموقع - تجريبي</string>
<string name="glasses_position_hint">الحركة الجسدية في الواقع الافتراضي وحول الشاشات الافتراضية. قد يستغرق إعادة تشغيل التتبع لحظة.</string>
<string name="glasses_eye_height">ارتفاع العين الحالي عن الأرض</string>
<string name="glasses_floor">ضبط مستوى الأرض من الارتفاع الحالي</string>
<string name="glasses_floor_set">ضبط مستوى الأرض</string>
<string name="glasses_spatial_setup">إعداد الكاميرا المجسمة وتشخيصها</string>
<string name="glasses_spatial_setup_hint">تحقق من معايرة الكاميرا وتتبع اليدين قبل اللعب.</string>
<string name="glasses_spatial_open">فتح الإعدادات</string>
<string name="glasses_hands">وحدات التحكم التي يتم تشغيلها باليدين</string>
<string name="glasses_hands_hint">استخدم كاميرات النظارات الذكية للتصويب بيديك، وقرّب للنقر، وأغلق يدك للإمساك في الواقع الافتراضي. تتحرك عصي التحكم وتدور. يحل هذا محل تتبع موقع Joy-Con. قد يحدث تأخير أو فقدان للتتبع أثناء اللعب.</string>
<string name="glasses_hands_ready_hint">فعّل وحدات التحكم التي يتم تشغيلها باليدين تحت كاميرات Luma Ultra في التتبع. اعرض اليدين المفتوحتين أولاً.</string>
<string name="glasses_hands_base">معايرة Luma Ultra المشتركة</string>
<string name="glasses_hands_base_hint">استخدام قاعدة Luma Ultra المشتركة. قم بمعايرة نظارتك الذكية للتحقق من دقتها.</string>
<string name="glasses_hands_calibration_verified">تم التحقق من صحة المعايرة المقاسة</string>
<string name="glasses_hands_calibration_required">يلزم التحقق</string>
<string name="glasses_hands_calibration_required_hint">تصبح وحدات التحكم التي يتم تشغيلها باليدين متاحة بعد اجتياز فحص المسافة المقاسة.</string>
<string name="tracking_recovery_lost">فقد التتبع</string>
<string name="tracking_recovery_look">انظر حولك ببطء في غرفة مضاءة جيدًا.\nحافظ على خلو مجال رؤية كاميرات التتبع، ووجه نظرك نحو الأثاث أو أي تفاصيل مرئية أخرى.</string>
<string name="tracking_recovery_restart_help">لا تزال عالقة؟ على هاتفك: النظارات الذكية → التتبع → فتح الإعداد → إظهار التشخيص → إعادة تشغيل التتبع.</string>
<string name="tracking_recovery_disconnected">تم فصل النظارات الذكية</string>
<string name="tracking_recovery_connect">تحقق من اتصال USB وأعد توصيل النظارات الذكية.\nاقبل مطالبة الوصول عبر USB على هاتفك إذا ظهرت.</string>
<string name="tracking_recovery_usb_help">بمجرد الاتصال، انظر حولك ببطء في غرفة مضاءة جيدًا لاستعادة التتبع.</string>
<string name="tracking_recovery_recenter">يلزم إعادة التوسيط</string>
<string name="tracking_recovery_recenter_steps">عاد التتبع إلى موقع جديد.\nانظر للأمام، ثم انقر على \"إعادة ضبط مركز التتبع\" على هاتفك.</string>
<string name="tracking_recovery_recenter_help">يمكنك أيضًا العثور على \"إعادة ضبط مركز التتبع\" في النظارات الذكية ← التتبع ← فتح الإعدادات. سيتم الاحتفاظ بمعايرة الكاميرا المحفوظة.</string>
<string name="tracking_recovery_stabilizing">ابقَ ثابتًا للحظة</string>
<string name="tracking_recovery_hold">جارٍ إعادة ضبط مركز التتبع.\nحافظ على ثبات النظارات الذكية أثناء استقرار التتبع.</string>
<string name="tracking_recovery_automatic">يتم إغلاق هذه الشاشة تلقائيًا عندما يكون تتبع الرأس جاهزًا.</string>
<string name="glasses_steady_hand_aim">التصويب المستقر بالأيدي</string>
<string name="glasses_steady_hand_aim_hint">صوب من كتفك إلى راحة يدك. قم بإيقاف التشغيل للدوران الذي يتم التحكم فيه بالمعصم.</string>
<string name="spatial_title">الإعداد المكاني</string>
<string name="spatial_wait_tracking">في انتظار التتبع...</string>
<string name="spatial_cameras_description">كاميرات التتبع اليسرى واليمنى</string>
<string name="spatial_camera_sync">مزامنة الكاميرا</string>
<string name="spatial_ready">جاهز</string>
<string name="spatial_sync_hint">في انتظار اصطفاف الكاميرا وتتبع الرأس. حافظ على النظارات الذكية متصلة.</string>
<string name="spatial_prepare">1. قم بإعداد هدف المعايرة</string>
<string name="spatial_print_actual">الطباعة بالحجم الفعلي</string>
<string name="spatial_print_hint">يجب أن يبلغ قياس كل مربع 30 مم (1.181 بوصة). قم بتركيب الورقة بشكل مسطح على سطح صلب في منطقة مضاءة جيدًا.</string>
<string name="spatial_print">طباعة لوحة المعايرة</string>
<string name="spatial_capture_group">2. التقط مناظر مختلفة</string>
<string name="spatial_board_stationary">حافظ على هدف المعايرة ثابتًا</string>
<string name="spatial_capture_hint">قم بتحريك النظارة الذكية بشكل أقرب وأبعد وعلى الجانب. اقلبهم إلى اليسار/اليمين، وزاويتهم لأعلى/لأسفل، وقم بإمالتهم جانبًا. احتفظ بهدف المعايرة في كلتا الكاميرتين وتوقف لفترة وجيزة قبل كل التقاط.</string>
<string name="spatial_start">ابدأ معايرة جديدة</string>
<string name="spatial_wait_sync">في انتظار مزامنة الكاميرا.</string>
<string name="spatial_start_hint">ابدأ معايرة جديدة لتجميع طرق العرض.</string>
<string name="spatial_views_hint">اجعل هدف المعايرة مرئيًا في كلا المعاينتين. التقط ما لا يقل عن 24 منظرًا حادًا من زوايا مختلفة.</string>
<string name="spatial_capturing">الالتقاط...</string>
<string name="spatial_capture">التقط عرض هدف المعايرة</string>
<string name="spatial_alignment">حساب محاذاة الكاميرا</string>
<string name="spatial_calculating">حساب…</string>
<string name="spatial_calculate">حساب المعايرة</string>
<string name="spatial_distance_group">3. التحقق من المسافة</string>
<string name="spatial_distance">المسافة المقاسة</string>
<string name="spatial_distance_hint">ابدأ بـ 12 بوصة أو 30 سم حتى يسهل اكتشاف العلامات المطبوعة. اختر القياس الدقيق الذي ستستخدمه.</string>
<string name="spatial_camera_board_distance">الكاميرا اليسرى لمركز هدف المعايرة</string>
<string name="spatial_checking_distance">فحص المسافة...</string>
<string name="spatial_validate">التحقق من المسافة</string>
<string name="spatial_diagnostics">التشخيص</string>
<string name="spatial_show_diagnostics">عرض التشخيص</string>
<string name="spatial_diagnostics_hint">توقيت الكاميرا وأدوات استرداد التتبع.</string>
<string name="spatial_camera_timing">توقيت الكاميرا</string>
<string name="spatial_hand_input">مدخلات من تتبع الأيدي</string>
<string name="spatial_wait_timing">انتظار توقيت الكاميرا.</string>
<string name="spatial_frames">صور الكاميرا</string>
<string name="spatial_gpu">كشف GPU للأيدي</string>
<string name="spatial_gpu_hint">المعالج CPU هو الخيار الافتراضي. استخدام GPU تجريبي وقد يؤدي إلى إعادة تشغيل SteamVR. يُعاد ضبط هذا الخيار عند إعادة تشغيل التتبع.</string>
<string name="spatial_lite">الكشف البسيط عن نماذج الأيدي</string>
<string name="spatial_lite_hint">استخدم كاشف راحة اليد Lite ونقاط مفتاح Lite الموجودة على اليدين. تحقق من دقة التصويب والقرص والاستيلاء. إعادة التعيين عند إعادة تشغيل التتبع.</string>
<string name="spatial_adaptive">تردد الإدخال التلقائي لتتبع العقارب</string>
<string name="spatial_adaptive_hint">اضبط الإدخال حسب سرعة المعالجة. إعادة التعيين عند إعادة تشغيل التتبع.</string>
<string name="spatial_half_rate">الحد من تردد الإدخال لتتبع العقارب</string>
<string name="spatial_half_rate_hint">استخدم نصف صور الكاميرا. قم بإيقاف التشغيل لمقارنة الحد الأقصى للتردد. إعادة التعيين عند إعادة تشغيل التتبع.</string>
<string name="spatial_diagnostic_capture">التقاط التشخيص</string>
<string name="spatial_diagnostic_capture_hint">احفظ زوجًا واحدًا من صور الاستريو محليًا. يتم حفظ الصور فقط عند الطلب.</string>
<string name="spatial_save_frame">حفظ صورة الكاميرا</string>
<string name="spatial_restart_tracking">إعادة تشغيل التتبع</string>
<string name="spatial_restart_hint">يُستخدم في حالة عدم استعادة التتبع في منطقة جيدة الإضاءة. تتطلب إعادة التشغيل أصل تتبع جديد.</string>
<string name="spatial_restart">إعادة التشغيل</string>
<string name="spatial_restart_title">إعادة تشغيل التتبع؟</string>
<string name="spatial_restart_confirm">ستُفقد مشاهد المعايرة غير المحفوظة. حافظ على ثبات النظارات الذكية بعد ذلك، وأعد ضبط مركز التتبع عند استعادة التتبع.</string>
<string name="spatial_views_count">%1$d / تم التقاط 24 مشاهدة</string>
<string name="spatial_capture_paused">تم إيقاف الالتقاط مؤقتًا: %1$s</string>
<string name="spatial_measure_hint">قِس %1$s. أظهر لوحة المعايرة كاملة في معاينتي الكاميرا، ثم ابقَ ثابتًا.</string>
<string name="spatial_timing_values">%1$.1f هرتز • عمر الصورة %2$.1f مللي ثانية • المعالجة %3$.1f مللي ثانية</string>
<string name="spatial_input_values">حد الإدخال %1$.1f هرتز • %2$.0f%% لصور الكاميرا. يستمر تتبع الرأس بأقصى تردد.</string>
<string name="spatial_frame_counts">تمت معالجة %1$d • تم إسقاط %2$d • تم تخطي %3$d بواسطة حد الإدخال</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck - معايرة Luma Ultra</string>
<string name="spatial_print_scale" formatted="false">الطباعة بحجم 100% / الحجم الفعلي. كل مربع: 30 ملم (1.181 بوصة).</string>
<string name="spatial_print_stationary">حافظ على هدف المعايرة مسطحًا وثابتًا؛ حرك النظارات الذكية بين اللقطات.</string>
<string name="spatial_print_check">فحص المقياس: 100 ملم (3.937 بوصة) بين علامات التجزئة</string>
<string name="spatial_distance_12in">12 بوصة (30.48 سم)</string>
<string name="spatial_distance_30cm">30 سم (11.81 بوصة)</string>
<string name="spatial_distance_20in">20 بوصة (50.8 سم)</string>
<string name="spatial_distance_50cm">50 سم (19.69 بوصة)</string>
<string name="spatial_disabled">معطل</string>
<string name="spatial_wait_stable">في انتظار التتبع المستقر</string>
<string name="spatial_disconnected">منقطع</string>
<string name="spatial_hands_off">تم إيقاف تشغيل وحدات التحكم التي يتم تشغيلها بالأيدي العارية</string>
<string name="spatial_tracking_off">تم إيقاف التتبع الموضعي أو تم فصل النظارات الذكية</string>
<string name="spatial_recenter_pending">تم طلب إعادة ضبط مركز التتبع - حافظ على ثباتك أثناء استقرار التتبع.</string>
<string name="spatial_tracking_lost">تم فقدان التتبع - واجه منطقة مضاءة جيدًا بتفاصيل مرئية</string>
<string name="spatial_origin_changed">تم تغيير نقطة الأصل للتتبع - انقر على \"إعادة ضبط مركز التتبع\"، ثم حافظ على ثباتك.</string>
<string name="spatial_active">6DoF نشط</string>
<string name="spatial_connect_hint">قم بتوصيل Luma Ultra وتمكين التتبع الموضعي.</string>
<string name="spatial_validation_session">تم التحقق من المعايرة لهذا الاتصال.</string>
<string name="spatial_validation_saved">تم التحقق من المعايرة وحفظها.</string>
<string name="spatial_wait_cameras">في انتظار كاميرات الاستريو</string>
<string name="spatial_wait_hands">انتظار تتبع الأيدي</string>
<string name="spatial_collect_instructions">أبقِ هدف المعايرة 5 × 7 ثابتًا. التقط أكثر من 24 مشاهدة أثناء تحريك النظارات الذكية وتدويرها.</string>
<string name="spatial_capture_pending">بانتظار صورة الكاميرا التالية. أبقِ النظارات ثابتة.</string>
<string name="spatial_validation_pending">جارٍ فحص صورة الكاميرا التالية. أبقِ لوحة المعايرة كاملة في مجال الرؤية ولا تتحرك.</string>
<string name="spatial_start_first">ابدأ المعايرة أولاً</string>
<string name="spatial_no_camera_frame">لم تصل أي صورة جديدة للكاميرا. تحقق من اتصال النظارات الذكية ومعاينات الكاميرا، ثم أعد المحاولة.</string>
<string name="spatial_clock_reset">إعادة ضبط ساعة الكاميرا</string>
<string name="spatial_frame_changed">تم تغيير نظام إحداثيات التتبع. تم حفظ مشاهدك لإعادة الحساب. أعد ضبط مركز التتبع، ثم ابدأ مجموعة جديدة لالتقاط المزيد من المشاهد.</string>
<string name="spatial_device_loaded">تم تحميل معايرة الجهاز</string>
<string name="spatial_base_loaded">تم تحميل معايرة قاعدة Luma Ultra</string>
<string name="spatial_calibration_needed">المعايرة مطلوبة</string>
<string name="spatial_tap_start">اضغط على بدء المعايرة الجديدة أولاً.</string>
<string name="spatial_capture_recenter_pending">مركز إعادة ضبط التتبع ينتظر استقرار التتبع. حافظ على ثباتك للحظة.</string>
<string name="spatial_capture_recenter">تم تغيير التتبع. انقر على \"إعادة ضبط مركز التتبع\" مرة واحدة، ثم ثبّت الكاميرا قبل التقاط الصورة.</string>
<string name="spatial_capture_unsynchronized">رُفض الالتقاط: لم يكن تتبع الرأس متزامنًا أو تغيّر أثناء الالتقاط. ابقَ ثابتًا وحاول مرة أخرى.</string>
<string name="spatial_align_first">احسب محاذاة الكاميرا قبل التحقق من المسافة.</string>
<string name="spatial_processing_failed">فشلت معالجة الكاميرا</string>
<string name="spatial_hand_head_changed">تم تغيير تتبع الرأس أثناء اكتشاف الأيدي</string>
<string name="spatial_hand_wait_sync">في انتظار تزامن الكاميرا والرأس</string>
<string name="spatial_no_wrists">لم يتم اكتشاف معصمين. احتفظ بمقابض وحدة التحكم Joy-Con أمام النظارات الذكية</string>
<string name="spatial_no_hands">لم يتم اكتشاف أي يد. أظهر راحتي يديك مفتوحتين أمام النظارات.</string>
<string name="spatial_one_camera_wrists">يمكن رؤية المعصمين في كاميرا واحدة فقط؛ احتفظ بمقابض وحدة التحكم الخاصة بك في العرض المشترك</string>
<string name="spatial_one_camera_hands">تظهر اليدان في كاميرا واحدة فقط؛ أبقِ راحتي يديك في مجال الرؤية المشترك.</string>
<string name="spatial_ambiguous_hands">كلتا الكاميرتين ترى الأيدي، لكن مطابقة اليسار/اليمين غامضة</string>
<string name="spatial_inconsistent_depth">تم اكتشاف اليدين، لكن قياس العمق بالكاميرتين غير متسق.</string>
<string name="spatial_rejected_wrist">تم رفض وضع المعصم عن طريق فحص الاستريو أو الحركة</string>
<string name="spatial_inconsistent_palm">تم اكتشاف نقاط بالكاميرتين، لكن شكل راحة اليد غير متسق.</string>
<string name="spatial_hands_need_calibration">المعايرة الكاملة وفحص المسافة لتمكين اليدين</string>
<string name="spatial_hands_wait_stable">جارٍ انتظار استقرار تتبع الرأس؛ أعد ضبط مركز التتبع إذا طُلب منك ذلك.</string>
<string name="spatial_old_frame">المراقبة بالكاميرات قديمة جدًا؛ في انتظار زوج جديد</string>
<string name="spatial_fit_ready">تناسب %1$.2f بكسل. قم بقياس المسافة التي حددتها من الكاميرا اليسرى إلى مركز هدف المعايرة، ثم قم بالتحقق من صحتها.</string>
<string name="spatial_opencv_error">OpenCV غير متاح: %1$s</string>
<string name="spatial_restored">تمت استعادة %1$d من المشاهدات المحفوظة. قم بحساب المعايرة لإعادة المحاولة، أو ابدأ مجموعة جديدة لالتقاط المزيد.</string>
<string name="spatial_captured">ملتقطة %1$d / 24 مشاهدة. حافظ على هدف المعايرة ثابتًا؛ حرك النظارات الذكية للعرض التالي.</string>
<string name="spatial_capture_corners">رُفض الالتقاط: تظهر %1$d / 12 زاوية مطلوبة في كلتا الكاميرتين. أظهر جزءًا أكبر من لوحة المعايرة، وابقَ ثابتًا ثم حاول مرة أخرى.</string>
<string name="spatial_validation_error">تعذر إنهاء فحص المسافة: %1$s. حاول ثانية.</string>
<string name="spatial_inference_error">تعذر اكتشاف اليدين: %1$s. غيّر خيار CPU/GPU أو أوقف التحكم باليدين ثم أعد تشغيله للمحاولة مجددًا.</string>
<string name="spatial_hand_timeout">تجاوز كشف الأيدي %1$d مللي ثانية؛ تم تجاهل الملاحظة</string>
<string name="spatial_wrists_tracked">%1$d / 2 وضعية معصم Joy-Con يتم قياسها بواسطة الاستريو</string>
<string name="spatial_palms_tracked">تتبع راحتي اليد: %1$d / 2؛ جاهزية ضم الأصابع: %2$d؛ جاهزية القبض: %3$d</string>
<string name="spatial_capture_saved">تم حفظ الالتقاط التشخيصي في %1$s</string>
<string name="spatial_hand_error">توقف تتبع الأيدي: %1$s</string>
<string name="spatial_stereo_loaded">استريو %1$d×%2$d؛ %3$s</string>
<string name="spatial_alignment_error">يختلف تتبع الرأس ولوحة المعايرة بمقدار %1$s (الحد الأقصى 5 سم / 2 بوصة). أبقِ اللوحة ثابتة وانتظر استقرار التتبع قبل بدء مجموعة جديدة. أدِر النظارات وأمِلها برفق، وتوقف قبل كل التقاط.</string>
<string name="spatial_need_views">التقط ما لا يقل عن 24 منظرًا متنوعًا لهدف المعايرة الثابت.</string>
<string name="spatial_need_rotation">قم بتدوير النظارات الذكية يمينًا ويسارًا، وإمالتها لأعلى ولأسفل؛ لا يمكن للحركة بدون دوران معايرة محاذاة الكاميرا.</string>
<string name="spatial_bad_baseline">خط الأساس غير المعقول للكاميرا</string>
<string name="spatial_need_orientations">حرك رأسك في اتجاهات أكثر تنوعًا.</string>
<string name="spatial_bad_rotation">تدوير الكاميرا للرأس غير صالح</string>
<string name="spatial_bad_alignment">محاذاة الكاميرا مع الرأس غير متناسقة.</string>
<string name="spatial_calibration_error">تعذر إنهاء المعايرة. %1$s تم حفظ مشاهداتك الملتقطة.</string>
<string name="spatial_reprojection_error">خطأ إعادة الإسقاط المعلق %1$.2f بكسل يتجاوز 2 بكسل. إعادة المعايرة.</string>
<string name="spatial_distance_corners">تم اكتشاف %1$d / 24 زاوية من نمط المعايرة في كلتا الكاميرتين. أبقِ اللوحة كاملة في مجال الرؤية ولا تتحرك. إذا كانت ظاهرة بالفعل، اختر 12 بوصة أو 30 سم وقِس هذه المسافة الأقرب قبل إعادة المحاولة.</string>
<string name="spatial_distance_depth">هدف المعايرة مرئي، ولكن لا يمكن قياس عمقه بشكل موثوق. أمسك النظارات الذكية بثبات وحافظ على هدف المعايرة مسطحًا ومضاء جيدًا، ثم أعد المحاولة.</string>
<string name="spatial_distance_invalid">قياس الكاميرا غير صالح. ابقَ ثابتًا وحاول مرة أخرى.</string>
<string name="spatial_distance_passed">تم اجتياز فحص المسافة.</string>
<string name="spatial_distance_retry">أعد فحص المسافة من الكاميرا اليسرى إلى مركز هدف المعايرة، ثم أعد المحاولة.</string>
<string name="spatial_distance_result">قياس الكاميرات %1$s؛ المسافة المختارة %2$s. %3$s</string>
<string name="spatial_wait_previews">انتظر معاينات الكاميرا قبل بدء المعايرة.</string>
<string name="spatial_saved_views_hint">يمكن إعادة حساب المشاهدات المحفوظة. ابدأ معايرة جديدة لالتقاطها في نظام إحداثيات التتبع الحالي.</string>
<string name="spatial_model_checksum_error">المجموع الاختباري لنموذج تتبع اليدين غير مطابق</string>
<string name="spatial_opencv_load_error">تعذر تحميل OpenCV</string>
</resources>
+214 -10
View File
@@ -976,7 +976,7 @@
<string name="ctrl_select_steam">Presiona Select para abrir el menú de Steam</string>
<string name="ctrl_joycon_vr">Joy-Con como controles de RV</string>
<string name="ctrl_joycon_vr_hint">Mientras SteamVR está activo, cada Joy-Con es una mano. Apúntalo al frente y mantén Home o Captura para recentrarlo.</string>
<string name="hand_camera_prompt_body">La cámara puede ver tus manos. Sin ningún control conectado, tus manos desnudas pasan a ser las manos de RV: junta el pulgar y el índice para hacer clic, cierra la mano para agarrar y, para abrir el menú, gira la palma hacia ti y junta los dedos. Con Joy-Con, los coloca donde están de verdad tus manos. Las imágenes se procesan en este dispositivo y nunca se guardan ni se envían.</string>
<string name="hand_camera_prompt_body">Sin ningún mando conectado, la cámara del teléfono rastrea tus manos en realidad virtual: pellizca con el pulgar y el índice para hacer clic, cierra la mano para agarrar y gira la palma hacia ti y pellizca para abrir el menú. Con los mandos Joy-Con emparejados, la cámara rastrea su posición mientras los mandos Joy-Con proporcionan la rotación y las entradas de los botones. Las imágenes se procesan en este dispositivo y nunca se guardan ni se envían.</string>
<string name="hand_camera_rear">Cámara trasera</string>
<string name="hand_camera_rear_hint">Recomendada. Suele ser más nítida y amplia.</string>
<string name="hand_camera_front">Cámara frontal</string>
@@ -984,7 +984,7 @@
<string name="hand_camera_allow">Usar cámara</string>
<string name="hand_camera_not_now">Ahora no</string>
<string name="hand_camera_setting">Seguimiento de manos por cámara</string>
<string name="hand_camera_setting_hint">Sin ningún control conectado, tus manos pasan a ser las manos de RV: junta los dedos para hacer clic y, con la palma hacia ti, júntalos para abrir el menú. Con Joy-Con, los coloca donde la cámara los ve. Las imágenes se quedan en este dispositivo.</string>
<string name="hand_camera_setting_hint">Usa la cámara del teléfono para gestos con las manos desnudas o para posicionar los mandos Joy-Con emparejados en realidad virtual. Los mandos Joy-Con proporcionan la rotación y las pulsaciones de los botones. Se pausa mientras las gafas inteligentes rastrean tus manos o los mandos Joy-Con. Las imágenes se procesan solo en este dispositivo.</string>
<string name="hand_camera_choice">Cámara</string>
<string name="hand_camera_access">Acceso a la cámara</string>
<string name="hand_camera_access_hint">Permite el acceso a la cámara para que el seguimiento de manos pueda empezar.</string>
@@ -2414,14 +2414,14 @@
<string name="glasses_display">Pantalla</string>
<string name="glasses_refresh">Frecuencia de actualización</string>
<string name="glasses_3d">3D (lado a lado)</string>
<string name="glasses_3d_hint">Cada ojo recibe su propia mitad de una imagen de 3840×1080. Si la imagen se pierde, mantén presionado el botón 2D/3D de las gafas.</string>
<string name="glasses_3d_hint">Cambia las gafas inteligentes a 3D lado a lado. El seguimiento de la cabeza se controla por separado.</string>
<string name="glasses_3d_failed">No se ha confirmado la señal 3D. Inténtalo de nuevo cuando vuelva la imagen 2D.</string>
<string name="glasses_no_signal">Sin imagen: las gafas están en un modo que este teléfono no puede enviar. Mantén presionado su botón 2D/3D o desconéctalas y vuelve a conectarlas.</string>
<string name="glasses_3d_refresh">Frecuencia en 3D</string>
<string name="glasses_auto3d">3D para juegos de RV</string>
<string name="glasses_auto3d_hint">Cambia a 3D mientras SteamVR está en marcha, y vuelve después.</string>
<string name="glasses_picture">Imagen 3D</string>
<string name="glasses_picture_hint">Automático: SteamVR llena la vista; los demás juegos flotan como una pantalla en 3D y se ven planos en 2D.</string>
<string name="glasses_picture">Imagen</string>
<string name="glasses_picture_hint">Muestra automáticamente contenido plano en ambos ojos y detecta SteamVR. Seleccione Juego lado a lado para contenido SBS.</string>
<string name="glasses_picture_auto">Automático</string>
<string name="glasses_picture_screen">Pantalla virtual</string>
<string name="glasses_picture_sbs">Juego lado a lado</string>
@@ -2431,20 +2431,20 @@
<string name="glasses_screen_no_output">Estos ajustes se aplican mientras Juego en las gafas está activado y el juego se muestra allí.</string>
<string name="glasses_screen_flat_hint">Automático mantiene este juego 2D a pantalla completa. Usa una pantalla virtual para ajustar su ancho, distancia y seguimiento de cabeza.</string>
<string name="glasses_screen_use">Usar pantalla virtual</string>
<string name="glasses_screen_direct_hint">SteamVR llena la vista. Ajusta su campo de visión abajo; el ancho y la distancia solo se aplican a una pantalla virtual.</string>
<string name="glasses_screen_direct_hint">SteamVR llena la vista. Ajuste su campo de visión en RV; el ancho y la distancia solo se aplican a una pantalla virtual.</string>
<string name="glasses_screen_sbs_hint">Los juegos lado a lado llenan cada ojo. El ancho y la distancia solo se aplican a una pantalla virtual.</string>
<string name="glasses_tracking_flat_hint">Automático mantiene este juego 2D fijo en la vista. Elige Pantalla virtual en la sección Pantalla para usar el seguimiento de cabeza.</string>
<string name="glasses_tracking_sbs_hint">El seguimiento de cabeza se aplica a una pantalla virtual o a un juego de SteamVR en marcha.</string>
<string name="glasses_hardware">Gafas</string>
<string name="glasses_hardware">Dispositivo</string>
<string name="glasses_brightness">Brillo</string>
<string name="glasses_volume">Volumen</string>
<string name="glasses_sunblock">Bloqueo solar</string>
<string name="glasses_sunblock_hint">Oscurece las lentes frontales.</string>
<string name="glasses_tracking">Seguimiento de cabeza</string>
<string name="glasses_tracking">Seguimiento</string>
<string name="glasses_head_tracking">Seguimiento de cabeza</string>
<string name="glasses_head_tracking_hint">Los sensores de las gafas giran la pantalla virtual y SteamVR con tu cabeza.</string>
<string name="glasses_recenter">Recentrar la vista</string>
<string name="glasses_recenter_button">Recentrar</string>
<string name="glasses_recenter_button">Volver a centrar la pantalla</string>
<string name="glasses_vr_fov">Campo de visión de SteamVR</string>
<string name="glasses_vr_fov_hint">Más bajo acerca SteamVR y llena las gafas; más alto muestra más a tu alrededor.</string>
<string name="glasses_playing">Jugando en %1$s</string>
@@ -2465,7 +2465,7 @@
<string name="glasses_calibration_reset_hint">¿Quitar la calibración guardada y restaurar los valores predeterminados del modelo? Recentra después de volver a ponerte las gafas.</string>
<string name="glasses_calibration_reset_button">Restablecer</string>
<string name="hand_camera_group">Seguimiento de manos</string>
<string name="hand_camera_prompt_title">¿Seguir tus manos con la cámara?</string>
<string name="hand_camera_prompt_title">¿Rastrear las manos o los mandos Joy-Con con la cámara del teléfono?</string>
<string name="display_picture_position">Posición de la imagen</string>
<string name="display_position_top">Arriba</string>
<string name="display_position_bottom">Abajo</string>
@@ -2657,4 +2657,208 @@
<string name="boot_movie_converting_started">Convirtiendo %1$s para Steam. Mantén DroidDeck abierto.</string>
<string name="boot_movie_unsupported">Este dispositivo no puede decodificar ese video.</string>
<string name="boot_movie_import_failed">No se pudo importar ese video</string>
<string name="glasses_experimental">Cámaras Luma Ultra — Experimental</string>
<string name="glasses_joycons">Seguimiento de posición de los Joy-Con</string>
<string name="glasses_joycons_hint">Usa las cámaras de las gafas inteligentes para posicionar tus mandos Joy-Con emparejados. La rotación y las pulsaciones de los botones provienen de los mandos Joy-Con. La posición se estima cuando tus muñecas están ocultas. Reemplaza los mandos operados con las manos desnudas y pausa el seguimiento de la cámara del teléfono. Apunta los mandos Joy-Con hacia adelante al conectar o restablecer el centro de seguimiento.</string>
<string name="glasses_joycons_requires_position">Activa primero el seguimiento de posición. Requiere mandos Joy-Con compatibles con movimiento en Android 12 o posterior. Desactivado por defecto.</string>
<string name="setup_tab_glasses">Gafas inteligentes</string>
<string name="setup_glasses_scope">La configuración de pantalla y seguimiento de cabeza se comparte entre los menús de DroidDeck, los juegos y las sesiones de escritorio.</string>
<string name="setup_glasses_connect">Conecta las gafas inteligentes para cambiar su configuración de pantalla y seguimiento. Tu configuración guardada se conserva al desconectarlas.</string>
<string name="glasses_vr">RV</string>
<string name="glasses_display_mode">Modo de visualización</string>
<string name="glasses_output_2d">Salida 2D</string>
<string name="glasses_output_3d">Salida 3D</string>
<string name="glasses_picture_group">Imagen y pantalla virtual</string>
<string name="glasses_position_group">Posición y nivel del suelo</string>
<string name="glasses_hands_group">Seguimiento de manos — Experimental</string>
<string name="glasses_tracking_status">Estado del seguimiento</string>
<string name="glasses_resolution_2d">Resolución 2D</string>
<string name="glasses_resolution_3d">Resolución 3D</string>
<string name="glasses_resolution_vr">Resolución RV</string>
<string name="glasses_resolution_auto">Automático</string>
<string name="glasses_resolution_inherit">Igual que 2D/3D</string>
<string name="glasses_resolution_hint">Resolución de salida por ojo.</string>
<string name="glasses_refresh_30_hint">Habilita el juego en gafas inteligentes para seleccionar el modo nativo de 30 Hz de la pantalla.</string>
<string name="glasses_3dof">Pantalla virtual 3DoF</string>
<string name="glasses_3dof_hint">Ancla los juegos planos frente a ti para que puedas mirar a tu alrededor. Habilita el juego en gafas inteligentes sin cambiar a 3D.</string>
<string name="glasses_screen_app_wide">El ancho y la distancia actualizan la pantalla virtual rastreada para los menús, juegos y sesiones de escritorio de DroidDeck de forma inmediata.</string>
<string name="glasses_head_tracking_spatial_vr_hint">El seguimiento posicional incluye la rotación de la cabeza en SteamVR. Para el seguimiento solo de rotación, desactive el seguimiento posicional.</string>
<string name="glasses_head_tracking_spatial_hint">Controla el movimiento de la cabeza en la pantalla virtual. El seguimiento posicional incluye la rotación de la cabeza en SteamVR.</string>
<string name="glasses_quick_actions">Acciones rápidas</string>
<string name="glasses_recenter_screen_hint">Mira hacia donde quieras que esté la pantalla y luego restablece el centro de seguimiento para ajustar su altura y dirección.</string>
<string name="glasses_recenter_joycons_button">Volver a centrar los Joy-Con</string>
<string name="phone_camera_tracking_group">Cámara del teléfono — Experimental</string>
<string name="phone_camera_tracking_setting">Rastrear manos o mandos Joy-Con</string>
<string name="glasses_6dof">Pantalla virtual 6DoF</string>
<string name="glasses_position">Seguimiento posicional — experimental</string>
<string name="glasses_position_hint">Movimiento físico en RV y alrededor de pantallas virtuales. Reiniciar el seguimiento tarda un momento.</string>
<string name="glasses_eye_height">Altura actual de los ojos sobre el suelo</string>
<string name="glasses_floor">Establecer el nivel del suelo desde la altura actual</string>
<string name="glasses_floor_set">Establecer el nivel del suelo</string>
<string name="glasses_spatial_setup">Configuración y diagnóstico de la cámara estéreo</string>
<string name="glasses_spatial_setup_hint">Comprobar la calibración de la cámara y el seguimiento de las manos antes de jugar.</string>
<string name="glasses_spatial_open">Abrir configuración</string>
<string name="glasses_hands">Mandos operados con las manos desnudas</string>
<string name="glasses_hands_hint">Usar las cámaras de las gafas inteligentes para apuntar con las manos, pellizcar para hacer clic y cerrar la mano para agarrar en RV. Los joysticks del mando se mueven y giran. Sustituye el seguimiento de posición de los Joy-Con. Puede haber retrasos o pérdida de seguimiento durante los juegos.</string>
<string name="glasses_hands_ready_hint">Habilitar el uso de mandos operados con las manos desnudas bajo las cámaras Luma Ultra en Seguimiento. Mostrar primero las manos abiertas.</string>
<string name="glasses_hands_base">Calibración compartida de Luma Ultra</string>
<string name="glasses_hands_base_hint">Usar la base compartida de Luma Ultra. Calibra tus gafas inteligentes para verificar su precisión.</string>
<string name="glasses_hands_calibration_verified">Calibración medida verificada</string>
<string name="glasses_hands_calibration_required">Validación requerida</string>
<string name="glasses_hands_calibration_required_hint">Los controladores operados con las manos desnudas estarán disponibles después de que la verificación de distancia medida sea exitosa.</string>
<string name="tracking_recovery_lost">Seguimiento perdido</string>
<string name="tracking_recovery_look">Mira a tu alrededor lentamente en una habitación bien iluminada.\nMantén las cámaras de seguimiento despejadas y enfoca los muebles u otros detalles visibles.</string>
<string name="tracking_recovery_restart_help">¿Sigues estancado? En su teléfono: gafas inteligentes → Seguimiento → Abrir configuración → Mostrar diagnóstico → Reiniciar seguimiento.</string>
<string name="tracking_recovery_disconnected">Gafas inteligentes desconectadas</string>
<string name="tracking_recovery_connect">Verifique la conexión USB y vuelva a conectar las gafas inteligentes.\nAcepte el mensaje de acceso USB en su teléfono si aparece.</string>
<string name="tracking_recovery_usb_help">Una vez conectado, mire lentamente a su alrededor en una habitación bien iluminada para restablecer el seguimiento.</string>
<string name="tracking_recovery_recenter">Es necesario volver a centrar</string>
<string name="tracking_recovery_recenter_steps">El seguimiento ha vuelto a una nueva posición.\nMira hacia adelante y luego toca \"Restablecer centro de seguimiento\" en tu teléfono.</string>
<string name="tracking_recovery_recenter_help">También puedes encontrar la opción para restablecer el centro de seguimiento en las gafas inteligentes → Seguimiento → Abrir configuración. Se conserva la calibración de la cámara guardada.</string>
<string name="tracking_recovery_stabilizing">Quédate quieto por un momento</string>
<string name="tracking_recovery_hold">El restablecimiento del centro de seguimiento está en curso.\nMantén las gafas inteligentes estables mientras se estabiliza el seguimiento.</string>
<string name="tracking_recovery_automatic">Esta pantalla se cierra automáticamente cuando el seguimiento de la cabeza está listo.</string>
<string name="glasses_steady_hand_aim">Apuntar estabilizado con las manos</string>
<string name="glasses_steady_hand_aim_hint">Apunta desde tu hombro a través de tu palma. Apague para rotación controlada por la muñeca.</string>
<string name="spatial_title">Configuración espacial</string>
<string name="spatial_wait_tracking">Esperando seguimiento…</string>
<string name="spatial_cameras_description">Cámaras de seguimiento izquierda y derecha</string>
<string name="spatial_camera_sync">Sincronización de cámaras</string>
<string name="spatial_ready">Listo</string>
<string name="spatial_sync_hint">Esperando que la cámara y el seguimiento de la cabeza se alineen. Mantenga las gafas inteligentes conectadas.</string>
<string name="spatial_prepare">1. Prepare el objetivo de calibración</string>
<string name="spatial_print_actual">Imprimir en tamaño real</string>
<string name="spatial_print_hint">Cada cuadrado debe medir 30 mm (1.181 in). Monte la hoja plana sobre una superficie rígida en un área bien iluminada.</string>
<string name="spatial_print">Imprimir patrón de calibración</string>
<string name="spatial_capture_group">2. Captura diferentes vistas</string>
<string name="spatial_board_stationary">Mantener estacionario el objetivo de calibración</string>
<string name="spatial_capture_hint">Acerque, aleje y lateralmente las gafas inteligentes. Gírelos hacia la izquierda/derecha, inclínelos hacia arriba/abajo e inclínelos hacia los lados. Mantenga el objetivo de calibración en ambas cámaras y haga una pausa breve antes de cada captura.</string>
<string name="spatial_start">Iniciar nueva calibración</string>
<string name="spatial_wait_sync">Esperando sincronización de cámaras.</string>
<string name="spatial_start_hint">Inicie una nueva calibración para recopilar vistas.</string>
<string name="spatial_views_hint">Mantenga el objetivo de calibración visible en ambas vistas previas. Capture al menos 24 vistas nítidas desde diferentes ángulos.</string>
<string name="spatial_capturing">Capturando…</string>
<string name="spatial_capture">Capturar vista del objetivo de calibración</string>
<string name="spatial_alignment">Calcular la alineación de la cámara</string>
<string name="spatial_calculating">Calculando…</string>
<string name="spatial_calculate">Calcular calibración</string>
<string name="spatial_distance_group">3. Verifique la distancia</string>
<string name="spatial_distance">Distancia medida</string>
<string name="spatial_distance_hint">Comience con 12 pulgadas o 30 cm para que los marcadores impresos sean más fáciles de detectar. Elige la medida exacta que utilizarás.</string>
<string name="spatial_camera_board_distance">Cámara izquierda al centro del objetivo de calibración</string>
<string name="spatial_checking_distance">Comprobando la distancia...</string>
<string name="spatial_validate">Validar distancia</string>
<string name="spatial_diagnostics">Diagnóstico</string>
<string name="spatial_show_diagnostics">Mostrar diagnóstico</string>
<string name="spatial_diagnostics_hint">Herramientas de recuperación de seguimiento y sincronización de cámaras.</string>
<string name="spatial_camera_timing">Sincronización de la cámara</string>
<string name="spatial_hand_input">Entrada del seguimiento de manos</string>
<string name="spatial_wait_timing">Esperando la sincronización de la cámara.</string>
<string name="spatial_frames">Imágenes de la cámara</string>
<string name="spatial_gpu">Detección de manos por GPU</string>
<string name="spatial_gpu_hint">CPU es el valor predeterminado. La GPU es experimental y puede hacer que SteamVR se reinicie. Esta opción se restablece cuando se reinicia el seguimiento.</string>
<string name="spatial_lite">Detección Lite de modelos de manos.</string>
<string name="spatial_lite_hint">Utilice el detector de palma Lite y los puntos clave Lite en las manos. Compruebe la precisión de apuntar, pellizcar y agarrar. Se reinicia cuando se reinicia el seguimiento.</string>
<string name="spatial_adaptive">Frecuencia de entrada automática para seguimiento de manos</string>
<string name="spatial_adaptive_hint">Ajusta la entrada a la velocidad de procesamiento. Se reinicia cuando se reinicia el seguimiento.</string>
<string name="spatial_half_rate">Limitar la frecuencia de entrada para el seguimiento de manos</string>
<string name="spatial_half_rate_hint">Utilice la mitad de las imágenes de la cámara. Apague para comparar la frecuencia máxima. Se reinicia cuando se reinicia el seguimiento.</string>
<string name="spatial_diagnostic_capture">Captura de diagnóstico</string>
<string name="spatial_diagnostic_capture_hint">Guarde un par de imágenes estéreo localmente. Las imágenes se guardan sólo cuando se solicitan.</string>
<string name="spatial_save_frame">Guardar imagen de la cámara</string>
<string name="spatial_restart_tracking">Reiniciar seguimiento</string>
<string name="spatial_restart_hint">Úselo si el seguimiento no se recupera en un área bien iluminada. Un reinicio requiere un nuevo origen de seguimiento.</string>
<string name="spatial_restart">Reiniciar</string>
<string name="spatial_restart_title">¿Reiniciar el seguimiento?</string>
<string name="spatial_restart_confirm">Se perderán las vistas de calibración no guardadas. Mantén las gafas inteligentes quietas y restablece el centro de seguimiento cuando se restablezca el seguimiento.</string>
<string name="spatial_views_count">%1$d / 24 vistas capturadas</string>
<string name="spatial_capture_paused">Captura en pausa: %1$s</string>
<string name="spatial_measure_hint">Medida %1$s. Muestre el objetivo de calibración completo en ambas vistas previas y luego quédese quieto.</string>
<string name="spatial_timing_values">%1$.1f Hz • antigüedad de la imagen %2$.1f ms • procesamiento %3$.1f ms</string>
<string name="spatial_input_values">Límite de entrada %1$.1f Hz • %2$.0f%% de imágenes de la cámara. El seguimiento de la cabeza continúa con su frecuencia máxima.</string>
<string name="spatial_frame_counts">%1$d procesado • %2$d descartado • %3$d omitido por límite de entrada</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck - Calibración de Luma Ultra</string>
<string name="spatial_print_scale" formatted="false">Imprimir a 100% / Tamaño real. Cada cuadrado: 30 mm (1,181 pulgadas).</string>
<string name="spatial_print_stationary">Mantenga el objetivo de calibración plano y estacionario; mueve las gafas inteligentes entre capturas.</string>
<string name="spatial_print_check">Verificación de escala: 100 mm (3,937 in) entre marcas</string>
<string name="spatial_distance_12in">12 pulgadas (30,48 cm)</string>
<string name="spatial_distance_30cm">30 cm (11,81 pulgadas)</string>
<string name="spatial_distance_20in">20 pulgadas (50,8 cm)</string>
<string name="spatial_distance_50cm">50 cm (19,69 pulgadas)</string>
<string name="spatial_disabled">Deshabilitado</string>
<string name="spatial_wait_stable">Esperando seguimiento estable</string>
<string name="spatial_disconnected">Desconectado</string>
<string name="spatial_hands_off">Los controladores operados con las manos desnudas están apagados</string>
<string name="spatial_tracking_off">El seguimiento posicional está desactivado o las gafas inteligentes están desconectadas</string>
<string name="spatial_recenter_pending">Se ha solicitado el restablecimiento del centro de seguimiento; mantén las gafas quietas mientras se estabiliza el seguimiento.</string>
<string name="spatial_tracking_lost">Seguimiento perdido: frente a un área bien iluminada con detalles visibles</string>
<string name="spatial_origin_changed">El origen del seguimiento ha cambiado; pulsa el botón para restablecer el centro de seguimiento y mantén las gafas quietas.</string>
<string name="spatial_active">6DoF activo</string>
<string name="spatial_connect_hint">Conecte Luma Ultra y habilite el seguimiento posicional.</string>
<string name="spatial_validation_session">Calibración verificada para esta conexión.</string>
<string name="spatial_validation_saved">Calibración verificada y guardada.</string>
<string name="spatial_wait_cameras">Esperando cámaras estéreo</string>
<string name="spatial_wait_hands">Esperando seguimiento de manos</string>
<string name="spatial_collect_instructions">Mantenga estacionario el objetivo de calibración 5 × 7. Capture más de 24 vistas mientras mueve y gira las gafas inteligentes.</string>
<string name="spatial_capture_pending">Esperando la siguiente imagen de la cámara. Mantenga quietas las gafas inteligentes.</string>
<string name="spatial_validation_pending">Comprobación de la siguiente imagen de la cámara. Mantenga visible el objetivo de calibración completo y manténgalo quieto.</string>
<string name="spatial_start_first">Iniciar la calibración primero</string>
<string name="spatial_no_camera_frame">No llegó ninguna imagen nueva de la cámara. Verifique la conexión de las gafas inteligentes y las vistas previas de la cámara, luego vuelva a intentarlo.</string>
<string name="spatial_clock_reset">Reinicio del reloj de la cámara</string>
<string name="spatial_frame_changed">El sistema de coordenadas de seguimiento ha cambiado. Tus vistas se guardan para su recálculo. Restablece el centro de seguimiento y, a continuación, inicia una nueva configuración para capturar más vistas.</string>
<string name="spatial_device_loaded">calibración del dispositivo cargada</string>
<string name="spatial_base_loaded">Calibración de base Luma Ultra cargada</string>
<string name="spatial_calibration_needed">calibración requerida</string>
<string name="spatial_tap_start">Toque Iniciar nueva calibración primero.</string>
<string name="spatial_capture_recenter_pending">El restablecimiento del centro de seguimiento está esperando un seguimiento estable. Mantén las gafas quietas un momento.</string>
<string name="spatial_capture_recenter">El seguimiento ha cambiado. Pulsa el botón para restablecer el centro de seguimiento una vez y, a continuación, mantén las gafas quietas antes de capturar.</string>
<string name="spatial_capture_unsynchronized">Captura rechazada: el seguimiento de la cabeza no se sincronizó o cambió durante la captura. Quédate quieto y vuelve a intentarlo.</string>
<string name="spatial_align_first">Calcule la alineación de la cámara antes de verificar la distancia.</string>
<string name="spatial_processing_failed">el procesamiento de la cámara falló</string>
<string name="spatial_hand_head_changed">El seguimiento de la cabeza cambió durante la detección de manos</string>
<string name="spatial_hand_wait_sync">Esperando la sincronización de la cámara y el cabezal</string>
<string name="spatial_no_wrists">No se detectaron muñecas. Mantenga las manijas del controlador Joy-Con frente a las gafas inteligentes</string>
<string name="spatial_no_hands">No se detectaron manos. Muestre las palmas abiertas frente a las gafas inteligentes.</string>
<string name="spatial_one_camera_wrists">Muñecas visibles en una sola cámara; mantenga los identificadores de su controlador en la vista compartida</string>
<string name="spatial_one_camera_hands">Manos visibles en una sola cámara; mantenga ambas palmas en la vista compartida</string>
<string name="spatial_ambiguous_hands">Ambas cámaras ven manos, pero la coincidencia izquierda/derecha es ambigua</string>
<string name="spatial_inconsistent_depth">Se detectaron manos, pero la profundidad estéreo no es consistente</string>
<string name="spatial_rejected_wrist">Posición de la muñeca rechazada por controles estéreo o de movimiento</string>
<string name="spatial_inconsistent_palm">Se detectaron puntos estéreo, pero la geometría de la palma es inconsistente</string>
<string name="spatial_hands_need_calibration">Calibración completa y verificación de distancia para permitir que las manos</string>
<string name="spatial_hands_wait_stable">Esperando un seguimiento de cabeza estable; restablece el centro de seguimiento si se solicita.</string>
<string name="spatial_old_frame">La observación con cámara es demasiado antigua; esperando un par nuevo</string>
<string name="spatial_fit_ready">Ajustar %1$.2f px. Mida la distancia seleccionada desde la cámara izquierda hasta el centro del objetivo de calibración y luego valide.</string>
<string name="spatial_opencv_error">OpenCV no disponible: %1$s</string>
<string name="spatial_restored">%1$d vistas guardadas restauradas. Calcule la calibración para volver a intentarlo o comience un nuevo conjunto para capturar más.</string>
<string name="spatial_captured">Capturado %1$d / 24 vistas. Mantenga fijo el objetivo de calibración; Mueva las gafas inteligentes para la siguiente vista.</string>
<string name="spatial_capture_corners">Captura rechazada: %1$d / 12 esquinas requeridas visibles en ambas cámaras. Muestre más del objetivo de calibración, quédese quieto y vuelva a intentarlo.</string>
<string name="spatial_validation_error">La verificación de distancia no pudo finalizar: %1$s. Intentar otra vez.</string>
<string name="spatial_inference_error">Detección de manos no disponible: %1$s. Cambie la CPU/GPU o cambie los controladores operados con las manos desnudas para volver a intentarlo.</string>
<string name="spatial_hand_timeout">La detección de manos superó los %1$d ms; observación descartada</string>
<string name="spatial_wrists_tracked">%1$d / 2 posiciones de muñeca Joy-Con medidas por estéreo</string>
<string name="spatial_palms_tracked">Palmas seguidas: %1$d / 2; gesto de pinza listo: %2$d; agarre listo: %3$d</string>
<string name="spatial_capture_saved">Captura de diagnóstico guardada en %1$s</string>
<string name="spatial_hand_error">Seguimiento de manos detenido: %1$s</string>
<string name="spatial_stereo_loaded">Estéreo %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">El seguimiento de la cabeza y el objetivo de calibración difieren en %1$s (límite de 5 cm/2 pulgadas). Mantenga fijo el objetivo de calibración y espere un seguimiento constante antes de comenzar una nueva serie. Gire e incline suavemente las gafas inteligentes; pausa antes de cada captura.</string>
<string name="spatial_need_views">Capture al menos 24 vistas variadas del objetivo de calibración estacionario.</string>
<string name="spatial_need_rotation">Gire las gafas inteligentes hacia la izquierda y hacia la derecha, e inclínelas hacia arriba y hacia abajo; el movimiento sin rotación no puede calibrar la alineación de la cámara.</string>
<string name="spatial_bad_baseline">Línea de base de cámara inverosímil</string>
<string name="spatial_need_orientations">Mueve la cabeza en orientaciones más variadas.</string>
<string name="spatial_bad_rotation">Rotación de cámara a cabezal no válida</string>
<string name="spatial_bad_alignment">La alineación entre la cámara y el cabezal es inconsistente.</string>
<string name="spatial_calibration_error">La calibración no pudo finalizar. %1$s Tus vistas capturadas se guardan.</string>
<string name="spatial_reprojection_error">El error de reproyección retenida %1$.2f px supera los 2 px. Recalibrar.</string>
<string name="spatial_distance_corners">%1$d / 24 esquinas de tablero de ajedrez detectadas en ambas cámaras. Mantenga visible el objetivo de calibración completo y manténgalo quieto. Si ya es visible, seleccione 12 pulgadas o 30 cm y mida esa distancia más cercana antes de volver a intentarlo.</string>
<string name="spatial_distance_depth">El objetivo de calibración es visible, pero su profundidad no se pudo medir de manera confiable. Mantenga las gafas inteligentes quietas y mantenga el objetivo de calibración plano y bien iluminado, luego vuelva a intentarlo.</string>
<string name="spatial_distance_invalid">La ​​medición de la cámara no fue válida. Quédate quieto y vuelve a intentarlo.</string>
<string name="spatial_distance_passed">Se pasó la verificación de distancia.</string>
<string name="spatial_distance_retry">Vuelva a verificar la distancia desde la cámara izquierda al centro del objetivo de calibración y luego vuelva a intentarlo.</string>
<string name="spatial_distance_result">Las cámaras midieron %1$s; distancia seleccionada %2$s. %3$s</string>
<string name="spatial_wait_previews">Espere las vistas previas de ambas cámaras antes de comenzar la calibración.</string>
<string name="spatial_saved_views_hint">Las vistas guardadas se pueden recalcular. Inicie una nueva calibración para capturar en el sistema de coordenadas de seguimiento actual.</string>
<string name="spatial_model_checksum_error">La suma de comprobación del modelo de mano no coincide</string>
<string name="spatial_opencv_load_error">OpenCV no se pudo cargar</string>
</resources>
+214 -10
View File
@@ -2366,14 +2366,14 @@
<string name="glasses_display">Екран</string>
<string name="glasses_refresh">Честота на опресняване</string>
<string name="glasses_3d">3D (един до друг)</string>
<string name="glasses_3d_hint">Всяко око получава своята половина от картина 3840×1080. Ако картината изчезне, задръжте бутона 2D/3D на очилата.</string>
<string name="glasses_3d_hint">Превключете смарт очилата на 3D едно до друго. Проследяването на главата се контролира отделно.</string>
<string name="glasses_3d_failed">3D сигналът не беше потвърден. Опитайте отново, когато 2D картината се върне.</string>
<string name="glasses_no_signal">Няма картина: очилата са в режим, който този телефон не може да изпрати. Задръжте бутона им 2D/3D или ги изключете и включете отново.</string>
<string name="glasses_3d_refresh">Честота на опресняване в 3D</string>
<string name="glasses_auto3d">3D за VR игри</string>
<string name="glasses_auto3d_hint">Превключва на 3D, докато SteamVR работи, и после обратно.</string>
<string name="glasses_picture">3D картина</string>
<string name="glasses_picture_hint">Автоматично: SteamVR запълва изгледа; другите игри висят като екран в 3D и остават плоски в 2D.</string>
<string name="glasses_picture">Картина</string>
<string name="glasses_picture_hint">Автоматично показва плоско съдържание и в двете очи и разпознава SteamVR. Изберете „Игра отстрани“ за SBS съдържание.</string>
<string name="glasses_picture_auto">Автоматично</string>
<string name="glasses_picture_screen">Виртуален екран</string>
<string name="glasses_picture_sbs">Игра 3D един до друг</string>
@@ -2383,20 +2383,20 @@
<string name="glasses_screen_no_output">Тези настройки се прилагат, докато опцията „Играта на очилата“ е включена и играта се показва там.</string>
<string name="glasses_screen_flat_hint">„Автоматично“ запазва тази 2D игра на цял екран. Използвайте виртуален екран, за да настроите ширината, разстоянието и проследяването на главата.</string>
<string name="glasses_screen_use">Използване на виртуален екран</string>
<string name="glasses_screen_direct_hint">SteamVR запълва изгледа. Настройте зрителното му поле по-долу; ширината и разстоянието се отнасят само за виртуален екран.</string>
<string name="glasses_screen_direct_hint">SteamVR запълва изгледа. Регулирайте зрителното си поле във VR; ширината и разстоянието се отнасят само за виртуален екран.</string>
<string name="glasses_screen_sbs_hint">Игрите 3D един до друг запълват всяко око. Ширината и разстоянието се отнасят само за виртуален екран.</string>
<string name="glasses_tracking_flat_hint">„Автоматично“ държи тази 2D игра неподвижна в изгледа. Изберете „Виртуален екран“ в „Екран“, за да използвате проследяване на главата.</string>
<string name="glasses_tracking_sbs_hint">Проследяването на главата се отнася за виртуален екран или за работеща игра в SteamVR.</string>
<string name="glasses_hardware">Очила</string>
<string name="glasses_hardware">Устройство</string>
<string name="glasses_brightness">Яркост</string>
<string name="glasses_volume">Сила на звука</string>
<string name="glasses_sunblock">Затъмняване</string>
<string name="glasses_sunblock_hint">Затъмнява предните стъкла.</string>
<string name="glasses_tracking">Проследяване на главата</string>
<string name="glasses_tracking">Проследяване</string>
<string name="glasses_head_tracking">Проследяване на главата</string>
<string name="glasses_head_tracking_hint">Собствените сензори на очилата завъртат виртуалния екран и SteamVR заедно с главата ви.</string>
<string name="glasses_recenter">Центриране на изгледа</string>
<string name="glasses_recenter_button">Центриране</string>
<string name="glasses_recenter_button">Центриране на екрана</string>
<string name="glasses_vr_fov">Зрително поле на SteamVR</string>
<string name="glasses_vr_fov_hint">По-ниско приближава SteamVR и запълва очилата; по-високо показва повече около вас.</string>
<string name="glasses_playing">Играете на %1$s</string>
@@ -2417,8 +2417,8 @@
<string name="glasses_calibration_reset_hint">Да се премахне ли запазеното калибриране и да се възстановят стойностите по подразбиране за модела? Центрирайте изгледа, след като сложите очилата отново.</string>
<string name="glasses_calibration_reset_button">Нулиране</string>
<string name="hand_camera_group">Проследяване на ръцете</string>
<string name="hand_camera_prompt_title">Да се проследяват ли ръцете ви с камерата?</string>
<string name="hand_camera_prompt_body">Камерата вижда ръцете ви. Когато няма свързан контролер, голите ви ръце стават VR ръцете: съберете палеца и показалеца, за да щракнете, свийте ръка, за да хванете, а за менюто обърнете дланта към себе си и съберете пръсти. С Joy-Con камерата ги поставя там, където са ръцете ви. Изображенията се обработват на това устройство и никога не се запазват или изпращат.</string>
<string name="hand_camera_prompt_title">Проследяване на ръце или контролери Joy-Con с камерата на телефона?</string>
<string name="hand_camera_prompt_body">Без свързан контролер, камерата на телефона проследява вашите голи ръце във VR: щипнете палеца и показалеца си, за да щракнете, затворете ръката си, за да хванете, и обърнете дланта си към себе си и щипнете, за да отворите менюто. С двойни контролери Joy-Con, камерата проследява позицията им, докато контролерите Joy-Con осигуряват въртене и натискане на бутони. Снимките се обработват на това устройство и никога не се запазват или изпращат.</string>
<string name="hand_camera_rear">Задна камера</string>
<string name="hand_camera_rear_hint">Препоръчително. Обикновено по-рязка и по-широкоъгълна.</string>
<string name="hand_camera_front">Предна камера</string>
@@ -2426,7 +2426,7 @@
<string name="hand_camera_allow">Използване на камерата</string>
<string name="hand_camera_not_now">Не сега</string>
<string name="hand_camera_setting">Проследяване на ръцете с камерата</string>
<string name="hand_camera_setting_hint">Когато няма свързан контролер, ръцете ви стават VR ръцете: съберете пръсти, за да щракнете, а за менюто обърнете дланта към себе си и съберете пръсти. С Joy-Con ги поставя там, където ги вижда камерата. Изображенията остават на това устройство.</string>
<string name="hand_camera_setting_hint">Използвайте камерата на телефона за жестове с голи ръце или за позициониране на сдвоени контролери Joy-Con във VR. Контролерите Joy-Con осигуряват въртене и натискане на бутони. Поставя на пауза, докато интелигентните очила проследяват ръцете ви или контролерите Joy-Con. Изображенията се обработват само на това устройство.</string>
<string name="hand_camera_choice">Камера</string>
<string name="hand_camera_access">Достъп до камерата</string>
<string name="hand_camera_access_hint">Разрешете достъп до камерата, за да започне проследяването на ръцете.</string>
@@ -2680,4 +2680,208 @@
<string name="boot_movie_converting_started">%1$s се конвертира за Steam. Не затваряйте DroidDeck.</string>
<string name="boot_movie_unsupported">Устройството не може да декодира това видео.</string>
<string name="boot_movie_import_failed">Видеото не можа да бъде импортирано</string>
<string name="glasses_experimental">Luma Ultra камери — Експериментално</string>
<string name="glasses_joycons">Проследяване на позицията на Joy-Con</string>
<string name="glasses_joycons_hint">Използвайте камерите на интелигентните очила, за да позиционирате сдвоените си контролери Joy-Con. Въртенето и натискането на бутоните идват от контролерите Joy-Con. Позицията се оценява, когато китките ви са скрити. Заменя контролерите, управлявани с голи ръце, и поставя на пауза проследяването с камерата на телефона. Насочете контролерите Joy-Con напред, когато свързвате или нулирате центъра за проследяване.</string>
<string name="glasses_joycons_requires_position">Първо активирайте проследяването на позицията. Изисква Joy-Con контролери с възможност за движение на Android 12 или по-нова версия. Изключено по подразбиране.</string>
<string name="setup_tab_glasses">Интелигентни очила</string>
<string name="setup_glasses_scope">Настройките за дисплей и проследяване на главата се споделят в менютата, игрите и настолните сесии на DroidDeck.</string>
<string name="setup_glasses_connect">Свържете интелигентните очила, за да промените настройките им за дисплей и проследяване. Запазените ви настройки се запазват, когато изключите връзката.</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">Режим на показване</string>
<string name="glasses_output_2d">2D изход</string>
<string name="glasses_output_3d">3D изход</string>
<string name="glasses_picture_group">Картина и виртуален екран</string>
<string name="glasses_position_group">Позиция и ниво на пода</string>
<string name="glasses_hands_group">Проследяване на ръцете — Експериментално</string>
<string name="glasses_tracking_status">Състояние на проследяване</string>
<string name="glasses_resolution_2d">2D резолюция</string>
<string name="glasses_resolution_3d">3D резолюция</string>
<string name="glasses_resolution_vr">VR резолюция</string>
<string name="glasses_resolution_auto">Автоматично</string>
<string name="glasses_resolution_inherit">Същото като 2D / 3D</string>
<string name="glasses_resolution_hint">Изходна резолюция за всяко око.</string>
<string name="glasses_refresh_30_hint">Активирайте „Игра на смарт очила“, за да изберете оригиналния 30 Hz режим на дисплея.</string>
<string name="glasses_3dof">Виртуален екран с 3DoF</string>
<string name="glasses_3dof_hint">Закрепете плоските игри пред себе си, за да можете да се оглеждате. Активира „Игра на смарт очила“, без да превключвате на 3D.</string>
<string name="glasses_screen_app_wide">Ширината и разстоянието актуализират незабавно проследения виртуален екран за менюта, игри и десктоп сесии на DroidDeck.</string>
<string name="glasses_head_tracking_spatial_vr_hint">Позиционното проследяване включва въртене на главата в SteamVR. За проследяване само с въртене, изключете позиционното проследяване.</string>
<string name="glasses_head_tracking_spatial_hint">Контролира движението на главата на виртуалния екран. Позиционното проследяване включва въртене на главата в SteamVR.</string>
<string name="glasses_quick_actions">Бързи действия</string>
<string name="glasses_recenter_screen_hint">Погледнете къде искате екранът, след което нулирайте центъра за проследяване, за да зададете неговата височина и посока.</string>
<string name="glasses_recenter_joycons_button">Центриране на Joy-Con</string>
<string name="phone_camera_tracking_group">Камера на телефона — Експериментално</string>
<string name="phone_camera_tracking_setting">Проследяване на ръце или контролери Joy-Con</string>
<string name="glasses_6dof">Виртуален екран с 6DoF</string>
<string name="glasses_position">Проследяване на позицията — експериментално</string>
<string name="glasses_position_hint">Физическо движение във VR и около виртуални екрани. Рестартирането на проследяването отнема малко време.</string>
<string name="glasses_eye_height">Текуща височина на очите над пода</string>
<string name="glasses_floor">Задаване на нивото на пода от текущата височина</string>
<string name="glasses_floor_set">Задаване на нивото на пода</string>
<string name="glasses_spatial_setup">Настройка и диагностика на стерео камерата</string>
<string name="glasses_spatial_setup_hint">Проверете калибрирането на камерата и проследяването на ръцете преди игра.</string>
<string name="glasses_spatial_open">Отваряне на настройката</string>
<string name="glasses_hands">Контролери, управлявани с голи ръце</string>
<string name="glasses_hands_hint">Използвайте камерите на интелигентните очила, за да се прицелвате с ръце, щипнете, за да щракнете, и затворете ръката си, за да хванете във VR. Геймпадовете се движат и завъртат. Заменя проследяването на позицията на Joy-Con. Може да забави или да загуби проследяване по време на игри.</string>
<string name="glasses_hands_ready_hint">Активирайте контролери, управлявани с голи ръце под камерите на Luma Ultra в Проследяване. Първо покажете отворени ръце.</string>
<string name="glasses_hands_base">Споделено калибриране на Luma Ultra</string>
<string name="glasses_hands_base_hint">Използване на споделената основа Luma Ultra. Калибрирайте собствените си интелигентни очила, за да проверите тяхната точност.</string>
<string name="glasses_hands_calibration_verified">Измереното калибриране е потвърдено</string>
<string name="glasses_hands_calibration_required">Изисква се валидиране</string>
<string name="glasses_hands_calibration_required_hint">Контролери, управлявани с голи ръце, стават достъпни след преминаване на проверката на измереното разстояние.</string>
<string name="tracking_recovery_lost">Загуба на проследяване</string>
<string name="tracking_recovery_look">Огледайте се бавно в добре осветена стая.\nДръжте камерите за проследяване чисти и ги насочвайте към мебели или други видими детайли.</string>
<string name="tracking_recovery_restart_help">Все още сте заседнали? На вашия телефон: смарт очила → Проследяване → Отворете настройката → Показване на диагностика → Рестартиране на проследяването.</string>
<string name="tracking_recovery_disconnected">Интелигентните очила са прекъснати</string>
<string name="tracking_recovery_connect">Проверете USB връзката и свържете отново смарт очилата.\nПриемете подканата за USB достъп на телефона си, ако се появи.</string>
<string name="tracking_recovery_usb_help">След като се свържете, огледайте се бавно в добре осветена стая, за да възстановите проследяването.</string>
<string name="tracking_recovery_recenter">Необходимо е повторно центриране</string>
<string name="tracking_recovery_recenter_steps">Проследяването се е върнало в нова позиция.\nПогледнете напред, след което докоснете „Нулиране на центъра за проследяване“ на телефона си.</string>
<string name="tracking_recovery_recenter_help">Можете също да намерите „Нулиране на центъра за проследяване“ в „Смарт очила“ → „Проследяване“ → „Отваряне на настройката“. Запазената ви калибрация на камерата се запазва.</string>
<string name="tracking_recovery_stabilizing">Задръжте неподвижно за момент</string>
<string name="tracking_recovery_hold">В процес е нулиране на центъра за проследяване.\nДръжте смарт очилата стабилно, докато проследяването се стабилизира.</string>
<string name="tracking_recovery_automatic">Този екран се затваря автоматично, когато проследяването на главата е готово.</string>
<string name="glasses_steady_hand_aim">Стабилизирано прицелване с ръце</string>
<string name="glasses_steady_hand_aim_hint">Прицелете се от рамото през дланта. Изключете за въртене, контролирано от китката.</string>
<string name="spatial_title">Пространствена настройка</string>
<string name="spatial_wait_tracking">Изчаква се проследяване...</string>
<string name="spatial_cameras_description">Лява и дясна камери за проследяване</string>
<string name="spatial_camera_sync">Синхронизация на камерата</string>
<string name="spatial_ready">Готово</string>
<string name="spatial_sync_hint">Изчакване камерата и проследяването на главата да се подредят. Поддържайте смарт очилата свързани.</string>
<string name="spatial_prepare">1. Подгответе мишената за калибриране</string>
<string name="spatial_print_actual">Отпечатайте в действителен размер</string>
<string name="spatial_print_hint">Всеки квадрат трябва да е с размери 30 mm (1,181 in). Монтирайте листа плоско върху твърда повърхност в добре осветено място.</string>
<string name="spatial_print">Отпечатване на калибрационната мишена</string>
<string name="spatial_capture_group">2. Уловете различни изгледи</string>
<string name="spatial_board_stationary">Дръжте мишената за калибриране неподвижна</string>
<string name="spatial_capture_hint">Преместете интелигентните очила по-близо, по-далеч и настрани. Завъртете ги наляво/надясно, наклонете ги нагоре/надолу и ги наклонете настрани. Задръжте целта за калибриране в двете камери и правете пауза за кратко преди всяко заснемане.</string>
<string name="spatial_start">Стартиране на ново калибриране</string>
<string name="spatial_wait_sync">Изчакване за синхронизиране на камерата.</string>
<string name="spatial_start_hint">Стартирайте ново калибриране за събиране на изгледи.</string>
<string name="spatial_views_hint">Поддържайте целта за калибриране видима и в двата прегледа. Уловете най-малко 24 ясни гледки от различни ъгли.</string>
<string name="spatial_capturing">Заснема се...</string>
<string name="spatial_capture">Заснемане на целевия изглед за калибриране</string>
<string name="spatial_alignment">Изчислете подравняването на камерата</string>
<string name="spatial_calculating">Изчислява се...</string>
<string name="spatial_calculate">Изчисляване на калибриране</string>
<string name="spatial_distance_group">3. Проверете разстоянието</string>
<string name="spatial_distance">Измерено разстояние</string>
<string name="spatial_distance_hint">Започнете с 12 инча или 30 см, така че отпечатаните маркери да се откриват по-лесно. Изберете точното измерване, което ще използвате.</string>
<string name="spatial_camera_board_distance">Лява камера към центъра на целта за калибриране</string>
<string name="spatial_checking_distance">Проверка на разстоянието...</string>
<string name="spatial_validate">Проверка на разстоянието</string>
<string name="spatial_diagnostics">Диагностика</string>
<string name="spatial_show_diagnostics">Показване на диагностика</string>
<string name="spatial_diagnostics_hint">Инструменти за възстановяване на времето и проследяването на камерата.</string>
<string name="spatial_camera_timing">Време на камерата</string>
<string name="spatial_hand_input">Входящи данни от проследяване на ръце</string>
<string name="spatial_wait_timing">Изчакване на времето на камерата.</string>
<string name="spatial_frames">Изображения от камера</string>
<string name="spatial_gpu">GPU разпознаване на ръце</string>
<string name="spatial_gpu_hint">По подразбиране се използва CPU. GPU е експериментален и може да предизвика рестартиране на SteamVR. Изборът се нулира при рестартиране на проследяването.</string>
<string name="spatial_lite">Леко откриване на модели на ръце</string>
<string name="spatial_lite_hint">Използвайте Lite детектор за длан и Lite ключови точки на ръцете. Проверете точността на прицелване, прищипване и хващане. Нулира се при рестартиране на проследяването.</string>
<string name="spatial_adaptive">Автоматична входна честота за проследяване на ръцете</string>
<string name="spatial_adaptive_hint">Коригиране на входа към скоростта на обработка. Нулира се при рестартиране на проследяването.</string>
<string name="spatial_half_rate">Ограничаване на входната честота за проследяване на ръцете</string>
<string name="spatial_half_rate_hint">Използвайте половината изображения от камерата. Изключете, за да сравните максималната честота. Нулира се при рестартиране на проследяването.</string>
<string name="spatial_diagnostic_capture">Диагностично улавяне</string>
<string name="spatial_diagnostic_capture_hint">Запазете една двойка стерео изображения локално. Изображенията се запазват само при поискване.</string>
<string name="spatial_save_frame">Запазване на изображението от камерата</string>
<string name="spatial_restart_tracking">Рестартиране на проследяването</string>
<string name="spatial_restart_hint">Използвайте, ако проследяването не се възстанови в добре осветена зона. Рестартирането изисква нов произход за проследяване.</string>
<string name="spatial_restart">Рестартиране</string>
<string name="spatial_restart_title">Рестартиране на проследяването?</string>
<string name="spatial_restart_confirm">Незапазените изгледи за калибриране ще бъдат загубени. Дръжте смарт очилата неподвижно след това и нулирайте центъра за проследяване, когато проследяването се възстанови.</string>
<string name="spatial_views_count">%1$d / 24 заснети изгледа</string>
<string name="spatial_capture_paused">Заснемането е на пауза: %1$s</string>
<string name="spatial_measure_hint">Мярка %1$s. Покажете пълната цел за калибриране и в двете визуализации, след което задръжте неподвижно.</string>
<string name="spatial_timing_values">%1$.1f Hz • възраст на изображението %2$.1f ms • обработка %3$.1f ms</string>
<string name="spatial_input_values">Входящо ограничение %1$.1f Hz • %2$.0f%% на изображения от камерата. Проследяването на главата продължава на максималната си честота.</string>
<string name="spatial_frame_counts">%1$d обработено • %2$d изпуснато • %3$d пропуснато от въведено ограничение</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck – калибриране на Luma Ultra</string>
<string name="spatial_print_scale" formatted="false">Печат при 100%/действителен размер. Всеки квадрат: 30 мм (1,181 инча).</string>
<string name="spatial_print_stationary">Дръжте мишената за калибриране плоска и неподвижна; преместете интелигентните очила между заснеманията.</string>
<string name="spatial_print_check">Проверка на мащаба: 100 mm (3,937 инча) между отметките</string>
<string name="spatial_distance_12in">12 инча (30,48 см)</string>
<string name="spatial_distance_30cm">30 см (11,81 инча)</string>
<string name="spatial_distance_20in">20 инча (50,8 см)</string>
<string name="spatial_distance_50cm">50 см (19,69 инча)</string>
<string name="spatial_disabled">Дезактивирано</string>
<string name="spatial_wait_stable">Изчакване на стабилно проследяване</string>
<string name="spatial_disconnected">Прекъсната връзка</string>
<string name="spatial_hands_off">Контролерите, работещи с голи ръце, са изключени</string>
<string name="spatial_tracking_off">Позиционното проследяване е изключено или смарт очилата са изключени</string>
<string name="spatial_recenter_pending">Заявено е нулиране на центъра за проследяване — задръжте неподвижно, докато проследяването се стабилизира.</string>
<string name="spatial_tracking_lost">Проследяването е загубено — лице в добре осветена зона с видими детайли</string>
<string name="spatial_origin_changed">Произходът на проследяване е променен — докоснете „Нулиране на центъра за проследяване“, след което задръжте неподвижно.</string>
<string name="spatial_active">6DoF активен</string>
<string name="spatial_connect_hint">Свържете Luma Ultra и активирайте позиционното проследяване.</string>
<string name="spatial_validation_session">Калибрирането е потвърдено за тази връзка.</string>
<string name="spatial_validation_saved">Калибрирането е потвърдено и запазено.</string>
<string name="spatial_wait_cameras">Изчакване на стерео камери</string>
<string name="spatial_wait_hands">Изчакване за проследяване на ръцете</string>
<string name="spatial_collect_instructions">Дръжте мишената за калибриране 5 × 7 неподвижна. Уловете 24+ изгледа, докато местите и въртите смарт очилата.</string>
<string name="spatial_capture_pending">Изчакване на следващото изображение от камерата. Дръжте умните очила неподвижно.</string>
<string name="spatial_validation_pending">Проверка на следващото изображение от камерата. Дръжте целта за пълно калибриране видима и задръжте неподвижно.</string>
<string name="spatial_start_first">Първо започнете калибриране</string>
<string name="spatial_no_camera_frame">Не е пристигнало ново изображение от камерата. Проверете връзката на интелигентните очила и прегледите на камерата, след което опитайте отново.</string>
<string name="spatial_clock_reset">Нулиране на часовника на фотоапарата</string>
<string name="spatial_frame_changed">Координатната система за проследяване е променена. Вашите изгледи са запазени за преизчисляване. Нулирайте центъра за проследяване, след което започнете нов набор, за да заснемете още изгледи.</string>
<string name="spatial_device_loaded">калибрирането на устройството е заредено</string>
<string name="spatial_base_loaded">Заредено е базовото калибриране на Luma Ultra</string>
<string name="spatial_calibration_needed">изисква се калибриране</string>
<string name="spatial_tap_start">Докоснете Първо стартиране на ново калибриране.</string>
<string name="spatial_capture_recenter_pending">„Нулиране на центъра за проследяване“ чака стабилно проследяване. Задръжте неподвижно за момент.</string>
<string name="spatial_capture_recenter">Проследяването е променено. Докоснете „Нулиране на центъра за проследяване“ веднъж, след което задръжте неподвижно, преди да заснемете.</string>
<string name="spatial_capture_unsynchronized">Заснемането е отхвърлено: проследяването на главата не беше синхронизирано или се промени по време на заснемането. Стойте неподвижно и опитайте отново.</string>
<string name="spatial_align_first">Изчислете подравняването на камерата, преди да проверите разстоянието.</string>
<string name="spatial_processing_failed">обработката на камерата е неуспешна</string>
<string name="spatial_hand_head_changed">Проследяването на главата е променено по време на откриване на ръце</string>
<string name="spatial_hand_wait_sync">Изчакване за синхронизиране на камерата и главата</string>
<string name="spatial_no_wrists">Не са открити китки. Дръжте дръжките на вашия контролер Joy-Con пред смарт очилата</string>
<string name="spatial_no_hands">Няма открити ръце. Покажете разтворени длани пред умните очила</string>
<string name="spatial_one_camera_wrists">Китките се виждат само в една камера; дръжте дръжките на контролера си в споделения изглед</string>
<string name="spatial_one_camera_hands">Ръцете се виждат само в една камера; дръжте двете длани в споделения изглед</string>
<string name="spatial_ambiguous_hands">И двете камери виждат ръце, но съвпадението ляво/дясно е двусмислено</string>
<string name="spatial_inconsistent_depth">Ръцете са открити, но стерео дълбочината е непостоянна</string>
<string name="spatial_rejected_wrist">Позиция на китката, отхвърлена от проверки за стерео или движение</string>
<string name="spatial_inconsistent_palm">Открити са стерео точки, но геометрията на дланта е несъвместима</string>
<string name="spatial_hands_need_calibration">Завършете калибрирането и проверката на разстоянието, за да активирате ръцете</string>
<string name="spatial_hands_wait_stable">Изчакване на стабилно проследяване на главата; нулиране на центъра за проследяване, ако е поискано.</string>
<string name="spatial_old_frame">Наблюдението на камерата е твърде старо; в очакване на нов чифт</string>
<string name="spatial_fit_ready">Побиране на %1$.2f пиксела. Измерете избраното разстояние от лявата камера до центъра на целта за калибриране, след което потвърдете.</string>
<string name="spatial_opencv_error">OpenCV не е наличен: %1$s</string>
<string name="spatial_restored">%1$d запазените изгледи са възстановени. Изчислете калибрирането, за да опитате отново, или започнете нов набор, за да заснемете повече.</string>
<string name="spatial_captured">Заснето %1$d / 24 прегледа. Дръжте целта за калибриране фиксирана; преместете смарт очилата за следващия изглед.</string>
<string name="spatial_capture_corners">Заснемането е отхвърлено: %1$d / 12 задължителни ъгъла, видими и в двете камери. Покажете още от целта за калибриране, задръжте неподвижно и опитайте отново.</string>
<string name="spatial_validation_error">Проверката на разстоянието не можа да завърши: %1$s. Опитайте отново.</string>
<string name="spatial_inference_error">Откриване на недостъпни ръце: %1$s. Променете CPU/GPU или превключете контролерите, управлявани с голи ръце, за да опитате отново.</string>
<string name="spatial_hand_timeout">Откриването на ръце е превишено %1$d ms; наблюдението е отхвърлено</string>
<string name="spatial_wrists_tracked">%1$d / 2 позиции на китката на Joy-Con, измерени чрез стерео</string>
<string name="spatial_palms_tracked">Проследени длани: %1$d / 2; готовност за щипване: %2$d; готовност за хващане: %3$d</string>
<string name="spatial_capture_saved">Записан диагностичен запис в %1$s</string>
<string name="spatial_hand_error">Проследяването на ръцете е спряно: %1$s</string>
<string name="spatial_stereo_loaded">Стерео %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">Проследяването на главата и целта за калибриране не съвпадат с %1$s (ограничение 5 см / 2 инча). Дръжте целта за калибриране фиксирана и изчакайте стабилно проследяване, преди да започнете нов набор. Внимателно завъртете и наклонете умните очила; пауза преди всяко заснемане.</string>
<string name="spatial_need_views">Заснемане на най-малко 24 разнообразни изгледа на неподвижната цел за калибриране.</string>
<string name="spatial_need_rotation">Завъртете умните очила наляво и надясно и ги наклонете нагоре и надолу; движение без завъртане не може да калибрира подравняването на камерата.</string>
<string name="spatial_bad_baseline">Неправдоподобна базова линия на камерата</string>
<string name="spatial_need_orientations">Преместете главата си в по-разнообразни ориентации.</string>
<string name="spatial_bad_rotation">Невалидно завъртане на камерата към главата</string>
<string name="spatial_bad_alignment">Подравняването на камерата към главата е непоследователно.</string>
<string name="spatial_calibration_error">Калибрирането не можа да завърши. %1$s Вашите заснети изгледи се запазват.</string>
<string name="spatial_reprojection_error">Грешка при повторна проекция на задържане %1$.2f px надвишава 2 px. Калибрирайте отново.</string>
<string name="spatial_distance_corners">%1$d / 24 ъгъла на шахматна дъска, открити и в двете камери. Дръжте целта за пълно калибриране видима и задръжте неподвижно. Ако вече се вижда, изберете 12 инча или 30 см и измерете това по-близко разстояние, преди да опитате отново.</string>
<string name="spatial_distance_depth">Целта за калибриране се вижда, но дълбочината й не може да бъде надеждно измерена. Дръжте умните очила неподвижно и дръжте мишената за калибриране плоска и добре осветена, след което опитайте отново.</string>
<string name="spatial_distance_invalid">Измерването на камерата е невалидно. Задръжте неподвижно и опитайте отново.</string>
<string name="spatial_distance_passed">Проверката на разстоянието е премината.</string>
<string name="spatial_distance_retry">Проверете отново разстоянието от лявата камера до центъра на целта за калибриране, след което опитайте отново.</string>
<string name="spatial_distance_result">Измерени камери %1$s; избраното разстояние %2$s. %3$s</string>
<string name="spatial_wait_previews">Изчакайте визуализациите на двете камери, преди да започнете калибрирането.</string>
<string name="spatial_saved_views_hint">Запазените изгледи могат да бъдат преизчислени. Започнете ново калибриране за заснемане в текущата координатна система за проследяване.</string>
<string name="spatial_model_checksum_error">Несъответствие на контролната сума на модела на ръката</string>
<string name="spatial_opencv_load_error">OpenCV не можа да се зареди</string>
</resources>
+214 -10
View File
@@ -2416,14 +2416,14 @@
<string name="glasses_display">Displej</string>
<string name="glasses_refresh">Obnovovací frekvence</string>
<string name="glasses_3d">3D (vedle sebe)</string>
<string name="glasses_3d_hint">Každé oko dostane vlastní polovinu obrazu 3840×1080. Pokud obraz vypadne, podržte na brýlích tlačítko 2D/3D.</string>
<string name="glasses_3d_hint">Přepněte chytré brýle do 3D zobrazení vedle sebe. Sledování hlavy se ovládá samostatně.</string>
<string name="glasses_3d_failed">Signál 3D se nepodařilo potvrdit. Zkuste to znovu, až se vrátí obraz 2D.</string>
<string name="glasses_no_signal">Žádný obraz: brýle jsou v režimu, který tento telefon neumí vyslat. Podržte jejich tlačítko 2D/3D, nebo je odpojte a znovu připojte.</string>
<string name="glasses_3d_refresh">Obnovovací frekvence ve 3D</string>
<string name="glasses_auto3d">3D pro hry VR</string>
<string name="glasses_auto3d_hint">Při běhu SteamVR přepne na 3D a potom zpět.</string>
<string name="glasses_picture">Obraz 3D</string>
<string name="glasses_picture_hint">Automaticky: SteamVR vyplní zorné pole; ostatní hry visí ve 3D jako obrazovka a ve 2D zůstanou ploché.</string>
<string name="glasses_picture">Obrázek</string>
<string name="glasses_picture_hint">Automaticky zobrazuje plochý obsah v obou očích a detekuje SteamVR. Pro obsah SBS vyberte možnost Hra vedle sebe.</string>
<string name="glasses_picture_auto">Automaticky</string>
<string name="glasses_picture_screen">Virtuální obrazovka</string>
<string name="glasses_picture_sbs">Hra 3D vedle sebe</string>
@@ -2433,20 +2433,20 @@
<string name="glasses_screen_no_output">Tato nastavení platí, když je zapnutá možnost „Hra v brýlích“ a hra se v nich zobrazuje.</string>
<string name="glasses_screen_flat_hint">Režim Automaticky drží tuto 2D hru na celou obrazovku. Chcete-li upravit její šířku, vzdálenost a sledování hlavy, použijte virtuální obrazovku.</string>
<string name="glasses_screen_use">Použít virtuální obrazovku</string>
<string name="glasses_screen_direct_hint">SteamVR vyplní celý pohled. Jeho zorné pole upravte níže; šířka a vzdálenost platí jen pro virtuální obrazovku.</string>
<string name="glasses_screen_direct_hint">SteamVR vyplní zobrazení. Upravte zorné pole ve VR; šířka a vzdálenost platí pouze pro virtuální obrazovku.</string>
<string name="glasses_screen_sbs_hint">Hry 3D vedle sebe vyplní zorné pole každého oka. Šířka a vzdálenost platí jen pro virtuální obrazovku.</string>
<string name="glasses_tracking_flat_hint">Režim Automaticky drží tuto 2D hru pevně v zorném poli. Chcete-li použít sledování hlavy, zvolte v části Displej možnost „Virtuální obrazovka“.</string>
<string name="glasses_tracking_sbs_hint">Sledování hlavy platí pro virtuální obrazovku nebo spuštěnou hru SteamVR.</string>
<string name="glasses_hardware">Brýle</string>
<string name="glasses_hardware">Zařízení</string>
<string name="glasses_brightness">Jas</string>
<string name="glasses_volume">Hlasitost</string>
<string name="glasses_sunblock">Zatmavení</string>
<string name="glasses_sunblock_hint">Ztmaví přední skla.</string>
<string name="glasses_tracking">Sledování hlavy</string>
<string name="glasses_tracking">Sledování</string>
<string name="glasses_head_tracking">Sledování hlavy</string>
<string name="glasses_head_tracking_hint">Vlastní senzory brýlí otáčejí virtuální obrazovkou a SteamVR podle pohybu hlavy.</string>
<string name="glasses_recenter">Vycentrovat pohled</string>
<string name="glasses_recenter_button">Vycentrovat</string>
<string name="glasses_recenter_button">Vycentrovat obrazovku</string>
<string name="glasses_vr_fov">Zorné pole SteamVR</string>
<string name="glasses_vr_fov_hint">Nižší hodnota přiblíží SteamVR a vyplní brýle; vyšší ukáže víc z okolí.</string>
<string name="glasses_playing">Hraje se na %1$s</string>
@@ -2467,8 +2467,8 @@
<string name="glasses_calibration_reset_hint">Odebrat uloženou kalibraci a obnovit výchozí hodnoty modelu? Po opětovném nasazení brýlí pohled vycentrujte.</string>
<string name="glasses_calibration_reset_button">Resetovat</string>
<string name="hand_camera_group">Sledování rukou</string>
<string name="hand_camera_prompt_title">Sledovat ruce pomocí kamery?</string>
<string name="hand_camera_prompt_body">Kamera vidí vaše ruce. Když není připojen žádný ovladač, stanou se vaše holé ruce rukama ve VR: spojením palce a ukazováčku kliknete, sevřením ruky uchopíte a menu otevřete, když dlaň otočíte k sobě a spojíte prsty. S Joy-Cony je umístí tam, kde vaše ruce skutečně jsou. Obrázky se zpracovávají na tomto zařízení a nikdy se neukládají ani neodesílají.</string>
<string name="hand_camera_prompt_title">Sledování rukou nebo ovladačů Joy-Con pomocí fotoaparátu telefonu?</string>
<string name="hand_camera_prompt_body">Bez připojeného ovladače sleduje fotoaparát telefonu ve VR vaše holé ruce: sevřením palce a ukazováčku kliknete, sevřením ruky uchopíte a otočením dlaně k sobě a sevřením otevřete nabídku. U spárovaných ovladačů Joy-Con fotoaparát sleduje jejich polohu, zatímco ovladače Joy-Con zajišťují otáčení a ovládání tlačítky. Snímky se zpracovávají na tomto zařízení a nikdy se neukládají ani neodesílají.</string>
<string name="hand_camera_rear">Zadní kamera</string>
<string name="hand_camera_rear_hint">Doporučeno. Obvykle ostřejší a širší.</string>
<string name="hand_camera_front">Přední kamera</string>
@@ -2476,7 +2476,7 @@
<string name="hand_camera_allow">Použít kameru</string>
<string name="hand_camera_not_now">Teď ne</string>
<string name="hand_camera_setting">Sledování rukou kamerou</string>
<string name="hand_camera_setting_hint">Když není připojen žádný ovladač, stanou se vaše ruce rukama ve VR: spojením prstů kliknete, dlaní k sobě a spojením prstů otevřete menu. S Joy-Cony je umístí tam, kde je vidí kamera. Obrázky zůstávají na tomto zařízení.</string>
<string name="hand_camera_setting_hint">Použijte kameru telefonu pro gesta holou rukou nebo k umístění spárovaných ovladačů Joy-Con ve VR. Ovladače Joy-Con poskytují otáčení a vstupy tlačítek. Pozastaví, zatímco chytré brýle sledují vaše ruce nebo ovladače Joy-Con. Snímky se zpracovávají pouze na tomto zařízení.</string>
<string name="hand_camera_choice">Kamera</string>
<string name="hand_camera_access">Přístup ke kameře</string>
<string name="hand_camera_access_hint">Povolte přístup ke kameře, aby mohlo začít sledování rukou.</string>
@@ -2748,4 +2748,208 @@
<string name="boot_movie_converting_started">Převádí se %1$s pro Steam. Nechte DroidDeck otevřený.</string>
<string name="boot_movie_unsupported">Toto zařízení video nedokáže dekódovat.</string>
<string name="boot_movie_import_failed">Video se nepodařilo importovat</string>
<string name="glasses_experimental">Kamery Luma Ultra — Experimentální</string>
<string name="glasses_joycons">Sledování polohy Joy-Conů</string>
<string name="glasses_joycons_hint">Použijte kamery chytrých brýlí k umístění spárovaných ovladačů Joy-Con. Otáčení a vstupy tlačítek pocházejí z ovladačů Joy-Con. Poloha se odhaduje, když máte skrytá zápěstí. Nahrazuje ovladače ovládané holýma rukama a pozastavuje sledování fotoaparátem telefonu. Při připojování nebo resetování centra sledování namiřte ovladače Joy-Con dopředu.</string>
<string name="glasses_joycons_requires_position">Nejprve povolte sledování polohy. Vyžaduje ovladače Joy-Con s funkcí detekce pohybu v systému Android 12 nebo novějším. Ve výchozím nastavení vypnuto.</string>
<string name="setup_tab_glasses">Chytré brýle</string>
<string name="setup_glasses_scope">Nastavení zobrazení a sledování hlavy jsou sdílena mezi nabídkami, hrami a relacemi na počítači DroidDeck.</string>
<string name="setup_glasses_connect">Připojte chytré brýle a změňte jejich nastavení zobrazení a sledování. Uložená nastavení zůstanou zachována i po odpojení.</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">Režim zobrazení</string>
<string name="glasses_output_2d">2D výstup</string>
<string name="glasses_output_3d">3D výstup</string>
<string name="glasses_picture_group">Obraz a virtuální obrazovka</string>
<string name="glasses_position_group">Poloha a úroveň podlahy</string>
<string name="glasses_hands_group">Sledování rukou — Experimentální</string>
<string name="glasses_tracking_status">Stav sledování</string>
<string name="glasses_resolution_2d">2D rozlišení</string>
<string name="glasses_resolution_3d">3D rozlišení</string>
<string name="glasses_resolution_vr">VR rozlišení</string>
<string name="glasses_resolution_auto">Automatické</string>
<string name="glasses_resolution_inherit">Stejné jako 2D / 3D</string>
<string name="glasses_resolution_hint">Výstupní rozlišení pro každé oko.</string>
<string name="glasses_refresh_30_hint">Povolte Hra na chytrých brýlích pro výběr nativního 30Hz režimu displeje.</string>
<string name="glasses_3dof">Virtuální obrazovka 3DoF</string>
<string name="glasses_3dof_hint">Ukotvěte ploché hry před sebe, abyste se mohli rozhlédnout. Povolí Hra na chytrých brýlích bez přepínání do 3D.</string>
<string name="glasses_screen_app_wide">Šířka a vzdálenost okamžitě aktualizují sledovanou virtuální obrazovku pro nabídky DroidDeck, hry a relace plochy.</string>
<string name="glasses_head_tracking_spatial_vr_hint">Sledování polohy zahrnuje rotaci hlavy ve SteamVR. Pro sledování pouze rotace vypněte sledování polohy.</string>
<string name="glasses_head_tracking_spatial_hint">Řídí pohyb hlavy na virtuální obrazovce. Sledování polohy zahrnuje rotaci hlavy ve SteamVR.</string>
<string name="glasses_quick_actions">Rychlé akce</string>
<string name="glasses_recenter_screen_hint">Podívejte se, kam chcete umístit obrazovku, a poté resetujte centrum sledování, abyste nastavili její výšku a směr.</string>
<string name="glasses_recenter_joycons_button">Vycentrovat Joy-Con</string>
<string name="phone_camera_tracking_group">Fotoaparát telefonu – Experimentální</string>
<string name="phone_camera_tracking_setting">Sledování rukou nebo ovladačů Joy-Con</string>
<string name="glasses_6dof">Virtuální obrazovka s 6DoF</string>
<string name="glasses_position">Sledování polohy – experimentální</string>
<string name="glasses_position_hint">Fyzický pohyb ve VR a kolem virtuálních obrazovek. Restartování sledování chvíli trvá.</string>
<string name="glasses_eye_height">Aktuální výška očí nad podlahou</string>
<string name="glasses_floor">Nastavit úroveň podlahy z aktuální výšky</string>
<string name="glasses_floor_set">Nastavit úroveň podlahy</string>
<string name="glasses_spatial_setup">Nastavení a diagnostika stereo kamery</string>
<string name="glasses_spatial_setup_hint">Před hraním zkontrolujte kalibraci kamery a sledování rukou.</string>
<string name="glasses_spatial_open">Otevřít nastavení</string>
<string name="glasses_hands">Ovladače ovládané holýma rukama</string>
<string name="glasses_hands_hint">Použijte kamery chytrých brýlí k míření rukama, sevřením prstů pro kliknutí a sevřením ruky pro uchopení ve VR. Gamepady se pohybují a otáčejí. Nahrazuje sledování polohy Joy-Con. Během her může docházet ke zpoždění nebo ztrátě sledování.</string>
<string name="glasses_hands_ready_hint">Povolte ovladače ovládané holýma rukama pod kamerami Luma Ultra v režimu Sledování. Nejprve zobrazte otevřené ruce.</string>
<string name="glasses_hands_base">Sdílená kalibrace Luma Ultra</string>
<string name="glasses_hands_base_hint">Používání sdílené základny Luma Ultra. Kalibrace vlastních chytrých brýlí pro ověření jejich přesnosti.</string>
<string name="glasses_hands_calibration_verified">Naměřená kalibrace ověřena</string>
<string name="glasses_hands_calibration_required">Vyžadováno ověření</string>
<string name="glasses_hands_calibration_required_hint">Ovladače ovládané holýma rukama budou k dispozici po úspěšném provedení kontroly naměřené vzdálenosti.</string>
<string name="tracking_recovery_lost">Sledování ztraceno</string>
<string name="tracking_recovery_look">Pomalu se rozhlédněte v dobře osvětlené místnosti.\nUdržujte sledovací kamery v dostatečné vzdálenosti a natočte je k nábytku nebo jiným viditelným detailům.</string>
<string name="tracking_recovery_restart_help">Stále se zasekl? Na telefonu: chytré brýle → Sledování → Otevřít nastavení → Zobrazit diagnostiku → Restartovat sledování.</string>
<string name="tracking_recovery_disconnected">Chytré brýle odpojeny</string>
<string name="tracking_recovery_connect">Zkontrolujte připojení USB a znovu připojte chytré brýle.\nPokud se zobrazí výzva k přístupu přes USB na telefonu, přijměte ji.</string>
<string name="tracking_recovery_usb_help">Po připojení se pomalu rozhlédněte v dobře osvětlené místnosti a obnovte sledování.</string>
<string name="tracking_recovery_recenter">Je nutné znovu vycentrovat sledování</string>
<string name="tracking_recovery_recenter_steps">Sledování se vrátilo do nové polohy.\nPodívejte se dopředu a poté klepněte na resetovat centrum sledování na telefonu.</string>
<string name="tracking_recovery_recenter_help">Obnovení centra sledování najdete také v chytrých brýlích → Sledování → Otevřít nastavení. Vaše uložená kalibrace fotoaparátu se zachová.</string>
<string name="tracking_recovery_stabilizing">Chvíli vydržte</string>
<string name="tracking_recovery_hold">Probíhá resetování centra sledování.\nDržte chytré brýle stabilně, dokud se sledování neustálí.</string>
<string name="tracking_recovery_automatic">Tato obrazovka se automaticky zavře, když je připraveno sledování hlavy.</string>
<string name="glasses_steady_hand_aim">Stabilizované míření rukama</string>
<string name="glasses_steady_hand_aim_hint">Zamiřte z ramene přes dlaň. Vypněte pro otáčení ovládané zápěstím.</string>
<string name="spatial_title">Prostorové nastavení</string>
<string name="spatial_wait_tracking">Čekání na sledování…</string>
<string name="spatial_cameras_description">Levé a pravé sledovací kamery</string>
<string name="spatial_camera_sync">Synchronizace kamery</string>
<string name="spatial_ready">Připraveno</string>
<string name="spatial_sync_hint">Čekání na seřazení kamery a sledování hlavy. Udržujte chytré brýle připojené.</string>
<string name="spatial_prepare">1. Připravte kalibrační cíl</string>
<string name="spatial_print_actual">Tisk ve skutečné velikosti</string>
<string name="spatial_print_hint">Každý čtverec musí měřit 30 mm (1,181 palce). Připevněte list naplocho na pevný povrch na dobře osvětlené místo.</string>
<string name="spatial_print">Vytisknout kalibrační terč</string>
<string name="spatial_capture_group">2. Zachyťte různé pohledy</string>
<string name="spatial_board_stationary">Udržujte kalibrační cíl nehybný</string>
<string name="spatial_capture_hint">Posuňte chytré brýle blíže, dále a do stran. Otočte je doleva/doprava, nakloňte je nahoru/dolů a nakloňte je do stran. Ponechejte kalibrační cíl v obou kamerách a před každým zachycením se na chvíli zastavte.</string>
<string name="spatial_start">Spusťte novou kalibraci</string>
<string name="spatial_wait_sync">Čekání na synchronizaci kamery.</string>
<string name="spatial_start_hint">Spusťte novou kalibraci pro sběr pohledů.</string>
<string name="spatial_views_hint">Ponechat cíl kalibrace viditelný v obou náhledech. Zachyťte alespoň 24 ostrých pohledů z různých úhlů.</string>
<string name="spatial_capturing">Zachycuji…</string>
<string name="spatial_capture">Zachycení pohledu na cíl kalibrace</string>
<string name="spatial_alignment">Vypočítat zarovnání kamery</string>
<string name="spatial_calculating">Počítám…</string>
<string name="spatial_calculate">Vypočítat kalibraci</string>
<string name="spatial_distance_group">3. Zkontrolujte vzdálenost</string>
<string name="spatial_distance">Změřená vzdálenost</string>
<string name="spatial_distance_hint">Začněte na 12 palcích nebo 30 cm, aby byly vytištěné značky snadněji rozpoznatelné. Vyberte si přesné měření, které budete používat.</string>
<string name="spatial_camera_board_distance">Levá kamera do středu kalibračního cíle</string>
<string name="spatial_checking_distance">Kontrola vzdálenosti…</string>
<string name="spatial_validate">Ověřte vzdálenost</string>
<string name="spatial_diagnostics">Diagnostika</string>
<string name="spatial_show_diagnostics">Zobrazit diagnostiku</string>
<string name="spatial_diagnostics_hint">Nástroje pro obnovu časování a sledování kamery.</string>
<string name="spatial_camera_timing">Časování kamery</string>
<string name="spatial_hand_input">Vstup ze sledování rukou</string>
<string name="spatial_wait_timing">Čekání na časování kamery.</string>
<string name="spatial_frames">Snímky z fotoaparátu</string>
<string name="spatial_gpu">GPU detekce rukou</string>
<string name="spatial_gpu_hint">CPU je výchozí. GPU je experimentální a může způsobit restartování SteamVR. Tato volba se resetuje při restartu sledování.</string>
<string name="spatial_lite">Lite detekce modelů rukou</string>
<string name="spatial_lite_hint">Použijte detektor dlaní Lite a klíčové body Lite na rukou. Zkontrolujte přesnost míření, štípnutí a uchopení. Resetuje se při restartu sledování.</string>
<string name="spatial_adaptive">Automatická vstupní frekvence pro sledování rukou</string>
<string name="spatial_adaptive_hint">Upravte vstup podle rychlosti zpracování. Resetuje se při restartu sledování.</string>
<string name="spatial_half_rate">Limitní vstupní frekvence pro sledování rukou</string>
<string name="spatial_half_rate_hint">Použijte polovinu snímků z fotoaparátu. Vypněte pro porovnání maximální frekvence. Resetuje se při restartu sledování.</string>
<string name="spatial_diagnostic_capture">Diagnostické zachycení</string>
<string name="spatial_diagnostic_capture_hint">Lokálně uložte jeden pár stereo obrázků. Obrázky se ukládají pouze na vyžádání.</string>
<string name="spatial_save_frame">Uložte snímek z fotoaparátu</string>
<string name="spatial_restart_tracking">Restartujte sledování</string>
<string name="spatial_restart_hint">Použijte, pokud se sledování neobnoví v dobře osvětlené oblasti. Restart vyžaduje nový zdroj sledování.</string>
<string name="spatial_restart">Restartujte</string>
<string name="spatial_restart_title">Restartovat sledování?</string>
<string name="spatial_restart_confirm">Neuložené kalibrační pohledy budou ztraceny. Poté držte chytré brýle v klidu a resetujte centrum sledování, jakmile se sledování obnoví.</string>
<string name="spatial_views_count">Zachyceno %1$d / 24 zobrazení</string>
<string name="spatial_capture_paused">Záznam pozastaven: %1$s</string>
<string name="spatial_measure_hint">Míra %1$s. Ukažte kompletní kalibrační cíl v obou náhledech a poté nehybně podržte.</string>
<string name="spatial_timing_values">%1$.1f Hz • stáří snímku %2$.1f ms • zpracování %3$.1f ms</string>
<string name="spatial_input_values">Limit vstupu %1$.1f Hz • %2$.0f%% snímků z kamery. Sledování hlavy pokračuje na maximální frekvenci.</string>
<string name="spatial_frame_counts">%1$d zpracováno • %2$d zrušeno • %3$d přeskočeno limitem vstupu</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck - Luma Ultra kalibrace</string>
<string name="spatial_print_scale" formatted="false">Tisk při 100% / Skutečná velikost. Každý čtverec: 30 mm (1,181 palce).</string>
<string name="spatial_print_stationary">Udržujte kalibrační cíl plochý a nehybný; přesouvejte chytré brýle mezi snímky.</string>
<string name="spatial_print_check">Kontrola měřítka: 100 mm (3,937 palce) mezi dílky</string>
<string name="spatial_distance_12in">12 palců (30,48 cm)</string>
<string name="spatial_distance_30cm">30 cm (11,81 palce)</string>
<string name="spatial_distance_20in">20 palců (50,8 cm)</string>
<string name="spatial_distance_50cm">50 cm (19,69 palce)</string>
<string name="spatial_disabled">Zakázáno</string>
<string name="spatial_wait_stable">Čekání na stabilní sledování</string>
<string name="spatial_disconnected">Odpojeno</string>
<string name="spatial_hands_off">Ovladače ovládané holýma rukama jsou vypnuté</string>
<string name="spatial_tracking_off">Sledování polohy je vypnuto nebo jsou odpojeny chytré brýle</string>
<string name="spatial_recenter_pending">Požadováno resetování centra sledování – držte se v klidu, dokud se sledování nestabilizuje.</string>
<string name="spatial_tracking_lost">Sledování ztraceno – čelte dobře osvětlené oblasti s viditelnými detaily</string>
<string name="spatial_origin_changed">Změněn počátek sledování – klepněte na resetování centra sledování a poté držte v klidu.</string>
<string name="spatial_active">6DoF aktivní</string>
<string name="spatial_connect_hint">Připojte Luma Ultra a povolte sledování polohy.</string>
<string name="spatial_validation_session">Kalibrace ověřena pro toto připojení.</string>
<string name="spatial_validation_saved">Kalibrace ověřena a uložena.</string>
<string name="spatial_wait_cameras">Čekání na stereo kamery</string>
<string name="spatial_wait_hands">Čekání na sledování rukou</string>
<string name="spatial_collect_instructions">Udržujte kalibrační terč 5 × 7 nehybný. Zachyťte více než 24 pohledů při pohybu a otáčení chytrými brýlemi.</string>
<string name="spatial_capture_pending">Čekání na další snímek z kamery. Držte chytré brýle v klidu.</string>
<string name="spatial_validation_pending">Kontrola dalšího snímku z kamery. Udržujte celý kalibrační cíl viditelný a držte jej v klidu.</string>
<string name="spatial_start_first">Nejprve spusťte kalibraci</string>
<string name="spatial_no_camera_frame">Nedorazil žádný nový snímek z kamery. Zkontrolujte připojení chytrých brýlí a náhledy fotoaparátu a zkuste to znovu.</string>
<string name="spatial_clock_reset">Hodiny fotoaparátu byly resetovány</string>
<string name="spatial_frame_changed">Souřadnicový systém sledování se změnil. Vaše pohledy jsou uloženy pro přepočet. Obnovte centrum sledování a poté spusťte novou sadu pro zachycení dalších pohledů.</string>
<string name="spatial_device_loaded">načtena kalibrace zařízení</string>
<string name="spatial_base_loaded">Načtena základní kalibrace Luma Ultra</string>
<string name="spatial_calibration_needed">nutná kalibrace</string>
<string name="spatial_tap_start">Nejprve klepněte na Spustit novou kalibraci.</string>
<string name="spatial_capture_recenter_pending">Obnovení centra sledování čeká na stabilní sledování. Chvíli držte v klidu.</string>
<string name="spatial_capture_recenter">Sledování se změnilo. Klepněte jednou na resetování centra sledování a poté držte v klidu před zachycením.</string>
<string name="spatial_capture_unsynchronized">Snímek odmítnut: sledování hlavy nebylo synchronizované nebo se během snímání změnilo. Nehýbejte se a zkuste to znovu.</string>
<string name="spatial_align_first">Před kontrolou vzdálenosti vypočítejte vyrovnání kamery.</string>
<string name="spatial_processing_failed">zpracování fotoaparátu se nezdařilo</string>
<string name="spatial_hand_head_changed">Sledování hlavy se změnilo během detekce rukou</string>
<string name="spatial_hand_wait_sync">Čekání na synchronizaci kamery a hlavy</string>
<string name="spatial_no_wrists">Nebyla zjištěna žádná zápěstí. Držte rukojeti ovladače Joy-Con před chytrými brýlemi</string>
<string name="spatial_no_hands">Nebyly zjištěny žádné ruce. Ukažte před chytrými brýlemi otevřené dlaně</string>
<string name="spatial_one_camera_wrists">Zápěstí viditelná pouze v jedné kameře; ponechte úchyty ovladače ve sdíleném zobrazení</string>
<string name="spatial_one_camera_hands">Ruce viditelné pouze v jedné kameře; mějte obě dlaně ve sdíleném pohledu</string>
<string name="spatial_ambiguous_hands">Obě kamery vidí ruce, ale shoda vlevo/vpravo je nejednoznačná</string>
<string name="spatial_inconsistent_depth">Ruce byly detekovány, ale stereo hloubka je nekonzistentní</string>
<string name="spatial_rejected_wrist">Poloha zápěstí zamítnuta stereo nebo kontrolou pohybu</string>
<string name="spatial_inconsistent_palm">Byly zjištěny stereo body, ale geometrie dlaně je nekonzistentní</string>
<string name="spatial_hands_need_calibration">Dokončete kalibraci a kontrolu vzdálenosti pro aktivaci rukou</string>
<string name="spatial_hands_wait_stable">Čekání na stabilní sledování hlavy; resetování centra sledování je možné, pokud je požadováno.</string>
<string name="spatial_old_frame">Pozorování kamerou je příliš staré; čekání na nový pár</string>
<string name="spatial_fit_ready">Přizpůsobit %1$.2f pixelům. Změřte vybranou vzdálenost od levé kamery ke středu kalibračního cíle a poté potvrďte.</string>
<string name="spatial_opencv_error">OpenCV není k dispozici: %1$s</string>
<string name="spatial_restored">Bylo obnoveno %1$d uložených zobrazení. Vypočítejte kalibraci a zkuste to znovu, nebo spusťte novou sadu a zachyťte více.</string>
<string name="spatial_captured">Pořízeno %1$d / 24 zobrazení. Udržujte kalibrační cíl pevný; posuňte chytré brýle pro další pohled.</string>
<string name="spatial_capture_corners">Snímek zamítnut: %1$d / 12 požadovaných rohů viditelných v obou kamerách. Ukažte více z kalibračního cíle, nehýbejte se a zkuste to znovu.</string>
<string name="spatial_validation_error">Kontrola vzdálenosti nemohla být dokončena: %1$s. Zkuste to znovu.</string>
<string name="spatial_inference_error">Detekce rukou není k dispozici: %1$s. Změňte CPU/GPU nebo přepněte řadiče ovládané holýma rukama a zkuste to znovu.</string>
<string name="spatial_hand_timeout">Detekce rukou přesáhla %1$d ms; pozorování vyřazeno</string>
<string name="spatial_wrists_tracked">%1$d / 2 polohy zápěstí Joy-Con měřené stereo</string>
<string name="spatial_palms_tracked">Sledované dlaně: %1$d / 2; připraveno sevření prstů: %2$d; připraveno uchopení: %3$d</string>
<string name="spatial_capture_saved">Diagnostický záznam byl uložen do %1$s</string>
<string name="spatial_hand_error">Sledování rukou zastaveno: %1$s</string>
<string name="spatial_stereo_loaded">Stereo %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">Sledování hlavy a kalibrační cíl nesouhlasí podle %1$s (limit 5 cm / 2 palce). Před spuštěním nové sady ponechte kalibrační cíl pevný a počkejte na stabilní sledování. Chytré brýle jemně otáčejte a naklánějte; pauza před každým zachycením.</string>
<string name="spatial_need_views">Zachyťte alespoň 24 různých pohledů na stacionární kalibrační cíl.</string>
<string name="spatial_need_rotation">Otočte chytré brýle doleva a doprava a naklánějte je nahoru a dolů; pohyb bez rotace nemůže kalibrovat zarovnání kamery.</string>
<string name="spatial_bad_baseline">Nepravděpodobná základní linie fotoaparátu</string>
<string name="spatial_need_orientations">Pohybujte hlavou různými směry.</string>
<string name="spatial_bad_rotation">Neplatné otáčení kamery proti hlavě</string>
<string name="spatial_bad_alignment">Zarovnání mezi kamerou a hlavou je nekonzistentní.</string>
<string name="spatial_calibration_error">Kalibraci nelze dokončit. %1$s Vaše zachycená zobrazení jsou uložena.</string>
<string name="spatial_reprojection_error">Zpožděná chyba reprojekce %1$.2f px přesahuje 2 px. Překalibrovat.</string>
<string name="spatial_distance_corners">%1$d / 24 šachovnicových rohů zjištěno v obou kamerách. Udržujte celý kalibrační cíl viditelný a držte jej v klidu. Pokud je již viditelná, vyberte 12 palců nebo 30 cm a před dalším pokusem změřte tuto bližší vzdálenost.</string>
<string name="spatial_distance_depth">Kalibrační terč je viditelný, ale jeho hloubku nebylo možné spolehlivě změřit. Držte chytré brýle v klidu a udržujte kalibrační terč plochý a dobře osvětlený, pak to zkuste znovu.</string>
<string name="spatial_distance_invalid">Měření kamery bylo neplatné. Nehýbejte se a zkuste to znovu.</string>
<string name="spatial_distance_passed">Kontrola vzdálenosti prošla.</string>
<string name="spatial_distance_retry">Znovu zkontrolujte vzdálenost od levé kamery ke středu kalibračního cíle a zkuste to znovu.</string>
<string name="spatial_distance_result">Fotoaparáty naměřeny %1$s; vybraná vzdálenost %2$s. %3$s</string>
<string name="spatial_wait_previews">Před zahájením kalibrace počkejte na náhledy obou kamer.</string>
<string name="spatial_saved_views_hint">Uložená zobrazení lze přepočítat. Spusťte novou kalibraci pro zachycení v aktuálním sledovacím souřadnicovém systému.</string>
<string name="spatial_model_checksum_error">Neshoda kontrolního součtu modelu ruky</string>
<string name="spatial_opencv_load_error">OpenCV se nepodařilo načíst</string>
</resources>
+214 -10
View File
@@ -2366,14 +2366,14 @@
<string name="glasses_display">Skærm</string>
<string name="glasses_refresh">Opdateringshastighed</string>
<string name="glasses_3d">3D (side-by-side)</string>
<string name="glasses_3d_hint">Hvert øje får sin egen halvdel af et billede på 3840×1080. Forsvinder billedet, så hold brillens 2D/3D-knap inde.</string>
<string name="glasses_3d_hint">Skift smartbrillerne til side-om-side 3D. Hovedsporing styres separat.</string>
<string name="glasses_3d_failed">3D-signalet blev ikke bekræftet. Prøv igen, når 2D-billedet er tilbage.</string>
<string name="glasses_no_signal">Intet billede: brillen er i en tilstand, som telefonen ikke kan sende. Hold dens 2D/3D-knap inde, eller tag stikket ud og sæt det i igen.</string>
<string name="glasses_3d_refresh">3D-opdateringshastighed</string>
<string name="glasses_auto3d">3D til VR-spil</string>
<string name="glasses_auto3d_hint">Skifter til 3D, mens SteamVR kører, og tilbage bagefter.</string>
<string name="glasses_picture">3D-billede</string>
<string name="glasses_picture_hint">Automatisk: SteamVR fylder synsfeltet; andre spil svæver som en skærm i 3D og forbliver flade i 2D.</string>
<string name="glasses_picture">Billede</string>
<string name="glasses_picture_hint">Automatisk viser fladt indhold i begge øjne og registrerer SteamVR. Vælg Side-om-side-spil for SBS-indhold.</string>
<string name="glasses_picture_auto">Automatisk</string>
<string name="glasses_picture_screen">Virtuel skærm</string>
<string name="glasses_picture_sbs">Side-by-side-spil</string>
@@ -2383,20 +2383,20 @@
<string name="glasses_screen_no_output">Disse indstillinger gælder, mens Spil på brillen er slået til, og spillet vises der.</string>
<string name="glasses_screen_flat_hint">Automatisk holder dette 2D-spil i fuld skærm. Brug en virtuel skærm for at justere bredde, afstand og hovedsporing.</string>
<string name="glasses_screen_use">Brug virtuel skærm</string>
<string name="glasses_screen_direct_hint">SteamVR fylder hele billedet. Juster synsfeltet nedenfor; bredde og afstand gælder kun for en virtuel skærm.</string>
<string name="glasses_screen_direct_hint">SteamVR udfylder visningen. Juster dens synsfelt i VR; bredde og afstand gælder kun for en virtuel skærm.</string>
<string name="glasses_screen_sbs_hint">Side-by-side-spil fylder hvert øje. Bredde og afstand gælder kun for en virtuel skærm.</string>
<string name="glasses_tracking_flat_hint">Automatisk holder dette 2D-spil fast i synsfeltet. Vælg Virtuel skærm under Skærm for at bruge hovedsporing.</string>
<string name="glasses_tracking_sbs_hint">Hovedsporing gælder for en virtuel skærm eller et kørende SteamVR-spil.</string>
<string name="glasses_hardware">Brille</string>
<string name="glasses_hardware">Enhed</string>
<string name="glasses_brightness">Lysstyrke</string>
<string name="glasses_volume">Lydstyrke</string>
<string name="glasses_sunblock">Solafskærmning</string>
<string name="glasses_sunblock_hint">Gør de forreste glas mørkere.</string>
<string name="glasses_tracking">Hovedsporing</string>
<string name="glasses_tracking">Sporing</string>
<string name="glasses_head_tracking">Hovedsporing</string>
<string name="glasses_head_tracking_hint">Brillens egne sensorer drejer den virtuelle skærm og SteamVR med dit hoved.</string>
<string name="glasses_recenter">Centrer visning</string>
<string name="glasses_recenter_button">Centrer</string>
<string name="glasses_recenter_button">Centrer skærmen igen</string>
<string name="glasses_vr_fov">SteamVR-synsfelt</string>
<string name="glasses_vr_fov_hint">Lavere bringer SteamVR tættere på og fylder brillen; højere viser mere omkring dig.</string>
<string name="glasses_playing">Spiller på %1$s</string>
@@ -2417,8 +2417,8 @@
<string name="glasses_calibration_reset_hint">Vil du fjerne den gemte kalibrering og gendanne modellens standardværdier? Centrer, når du har taget brillen på igen.</string>
<string name="glasses_calibration_reset_button">Nulstil</string>
<string name="hand_camera_group">Håndsporing</string>
<string name="hand_camera_prompt_title">Spor dine hænder med kameraet?</string>
<string name="hand_camera_prompt_body">Kameraet kan se dine hænder. Uden tilsluttet controller bliver dine bare hænder til VR-hænderne: knib tommel- og pegefinger sammen for at klikke, luk hånden for at gribe, og vend håndfladen mod dig og knib for at åbne menuen. Med Joy-Cons placerer det dem, hvor dine hænder virkelig er. Billeder behandles på denne enhed og gemmes eller sendes aldrig.</string>
<string name="hand_camera_prompt_title">Spor hænder eller Joy-Con-controllere med telefonkameraet?</string>
<string name="hand_camera_prompt_body">Uden en controller tilsluttet, sporer telefonkameraet dine bare hænder i VR: knib din tommelfinger og pegefinger for at klikke, luk din hånd for at gribe, og drej din håndflade mod dig og knib for at åbne menuen. Med parrede Joy-Con-controllere sporer kameraet deres position, mens Joy-Con-controllerne giver rotation og knapinput. Billeder behandles på denne enhed og gemmes eller sendes aldrig.</string>
<string name="hand_camera_rear">Bagkamera</string>
<string name="hand_camera_rear_hint">Anbefales. Som regel skarpere og bredere.</string>
<string name="hand_camera_front">Frontkamera</string>
@@ -2426,7 +2426,7 @@
<string name="hand_camera_allow">Brug kamera</string>
<string name="hand_camera_not_now">Ikke nu</string>
<string name="hand_camera_setting">Håndsporing med kamera</string>
<string name="hand_camera_setting_hint">Uden tilsluttet controller bliver dine hænder til VR-hænderne: knib for at klikke, vend håndfladen mod dig og knib for menuen. Med Joy-Cons placeres de, hvor kameraet ser dem. Billeder bliver på denne enhed.</string>
<string name="hand_camera_setting_hint">Brug telefonkameraet til bevægelser med bare hænder eller til at placere parrede Joy-Con-controllere i VR. Joy-Con-controllere giver rotation og knapinput. Pauserer, mens smartglassene sporer dine hænder eller Joy-Con-controllere. Billeder behandles kun på denne enhed.</string>
<string name="hand_camera_choice">Kamera</string>
<string name="hand_camera_access">Kameraadgang</string>
<string name="hand_camera_access_hint">Tillad kameraadgang, så håndsporing kan starte.</string>
@@ -2680,4 +2680,208 @@
<string name="boot_movie_converting_started">Konverterer %1$s til Steam. Hold DroidDeck åben.</string>
<string name="boot_movie_unsupported">Enheden kan ikke afkode videoen.</string>
<string name="boot_movie_import_failed">Kunne ikke importere videoen</string>
<string name="glasses_experimental">Luma Ultra-kameraer — Eksperimentel</string>
<string name="glasses_joycons">Joy-Con-positionssporing</string>
<string name="glasses_joycons_hint">Brug smartglass-kameraerne til at placere dine parrede Joy-Con-controllere. Rotation og knapinput kommer fra Joy-Con-controllerne. Positionen estimeres, når dine håndled er skjulte. Erstatter controllere, der betjenes med bare hænder, og sætter telefonkameraets sporing på pause. Ret Joy-Con-controllerne fremad, når du tilslutter eller nulstiller sporingscentret.</string>
<string name="glasses_joycons_requires_position">Aktiver positionssporing først. Kræver bevægelseskompatible Joy-Con-controllere på Android 12 eller nyere. Fra som standard.</string>
<string name="setup_tab_glasses">Smartglass</string>
<string name="setup_glasses_scope">Indstillinger for skærm- og hovedsporing deles på tværs af DroidDeck-menuer, spil og desktopsessioner.</string>
<string name="setup_glasses_connect">Tilslut smartglass for at ændre deres skærm- og sporingsindstillinger. Dine gemte indstillinger gemmes, når du afbryder forbindelsen.</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">Visningstilstand</string>
<string name="glasses_output_2d">2D-output</string>
<string name="glasses_output_3d">3D-output</string>
<string name="glasses_picture_group">Billede og virtuel skærm</string>
<string name="glasses_position_group">Position og gulvniveau</string>
<string name="glasses_hands_group">Sporing af hænder — Eksperimentel</string>
<string name="glasses_tracking_status">Sporingsstatus</string>
<string name="glasses_resolution_2d">2D-opløsning</string>
<string name="glasses_resolution_3d">3D-opløsning</string>
<string name="glasses_resolution_vr">VR-opløsning</string>
<string name="glasses_resolution_auto">Automatisk</string>
<string name="glasses_resolution_inherit">Samme som 2D / 3D</string>
<string name="glasses_resolution_hint">Outputopløsning pr. øje.</string>
<string name="glasses_refresh_30_hint">Aktiver Spil på smartbriller for at vælge skærmens native 30 Hz-tilstand.</string>
<string name="glasses_3dof">3DoF virtuel skærm</string>
<string name="glasses_3dof_hint">Forankre flade spil foran dig, så du kan se dig omkring. Aktiverer Spil på smartbriller uden at skifte til 3D.</string>
<string name="glasses_screen_app_wide">Bredde og afstand opdaterer den sporede virtuelle skærm til DroidDeck-menuer, spil og desktopsessioner med det samme.</string>
<string name="glasses_head_tracking_spatial_vr_hint">Positionssporing inkluderer hovedrotation i SteamVR. For sporing kun med rotation skal du slå positionssporing fra.</string>
<string name="glasses_head_tracking_spatial_hint">Styrer hovedbevægelse på den virtuelle skærm. Positionssporing inkluderer hovedrotation i SteamVR.</string>
<string name="glasses_quick_actions">Hurtige handlinger</string>
<string name="glasses_recenter_screen_hint">Se, hvor du vil have skærmen, og nulstil derefter sporingscentret for at indstille dens højde og retning.</string>
<string name="glasses_recenter_joycons_button">Centrer Joy-Con igen</string>
<string name="phone_camera_tracking_group">Telefonkamera — Eksperimentel</string>
<string name="phone_camera_tracking_setting">Spor hænder eller Joy-Con-controllere</string>
<string name="glasses_6dof">6DoF virtuel skærm</string>
<string name="glasses_position">Positionssporing — eksperimentel</string>
<string name="glasses_position_hint">Fysisk bevægelse i VR og omkring virtuelle skærme. Genstart af sporing tager et øjeblik.</string>
<string name="glasses_eye_height">Aktuel øjenhøjde over gulv</string>
<string name="glasses_floor">Indstil gulvniveau fra aktuel højde</string>
<string name="glasses_floor_set">Indstil gulvniveau</string>
<string name="glasses_spatial_setup">Opsætning og diagnosticering af stereokamera</string>
<string name="glasses_spatial_setup_hint">Kontroller kamerakalibrering og sporing af hænder før du spiller.</string>
<string name="glasses_spatial_open">Åbn opsætning</string>
<string name="glasses_hands">Controllere, der betjenes med bare hænder</string>
<string name="glasses_hands_hint">Brug smartbrillernes kameraer til at sigte med dine hænder, knib for at klikke og luk din hånd for at gribe i VR. Gamepad-pinde bevæger sig og drejer. Erstatter Joy-Con-positionssporing. Kan forsinke eller miste sporing under spil.</string>
<string name="glasses_hands_ready_hint">Aktiver controllere, der betjenes med bare hænder under Luma Ultra-kameraer i Sporing. Vis åbne hænder først.</string>
<string name="glasses_hands_base">Delt Luma Ultra-kalibrering</string>
<string name="glasses_hands_base_hint">Brug af den delte Luma Ultra-base. Kalibrer dine egne smartbriller for at bekræfte deres nøjagtighed.</string>
<string name="glasses_hands_calibration_verified">Målt kalibrering verificeret</string>
<string name="glasses_hands_calibration_required">Validering påkrævet</string>
<string name="glasses_hands_calibration_required_hint">Controllere, der betjenes med bare hænder, bliver tilgængelige, når den målte afstandskontrol er bestået.</string>
<string name="tracking_recovery_lost">Sporing mistet</string>
<string name="tracking_recovery_look">Se dig langsomt omkring i et godt oplyst rum.\nHold sporingskameraerne fri, og vend dig mod møbler eller andre synlige detaljer.</string>
<string name="tracking_recovery_restart_help">Stadig fast? På din telefon: smarte briller → Sporing → Åbn opsætning → Vis diagnostik → Genstart sporing.</string>
<string name="tracking_recovery_disconnected">Smart-briller afbrudt</string>
<string name="tracking_recovery_connect">Tjek USB-forbindelsen, og tilslut smartbrillerne igen.\nAccepter USB-adgangsprompten på din telefon, hvis den vises.</string>
<string name="tracking_recovery_usb_help">Når du er tilsluttet, skal du se dig langsomt omkring i et godt oplyst rum for at genoprette sporingen.</string>
<string name="tracking_recovery_recenter">Centrering er nødvendig</string>
<string name="tracking_recovery_recenter_steps">Sporingen er vendt tilbage i en ny position.\nSe fremad, og tryk derefter på nulstil sporingscenter på din telefon.</string>
<string name="tracking_recovery_recenter_help">Du kan også finde nulstilling af sporingscenter i smartbriller → Sporing → Åbn opsætning. Din gemte kamerakalibrering gemmes.</string>
<string name="tracking_recovery_stabilizing">Hold stille et øjeblik</string>
<string name="tracking_recovery_hold">Nulstilling af sporingscenter er i gang.\nHold smartbrillerne stabile, mens sporingen stabiliseres.</string>
<string name="tracking_recovery_automatic">Denne skærm lukker automatisk, når hovedsporing er klar.</string>
<string name="glasses_steady_hand_aim">Stabiliseret sigte med hænderne</string>
<string name="glasses_steady_hand_aim_hint">Sigt fra din skulder gennem din håndflade. Slå denne indstilling fra for håndledsstyret rotation.</string>
<string name="spatial_title">Rumlig opsætning</string>
<string name="spatial_wait_tracking">Venter på sporing...</string>
<string name="spatial_cameras_description">Venstre og højre sporingskamera</string>
<string name="spatial_camera_sync">Kamerasynkronisering</string>
<string name="spatial_ready">Klar</string>
<string name="spatial_sync_hint">Venter på, at kamera og hovedsporing er på linje. Hold de smarte briller forbundet.</string>
<string name="spatial_prepare">1. Forbered kalibreringsmålet</string>
<string name="spatial_print_actual">Udskriv i faktisk størrelse</string>
<string name="spatial_print_hint">Hver firkant skal måle 30 mm (1.181 in). Monter pladen fladt på en stiv overflade i et godt oplyst område.</string>
<string name="spatial_print">Udskriv kalibreringsmønster</string>
<string name="spatial_capture_group">2. Optag forskellige visninger</string>
<string name="spatial_board_stationary">Hold kalibreringsmålet stationært</string>
<string name="spatial_capture_hint">Flyt de smarte briller tættere på, længere og sidelæns. Drej dem til venstre/højre, vinkle dem op/ned, og vip dem til siden. Behold kalibreringsmålet i begge kameraer, og hold en kort pause før hver optagelse.</string>
<string name="spatial_start">Start ny kalibrering</string>
<string name="spatial_wait_sync">Venter på kamerasynkronisering.</string>
<string name="spatial_start_hint">Start en ny kalibrering for at indsamle visninger.</string>
<string name="spatial_views_hint">Hold kalibreringsmålet synligt i begge forhåndsvisninger. Optag mindst 24 skarpe billeder fra forskellige vinkler.</string>
<string name="spatial_capturing">Optager...</string>
<string name="spatial_capture">Optag kalibreringsmålvisning</string>
<string name="spatial_alignment">Beregn kamerajustering</string>
<string name="spatial_calculating">Beregner...</string>
<string name="spatial_calculate">Beregn kalibrering</string>
<string name="spatial_distance_group">3. Kontroller afstanden</string>
<string name="spatial_distance">Målt afstand</string>
<string name="spatial_distance_hint">Start med 12 tommer eller 30 cm, så de trykte markører er nemmere at opdage. Vælg den nøjagtige måling, du vil bruge.</string>
<string name="spatial_camera_board_distance">Venstre kamera til kalibreringsmålcenter</string>
<string name="spatial_checking_distance">Kontrollerer afstand...</string>
<string name="spatial_validate">Valider afstand</string>
<string name="spatial_diagnostics">Diagnostik</string>
<string name="spatial_show_diagnostics">Vis diagnostik</string>
<string name="spatial_diagnostics_hint">Værktøjer til gendannelse af kameratiming og sporing.</string>
<string name="spatial_camera_timing">Kameratiming</string>
<string name="spatial_hand_input">Input fra sporing af hænder</string>
<string name="spatial_wait_timing">Venter på kameratiming.</string>
<string name="spatial_frames">Kamerabilleder</string>
<string name="spatial_gpu">GPU-detektering af hænder</string>
<string name="spatial_gpu_hint">CPU er standard. GPU er eksperimentel og kan få SteamVR til at genstarte. Dette valg nulstilles, når sporingen genstartes.</string>
<string name="spatial_lite">Lite-detektion af håndmodeller</string>
<string name="spatial_lite_hint">Brug Lite håndfladedetektor og Lite nøglepunkter på hænderne. Tjek sigte-, klem- og grebsnøjagtighed. Nulstiller, når sporing genstartes.</string>
<string name="spatial_adaptive">Automatisk indgangsfrekvens til sporing af hænder</string>
<string name="spatial_adaptive_hint">Juster input til behandlingshastighed. Nulstiller, når sporing genstartes.</string>
<string name="spatial_half_rate">Begræns inputfrekvens for sporing af hænder</string>
<string name="spatial_half_rate_hint">Brug halvdelen af kamerabillederne. Sluk for at sammenligne den maksimale frekvens. Nulstiller, når sporing genstartes.</string>
<string name="spatial_diagnostic_capture">Diagnostisk optagelse</string>
<string name="spatial_diagnostic_capture_hint">Gem ét stereobilledpar lokalt. Billeder gemmes kun efter anmodning.</string>
<string name="spatial_save_frame">Gem kamerabillede</string>
<string name="spatial_restart_tracking">Genstart sporing</string>
<string name="spatial_restart_hint">Bruges, hvis sporingen ikke genoprettes i et godt oplyst område. En genstart kræver en ny sporingsoprindelse.</string>
<string name="spatial_restart">Genstart</string>
<string name="spatial_restart_title">Genstart sporing?</string>
<string name="spatial_restart_confirm">Ikke-gemte kalibreringsvisninger vil gå tabt. Hold smartbrillerne stille bagefter, og nulstil sporingscenteret, når sporingen er genoprettet.</string>
<string name="spatial_views_count">%1$d / 24 visninger taget</string>
<string name="spatial_capture_paused">Optagelse sat på pause: %1$s</string>
<string name="spatial_measure_hint">Mål %1$s. Vis det komplette kalibreringsmål i begge forhåndsvisninger, og hold derefter stille.</string>
<string name="spatial_timing_values">%1$.1f Hz • billedalder %2$.1f ms • behandler %3$.1f ms</string>
<string name="spatial_input_values">Inputgrænse %1$.1f Hz • %2$.0f%% for kamerabilleder. Hovedsporing fortsætter ved sin maksimale frekvens.</string>
<string name="spatial_frame_counts">%1$d behandlet • %2$d droppet • %3$d sprunget over af inputgrænse</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck - Luma Ultra kalibrering</string>
<string name="spatial_print_scale" formatted="false">Udskriv ved 100% / faktisk størrelse. Hver firkant: 30 mm (1.181 tommer).</string>
<string name="spatial_print_stationary">Hold kalibreringsmålet fladt og stationært; flyt de smarte briller mellem optagelserne.</string>
<string name="spatial_print_check">Skalakontrol: 100 mm (3.937 in) mellem kryds</string>
<string name="spatial_distance_12in">12 tommer (30,48 cm)</string>
<string name="spatial_distance_30cm">30 cm (11,81 tommer)</string>
<string name="spatial_distance_20in">20 tommer (50,8 cm)</string>
<string name="spatial_distance_50cm">50 cm (19,69 tommer)</string>
<string name="spatial_disabled">Deaktiveret</string>
<string name="spatial_wait_stable">Venter på stabil sporing</string>
<string name="spatial_disconnected">Afbrudt</string>
<string name="spatial_hands_off">Controllere, der betjenes med bare hænder, er slukket</string>
<string name="spatial_tracking_off">Positionssporing er slået fra, eller smartbriller er afbrudt</string>
<string name="spatial_recenter_pending">Nulstilling af sporingscenter anmodes — hold stille, mens sporingen stabiliserer sig.</string>
<string name="spatial_tracking_lost">Sporing er gået tabt – vend ind mod et godt oplyst område med synlige detaljer</string>
<string name="spatial_origin_changed">Sporingsoprindelse ændret — tryk på nulstil sporingscenter, og hold derefter stille.</string>
<string name="spatial_active">6DoF aktiv</string>
<string name="spatial_connect_hint">Tilslut Luma Ultra og aktiver positionssporing.</string>
<string name="spatial_validation_session">Kalibrering verificeret for denne forbindelse.</string>
<string name="spatial_validation_saved">Kalibrering verificeret og gemt.</string>
<string name="spatial_wait_cameras">Venter på stereokameraer</string>
<string name="spatial_wait_hands">Venter på sporing af hænder</string>
<string name="spatial_collect_instructions">Hold 5 × 7 kalibreringsmålet stationært. Optag 24+ visninger, mens du flytter og roterer de smarte briller.</string>
<string name="spatial_capture_pending">Venter på det næste kamerabillede. Hold de smarte briller stille.</string>
<string name="spatial_validation_pending">Kontrollerer det næste kamerabillede. Hold hele kalibreringsmålet synligt, og hold det stille.</string>
<string name="spatial_start_first">Start kalibreringen først</string>
<string name="spatial_no_camera_frame">Intet nyt kamerabillede ankom. Tjek forbindelsen til smarte briller og forhåndsvisninger af kameraet, og prøv derefter igen.</string>
<string name="spatial_clock_reset">Nulstilling af kameraur</string>
<string name="spatial_frame_changed">Sporingskoordinatsystem ændret. Dine visninger gemmes til genberegning. Nulstil sporingscenter, og start derefter et nyt sæt for at optage flere visninger.</string>
<string name="spatial_device_loaded">enhedskalibrering indlæst</string>
<string name="spatial_base_loaded">Luma Ultra base kalibrering indlæst</string>
<string name="spatial_calibration_needed">kalibrering påkrævet</string>
<string name="spatial_tap_start">Tryk på Start ny kalibrering først.</string>
<string name="spatial_capture_recenter_pending">Nulstil sporingscenter venter på stabil sporing. Hold stille et øjeblik.</string>
<string name="spatial_capture_recenter">Sporing ændret. Tryk på nulstil sporingscenter én gang, og hold derefter stille, før du optager.</string>
<string name="spatial_capture_unsynchronized">Optagelse afvist: Hovedsporingen var ikke synkroniseret, eller den ændrede sig under optagelsen. Hold stille og prøv igen.</string>
<string name="spatial_align_first">Beregn kamerajustering, før du kontrollerer afstanden.</string>
<string name="spatial_processing_failed">kamerabehandling mislykkedes</string>
<string name="spatial_hand_head_changed">Hovedsporing ændret under registrering af hænder</string>
<string name="spatial_hand_wait_sync">Venter på kamera- og hovedsynkronisering</string>
<string name="spatial_no_wrists">Ingen håndled opdaget. Hold dine Joy-Con-controllerhåndtag foran de smarte briller</string>
<string name="spatial_no_hands">Ingen hænder fundet. Vis åbne håndflader foran de smarte briller</string>
<string name="spatial_one_camera_wrists">Håndled synlige i kun ét kamera; hold dine controllerhåndtag i den delte visning</string>
<string name="spatial_one_camera_hands">Hænderne er kun synlige i ét kamera; hold begge håndflader i den delte visning</string>
<string name="spatial_ambiguous_hands">Begge kameraer ser hænder, men venstre/højre-matchning er tvetydig</string>
<string name="spatial_inconsistent_depth">Hænder registreret, men stereodybden er inkonsekvent</string>
<string name="spatial_rejected_wrist">Håndledsposition afvist af stereo- eller bevægelsestjek</string>
<string name="spatial_inconsistent_palm">Stereopunkter registreret, men palmegeometrien er inkonsekvent</string>
<string name="spatial_hands_need_calibration">Fuldfør kalibrering og afstandskontrol for at aktivere hænder</string>
<string name="spatial_hands_wait_stable">Venter på stabil hovedsporing; nulstil sporingscenter, hvis det anmodes om.</string>
<string name="spatial_old_frame">Kameraobservation er for gammel; venter på et nyt par</string>
<string name="spatial_fit_ready">Tilpas %1$.2f px. Mål din valgte afstand fra venstre kamera til kalibreringsmålets centrum, og valider derefter.</string>
<string name="spatial_opencv_error">OpenCV utilgængelig: %1$s</string>
<string name="spatial_restored">%1$d gemte visninger gendannet. Beregn kalibrering for at prøve igen, eller start et nyt sæt for at fange mere.</string>
<string name="spatial_captured">Optaget %1$d / 24 visninger. Hold kalibreringsmålet fast; flyt de smarte briller til næste visning.</string>
<string name="spatial_capture_corners">Optagelse afvist: %1$d / 12 påkrævede hjørner synlige i begge kameraer. Vis mere af kalibreringsmålet, hold stille, og prøv derefter igen.</string>
<string name="spatial_validation_error">Afstandskontrol kunne ikke afsluttes: %1$s. Prøv igen.</string>
<string name="spatial_inference_error">Registrering af hænder utilgængelig: %1$s. Skift CPU/GPU eller skift controllere betjent med bare hænder for at prøve igen.</string>
<string name="spatial_hand_timeout">Registrering af hænder overskredet %1$d ms; observation forkastet</string>
<string name="spatial_wrists_tracked">%1$d / 2 Joy-Con håndledspositioner målt ved stereo</string>
<string name="spatial_palms_tracked">Håndflader sporet: %1$d / 2; knibebevægelse klar: %2$d; gribebevægelse klar: %3$d</string>
<string name="spatial_capture_saved">Gemt diagnostisk optagelse i %1$s</string>
<string name="spatial_hand_error">Sporing af hænder stoppet: %1$s</string>
<string name="spatial_stereo_loaded">Stereo %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">Hovedsporing og kalibreringsmålet er uenige med %1$s (grænse 5 cm / 2 in). Hold kalibreringsmålet fast, og vent på konstant sporing, før du starter et nyt sæt. Drej og vip de smarte briller forsigtigt; pause før hver optagelse.</string>
<string name="spatial_need_views">Optag mindst 24 forskellige visninger af det stationære kalibreringsmål.</string>
<string name="spatial_need_rotation">Drej de smarte briller til venstre og højre, og vip dem op og ned; bevægelse uden rotation kan ikke kalibrere kamerajustering.</string>
<string name="spatial_bad_baseline">Usandsynlig kameragrundlinje</string>
<string name="spatial_need_orientations">Bevæg dit hoved gennem mere varierede orienteringer.</string>
<string name="spatial_bad_rotation">Ugyldig kamera-til-hoved-rotation</string>
<string name="spatial_bad_alignment">Kamera-til-hoved justering er inkonsekvent.</string>
<string name="spatial_calibration_error">Kalibrering kunne ikke afsluttes. %1$s Dine optagne visninger gemmes.</string>
<string name="spatial_reprojection_error">Udholdt reprojektionsfejl %1$.2f px overstiger 2 px. Kalibrer igen.</string>
<string name="spatial_distance_corners">%1$d / 24 skakternet hjørner registreret i begge kameraer. Hold hele kalibreringsmålet synligt, og hold det stille. Hvis det allerede er synligt, skal du vælge 12 tommer eller 30 cm og måle den tættere afstand, før du prøver igen.</string>
<string name="spatial_distance_depth">Kalibreringsmålet er synligt, men dets dybde kunne ikke måles pålideligt. Hold de smarte briller stille og hold kalibreringsmålet fladt og godt oplyst, og prøv derefter igen.</string>
<string name="spatial_distance_invalid">Kameramålingen var ugyldig. Hold stille og prøv igen.</string>
<string name="spatial_distance_passed">Afstandskontrol bestået.</string>
<string name="spatial_distance_retry">Kontroller igen afstanden fra venstre kamera til kalibreringsmålets centrum, og prøv derefter igen.</string>
<string name="spatial_distance_result">Kameraer målt %1$s; valgt afstand %2$s. %3$s</string>
<string name="spatial_wait_previews">Vent på forhåndsvisning af begge kameraer, før du starter kalibreringen.</string>
<string name="spatial_saved_views_hint">Gemte visninger kan genberegnes. Start en ny kalibrering for at fange i det aktuelle sporingskoordinatsystem.</string>
<string name="spatial_model_checksum_error">Håndmodel kontrolsum uoverensstemmelse</string>
<string name="spatial_opencv_load_error">OpenCV kunne ikke indlæse</string>
</resources>
+214 -10
View File
@@ -2352,14 +2352,14 @@
<string name="glasses_display">Anzeige</string>
<string name="glasses_refresh">Bildwiederholrate</string>
<string name="glasses_3d">3D (Side-by-Side)</string>
<string name="glasses_3d_hint">Jedes Auge bekommt seine eigene Hälfte eines 3840×1080-Bildes. Fällt das Bild aus, halte die 2D/3D-Taste der Brille gedrückt.</string>
<string name="glasses_3d_hint">Schalten Sie die Datenbrille auf Side-by-Side-3D um. Das Headtracking wird separat gesteuert.</string>
<string name="glasses_3d_failed">Das 3D-Signal wurde nicht bestätigt. Versuche es erneut, sobald das 2D-Bild wieder da ist.</string>
<string name="glasses_no_signal">Kein Bild: Die Brille ist in einem Modus, den dieses Smartphone nicht ausgeben kann. Halte ihre 2D/3D-Taste gedrückt oder steck sie aus und wieder ein.</string>
<string name="glasses_3d_refresh">3D-Bildwiederholrate</string>
<string name="glasses_auto3d">3D für VR-Spiele</string>
<string name="glasses_auto3d_hint">Wechselt zu 3D, solange SteamVR läuft, und danach zurück.</string>
<string name="glasses_picture">3D-Bild</string>
<string name="glasses_picture_hint">Automatisch: SteamVR füllt die Sicht; andere Spiele schweben in 3D als Bildschirm und bleiben in 2D flach.</string>
<string name="glasses_picture">Bild</string>
<string name="glasses_picture_hint">Automatisch zeigt flache Inhalte in beiden Augen an und erkennt SteamVR. Wählen Sie „Side-by-Side-Spiel“ für SBS-Inhalte.</string>
<string name="glasses_picture_auto">Automatisch</string>
<string name="glasses_picture_screen">Virtueller Bildschirm</string>
<string name="glasses_picture_sbs">Side-by-Side-Spiel</string>
@@ -2369,20 +2369,20 @@
<string name="glasses_screen_no_output">Diese Einstellungen gelten, solange „Spiel auf der Brille“ aktiviert ist und das Spiel dort angezeigt wird.</string>
<string name="glasses_screen_flat_hint">„Automatisch“ zeigt dieses 2D-Spiel im Vollbild. Nutze einen virtuellen Bildschirm, um Breite, Abstand und Kopf-Tracking anzupassen.</string>
<string name="glasses_screen_use">Virtuellen Bildschirm nutzen</string>
<string name="glasses_screen_direct_hint">SteamVR füllt die Sicht. Passe unten das Sichtfeld an; Breite und Abstand gelten nur für einen virtuellen Bildschirm.</string>
<string name="glasses_screen_direct_hint">SteamVR füllt die Ansicht. Passen Sie das Sichtfeld in VR an. Breite und Abstand gelten nur für einen virtuellen Bildschirm.</string>
<string name="glasses_screen_sbs_hint">Side-by-Side-Spiele füllen jedes Auge. Breite und Abstand gelten nur für einen virtuellen Bildschirm.</string>
<string name="glasses_tracking_flat_hint">„Automatisch“ hält dieses 2D-Spiel fest im Blickfeld. Wähle unter „Anzeige“ die Option „Virtueller Bildschirm“, um Kopf-Tracking zu nutzen.</string>
<string name="glasses_tracking_sbs_hint">Kopf-Tracking gilt für einen virtuellen Bildschirm oder ein laufendes SteamVR-Spiel.</string>
<string name="glasses_hardware">Brille</string>
<string name="glasses_hardware">Gerät</string>
<string name="glasses_brightness">Helligkeit</string>
<string name="glasses_volume">Lautstärke</string>
<string name="glasses_sunblock">Sonnenschutz</string>
<string name="glasses_sunblock_hint">Dunkelt die vorderen Gläser ab.</string>
<string name="glasses_tracking">Kopf-Tracking</string>
<string name="glasses_tracking">Verfolgung</string>
<string name="glasses_head_tracking">Kopf-Tracking</string>
<string name="glasses_head_tracking_hint">Die eigenen Sensoren der Brille drehen den virtuellen Bildschirm und SteamVR mit deinem Kopf mit.</string>
<string name="glasses_recenter">Ansicht zentrieren</string>
<string name="glasses_recenter_button">Zentrieren</string>
<string name="glasses_recenter_button">Bildschirm neu zentrieren</string>
<string name="glasses_vr_fov">SteamVR-Sichtfeld</string>
<string name="glasses_vr_fov_hint">Niedriger holt SteamVR näher heran und füllt die Brille; höher zeigt mehr von deiner Umgebung.</string>
<string name="glasses_playing">Spiel läuft auf %1$s</string>
@@ -2403,8 +2403,8 @@
<string name="glasses_calibration_reset_hint">Gespeicherte Kalibrierung entfernen und die Standardwerte des Modells wiederherstellen? Zentriere die Ansicht, nachdem du die Brille wieder aufgesetzt hast.</string>
<string name="glasses_calibration_reset_button">Zurücksetzen</string>
<string name="hand_camera_group">Hand-Tracking</string>
<string name="hand_camera_prompt_title">Deine Hände mit der Kamera verfolgen?</string>
<string name="hand_camera_prompt_body">Die Kamera sieht deine Hände. Ist kein Controller verbunden, werden deine bloßen Hände zu den VR-Händen: Daumen und Zeigefinger zusammendrücken zum Klicken, die Hand schließen zum Greifen, und für das Menü die Handfläche zu dir drehen und zusammendrücken. Mit Joy-Cons setzt sie sie dorthin, wo deine Hände wirklich sind. Bilder werden auf diesem Gerät verarbeitet und nie gespeichert oder gesendet.</string>
<string name="hand_camera_prompt_title">Hände oder Joy-Con-Controller mit der Telefonkamera verfolgen?</string>
<string name="hand_camera_prompt_body">Wenn kein Controller angeschlossen ist, verfolgt die Telefonkamera Ihre bloßen Hände in VR: Drücken Sie Daumen und Zeigefinger, um zu klicken, schließen Sie Ihre Hand, um zu greifen, und drehen Sie Ihre Handfläche in Ihre Richtung und drücken Sie, um das Menü zu öffnen. Bei gekoppelten Joy-Con-Controllern verfolgt die Kamera ihre Position, während die Joy-Con-Controller Drehungen und Tasteneingaben ermöglichen. Bilder werden auf diesem Gerät verarbeitet und niemals gespeichert oder gesendet.</string>
<string name="hand_camera_rear">Rückkamera</string>
<string name="hand_camera_rear_hint">Empfohlen. Meist schärfer und weitwinkliger.</string>
<string name="hand_camera_front">Frontkamera</string>
@@ -2412,7 +2412,7 @@
<string name="hand_camera_allow">Kamera verwenden</string>
<string name="hand_camera_not_now">Nicht jetzt</string>
<string name="hand_camera_setting">Hand-Tracking per Kamera</string>
<string name="hand_camera_setting_hint">Ist kein Controller verbunden, werden deine Hände zu den VR-Händen: zusammendrücken zum Klicken, Handfläche zu dir und zusammendrücken für das Menü. Mit Joy-Cons platziert sie diese dort, wo die Kamera sie sieht. Bilder bleiben auf diesem Gerät.</string>
<string name="hand_camera_setting_hint">Verwenden Sie die Smartphone-Kamera für Gesten mit bloßen Händen oder zur Positionierung gekoppelter Joy-Con-Controller in VR. Die Joy-Con-Controller ermöglichen Drehung und Tasteneingaben. Die Verfolgung wird pausiert, während die Smartbrille Ihre Hände oder Joy-Con-Controller verfolgt. Bilder werden nur auf diesem Gerät verarbeitet.</string>
<string name="hand_camera_choice">Kamera</string>
<string name="hand_camera_access">Kamerazugriff</string>
<string name="hand_camera_access_hint">Erlaube den Kamerazugriff, damit das Hand-Tracking starten kann.</string>
@@ -2680,4 +2680,208 @@
<string name="boot_movie_converting_started">%1$s wird für Steam umgewandelt. DroidDeck geöffnet lassen.</string>
<string name="boot_movie_unsupported">Dieses Gerät kann das Video nicht dekodieren.</string>
<string name="boot_movie_import_failed">Das Video konnte nicht importiert werden</string>
<string name="glasses_experimental">Luma Ultra-Kameras – Experimentell</string>
<string name="glasses_joycons">Joy-Con-Positionsverfolgung</string>
<string name="glasses_joycons_hint">Positionieren Sie Ihre gekoppelten Joy-Con-Controller mithilfe der Kameras Ihrer Smartbrille. Drehung und Tasteneingaben erfolgen über die Joy-Con-Controller. Die Position wird geschätzt, wenn Ihre Handgelenke verdeckt sind. Ersetzt Controller, die mit bloßen Händen bedient werden, und pausiert die Verfolgung per Smartphone-Kamera. Richten Sie die Joy-Con-Controller nach vorn, wenn Sie die Verbindung herstellen oder das Tracking-Center zurücksetzen.</string>
<string name="glasses_joycons_requires_position">Aktivieren Sie zuerst die Positionsverfolgung. Erfordert bewegungsfähige Joy-Con-Controller auf Android 12 oder höher. Standardmäßig deaktiviert.</string>
<string name="setup_tab_glasses">Intelligente Brille</string>
<string name="setup_glasses_scope">Anzeige- und Head-Tracking-Einstellungen werden in allen DroidDeck-Menüs, Spielen und Desktop-Sitzungen gemeinsam genutzt.</string>
<string name="setup_glasses_connect">Schließen Sie Datenbrillen an, um deren Anzeige- und Tracking-Einstellungen zu ändern. Ihre gespeicherten Einstellungen bleiben erhalten, wenn Sie die Verbindung trennen.</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">Anzeigemodus</string>
<string name="glasses_output_2d">2D-Ausgabe</string>
<string name="glasses_output_3d">3D-Ausgabe</string>
<string name="glasses_picture_group">Bild und virtueller Bildschirm</string>
<string name="glasses_position_group">Position und Bodenhöhe</string>
<string name="glasses_hands_group">Verfolgung von Händen – Experimentell</string>
<string name="glasses_tracking_status">Tracking-Status</string>
<string name="glasses_resolution_2d">2D-Auflösung</string>
<string name="glasses_resolution_3d">3D-Auflösung</string>
<string name="glasses_resolution_vr">VR-Auflösung</string>
<string name="glasses_resolution_auto">Automatisch</string>
<string name="glasses_resolution_inherit">Identisch mit 2D/3D</string>
<string name="glasses_resolution_hint">Ausgabeauflösung pro Auge.</string>
<string name="glasses_refresh_30_hint">Aktivieren Sie „Spiel auf Smart Glasses“, um den nativen 30-Hz-Modus des Displays auszuwählen.</string>
<string name="glasses_3dof">Virtueller 3DoF-Bildschirm</string>
<string name="glasses_3dof_hint">Verankern Sie flache Spiele vor sich, damit Sie sich umschauen können. Ermöglicht das Spielen auf Smart Glasses, ohne auf 3D umzuschalten.</string>
<string name="glasses_screen_app_wide">Breite und Abstand aktualisieren den verfolgten virtuellen Bildschirm für DroidDeck-Menüs, Spiele und Desktop-Sitzungen sofort.</string>
<string name="glasses_head_tracking_spatial_vr_hint">Die Positionsverfolgung umfasst die Kopfrotation in SteamVR. Für eine reine Rotationsverfolgung schalten Sie die Positionsverfolgung aus.</string>
<string name="glasses_head_tracking_spatial_hint">Steuert die Kopfbewegung auf dem virtuellen Bildschirm. Die Positionsverfolgung umfasst die Kopfdrehung in SteamVR.</string>
<string name="glasses_quick_actions">Schnelle Aktionen</string>
<string name="glasses_recenter_screen_hint">Richten Sie Ihren Blick auf die gewünschte Position des Bildschirms und setzen Sie anschließend das Tracking-Center zurück, um Höhe und Ausrichtung festzulegen.</string>
<string name="glasses_recenter_joycons_button">Joy-Con neu zentrieren</string>
<string name="phone_camera_tracking_group">Telefonkamera – Experimentell</string>
<string name="phone_camera_tracking_setting">Track-Hände oder Joy-Con-Controller</string>
<string name="glasses_6dof">6DoF virtueller Bildschirm</string>
<string name="glasses_position">Positionsverfolgung – experimentell</string>
<string name="glasses_position_hint">Physische Bewegung in VR und auf virtuellen Bildschirmen. Der Neustart des Trackings dauert einen Moment.</string>
<string name="glasses_eye_height">Aktuelle Augenhöhe über dem Boden</string>
<string name="glasses_floor">Bodenhöhe ausgehend von der aktuellen Höhe einstellen</string>
<string name="glasses_floor_set">Bodenniveau einstellen</string>
<string name="glasses_spatial_setup">Einrichtung und Diagnose der Stereokamera</string>
<string name="glasses_spatial_setup_hint">Überprüfen Sie vor dem Spielen die Kamerakalibrierung und das Tracking der Hände.</string>
<string name="glasses_spatial_open">Setup öffnen</string>
<string name="glasses_hands">Controller werden mit bloßen Händen bedient</string>
<string name="glasses_hands_hint">Verwenden Sie die Smart-Brillen-Kameras, um mit Ihren Händen zu zielen, drücken Sie, um zu klicken, und schließen Sie Ihre Hand, um in VR zu greifen. Gamepad-Sticks bewegen und drehen sich. Ersetzt die Joy-Con-Positionsverfolgung. Während des Spielens kann es zu Verzögerungen oder Trackingverlusten kommen.</string>
<string name="glasses_hands_ready_hint">Aktivieren Sie Controller, die mit bloßen Händen unter Luma Ultra-Kameras in Tracking bedient werden. Zeigen Sie zuerst offene Hände.</string>
<string name="glasses_hands_base">Gemeinsame Luma Ultra-Kalibrierung</string>
<string name="glasses_hands_base_hint">Verwendung der gemeinsamen Luma Ultra-Basis. Kalibrieren Sie Ihre eigene Datenbrille, um deren Genauigkeit zu überprüfen.</string>
<string name="glasses_hands_calibration_verified">Gemessene Kalibrierung verifiziert</string>
<string name="glasses_hands_calibration_required">Validierung erforderlich</string>
<string name="glasses_hands_calibration_required_hint">Controller, die mit bloßen Händen bedient werden, werden verfügbar, nachdem die gemessene Distanzprüfung bestanden wurde.</string>
<string name="tracking_recovery_lost">Tracking verloren</string>
<string name="tracking_recovery_look">Schauen Sie sich langsam in einem gut beleuchteten Raum um.\nHalten Sie die Tracking-Kameras frei und blicken Sie auf Möbel oder andere sichtbare Details.</string>
<string name="tracking_recovery_restart_help">Immer noch nicht weitergekommen? Auf Ihrem Telefon: Datenbrille → Tracking → Setup öffnen → Diagnose anzeigen → Tracking neu starten.</string>
<string name="tracking_recovery_disconnected">Datenbrille getrennt</string>
<string name="tracking_recovery_connect">Überprüfen Sie die USB-Verbindung und schließen Sie die Datenbrille erneut an.\nAkzeptieren Sie die USB-Zugriffsaufforderung auf Ihrem Telefon, falls diese angezeigt wird.</string>
<string name="tracking_recovery_usb_help">Sobald die Verbindung hergestellt ist, schauen Sie sich langsam in einem gut beleuchteten Raum um, um die Ortung wiederherzustellen.</string>
<string name="tracking_recovery_recenter">Neuzentrierung erforderlich</string>
<string name="tracking_recovery_recenter_steps">Die Verfolgung wurde in einer neuen Position fortgesetzt.\nBlicken Sie nach vorn und tippen Sie anschließend auf Ihrem Smartphone auf „Tracking-Center zurücksetzen“.</string>
<string name="tracking_recovery_recenter_help">Sie können das Tracking-Zentrum auch unter Smart Glasses → Tracking → Einstellungen öffnen zurücksetzen. Ihre gespeicherte Kamerakalibrierung bleibt erhalten.</string>
<string name="tracking_recovery_stabilizing">Halten Sie einen Moment still</string>
<string name="tracking_recovery_hold">Das Zurücksetzen des Tracking-Zentrums wird durchgeführt.\nHalten Sie die Smart Glasses ruhig, bis sich das Tracking stabilisiert hat.</string>
<string name="tracking_recovery_automatic">Dieser Bildschirm wird automatisch geschlossen, wenn die Kopfverfolgung bereit ist.</string>
<string name="glasses_steady_hand_aim">Stabilisiertes Zielen mit den Händen</string>
<string name="glasses_steady_hand_aim_hint">Zielen Sie von Ihrer Schulter durch Ihre Handfläche. Ausschalten für handgelenkgesteuerte Drehung.</string>
<string name="spatial_title">Räumliche Einrichtung</string>
<string name="spatial_wait_tracking">Warten auf Tracking…</string>
<string name="spatial_cameras_description">Linke und rechte Trackingkameras</string>
<string name="spatial_camera_sync">Kamerasynchronisation</string>
<string name="spatial_ready">Fertig</string>
<string name="spatial_sync_hint">Warten auf die Ausrichtung von Kamera und Head-Tracking. Halten Sie die Datenbrille verbunden.</string>
<string name="spatial_prepare">1. Bereiten Sie das Kalibrierungsziel vor</string>
<string name="spatial_print_actual">Drucken in Originalgröße</string>
<string name="spatial_print_hint">Jedes Quadrat muss 30 mm (1,181 Zoll) messen. Montieren Sie die Platte flach auf einer festen Oberfläche an einem gut beleuchteten Ort.</string>
<string name="spatial_print">Kalibrierungsmuster drucken</string>
<string name="spatial_capture_group">2. Erfassen Sie verschiedene Ansichten</string>
<string name="spatial_board_stationary">Halten Sie das Kalibrierungsziel stationär</string>
<string name="spatial_capture_hint">Bewegen Sie die Datenbrille näher, weiter und seitwärts. Drehen Sie sie nach links/rechts, neigen Sie sie nach oben/unten und neigen Sie sie zur Seite. Behalten Sie das Kalibrierungsziel in beiden Kameras bei und halten Sie vor jeder Aufnahme kurz inne.</string>
<string name="spatial_start">Neue Kalibrierung starten</string>
<string name="spatial_wait_sync">Warten auf Kamerasynchronisation.</string>
<string name="spatial_start_hint">Starten Sie eine neue Kalibrierung, um Ansichten zu sammeln.</string>
<string name="spatial_views_hint">Lassen Sie das Kalibrierungsziel in beiden Vorschauen sichtbar. Nehmen Sie mindestens 24 scharfe Ansichten aus verschiedenen Blickwinkeln auf.</string>
<string name="spatial_capturing">Aufnahme...</string>
<string name="spatial_capture">Kalibrierungszielansicht erfassen</string>
<string name="spatial_alignment">Kameraausrichtung berechnen</string>
<string name="spatial_calculating">Berechnen…</string>
<string name="spatial_calculate">Kalibrierung berechnen</string>
<string name="spatial_distance_group">3. Überprüfen Sie den Abstand</string>
<string name="spatial_distance">Gemessene Distanz</string>
<string name="spatial_distance_hint">Beginnen Sie mit 12 Zoll oder 30 cm, damit die gedruckten Markierungen leichter zu erkennen sind. Wählen Sie das genaue Maß, das Sie verwenden möchten.</string>
<string name="spatial_camera_board_distance">Linke Kamera zur Kalibrierungszielmitte</string>
<string name="spatial_checking_distance">Abstand prüfen…</string>
<string name="spatial_validate">Distanz validieren</string>
<string name="spatial_diagnostics">Diagnose</string>
<string name="spatial_show_diagnostics">Diagnose anzeigen</string>
<string name="spatial_diagnostics_hint">Tools zur Kamera-Timing- und Tracking-Wiederherstellung.</string>
<string name="spatial_camera_timing">Kamera-Timing</string>
<string name="spatial_hand_input">Eingabe aus der Handverfolgung</string>
<string name="spatial_wait_timing">Warten auf Kamera-Timing.</string>
<string name="spatial_frames">Kamerabilder</string>
<string name="spatial_gpu">GPU-Erkennung von Händen</string>
<string name="spatial_gpu_hint">CPU ist die Standardeinstellung. Die GPU ist experimentell und kann einen Neustart von SteamVR verursachen. Diese Auswahl wird zurückgesetzt, wenn die Verfolgung neu gestartet wird.</string>
<string name="spatial_lite">Lite-Erkennung von Handmodellen</string>
<string name="spatial_lite_hint">Verwenden Sie den Lite-Handflächendetektor und die Lite-Schlüsselpunkte an den Händen. Überprüfen Sie die Ziel-, Klemm- und Greifgenauigkeit. Wird beim Neustart der Verfolgung zurückgesetzt.</string>
<string name="spatial_adaptive">Automatische Eingabefrequenz für die Handverfolgung</string>
<string name="spatial_adaptive_hint">Passen Sie die Eingabe an die Verarbeitungsgeschwindigkeit an. Wird beim Neustart der Verfolgung zurückgesetzt.</string>
<string name="spatial_half_rate">Eingangsfrequenz für die Handverfolgung begrenzen</string>
<string name="spatial_half_rate_hint">Verwenden Sie die Hälfte der Kamerabilder. Schalten Sie diese Option aus, um die maximale Frequenz zu vergleichen. Wird beim Neustart der Verfolgung zurückgesetzt.</string>
<string name="spatial_diagnostic_capture">Diagnoseerfassung</string>
<string name="spatial_diagnostic_capture_hint">Speichern Sie ein Stereobildpaar lokal. Bilder werden nur auf Anfrage gespeichert.</string>
<string name="spatial_save_frame">Kamerabild speichern</string>
<string name="spatial_restart_tracking">Tracking neu starten</string>
<string name="spatial_restart_hint">Verwenden Sie diese Option, wenn die Verfolgung in einem gut beleuchteten Bereich nicht wiederhergestellt werden kann. Ein Neustart erfordert einen neuen Tracking-Ursprung.</string>
<string name="spatial_restart">Neustart</string>
<string name="spatial_restart_title">Tracking neu starten?</string>
<string name="spatial_restart_confirm">Nicht gespeicherte Kalibrierungsansichten gehen verloren. Halten Sie die Smart Glasses anschließend ruhig und setzen Sie das Tracking-Zentrum zurück, sobald das Tracking wiederhergestellt ist.</string>
<string name="spatial_views_count">%1$d / 24 Aufrufe erfasst</string>
<string name="spatial_capture_paused">Aufnahme angehalten: %1$s</string>
<string name="spatial_measure_hint">Messen Sie %1$s. Zeigen Sie das vollständige Kalibrierungsziel in beiden Vorschauen an und halten Sie dann still.</string>
<string name="spatial_timing_values">%1$.1f Hz • Bildalter %2$.1f ms • Verarbeitung %3$.1f ms</string>
<string name="spatial_input_values">Eingabegrenze %1$.1f Hz • %2$.0f%% der Kamerabilder. Die Kopfverfolgung wird mit maximaler Frequenz fortgesetzt.</string>
<string name="spatial_frame_counts">%1$d verarbeitet • %2$d gelöscht • %3$d durch Eingabelimit übersprungen</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck – Luma Ultra-Kalibrierung</string>
<string name="spatial_print_scale" formatted="false">Drucken mit 100 % / Tatsächliche Größe. Jedes Quadrat: 30 mm (1,181 Zoll).</string>
<string name="spatial_print_stationary">Halten Sie das Kalibrierungsziel flach und stationär; Bewegen Sie die Datenbrille zwischen den Aufnahmen.</string>
<string name="spatial_print_check">Maßstabsprüfung: 100 mm (3,937 Zoll) zwischen den Teilstrichen</string>
<string name="spatial_distance_12in">12 Zoll (30,48 cm)</string>
<string name="spatial_distance_30cm">30 cm (11,81 Zoll)</string>
<string name="spatial_distance_20in">20 Zoll (50,8 cm)</string>
<string name="spatial_distance_50cm">50 cm (19,69 Zoll)</string>
<string name="spatial_disabled">Deaktiviert</string>
<string name="spatial_wait_stable">Warten auf stabiles Tracking</string>
<string name="spatial_disconnected">Nicht verbunden</string>
<string name="spatial_hands_off">Mit bloßen Händen bediente Controller sind ausgeschaltet</string>
<string name="spatial_tracking_off">Die Positionsverfolgung ist ausgeschaltet oder die Datenbrille ist nicht angeschlossen</string>
<string name="spatial_recenter_pending">Zurücksetzen des Tracking-Zentrums angefordert – halten Sie die Smart Glasses ruhig, bis sich das Tracking stabilisiert hat.</string>
<string name="spatial_tracking_lost">Tracking verloren – richten Sie Ihren Blick auf einen gut beleuchteten Bereich mit sichtbaren Details</string>
<string name="spatial_origin_changed">Tracking-Ursprung geändert – tippen Sie auf „Tracking-Zentrum zurücksetzen“ und halten Sie die Smart Glasses ruhig.</string>
<string name="spatial_active">6DoF aktiv</string>
<string name="spatial_connect_hint">Luma Ultra anschließen und Positionsverfolgung aktivieren.</string>
<string name="spatial_validation_session">Kalibrierung für diese Verbindung überprüft.</string>
<string name="spatial_validation_saved">Kalibrierung überprüft und gespeichert.</string>
<string name="spatial_wait_cameras">Warten auf Stereokameras</string>
<string name="spatial_wait_hands">Warten auf die Ortung der Hände</string>
<string name="spatial_collect_instructions">Halten Sie das 5 × 7-Kalibrierungsziel stationär. Erfassen Sie mehr als 24 Ansichten, während Sie die Datenbrille bewegen und drehen.</string>
<string name="spatial_capture_pending">Warten auf das nächste Kamerabild. Halten Sie die Datenbrille ruhig.</string>
<string name="spatial_validation_pending">Überprüfen des nächsten Kamerabildes. Halten Sie das vollständige Kalibrierungsziel sichtbar und halten Sie es ruhig.</string>
<string name="spatial_start_first">Starten Sie zuerst die Kalibrierung</string>
<string name="spatial_no_camera_frame">Es ist kein neues Kamerabild eingetroffen. Überprüfen Sie die Datenbrillenverbindung und die Kameravorschau und versuchen Sie es dann erneut.</string>
<string name="spatial_clock_reset">Kamerauhr zurückgesetzt</string>
<string name="spatial_frame_changed">Tracking-Koordinatensystem geändert. Ihre Ansichten werden zur Neuberechnung gespeichert. Setzen Sie das Tracking-Zentrum zurück und starten Sie dann eine neue Aufnahme, um weitere Ansichten zu erfassen.</string>
<string name="spatial_device_loaded">Gerätekalibrierung geladen</string>
<string name="spatial_base_loaded">Luma Ultra-Basiskalibrierung geladen</string>
<string name="spatial_calibration_needed">Kalibrierung erforderlich</string>
<string name="spatial_tap_start">Tippen Sie auf „Zuerst neue Kalibrierung starten“.</string>
<string name="spatial_capture_recenter_pending">Das Zurücksetzen des Tracking-Zentrums wartet auf stabiles Tracking. Halten Sie die Smart Glasses kurz ruhig.</string>
<string name="spatial_capture_recenter">Tracking geändert. Tippen Sie einmal auf „Tracking-Zentrum zurücksetzen“ und halten Sie die Smart Glasses vor der Aufnahme ruhig.</string>
<string name="spatial_capture_unsynchronized">Aufnahme abgelehnt: Die Kopfverfolgung war nicht synchron oder hat sich während der Aufnahme geändert. Halten Sie still und versuchen Sie es erneut.</string>
<string name="spatial_align_first">Berechnen Sie die Kameraausrichtung, bevor Sie den Abstand überprüfen.</string>
<string name="spatial_processing_failed">Kameraverarbeitung fehlgeschlagen</string>
<string name="spatial_hand_head_changed">Die Kopfverfolgung wurde während der Handerkennung geändert</string>
<string name="spatial_hand_wait_sync">Warten auf Kamera- und Kopfsynchronisation</string>
<string name="spatial_no_wrists">Keine Handgelenke erkannt. Halten Sie die Griffe Ihres Joy-Con-Controllers vor der Datenbrille</string>
<string name="spatial_no_hands">Keine Hände erkannt. Zeigen Sie offene Handflächen vor der Datenbrille</string>
<string name="spatial_one_camera_wrists">Handgelenke nur in einer Kamera sichtbar; Behalten Sie Ihre Controller-Handles in der freigegebenen Ansicht</string>
<string name="spatial_one_camera_hands">Hände nur in einer Kamera sichtbar; Halten Sie beide Handflächen im gemeinsamen Blickfeld</string>
<string name="spatial_ambiguous_hands">Beide Kameras sehen Hände, aber die Links/Rechts-Zuordnung ist nicht eindeutig</string>
<string name="spatial_inconsistent_depth">Hände erkannt, aber die Stereotiefe ist inkonsistent</string>
<string name="spatial_rejected_wrist">Handgelenksposition wurde durch Stereo- oder Bewegungsprüfungen abgelehnt</string>
<string name="spatial_inconsistent_palm">Stereopunkte erkannt, aber die Handflächengeometrie ist inkonsistent</string>
<string name="spatial_hands_need_calibration">Schließen Sie die Kalibrierung und die Distanzprüfung ab, um die Hände freizugeben</string>
<string name="spatial_hands_wait_stable">Warten auf stabiles Head-Tracking; Tracking-Zentrum bei Bedarf zurücksetzen.</string>
<string name="spatial_old_frame">Kamerabeobachtung ist zu alt; warte auf ein neues Paar</string>
<string name="spatial_fit_ready">%1$.2f px anpassen. Messen Sie den ausgewählten Abstand von der linken Kamera zum Zentrum des Kalibrierungsziels und bestätigen Sie ihn anschließend.</string>
<string name="spatial_opencv_error">OpenCV nicht verfügbar: %1$s</string>
<string name="spatial_restored">%1$d gespeicherte Ansichten wiederhergestellt. Berechnen Sie die Kalibrierung, um es erneut zu versuchen, oder starten Sie einen neuen Satz, um mehr zu erfassen.</string>
<string name="spatial_captured">Aufgenommen %1$d / 24 Aufrufe. Halten Sie das Kalibrierungsziel fest; Bewegen Sie die Datenbrille für die nächste Ansicht.</string>
<string name="spatial_capture_corners">Aufnahme abgelehnt: %1$d / 12 erforderliche Ecken in beiden Kameras sichtbar. Zeigen Sie mehr vom Kalibrierungsziel, halten Sie still und versuchen Sie es dann erneut.</string>
<string name="spatial_validation_error">Entfernungsprüfung konnte nicht abgeschlossen werden: %1$s. Versuchen Sie es erneut.</string>
<string name="spatial_inference_error">Handerkennung nicht verfügbar: %1$s. Wechseln Sie CPU/GPU oder schalten Sie mit bloßen Händen bediente Controller um, um es erneut zu versuchen.</string>
<string name="spatial_hand_timeout">Erkennung von Händen überschreitet %1$d ms; Beobachtung verworfen</string>
<string name="spatial_wrists_tracked">%1$d / 2 Joy-Con-Handgelenkspositionen, gemessen per Stereo</string>
<string name="spatial_palms_tracked">Verfolgte Handflächen: %1$d / 2; Pinch bereit: %2$d; Greifen bereit: %3$d</string>
<string name="spatial_capture_saved">Diagnoseerfassung gespeichert unter %1$s</string>
<string name="spatial_hand_error">Handverfolgung gestoppt: %1$s</string>
<string name="spatial_stereo_loaded">Stereo %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">Head-Tracking und das Kalibrierungsziel stimmen um %1$s nicht überein (Grenzwert 5 cm / 2 Zoll). Halten Sie das Kalibrierungsziel fixiert und warten Sie auf eine stabile Verfolgung, bevor Sie mit einem neuen Satz beginnen. Drehen und neigen Sie die Datenbrille vorsichtig; Machen Sie vor jeder Aufnahme eine Pause.</string>
<string name="spatial_need_views">Erfassen Sie mindestens 24 verschiedene Ansichten des stationären Kalibrierungsziels.</string>
<string name="spatial_need_rotation">Drehen Sie die Datenbrille nach links und rechts und neigen Sie sie nach oben und unten. Eine Bewegung ohne Drehung kann die Kameraausrichtung nicht kalibrieren.</string>
<string name="spatial_bad_baseline">Unplausible Kamerabasislinie</string>
<string name="spatial_need_orientations">Bewegen Sie Ihren Kopf durch verschiedene Ausrichtungen.</string>
<string name="spatial_bad_rotation">Ungültige Kamera-zu-Kopf-Rotation</string>
<string name="spatial_bad_alignment">Die Ausrichtung der Kamera zum Kopf ist inkonsistent.</string>
<string name="spatial_calibration_error">Die Kalibrierung konnte nicht abgeschlossen werden. %1$s Ihre erfassten Ansichten werden gespeichert.</string>
<string name="spatial_reprojection_error">Reprojektionsfehler bei der Validierung %1$.2f px überschreitet 2 px. Neu kalibrieren.</string>
<string name="spatial_distance_corners">%1$d / 24 Schachbrettecken in beiden Kameras erkannt. Halten Sie das vollständige Kalibrierungsziel sichtbar und halten Sie es ruhig. Wenn es bereits sichtbar ist, wählen Sie 12 Zoll oder 30 cm und messen Sie den näheren Abstand, bevor Sie es erneut versuchen.</string>
<string name="spatial_distance_depth">Das Kalibrierungsziel ist sichtbar, seine Tiefe konnte jedoch nicht zuverlässig gemessen werden. Halten Sie die Datenbrille ruhig und halten Sie das Kalibrierungsziel flach und gut beleuchtet. Versuchen Sie es dann erneut.</string>
<string name="spatial_distance_invalid">Die Kameramessung war ungültig. Halten Sie still und versuchen Sie es erneut.</string>
<string name="spatial_distance_passed">Distanzprüfung bestanden.</string>
<string name="spatial_distance_retry">Überprüfen Sie den Abstand von der linken Kamera zur Mitte des Kalibrierungsziels erneut und versuchen Sie es dann erneut.</string>
<string name="spatial_distance_result">Kameras gemessen %1$s; ausgewählte Entfernung %2$s. %3$s</string>
<string name="spatial_wait_previews">Warten Sie auf die Vorschau beider Kameras, bevor Sie mit der Kalibrierung beginnen.</string>
<string name="spatial_saved_views_hint">Gespeicherte Ansichten können neu berechnet werden. Starten Sie eine neue Kalibrierung zur Erfassung im aktuellen Tracking-Koordinatensystem.</string>
<string name="spatial_model_checksum_error">Die Prüfsumme des Handmodells stimmt nicht überein</string>
<string name="spatial_opencv_load_error">OpenCV konnte nicht geladen werden</string>
</resources>
+214 -10
View File
@@ -2366,14 +2366,14 @@
<string name="glasses_display">Οθόνη</string>
<string name="glasses_refresh">Ρυθμός ανανέωσης</string>
<string name="glasses_3d">3D (δίπλα-δίπλα)</string>
<string name="glasses_3d_hint">Κάθε μάτι παίρνει το δικό του μισό μιας εικόνας 3840×1080. Αν η εικόνα χαθεί, κρατήστε πατημένο το κουμπί 2D/3D των γυαλιών.</string>
<string name="glasses_3d_hint">Αλλάξτε τα έξυπνα γυαλιά σε 3D δίπλα-δίπλα. Η παρακολούθηση κεφαλιού ελέγχεται ξεχωριστά.</string>
<string name="glasses_3d_failed">Το σήμα 3D δεν επιβεβαιώθηκε. Δοκιμάστε ξανά μόλις επανέλθει η εικόνα 2D.</string>
<string name="glasses_no_signal">Δεν υπάρχει εικόνα: τα γυαλιά είναι σε λειτουργία που αυτό το τηλέφωνο δεν μπορεί να στείλει. Κρατήστε πατημένο το κουμπί 2D/3D τους ή αποσυνδέστε τα και συνδέστε τα ξανά.</string>
<string name="glasses_3d_refresh">Ρυθμός ανανέωσης 3D</string>
<string name="glasses_auto3d">3D για παιχνίδια VR</string>
<string name="glasses_auto3d_hint">Μεταβαίνει σε 3D όσο εκτελείται το SteamVR και επιστρέφει μετά.</string>
<string name="glasses_picture">Εικόνα 3D</string>
<string name="glasses_picture_hint">Αυτόματο: το SteamVR γεμίζει το οπτικό πεδίο· τα άλλα παιχνίδια αιωρούνται ως οθόνη στο 3D και μένουν επίπεδα στο 2D.</string>
<string name="glasses_picture">Εικόνα</string>
<string name="glasses_picture_hint">Εμφανίζει αυτόματα επίπεδο περιεχόμενο και στα δύο μάτια και ανιχνεύει το SteamVR. Επιλέξτε Side-by-side game για περιεχόμενο SBS.</string>
<string name="glasses_picture_auto">Αυτόματο</string>
<string name="glasses_picture_screen">Εικονική οθόνη</string>
<string name="glasses_picture_sbs">Παιχνίδι δίπλα-δίπλα</string>
@@ -2383,20 +2383,20 @@
<string name="glasses_screen_no_output">Αυτές οι ρυθμίσεις ισχύουν όσο είναι ενεργό το «Παιχνίδι στα γυαλιά» και το παιχνίδι εμφανίζεται εκεί.</string>
<string name="glasses_screen_flat_hint">Το «Αυτόματο» κρατά αυτό το παιχνίδι 2D σε πλήρη οθόνη. Χρησιμοποιήστε εικονική οθόνη για να ρυθμίσετε το πλάτος, την απόσταση και την παρακολούθηση κεφαλιού.</string>
<string name="glasses_screen_use">Χρήση εικονικής οθόνης</string>
<string name="glasses_screen_direct_hint">Το SteamVR γεμίζει όλη την εικόνα. Ρυθμίστε το οπτικό του πεδίο παρακάτω· το πλάτος και η απόσταση ισχύουν μόνο για εικονική οθόνη.</string>
<string name="glasses_screen_direct_hint">Το SteamVR γεμίζει την προβολή. Προσαρμόστε το οπτικό του πεδίο στο VR. Το πλάτος και η απόσταση ισχύουν μόνο για μια εικονική οθόνη.</string>
<string name="glasses_screen_sbs_hint">Τα παιχνίδια δίπλα-δίπλα γεμίζουν το πεδίο κάθε ματιού. Το πλάτος και η απόσταση ισχύουν μόνο για εικονική οθόνη.</string>
<string name="glasses_tracking_flat_hint">Το «Αυτόματο» κρατά αυτό το παιχνίδι 2D σταθερό στο οπτικό πεδίο. Επιλέξτε «Εικονική οθόνη» στην ενότητα «Οθόνη» για να χρησιμοποιήσετε παρακολούθηση κεφαλιού.</string>
<string name="glasses_tracking_sbs_hint">Η παρακολούθηση κεφαλιού ισχύει για εικονική οθόνη ή για παιχνίδι SteamVR που εκτελείται.</string>
<string name="glasses_hardware">Γυαλιά</string>
<string name="glasses_hardware">Συσκευή</string>
<string name="glasses_brightness">Φωτεινότητα</string>
<string name="glasses_volume">Ένταση ήχου</string>
<string name="glasses_sunblock">Σκίαση</string>
<string name="glasses_sunblock_hint">Σκουραίνει τους μπροστινούς φακούς.</string>
<string name="glasses_tracking">Παρακολούθηση κεφαλιού</string>
<string name="glasses_tracking">Παρακολούθηση</string>
<string name="glasses_head_tracking">Παρακολούθηση κεφαλιού</string>
<string name="glasses_head_tracking_hint">Οι αισθητήρες των ίδιων των γυαλιών περιστρέφουν την εικονική οθόνη και το SteamVR μαζί με το κεφάλι σας.</string>
<string name="glasses_recenter">Επανακεντράρισμα προβολής</string>
<string name="glasses_recenter_button">Επανακεντράρισμα</string>
<string name="glasses_recenter_button">Επανακεντράρισμα οθόνης</string>
<string name="glasses_vr_fov">Οπτικό πεδίο SteamVR</string>
<string name="glasses_vr_fov_hint">Χαμηλότερο: το SteamVR έρχεται πιο κοντά και γεμίζει τα γυαλιά· υψηλότερο: βλέπετε περισσότερα γύρω σας.</string>
<string name="glasses_playing">Παίζετε στα %1$s</string>
@@ -2417,8 +2417,8 @@
<string name="glasses_calibration_reset_hint">Να καταργηθεί η αποθηκευμένη βαθμονόμηση και να επανέλθουν οι προεπιλογές του μοντέλου; Κάντε επανακεντράρισμα αφού φορέσετε ξανά τα γυαλιά.</string>
<string name="glasses_calibration_reset_button">Επαναφορά</string>
<string name="hand_camera_group">Παρακολούθηση χεριών</string>
<string name="hand_camera_prompt_title">Να παρακολουθούνται τα χέρια σας με την κάμερα;</string>
<string name="hand_camera_prompt_body">Η κάμερα μπορεί να δει τα χέρια σας. Χωρίς συνδεδεμένο χειριστήριο, τα γυμνά χέρια σας γίνονται τα χέρια VR: ενώστε τον αντίχειρα με τον δείκτη για κλικ, κλείστε το χέρι για να πιάσετε, και για το μενού γυρίστε την παλάμη προς το μέρος σας και ενώστε τα δάχτυλα. Με Joy-Con, τα τοποθετεί εκεί όπου βρίσκονται πραγματικά τα χέρια σας. Οι εικόνες επεξεργάζονται σε αυτή τη συσκευή και δεν αποθηκεύονται ούτε αποστέλλονται ποτέ.</string>
<string name="hand_camera_prompt_title">Παρακολούθηση χεριών ή χειριστηρίων Joy-Con με την κάμερα του τηλεφώνου;</string>
<string name="hand_camera_prompt_body">Χωρίς συνδεδεμένο χειριστήριο, η κάμερα του τηλεφώνου παρακολουθεί τα γυμνά σας χέρια σε εικονική πραγματικότητα: τσιμπήστε τον αντίχειρα και τον δείκτη σας για να κάνετε κλικ, κλείστε το χέρι σας για να το πιάσετε και στρέψτε την παλάμη σας προς το μέρος σας και τσιμπήστε για να ανοίξετε το μενού. Με τα ζευγαρωμένα χειριστήρια Joy-Con, η κάμερα παρακολουθεί τη θέση τους, ενώ τα χειριστήρια Joy-Con παρέχουν περιστροφή και κουμπιά. Οι εικόνες υποβάλλονται σε επεξεργασία σε αυτήν τη συσκευή και δεν αποθηκεύονται ή αποστέλλονται ποτέ.</string>
<string name="hand_camera_rear">Πίσω κάμερα</string>
<string name="hand_camera_rear_hint">Προτείνεται. Συνήθως πιο ευκρινής και ευρύτερη.</string>
<string name="hand_camera_front">Μπροστινή κάμερα</string>
@@ -2426,7 +2426,7 @@
<string name="hand_camera_allow">Χρήση κάμερας</string>
<string name="hand_camera_not_now">Όχι τώρα</string>
<string name="hand_camera_setting">Παρακολούθηση χεριών με κάμερα</string>
<string name="hand_camera_setting_hint">Χωρίς συνδεδεμένο χειριστήριο, τα χέρια σας γίνονται τα χέρια VR: ενώστε τα δάχτυλα για κλικ, και με την παλάμη προς εσάς ενώστε τα για το μενού. Με Joy-Con, τα τοποθετεί εκεί όπου τα βλέπει η κάμερα. Οι εικόνες παραμένουν σε αυτή τη συσκευή.</string>
<string name="hand_camera_setting_hint">Χρησιμοποιήστε την κάμερα του τηλεφώνου για χειρονομίες με γυμνά χέρια ή για να τοποθετήσετε τα συνδεδεμένα χειριστήρια Joy-Con σε εικονική πραγματικότητα. Τα χειριστήρια Joy-Con παρέχουν περιστροφή και εντολές κουμπιών. Παύει ενώ τα έξυπνα γυαλιά παρακολουθούν τα χέρια σας ή τα χειριστήρια Joy-Con. Οι εικόνες υποβάλλονται σε επεξεργασία μόνο σε αυτήν τη συσκευή.</string>
<string name="hand_camera_choice">Κάμερα</string>
<string name="hand_camera_access">Πρόσβαση στην κάμερα</string>
<string name="hand_camera_access_hint">Επιτρέψτε την πρόσβαση στην κάμερα για να ξεκινήσει η παρακολούθηση χεριών.</string>
@@ -2680,4 +2680,208 @@
<string name="boot_movie_converting_started">Μετατροπή του %1$s για το Steam. Αφήστε το DroidDeck ανοιχτό.</string>
<string name="boot_movie_unsupported">Η συσκευή δεν μπορεί να αποκωδικοποιήσει αυτό το βίντεο.</string>
<string name="boot_movie_import_failed">Δεν ήταν δυνατή η εισαγωγή του βίντεο</string>
<string name="glasses_experimental">Κάμερες Luma Ultra — Πειραματικό</string>
<string name="glasses_joycons">Παρακολούθηση θέσης Joy-Con</string>
<string name="glasses_joycons_hint">Χρησιμοποιήστε τις κάμερες των έξυπνων γυαλιών για να τοποθετήσετε τα συνδεδεμένα χειριστήρια Joy-Con. Η περιστροφή και οι εντολές κουμπιών προέρχονται από τα χειριστήρια Joy-Con. Η θέση εκτιμάται όταν οι καρποί σας είναι κρυμμένοι. Αντικαθιστά τα χειριστήρια που λειτουργούν με γυμνά χέρια και διακόπτει την παρακολούθηση της κάμερας του τηλεφώνου. Στρέψτε τα χειριστήρια Joy-Con προς τα εμπρός κατά τη σύνδεση ή την επαναφορά του κέντρου παρακολούθησης.</string>
<string name="glasses_joycons_requires_position">Ενεργοποιήστε πρώτα την παρακολούθηση θέσης. Απαιτούνται χειριστήρια Joy-Con με δυνατότητα κίνησης σε Android 12 ή νεότερη έκδοση. Απενεργοποιημένα από προεπιλογή.</string>
<string name="setup_tab_glasses">Έξυπνα γυαλιά</string>
<string name="setup_glasses_scope">Οι ρυθμίσεις οθόνης και παρακολούθησης κεφαλιού είναι κοινές σε όλα τα μενού, τα παιχνίδια και τις συνεδρίες επιφάνειας εργασίας του DroidDeck.</string>
<string name="setup_glasses_connect">Συνδέστε τα έξυπνα γυαλιά για να αλλάξετε τις ρυθμίσεις οθόνης και παρακολούθησης. Οι αποθηκευμένες ρυθμίσεις σας διατηρούνται όταν αποσυνδεθείτε.</string>
<string name="glasses_vr">VR (Εικονική πραγματικότητα)</string>
<string name="glasses_display_mode">Λειτουργία οθόνης</string>
<string name="glasses_output_2d">Έξοδος 2D</string>
<string name="glasses_output_3d">Έξοδος 3D</string>
<string name="glasses_picture_group">Εικόνα και εικονική οθόνη</string>
<string name="glasses_position_group">Θέση και επίπεδο δαπέδου</string>
<string name="glasses_hands_group">Παρακολούθηση χεριών — Πειραματικό</string>
<string name="glasses_tracking_status">Κατάσταση παρακολούθησης</string>
<string name="glasses_resolution_2d">Ανάλυση 2D</string>
<string name="glasses_resolution_3d">Ανάλυση 3D</string>
<string name="glasses_resolution_vr">Ανάλυση VR</string>
<string name="glasses_resolution_auto">Αυτόματη</string>
<string name="glasses_resolution_inherit">Ίδιο με 2D / 3D</string>
<string name="glasses_resolution_hint">Ανάλυση εξόδου ανά μάτι.</string>
<string name="glasses_refresh_30_hint">Ενεργοποιήστε το Παιχνίδι σε έξυπνα γυαλιά για να επιλέξετε την εγγενή λειτουργία 30 Hz της οθόνης.</string>
<string name="glasses_3dof">Εικονική οθόνη 3DoF</string>
<string name="glasses_3dof_hint">Αγκυρώστε τα επίπεδα παιχνίδια μπροστά σας, ώστε να μπορείτε να κοιτάτε τριγύρω. Ενεργοποιεί το Παιχνίδι σε έξυπνα γυαλιά χωρίς να μεταβείτε σε 3D.</string>
<string name="glasses_screen_app_wide">Το πλάτος και η απόσταση ενημερώνουν αμέσως την εικονική οθόνη που παρακολουθείται για τα μενού, τα παιχνίδια και τις συνεδρίες επιφάνειας εργασίας του DroidDeck.</string>
<string name="glasses_head_tracking_spatial_vr_hint">Η παρακολούθηση θέσης περιλαμβάνει την περιστροφή του κεφαλιού στο SteamVR. Για παρακολούθηση μόνο με περιστροφή, απενεργοποιήστε την παρακολούθηση θέσης.</string>
<string name="glasses_head_tracking_spatial_hint">Ελέγχει την κίνηση του κεφαλιού στην εικονική οθόνη. Η παρακολούθηση θέσης περιλαμβάνει την περιστροφή του κεφαλιού στο SteamVR.</string>
<string name="glasses_quick_actions">Γρήγορες ενέργειες</string>
<string name="glasses_recenter_screen_hint">Κοιτάξτε πού θέλετε την οθόνη και, στη συνέχεια, επαναφέρετε το κέντρο παρακολούθησης για να ορίσετε το ύψος και την κατεύθυνσή της.</string>
<string name="glasses_recenter_joycons_button">Επανακεντράρισμα Joy-Con</string>
<string name="phone_camera_tracking_group">Κάμερα τηλεφώνου — Πειραματικό</string>
<string name="phone_camera_tracking_setting">Παρακολούθηση χεριών ή χειριστηρίων Joy-Con</string>
<string name="glasses_6dof">Εικονική οθόνη 6DoF</string>
<string name="glasses_position">Παρακολούθηση θέσης — πειραματική</string>
<string name="glasses_position_hint">Φυσική κίνηση σε εικονική πραγματικότητα και γύρω από εικονικές οθόνες. Η επανεκκίνηση της παρακολούθησης διαρκεί λίγο χρόνο.</string>
<string name="glasses_eye_height">Τρέχον ύψος ματιών πάνω από το πάτωμα</string>
<string name="glasses_floor">Ορισμός επιπέδου δαπέδου από το τρέχον ύψος</string>
<string name="glasses_floor_set">Ορισμός επιπέδου δαπέδου</string>
<string name="glasses_spatial_setup">Ρύθμιση και διαγνωστικά στερεοσκοπικής κάμερας</string>
<string name="glasses_spatial_setup_hint">Ελέγξτε τη βαθμονόμηση της κάμερας και την παρακολούθηση των χεριών πριν από το παιχνίδι.</string>
<string name="glasses_spatial_open">Άνοιγμα ρύθμισης</string>
<string name="glasses_hands">Χειριστήρια που λειτουργούν με γυμνά χέρια</string>
<string name="glasses_hands_hint">Χρησιμοποιήστε τις κάμερες έξυπνων γυαλιών για να στοχεύσετε με τα χέρια σας, πιέστε για να κάνετε κλικ και κλείστε το χέρι σας για να το πιάσετε σε εικονική πραγματικότητα. Τα sticks του Gamepad κινούνται και περιστρέφονται. Αντικαθιστά την παρακολούθηση θέσης Joy-Con. Ενδέχεται να καθυστερήσει ή να χάσει την παρακολούθηση κατά τη διάρκεια των παιχνιδιών.</string>
<string name="glasses_hands_ready_hint">Ενεργοποιήστε τα χειριστήρια που λειτουργούν με γυμνά χέρια κάτω από τις κάμερες Luma Ultra στην Παρακολούθηση. Εμφανίστε πρώτα τα ανοιχτά χέρια.</string>
<string name="glasses_hands_base">Κοινόχρηστη βαθμονόμηση Luma Ultra</string>
<string name="glasses_hands_base_hint">Χρήση της κοινόχρηστης βάσης Luma Ultra. Βαθμονομήστε τα δικά σας έξυπνα γυαλιά για να επαληθεύσετε την ακρίβειά τους.</string>
<string name="glasses_hands_calibration_verified">Επαληθεύτηκε η μετρούμενη βαθμονόμηση</string>
<string name="glasses_hands_calibration_required">Απαιτείται επικύρωση</string>
<string name="glasses_hands_calibration_required_hint">Τα χειριστήρια που λειτουργούν με γυμνά χέρια γίνονται διαθέσιμα μετά την ολοκλήρωση του ελέγχου της μετρούμενης απόστασης.</string>
<string name="tracking_recovery_lost">Χάθηκε η παρακολούθηση</string>
<string name="tracking_recovery_look">Κοιτάξτε αργά τριγύρω σε ένα καλά φωτισμένο δωμάτιο.\nΔιατηρήστε τις κάμερες παρακολούθησης καθαρές και στραμμένες προς τα έπιπλα ή άλλες ορατές λεπτομέρειες.</string>
<string name="tracking_recovery_restart_help">Ακόμα κολλημένοι; Στο τηλέφωνό σας: έξυπνα γυαλιά → Παρακολούθηση → Άνοιγμα ρύθμισης → Εμφάνιση διαγνωστικών → Επανεκκίνηση παρακολούθησης.</string>
<string name="tracking_recovery_disconnected">Τα έξυπνα γυαλιά αποσυνδέθηκαν</string>
<string name="tracking_recovery_connect">Ελέγξτε τη σύνδεση USB και επανασυνδέστε τα έξυπνα γυαλιά.\nΑποδεχτείτε το μήνυμα προτροπής πρόσβασης USB στο τηλέφωνό σας, εάν εμφανιστεί.</string>
<string name="tracking_recovery_usb_help">Μόλις συνδεθείτε, κοιτάξτε γύρω σας αργά σε ένα καλά φωτισμένο δωμάτιο για να επαναφέρετε την παρακολούθηση.</string>
<string name="tracking_recovery_recenter">Απαιτείται επανακεντράρισμα</string>
<string name="tracking_recovery_recenter_steps">Η παρακολούθηση επέστρεψε σε νέα θέση.\nΚοιτάξτε προς τα εμπρός και, στη συνέχεια, πατήστε επαναφορά κέντρου παρακολούθησης στο τηλέφωνό σας.</string>
<string name="tracking_recovery_recenter_help">Μπορείτε επίσης να βρείτε την επαναφορά του κέντρου παρακολούθησης στα έξυπνα γυαλιά → Παρακολούθηση → Άνοιγμα ρυθμίσεων. Η αποθηκευμένη βαθμονόμηση της κάμερας διατηρείται.</string>
<string name="tracking_recovery_stabilizing">Μείνετε ακίνητοι για μια στιγμή</string>
<string name="tracking_recovery_hold">Η επαναφορά του κέντρου παρακολούθησης βρίσκεται σε εξέλιξη.\nΔιατηρήστε τα έξυπνα γυαλιά σταθερά ενώ η παρακολούθηση σταθεροποιείται.</string>
<string name="tracking_recovery_automatic">Αυτή η οθόνη κλείνει αυτόματα όταν είναι έτοιμη η παρακολούθηση κεφαλής.</string>
<string name="glasses_steady_hand_aim">Σταθεροποιημένη σκόπευση με τα χέρια</string>
<string name="glasses_steady_hand_aim_hint">Στόχευσε από τον ώμο μέσα από την παλάμη σου. Απενεργοποιήστε για περιστροφή ελεγχόμενη από τον καρπό.</string>
<string name="spatial_title">Χωρική ρύθμιση</string>
<string name="spatial_wait_tracking">Αναμονή για παρακολούθηση…</string>
<string name="spatial_cameras_description">Αριστερή και δεξιά κάμερες παρακολούθησης</string>
<string name="spatial_camera_sync">Συγχρονισμός κάμερας</string>
<string name="spatial_ready">Έτοιμο</string>
<string name="spatial_sync_hint">Αναμονή για την ευθυγράμμιση της κάμερας και της παρακολούθησης κεφαλιού. Διατηρήστε τα έξυπνα γυαλιά συνδεδεμένα.</string>
<string name="spatial_prepare">1. Προετοιμάστε τον στόχο βαθμονόμησης</string>
<string name="spatial_print_actual">Εκτύπωση σε πραγματικό μέγεθος</string>
<string name="spatial_print_hint">Κάθε πλευρά του τετραγώνου πρέπει να έχει μήκος 30 mm (1.181 in). Στερεώστε το φύλλο επίπεδα σε μια άκαμπτη επιφάνεια σε καλά φωτισμένο χώρο.</string>
<string name="spatial_print">Εκτύπωση στόχου βαθμονόμησης</string>
<string name="spatial_capture_group">2. Καταγράψτε διαφορετικές προβολές</string>
<string name="spatial_board_stationary">Διατηρήστε σταθερό τον στόχο βαθμονόμησης</string>
<string name="spatial_capture_hint">Μετακινήστε τα έξυπνα γυαλιά πιο κοντά, πιο μακριά και στο πλάι. Γυρίστε τα αριστερά/δεξιά, γωνίστε τα πάνω/κάτω και γείρετε τα προς τα πλάγια. Διατηρήστε τον στόχο βαθμονόμησης και στις δύο κάμερες και κάντε μια σύντομη παύση πριν από κάθε λήψη.</string>
<string name="spatial_start">Έναρξη νέας βαθμονόμησης</string>
<string name="spatial_wait_sync">Αναμονή για συγχρονισμό κάμερας.</string>
<string name="spatial_start_hint">Ξεκινήστε μια νέα βαθμονόμηση για τη συλλογή προβολών.</string>
<string name="spatial_views_hint">Διατηρήστε τον στόχο βαθμονόμησης ορατός και στις δύο προεπισκοπήσεις. Καταγράψτε τουλάχιστον 24 ευκρινείς προβολές από διαφορετικές γωνίες.</string>
<string name="spatial_capturing">Λήψη…</string>
<string name="spatial_capture">Λήψη προβολής στόχου βαθμονόμησης</string>
<string name="spatial_alignment">Υπολογισμός ευθυγράμμισης κάμερας</string>
<string name="spatial_calculating">Υπολογισμός…</string>
<string name="spatial_calculate">Υπολογισμός βαθμονόμησης</string>
<string name="spatial_distance_group">3. Ελέγξτε την απόσταση</string>
<string name="spatial_distance">Μετρημένη απόσταση</string>
<string name="spatial_distance_hint">Ξεκινήστε με 12 ίντσες ή 30 cm, ώστε οι εκτυπωμένοι δείκτες να είναι ευκολότερος να ανιχνευθούν. Επιλέξτε την ακριβή μέτρηση που θα χρησιμοποιήσετε.</string>
<string name="spatial_camera_board_distance">Αριστερή κάμερα στο κέντρο στόχου βαθμονόμησης</string>
<string name="spatial_checking_distance">Έλεγχος απόστασης…</string>
<string name="spatial_validate">Επικύρωση απόστασης</string>
<string name="spatial_diagnostics">Διαγνωστικά</string>
<string name="spatial_show_diagnostics">Εμφάνιση διαγνωστικών</string>
<string name="spatial_diagnostics_hint">Εργαλεία ανάκτησης χρονισμού και παρακολούθησης κάμερας.</string>
<string name="spatial_camera_timing">Χρονισμός κάμερας</string>
<string name="spatial_hand_input">Είσοδος από παρακολούθηση χεριών</string>
<string name="spatial_wait_timing">Αναμονή για τον χρονισμό της κάμερας.</string>
<string name="spatial_frames">Εικόνες κάμερας</string>
<string name="spatial_gpu">Ανίχνευση χεριών με GPU</string>
<string name="spatial_gpu_hint">Η CPU είναι η προεπιλογή. Η GPU είναι πειραματική και μπορεί να προκαλέσει επανεκκίνηση του SteamVR. Αυτή η επιλογή επαναφέρεται κατά την επανεκκίνηση της παρακολούθησης.</string>
<string name="spatial_lite">Lite ανίχνευση μοντέλων χεριών</string>
<string name="spatial_lite_hint">Χρησιμοποιήστε τον ανιχνευτή παλάμης Lite και τα βασικά σημεία Lite στα χέρια. Ελέγξτε την ακρίβεια στόχευσης, τσιμπήστε και πιάστε. Επαναφέρεται κατά την επανεκκίνηση της παρακολούθησης.</string>
<string name="spatial_adaptive">Αυτόματη συχνότητα εισόδου για παρακολούθηση των χεριών</string>
<string name="spatial_adaptive_hint">Προσαρμόστε την είσοδο στην ταχύτητα επεξεργασίας. Επαναφέρεται κατά την επανεκκίνηση της παρακολούθησης.</string>
<string name="spatial_half_rate">Περιορίστε τη συχνότητα εισόδου για την παρακολούθηση των χεριών</string>
<string name="spatial_half_rate_hint">Χρησιμοποιήστε τις μισές εικόνες της κάμερας. Απενεργοποιήστε για να συγκρίνετε τη μέγιστη συχνότητα. Επαναφέρεται κατά την επανεκκίνηση της παρακολούθησης.</string>
<string name="spatial_diagnostic_capture">Διαγνωστική σύλληψη</string>
<string name="spatial_diagnostic_capture_hint">Αποθηκεύστε ένα ζεύγος στερεοφωνικής εικόνας τοπικά. Οι εικόνες αποθηκεύονται μόνο όταν ζητηθούν.</string>
<string name="spatial_save_frame">Αποθήκευση εικόνας κάμερας</string>
<string name="spatial_restart_tracking">Επανεκκινήστε την παρακολούθηση</string>
<string name="spatial_restart_hint">Χρησιμοποιήστε το εάν η παρακολούθηση δεν επανέρχεται σε καλά φωτισμένη περιοχή. Η επανεκκίνηση απαιτεί νέα προέλευση παρακολούθησης.</string>
<string name="spatial_restart">Επανεκκίνηση</string>
<string name="spatial_restart_title">Επανεκκίνηση της παρακολούθησης;</string>
<string name="spatial_restart_confirm">Οι μη αποθηκευμένες προβολές βαθμονόμησης θα χαθούν. Κρατήστε τα έξυπνα γυαλιά ακίνητα αργότερα και επαναφέρετε το κέντρο παρακολούθησης όταν αποκατασταθεί η παρακολούθηση.</string>
<string name="spatial_views_count">%1$d / 24 προβολές καταγράφηκαν</string>
<string name="spatial_capture_paused">Λήψη σε παύση: %1$s</string>
<string name="spatial_measure_hint">Μέτρο %1$s. Εμφάνιση του πλήρους στόχου βαθμονόμησης και στις δύο προεπισκοπήσεις και, στη συνέχεια, κρατήστε ακίνητο.</string>
<string name="spatial_timing_values">%1$.1f Hz • ηλικία εικόνας %2$.1f ms • επεξεργασία %3$.1f ms</string>
<string name="spatial_input_values">Όριο εισόδου %1$.1f Hz • %2$.0f%% εικόνων κάμερας. Η παρακολούθηση της κεφαλής συνεχίζεται στη μέγιστη συχνότητά της.</string>
<string name="spatial_frame_counts">%1$d υποβλήθηκε σε επεξεργασία • %2$d απορρίφθηκε • %3$d παραβλέφθηκε από το όριο εισαγωγής</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">Βαθμονόμηση DroidDeck - Luma Ultra</string>
<string name="spatial_print_scale" formatted="false">Εκτύπωση σε 100% / Πραγματικό μέγεθος. Κάθε τετράγωνο: 30 mm (1.181 in).</string>
<string name="spatial_print_stationary">Διατηρήστε το στόχο βαθμονόμησης επίπεδο και ακίνητο. μετακινήστε τα έξυπνα γυαλιά ανάμεσα στις λήψεις.</string>
<string name="spatial_print_check">Έλεγχος κλίμακας: 100 mm (3.937 ίντσες) μεταξύ των κροτώνων</string>
<string name="spatial_distance_12in">12 ίντσες (30,48 cm)</string>
<string name="spatial_distance_30cm">30 cm (11,81 ίντσες)</string>
<string name="spatial_distance_20in">20 ίντσες (50,8 cm)</string>
<string name="spatial_distance_50cm">50 cm (19,69 ίντσες)</string>
<string name="spatial_disabled">Απενεργοποιημένο</string>
<string name="spatial_wait_stable">Αναμονή για σταθερή παρακολούθηση</string>
<string name="spatial_disconnected">Αποσυνδέθηκε</string>
<string name="spatial_hands_off">Οι ελεγκτές που λειτουργούν με γυμνά χέρια είναι απενεργοποιημένοι</string>
<string name="spatial_tracking_off">Η παρακολούθηση θέσης είναι απενεργοποιημένη ή τα έξυπνα γυαλιά έχουν αποσυνδεθεί</string>
<string name="spatial_recenter_pending">Ζητήθηκε επαναφορά του κέντρου παρακολούθησης — μείνετε ακίνητοι ενώ η παρακολούθηση σταθεροποιείται</string>
<string name="spatial_tracking_lost">Παρακολούθηση χαμένη — αντιμετωπίστε μια καλά φωτισμένη περιοχή με ορατές λεπτομέρειες</string>
<string name="spatial_origin_changed">Άλλαξε η προέλευση παρακολούθησης — πατήστε επαναφορά του κέντρου παρακολούθησης και, στη συνέχεια, κρατήστε ακίνητοι</string>
<string name="spatial_active">6DoF ενεργό</string>
<string name="spatial_connect_hint">Συνδέστε το Luma Ultra και ενεργοποιήστε την παρακολούθηση θέσης.</string>
<string name="spatial_validation_session">Η βαθμονόμηση επαληθεύτηκε για αυτήν τη σύνδεση.</string>
<string name="spatial_validation_saved">Η βαθμονόμηση επαληθεύτηκε και αποθηκεύτηκε.</string>
<string name="spatial_wait_cameras">Αναμονή για στερεοφωνικές κάμερες</string>
<string name="spatial_wait_hands">Αναμονή για παρακολούθηση των χεριών</string>
<string name="spatial_collect_instructions">Διατηρήστε σταθερό τον στόχο βαθμονόμησης 5 × 7. Καταγράψτε 24+ προβολές ενώ μετακινείτε και περιστρέφετε τα έξυπνα γυαλιά.</string>
<string name="spatial_capture_pending">Αναμονή για την επόμενη εικόνα κάμερας. Κρατήστε τα έξυπνα γυαλιά ακίνητα.</string>
<string name="spatial_validation_pending">Έλεγχος της επόμενης εικόνας κάμερας. Διατηρήστε ορατό τον πλήρη στόχο βαθμονόμησης και κρατήστε ακίνητο.</string>
<string name="spatial_start_first">Ξεκινήστε πρώτα τη βαθμονόμηση</string>
<string name="spatial_no_camera_frame">Δεν έφτασε καμία νέα εικόνα κάμερας. Ελέγξτε τη σύνδεση των έξυπνων γυαλιών και τις προεπισκοπήσεις της κάμερας και, στη συνέχεια, δοκιμάστε ξανά.</string>
<string name="spatial_clock_reset">Επαναφορά ρολογιού κάμερας</string>
<string name="spatial_frame_changed">Το σύστημα συντεταγμένων παρακολούθησης άλλαξε. Οι προβολές σας αποθηκεύονται για επανυπολογισμό. Επαναφέρετε το κέντρο παρακολούθησης και, στη συνέχεια, ξεκινήστε ένα νέο σύνολο για να καταγράψετε περισσότερες προβολές.</string>
<string name="spatial_device_loaded">φορτώθηκε η βαθμονόμηση της συσκευής</string>
<string name="spatial_base_loaded">Φορτώθηκε η βαθμονόμηση βάσης Luma Ultra</string>
<string name="spatial_calibration_needed">απαιτείται βαθμονόμηση</string>
<string name="spatial_tap_start">Πατήστε Πρώτα Έναρξη νέας βαθμονόμησης.</string>
<string name="spatial_capture_recenter_pending">Η επαναφορά του κέντρου παρακολούθησης αναμένει σταθερή παρακολούθηση. Κρατήστε ακίνητοι για μια στιγμή.</string>
<string name="spatial_capture_recenter">Η παρακολούθηση άλλαξε. Πατήστε επαναφορά του κέντρου παρακολούθησης μία φορά και, στη συνέχεια, κρατήστε ακίνητοι πριν από τη λήψη.</string>
<string name="spatial_capture_unsynchronized">Η λήψη απορρίφθηκε: η παρακολούθηση κεφαλής δεν ήταν συγχρονισμένη ή άλλαξε κατά τη λήψη. Μείνετε ακίνητοι και δοκιμάστε ξανά.</string>
<string name="spatial_align_first">Υπολογίστε την ευθυγράμμιση της κάμερας πριν ελέγξετε την απόσταση.</string>
<string name="spatial_processing_failed">Η επεξεργασία της κάμερας απέτυχε</string>
<string name="spatial_hand_head_changed">Η παρακολούθηση κεφαλιού άλλαξε κατά την ανίχνευση των χεριών</string>
<string name="spatial_hand_wait_sync">Αναμονή για συγχρονισμό κάμερας και κεφαλής</string>
<string name="spatial_no_wrists">Δεν εντοπίστηκαν καρποί. Κρατήστε τις λαβές του ελεγκτή Joy-Con μπροστά από τα έξυπνα γυαλιά</string>
<string name="spatial_no_hands">Δεν εντοπίστηκαν χέρια. Δείξτε ανοιχτές παλάμες μπροστά από τα έξυπνα γυαλιά</string>
<string name="spatial_one_camera_wrists">Καρποί ορατοί μόνο σε μία κάμερα. κρατήστε τις λαβές του ελεγκτή σας στην κοινόχρηστη προβολή</string>
<string name="spatial_one_camera_hands">Τα χέρια ορατά μόνο σε μία κάμερα. κρατήστε και τις δύο παλάμες στην κοινή θέα</string>
<string name="spatial_ambiguous_hands">Και οι δύο κάμερες βλέπουν τα χέρια, αλλά η αντιστοίχιση αριστερά/δεξιά είναι διφορούμενη</string>
<string name="spatial_inconsistent_depth">Εντοπίστηκαν χέρια, αλλά το στερεοφωνικό βάθος είναι ασυνεπές</string>
<string name="spatial_rejected_wrist">Η θέση του καρπού απορρίπτεται με στερεοφωνικό έλεγχο ή έλεγχο κίνησης</string>
<string name="spatial_inconsistent_palm">Εντοπίστηκαν στερεοφωνικά σημεία, αλλά η γεωμετρία της παλάμης είναι ασυνεπής</string>
<string name="spatial_hands_need_calibration">Ολοκληρώστε τη βαθμονόμηση και τον έλεγχο απόστασης για να ενεργοποιήσετε τα χέρια</string>
<string name="spatial_hands_wait_stable">Αναμονή για σταθερή παρακολούθηση κεφαλής. Επαναφέρετε το κέντρο παρακολούθησης εάν ζητηθεί</string>
<string name="spatial_old_frame">Η παρατήρηση με κάμερα είναι πολύ παλιά. περιμένοντας ένα φρέσκο ζευγάρι</string>
<string name="spatial_fit_ready">Προσαρμογή %1$.2f px. Μετρήστε την επιλεγμένη απόσταση από την αριστερή κάμερα μέχρι το κέντρο στόχου βαθμονόμησης και, στη συνέχεια, επικυρώστε.</string>
<string name="spatial_opencv_error">Το OpenCV δεν είναι διαθέσιμο: %1$s</string>
<string name="spatial_restored">Αποκαταστάθηκαν οι αποθηκευμένες προβολές %1$d. Υπολογίστε τη βαθμονόμηση για να προσπαθήσετε ξανά ή ξεκινήστε ένα νέο σύνολο για να καταγράψετε περισσότερα.</string>
<string name="spatial_captured">Λήψη %1$d / 24 προβολές. Διατηρήστε σταθερό τον στόχο βαθμονόμησης. μετακινήστε τα έξυπνα γυαλιά για την επόμενη προβολή.</string>
<string name="spatial_capture_corners">Η λήψη απορρίφθηκε: %1$d / 12 απαιτούμενες γωνίες ορατές και στις δύο κάμερες. Εμφάνιση μεγαλύτερου μέρους του στόχου βαθμονόμησης, κρατήστε ακίνητο και μετά δοκιμάστε ξανά.</string>
<string name="spatial_validation_error">Ο έλεγχος απόστασης δεν μπόρεσε να ολοκληρωθεί: %1$s. Προσπαθήστε ξανά.</string>
<string name="spatial_inference_error">Μη διαθέσιμος εντοπισμός χεριών: %1$s. Αλλάξτε την CPU/GPU ή αλλάξτε τους ελεγκτές που λειτουργούν με γυμνά χέρια για να προσπαθήσετε ξανά.</string>
<string name="spatial_hand_timeout">Η ανίχνευση χεριών υπερέβη τα %1$d ms. η παρατήρηση απορρίφθηκε</string>
<string name="spatial_wrists_tracked">%1$d / 2 θέσεις καρπού Joy-Con μετρημένες με στερεοφωνικό</string>
<string name="spatial_palms_tracked">Παρακολουθούμενες παλάμες: %1$d / 2· ετοιμότητα τσιμπήματος: %2$d· ετοιμότητα λαβής: %3$d</string>
<string name="spatial_capture_saved">Αποθηκεύτηκε η διαγνωστική λήψη στο %1$s</string>
<string name="spatial_hand_error">Η παρακολούθηση των χεριών σταμάτησε: %1$s</string>
<string name="spatial_stereo_loaded">Στερεοφωνικό %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">Η παρακολούθηση κεφαλής και ο στόχος βαθμονόμησης διαφωνούν κατά %1$s (όριο 5 cm / 2 in). Διατηρήστε σταθερό τον στόχο βαθμονόμησης και περιμένετε για σταθερή παρακολούθηση πριν ξεκινήσετε ένα νέο σετ. Γυρίστε και γείρετε τα έξυπνα γυαλιά απαλά. παύση πριν από κάθε λήψη.</string>
<string name="spatial_need_views">Καταγράψτε τουλάχιστον 24 διαφορετικές όψεις του σταθερού στόχου βαθμονόμησης.</string>
<string name="spatial_need_rotation">Γυρίστε τα έξυπνα γυαλιά αριστερά και δεξιά και γείρετε τα πάνω-κάτω. Η κίνηση χωρίς περιστροφή δεν μπορεί να βαθμονομήσει την ευθυγράμμιση της κάμερας.</string>
<string name="spatial_bad_baseline">Απίστευτη γραμμή βάσης κάμερας</string>
<string name="spatial_need_orientations">Μετακινήστε το κεφάλι σας σε πιο ποικίλους προσανατολισμούς.</string>
<string name="spatial_bad_rotation">Μη έγκυρη περιστροφή κάμερας με κεφαλή</string>
<string name="spatial_bad_alignment">Η ευθυγράμμιση κάμερας με κεφαλή είναι ασυνεπής.</string>
<string name="spatial_calibration_error">Η βαθμονόμηση δεν μπόρεσε να ολοκληρωθεί. %1$s Οι προβολές που έχετε καταγράψει αποθηκεύονται.</string>
<string name="spatial_reprojection_error">Το παρατεταμένο σφάλμα επαναπροβολής %1$.2f px υπερβαίνει τα 2 px. Επαναβαθμονόμηση.</string>
<string name="spatial_distance_corners">Εντοπίστηκαν %1$d / 24 γωνίες σκακιέρας και στις δύο κάμερες. Κρατήστε ολόκληρο τον στόχο βαθμονόμησης ορατό και παραμείνετε ακίνητοι. Εάν είναι ήδη ορατός, επιλέξτε 12 in ή 30 cm και μετρήστε αυτήν τη μικρότερη απόσταση πριν προσπαθήσετε ξανά.</string>
<string name="spatial_distance_depth">Ο στόχος βαθμονόμησης είναι ορατός, αλλά το βάθος του δεν μπορούσε να μετρηθεί αξιόπιστα. Κρατήστε τα έξυπνα γυαλιά ακίνητα και διατηρήστε το στόχο βαθμονόμησης επίπεδο και καλά φωτισμένο και, στη συνέχεια, δοκιμάστε ξανά.</string>
<string name="spatial_distance_invalid">Η μέτρηση της κάμερας δεν ήταν έγκυρη. Μείνετε ακίνητοι και δοκιμάστε ξανά.</string>
<string name="spatial_distance_passed">Ο έλεγχος εξ αποστάσεως πέρασε.</string>
<string name="spatial_distance_retry">Ελέγξτε ξανά την απόσταση από την αριστερή κάμερα μέχρι το κέντρο στόχου βαθμονόμησης και, στη συνέχεια, δοκιμάστε ξανά.</string>
<string name="spatial_distance_result">Κάμερες μέτρησης %1$s. επιλεγμένη απόσταση %2$s. %3$s</string>
<string name="spatial_wait_previews">Περιμένετε και για τις δύο προεπισκοπήσεις κάμερας πριν ξεκινήσετε τη βαθμονόμηση.</string>
<string name="spatial_saved_views_hint">Οι αποθηκευμένες προβολές μπορούν να υπολογιστούν εκ νέου. Ξεκινήστε μια νέα βαθμονόμηση για αποτύπωση στο τρέχον σύστημα συντεταγμένων παρακολούθησης.</string>
<string name="spatial_model_checksum_error">Αναντιστοιχία αθροίσματος ελέγχου μοντέλου χεριού</string>
<string name="spatial_opencv_load_error">Δεν ήταν δυνατή η φόρτωση του OpenCV</string>
</resources>
+214 -10
View File
@@ -2374,14 +2374,14 @@
<string name="glasses_display">Pantalla</string>
<string name="glasses_refresh">Frecuencia de actualización</string>
<string name="glasses_3d">3D (lado a lado)</string>
<string name="glasses_3d_hint">Cada ojo recibe su propia mitad de una imagen de 3840×1080. Si la imagen se pierde, mantén pulsado el botón 2D/3D de las gafas.</string>
<string name="glasses_3d_hint">Cambia las gafas inteligentes a 3D lado a lado. El seguimiento de la cabeza se controla por separado.</string>
<string name="glasses_3d_failed">No se ha confirmado la señal 3D. Inténtalo de nuevo cuando vuelva la imagen 2D.</string>
<string name="glasses_no_signal">Sin imagen: las gafas están en un modo que este teléfono no puede enviar. Mantén pulsado su botón 2D/3D o desconéctalas y vuelve a conectarlas.</string>
<string name="glasses_3d_refresh">Frecuencia en 3D</string>
<string name="glasses_auto3d">3D para juegos de RV</string>
<string name="glasses_auto3d_hint">Cambia a 3D mientras SteamVR está en marcha, y vuelve después.</string>
<string name="glasses_picture">Imagen 3D</string>
<string name="glasses_picture_hint">Automático: SteamVR llena la vista; los demás juegos flotan como una pantalla en 3D y se ven planos en 2D.</string>
<string name="glasses_picture">Imagen</string>
<string name="glasses_picture_hint">Muestra automáticamente contenido plano en ambos ojos y detecta SteamVR. Seleccione Juego lado a lado para contenido SBS.</string>
<string name="glasses_picture_auto">Automático</string>
<string name="glasses_picture_screen">Pantalla virtual</string>
<string name="glasses_picture_sbs">Juego lado a lado</string>
@@ -2391,20 +2391,20 @@
<string name="glasses_screen_no_output">Estos ajustes se aplican mientras Juego en las gafas está activado y el juego se muestra allí.</string>
<string name="glasses_screen_flat_hint">Automático mantiene este juego 2D a pantalla completa. Usa una pantalla virtual para ajustar su ancho, distancia y seguimiento de cabeza.</string>
<string name="glasses_screen_use">Usar pantalla virtual</string>
<string name="glasses_screen_direct_hint">SteamVR llena la vista. Ajusta su campo de visión abajo; el ancho y la distancia solo se aplican a una pantalla virtual.</string>
<string name="glasses_screen_direct_hint">SteamVR llena la vista. Ajuste su campo de visión en RV; el ancho y la distancia solo se aplican a una pantalla virtual.</string>
<string name="glasses_screen_sbs_hint">Los juegos lado a lado llenan cada ojo. El ancho y la distancia solo se aplican a una pantalla virtual.</string>
<string name="glasses_tracking_flat_hint">Automático mantiene este juego 2D fijo en la vista. Elige Pantalla virtual en la sección Pantalla para usar el seguimiento de cabeza.</string>
<string name="glasses_tracking_sbs_hint">El seguimiento de cabeza se aplica a una pantalla virtual o a un juego de SteamVR en marcha.</string>
<string name="glasses_hardware">Gafas</string>
<string name="glasses_hardware">Dispositivo</string>
<string name="glasses_brightness">Brillo</string>
<string name="glasses_volume">Volumen</string>
<string name="glasses_sunblock">Bloqueo solar</string>
<string name="glasses_sunblock_hint">Oscurece las lentes frontales.</string>
<string name="glasses_tracking">Seguimiento de cabeza</string>
<string name="glasses_tracking">Seguimiento</string>
<string name="glasses_head_tracking">Seguimiento de cabeza</string>
<string name="glasses_head_tracking_hint">Los sensores de las gafas giran la pantalla virtual y SteamVR con tu cabeza.</string>
<string name="glasses_recenter">Recentrar la vista</string>
<string name="glasses_recenter_button">Recentrar</string>
<string name="glasses_recenter_button">Volver a centrar la pantalla</string>
<string name="glasses_vr_fov">Campo de visión de SteamVR</string>
<string name="glasses_vr_fov_hint">Más bajo acerca SteamVR y llena las gafas; más alto muestra más a tu alrededor.</string>
<string name="glasses_playing">Jugando en %1$s</string>
@@ -2425,8 +2425,8 @@
<string name="glasses_calibration_reset_hint">¿Quitar la calibración guardada y restaurar los valores predeterminados del modelo? Recentra después de volver a ponerte las gafas.</string>
<string name="glasses_calibration_reset_button">Restablecer</string>
<string name="hand_camera_group">Seguimiento de manos</string>
<string name="hand_camera_prompt_title">¿Seguir tus manos con la cámara?</string>
<string name="hand_camera_prompt_body">La cámara puede ver tus manos. Sin ningún mando conectado, tus manos desnudas pasan a ser las manos de RV: junta el pulgar y el índice para hacer clic, cierra la mano para agarrar y, para abrir el menú, gira la palma hacia ti y junta los dedos. Con Joy-Con, los coloca donde están de verdad tus manos. Las imágenes se procesan en este dispositivo y nunca se guardan ni se envían.</string>
<string name="hand_camera_prompt_title">¿Rastrear las manos o los mandos Joy-Con con la cámara del teléfono?</string>
<string name="hand_camera_prompt_body">Sin ningún mando conectado, la cámara del teléfono rastrea tus manos en realidad virtual: pellizca con el pulgar y el índice para hacer clic, cierra la mano para agarrar y gira la palma hacia ti y pellizca para abrir el menú. Con los mandos Joy-Con emparejados, la cámara rastrea su posición mientras los mandos Joy-Con proporcionan la rotación y las entradas de los botones. Las imágenes se procesan en este dispositivo y nunca se guardan ni se envían.</string>
<string name="hand_camera_rear">Cámara trasera</string>
<string name="hand_camera_rear_hint">Recomendada. Suele ser más nítida y amplia.</string>
<string name="hand_camera_front">Cámara frontal</string>
@@ -2434,7 +2434,7 @@
<string name="hand_camera_allow">Usar cámara</string>
<string name="hand_camera_not_now">Ahora no</string>
<string name="hand_camera_setting">Seguimiento de manos por cámara</string>
<string name="hand_camera_setting_hint">Sin ningún mando conectado, tus manos pasan a ser las manos de RV: junta los dedos para hacer clic y, con la palma hacia ti, júntalos para abrir el menú. Con Joy-Con, los coloca donde la cámara los ve. Las imágenes se quedan en este dispositivo.</string>
<string name="hand_camera_setting_hint">Usa la cámara del teléfono para gestos con las manos desnudas o para posicionar los mandos Joy-Con emparejados en realidad virtual. Los mandos Joy-Con proporcionan la rotación y las pulsaciones de los botones. Se pausa mientras las gafas inteligentes rastrean tus manos o los mandos Joy-Con. Las imágenes se procesan solo en este dispositivo.</string>
<string name="hand_camera_choice">Cámara</string>
<string name="hand_camera_access">Acceso a la cámara</string>
<string name="hand_camera_access_hint">Permite el acceso a la cámara para que el seguimiento de manos pueda empezar.</string>
@@ -2705,4 +2705,208 @@
<string name="boot_movie_converting_started">Convirtiendo %1$s para Steam. Mantén DroidDeck abierto.</string>
<string name="boot_movie_unsupported">Este dispositivo no puede decodificar ese vídeo.</string>
<string name="boot_movie_import_failed">No se pudo importar ese vídeo</string>
<string name="glasses_experimental">Cámaras Luma Ultra — Experimental</string>
<string name="glasses_joycons">Seguimiento de posición de los Joy-Con</string>
<string name="glasses_joycons_hint">Usa las cámaras de las gafas inteligentes para posicionar tus mandos Joy-Con emparejados. La rotación y las pulsaciones de los botones provienen de los mandos Joy-Con. La posición se estima cuando tus muñecas están ocultas. Reemplaza los mandos operados con las manos desnudas y pausa el seguimiento de la cámara del teléfono. Apunta los mandos Joy-Con hacia adelante al conectar o restablecer el centro de seguimiento.</string>
<string name="glasses_joycons_requires_position">Activa primero el seguimiento de posición. Requiere mandos Joy-Con compatibles con movimiento en Android 12 o posterior. Desactivado por defecto.</string>
<string name="setup_tab_glasses">Gafas inteligentes</string>
<string name="setup_glasses_scope">La configuración de pantalla y seguimiento de cabeza se comparte entre los menús de DroidDeck, los juegos y las sesiones de escritorio.</string>
<string name="setup_glasses_connect">Conecta las gafas inteligentes para cambiar su configuración de pantalla y seguimiento. Tu configuración guardada se conserva al desconectarlas.</string>
<string name="glasses_vr">RV</string>
<string name="glasses_display_mode">Modo de visualización</string>
<string name="glasses_output_2d">Salida 2D</string>
<string name="glasses_output_3d">Salida 3D</string>
<string name="glasses_picture_group">Imagen y pantalla virtual</string>
<string name="glasses_position_group">Posición y nivel del suelo</string>
<string name="glasses_hands_group">Seguimiento de manos — Experimental</string>
<string name="glasses_tracking_status">Estado del seguimiento</string>
<string name="glasses_resolution_2d">Resolución 2D</string>
<string name="glasses_resolution_3d">Resolución 3D</string>
<string name="glasses_resolution_vr">Resolución RV</string>
<string name="glasses_resolution_auto">Automático</string>
<string name="glasses_resolution_inherit">Igual que 2D/3D</string>
<string name="glasses_resolution_hint">Resolución de salida por ojo.</string>
<string name="glasses_refresh_30_hint">Habilita el juego en gafas inteligentes para seleccionar el modo nativo de 30 Hz de la pantalla.</string>
<string name="glasses_3dof">Pantalla virtual 3DoF</string>
<string name="glasses_3dof_hint">Ancla los juegos planos frente a ti para que puedas mirar a tu alrededor. Habilita el juego en gafas inteligentes sin cambiar a 3D.</string>
<string name="glasses_screen_app_wide">El ancho y la distancia actualizan la pantalla virtual rastreada para los menús, juegos y sesiones de escritorio de DroidDeck de forma inmediata.</string>
<string name="glasses_head_tracking_spatial_vr_hint">El seguimiento posicional incluye la rotación de la cabeza en SteamVR. Para el seguimiento solo de rotación, desactive el seguimiento posicional.</string>
<string name="glasses_head_tracking_spatial_hint">Controla el movimiento de la cabeza en la pantalla virtual. El seguimiento posicional incluye la rotación de la cabeza en SteamVR.</string>
<string name="glasses_quick_actions">Acciones rápidas</string>
<string name="glasses_recenter_screen_hint">Mira hacia donde quieras que esté la pantalla y luego restablece el centro de seguimiento para ajustar su altura y dirección.</string>
<string name="glasses_recenter_joycons_button">Volver a centrar los Joy-Con</string>
<string name="phone_camera_tracking_group">Cámara del teléfono — Experimental</string>
<string name="phone_camera_tracking_setting">Rastrear manos o mandos Joy-Con</string>
<string name="glasses_6dof">Pantalla virtual 6DoF</string>
<string name="glasses_position">Seguimiento posicional — experimental</string>
<string name="glasses_position_hint">Movimiento físico en RV y alrededor de pantallas virtuales. Reiniciar el seguimiento tarda un momento.</string>
<string name="glasses_eye_height">Altura actual de los ojos sobre el suelo</string>
<string name="glasses_floor">Establecer el nivel del suelo desde la altura actual</string>
<string name="glasses_floor_set">Establecer el nivel del suelo</string>
<string name="glasses_spatial_setup">Configuración y diagnóstico de la cámara estéreo</string>
<string name="glasses_spatial_setup_hint">Comprobar la calibración de la cámara y el seguimiento de las manos antes de jugar.</string>
<string name="glasses_spatial_open">Abrir configuración</string>
<string name="glasses_hands">Mandos operados con las manos desnudas</string>
<string name="glasses_hands_hint">Usar las cámaras de las gafas inteligentes para apuntar con las manos, pellizcar para hacer clic y cerrar la mano para agarrar en RV. Los joysticks del mando se mueven y giran. Sustituye el seguimiento de posición de los Joy-Con. Puede haber retrasos o pérdida de seguimiento durante los juegos.</string>
<string name="glasses_hands_ready_hint">Habilitar el uso de mandos operados con las manos desnudas bajo las cámaras Luma Ultra en Seguimiento. Mostrar primero las manos abiertas.</string>
<string name="glasses_hands_base">Calibración compartida de Luma Ultra</string>
<string name="glasses_hands_base_hint">Usar la base compartida de Luma Ultra. Calibra tus gafas inteligentes para verificar su precisión.</string>
<string name="glasses_hands_calibration_verified">Calibración medida verificada</string>
<string name="glasses_hands_calibration_required">Validación requerida</string>
<string name="glasses_hands_calibration_required_hint">Los controladores operados con las manos desnudas estarán disponibles después de que la verificación de distancia medida sea exitosa.</string>
<string name="tracking_recovery_lost">Seguimiento perdido</string>
<string name="tracking_recovery_look">Mira a tu alrededor lentamente en una habitación bien iluminada.\nMantén las cámaras de seguimiento despejadas y enfoca los muebles u otros detalles visibles.</string>
<string name="tracking_recovery_restart_help">¿Sigues estancado? En su teléfono: gafas inteligentes → Seguimiento → Abrir configuración → Mostrar diagnóstico → Reiniciar seguimiento.</string>
<string name="tracking_recovery_disconnected">Gafas inteligentes desconectadas</string>
<string name="tracking_recovery_connect">Verifique la conexión USB y vuelva a conectar las gafas inteligentes.\nAcepte el mensaje de acceso USB en su teléfono si aparece.</string>
<string name="tracking_recovery_usb_help">Una vez conectado, mire lentamente a su alrededor en una habitación bien iluminada para restablecer el seguimiento.</string>
<string name="tracking_recovery_recenter">Es necesario volver a centrar</string>
<string name="tracking_recovery_recenter_steps">El seguimiento ha vuelto a una nueva posición.\nMira hacia adelante y luego toca \"Restablecer centro de seguimiento\" en tu teléfono.</string>
<string name="tracking_recovery_recenter_help">También puedes encontrar la opción para restablecer el centro de seguimiento en las gafas inteligentes → Seguimiento → Abrir configuración. Se conserva la calibración de la cámara guardada.</string>
<string name="tracking_recovery_stabilizing">Quédate quieto por un momento</string>
<string name="tracking_recovery_hold">El restablecimiento del centro de seguimiento está en curso.\nMantén las gafas inteligentes estables mientras se estabiliza el seguimiento.</string>
<string name="tracking_recovery_automatic">Esta pantalla se cierra automáticamente cuando el seguimiento de la cabeza está listo.</string>
<string name="glasses_steady_hand_aim">Apuntar estabilizado con las manos</string>
<string name="glasses_steady_hand_aim_hint">Apunta desde tu hombro a través de tu palma. Apague para rotación controlada por la muñeca.</string>
<string name="spatial_title">Configuración espacial</string>
<string name="spatial_wait_tracking">Esperando seguimiento…</string>
<string name="spatial_cameras_description">Cámaras de seguimiento izquierda y derecha</string>
<string name="spatial_camera_sync">Sincronización de cámaras</string>
<string name="spatial_ready">Listo</string>
<string name="spatial_sync_hint">Esperando que la cámara y el seguimiento de la cabeza se alineen. Mantenga las gafas inteligentes conectadas.</string>
<string name="spatial_prepare">1. Prepare el objetivo de calibración</string>
<string name="spatial_print_actual">Imprimir en tamaño real</string>
<string name="spatial_print_hint">Cada cuadrado debe medir 30 mm (1.181 in). Monte la hoja plana sobre una superficie rígida en un área bien iluminada.</string>
<string name="spatial_print">Imprimir patrón de calibración</string>
<string name="spatial_capture_group">2. Captura diferentes vistas</string>
<string name="spatial_board_stationary">Mantener estacionario el objetivo de calibración</string>
<string name="spatial_capture_hint">Acerque, aleje y lateralmente las gafas inteligentes. Gírelos hacia la izquierda/derecha, inclínelos hacia arriba/abajo e inclínelos hacia los lados. Mantenga el objetivo de calibración en ambas cámaras y haga una pausa breve antes de cada captura.</string>
<string name="spatial_start">Iniciar nueva calibración</string>
<string name="spatial_wait_sync">Esperando sincronización de cámaras.</string>
<string name="spatial_start_hint">Inicie una nueva calibración para recopilar vistas.</string>
<string name="spatial_views_hint">Mantenga el objetivo de calibración visible en ambas vistas previas. Capture al menos 24 vistas nítidas desde diferentes ángulos.</string>
<string name="spatial_capturing">Capturando…</string>
<string name="spatial_capture">Capturar vista del objetivo de calibración</string>
<string name="spatial_alignment">Calcular la alineación de la cámara</string>
<string name="spatial_calculating">Calculando…</string>
<string name="spatial_calculate">Calcular calibración</string>
<string name="spatial_distance_group">3. Verifique la distancia</string>
<string name="spatial_distance">Distancia medida</string>
<string name="spatial_distance_hint">Comience con 12 pulgadas o 30 cm para que los marcadores impresos sean más fáciles de detectar. Elige la medida exacta que utilizarás.</string>
<string name="spatial_camera_board_distance">Cámara izquierda al centro del objetivo de calibración</string>
<string name="spatial_checking_distance">Comprobando la distancia...</string>
<string name="spatial_validate">Validar distancia</string>
<string name="spatial_diagnostics">Diagnóstico</string>
<string name="spatial_show_diagnostics">Mostrar diagnóstico</string>
<string name="spatial_diagnostics_hint">Herramientas de recuperación de seguimiento y sincronización de cámaras.</string>
<string name="spatial_camera_timing">Sincronización de la cámara</string>
<string name="spatial_hand_input">Entrada del seguimiento de manos</string>
<string name="spatial_wait_timing">Esperando la sincronización de la cámara.</string>
<string name="spatial_frames">Imágenes de la cámara</string>
<string name="spatial_gpu">Detección de manos por GPU</string>
<string name="spatial_gpu_hint">CPU es el valor predeterminado. La GPU es experimental y puede hacer que SteamVR se reinicie. Esta opción se restablece cuando se reinicia el seguimiento.</string>
<string name="spatial_lite">Detección Lite de modelos de manos.</string>
<string name="spatial_lite_hint">Utilice el detector de palma Lite y los puntos clave Lite en las manos. Compruebe la precisión de apuntar, pellizcar y agarrar. Se reinicia cuando se reinicia el seguimiento.</string>
<string name="spatial_adaptive">Frecuencia de entrada automática para seguimiento de manos</string>
<string name="spatial_adaptive_hint">Ajusta la entrada a la velocidad de procesamiento. Se reinicia cuando se reinicia el seguimiento.</string>
<string name="spatial_half_rate">Limitar la frecuencia de entrada para el seguimiento de manos</string>
<string name="spatial_half_rate_hint">Utilice la mitad de las imágenes de la cámara. Apague para comparar la frecuencia máxima. Se reinicia cuando se reinicia el seguimiento.</string>
<string name="spatial_diagnostic_capture">Captura de diagnóstico</string>
<string name="spatial_diagnostic_capture_hint">Guarde un par de imágenes estéreo localmente. Las imágenes se guardan sólo cuando se solicitan.</string>
<string name="spatial_save_frame">Guardar imagen de la cámara</string>
<string name="spatial_restart_tracking">Reiniciar seguimiento</string>
<string name="spatial_restart_hint">Úselo si el seguimiento no se recupera en un área bien iluminada. Un reinicio requiere un nuevo origen de seguimiento.</string>
<string name="spatial_restart">Reiniciar</string>
<string name="spatial_restart_title">¿Reiniciar el seguimiento?</string>
<string name="spatial_restart_confirm">Se perderán las vistas de calibración no guardadas. Mantén las gafas inteligentes quietas y restablece el centro de seguimiento cuando se restablezca el seguimiento.</string>
<string name="spatial_views_count">%1$d / 24 vistas capturadas</string>
<string name="spatial_capture_paused">Captura en pausa: %1$s</string>
<string name="spatial_measure_hint">Medida %1$s. Muestre el objetivo de calibración completo en ambas vistas previas y luego quédese quieto.</string>
<string name="spatial_timing_values">%1$.1f Hz • antigüedad de la imagen %2$.1f ms • procesamiento %3$.1f ms</string>
<string name="spatial_input_values">Límite de entrada %1$.1f Hz • %2$.0f%% de imágenes de la cámara. El seguimiento de la cabeza continúa con su frecuencia máxima.</string>
<string name="spatial_frame_counts">%1$d procesado • %2$d descartado • %3$d omitido por límite de entrada</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck - Calibración de Luma Ultra</string>
<string name="spatial_print_scale" formatted="false">Imprimir a 100% / Tamaño real. Cada cuadrado: 30 mm (1,181 pulgadas).</string>
<string name="spatial_print_stationary">Mantenga el objetivo de calibración plano y estacionario; mueve las gafas inteligentes entre capturas.</string>
<string name="spatial_print_check">Verificación de escala: 100 mm (3,937 in) entre marcas</string>
<string name="spatial_distance_12in">12 pulgadas (30,48 cm)</string>
<string name="spatial_distance_30cm">30 cm (11,81 pulgadas)</string>
<string name="spatial_distance_20in">20 pulgadas (50,8 cm)</string>
<string name="spatial_distance_50cm">50 cm (19,69 pulgadas)</string>
<string name="spatial_disabled">Deshabilitado</string>
<string name="spatial_wait_stable">Esperando seguimiento estable</string>
<string name="spatial_disconnected">Desconectado</string>
<string name="spatial_hands_off">Los controladores operados con las manos desnudas están apagados</string>
<string name="spatial_tracking_off">El seguimiento posicional está desactivado o las gafas inteligentes están desconectadas</string>
<string name="spatial_recenter_pending">Se ha solicitado el restablecimiento del centro de seguimiento; mantén las gafas quietas mientras se estabiliza el seguimiento.</string>
<string name="spatial_tracking_lost">Seguimiento perdido: frente a un área bien iluminada con detalles visibles</string>
<string name="spatial_origin_changed">El origen del seguimiento ha cambiado; pulsa el botón para restablecer el centro de seguimiento y mantén las gafas quietas.</string>
<string name="spatial_active">6DoF activo</string>
<string name="spatial_connect_hint">Conecte Luma Ultra y habilite el seguimiento posicional.</string>
<string name="spatial_validation_session">Calibración verificada para esta conexión.</string>
<string name="spatial_validation_saved">Calibración verificada y guardada.</string>
<string name="spatial_wait_cameras">Esperando cámaras estéreo</string>
<string name="spatial_wait_hands">Esperando seguimiento de manos</string>
<string name="spatial_collect_instructions">Mantenga estacionario el objetivo de calibración 5 × 7. Capture más de 24 vistas mientras mueve y gira las gafas inteligentes.</string>
<string name="spatial_capture_pending">Esperando la siguiente imagen de la cámara. Mantenga quietas las gafas inteligentes.</string>
<string name="spatial_validation_pending">Comprobación de la siguiente imagen de la cámara. Mantenga visible el objetivo de calibración completo y manténgalo quieto.</string>
<string name="spatial_start_first">Iniciar la calibración primero</string>
<string name="spatial_no_camera_frame">No llegó ninguna imagen nueva de la cámara. Verifique la conexión de las gafas inteligentes y las vistas previas de la cámara, luego vuelva a intentarlo.</string>
<string name="spatial_clock_reset">Reinicio del reloj de la cámara</string>
<string name="spatial_frame_changed">El sistema de coordenadas de seguimiento ha cambiado. Tus vistas se guardan para su recálculo. Restablece el centro de seguimiento y, a continuación, inicia una nueva configuración para capturar más vistas.</string>
<string name="spatial_device_loaded">calibración del dispositivo cargada</string>
<string name="spatial_base_loaded">Calibración de base Luma Ultra cargada</string>
<string name="spatial_calibration_needed">calibración requerida</string>
<string name="spatial_tap_start">Toque Iniciar nueva calibración primero.</string>
<string name="spatial_capture_recenter_pending">El restablecimiento del centro de seguimiento está esperando un seguimiento estable. Mantén las gafas quietas un momento.</string>
<string name="spatial_capture_recenter">El seguimiento ha cambiado. Pulsa el botón para restablecer el centro de seguimiento una vez y, a continuación, mantén las gafas quietas antes de capturar.</string>
<string name="spatial_capture_unsynchronized">Captura rechazada: el seguimiento de la cabeza no se sincronizó o cambió durante la captura. Quédate quieto y vuelve a intentarlo.</string>
<string name="spatial_align_first">Calcule la alineación de la cámara antes de verificar la distancia.</string>
<string name="spatial_processing_failed">el procesamiento de la cámara falló</string>
<string name="spatial_hand_head_changed">El seguimiento de la cabeza cambió durante la detección de manos</string>
<string name="spatial_hand_wait_sync">Esperando la sincronización de la cámara y el cabezal</string>
<string name="spatial_no_wrists">No se detectaron muñecas. Mantenga las manijas del controlador Joy-Con frente a las gafas inteligentes</string>
<string name="spatial_no_hands">No se detectaron manos. Muestre las palmas abiertas frente a las gafas inteligentes.</string>
<string name="spatial_one_camera_wrists">Muñecas visibles en una sola cámara; mantenga los identificadores de su controlador en la vista compartida</string>
<string name="spatial_one_camera_hands">Manos visibles en una sola cámara; mantenga ambas palmas en la vista compartida</string>
<string name="spatial_ambiguous_hands">Ambas cámaras ven manos, pero la coincidencia izquierda/derecha es ambigua</string>
<string name="spatial_inconsistent_depth">Se detectaron manos, pero la profundidad estéreo no es consistente</string>
<string name="spatial_rejected_wrist">Posición de la muñeca rechazada por controles estéreo o de movimiento</string>
<string name="spatial_inconsistent_palm">Se detectaron puntos estéreo, pero la geometría de la palma es inconsistente</string>
<string name="spatial_hands_need_calibration">Calibración completa y verificación de distancia para permitir que las manos</string>
<string name="spatial_hands_wait_stable">Esperando un seguimiento de cabeza estable; restablece el centro de seguimiento si se solicita.</string>
<string name="spatial_old_frame">La observación con cámara es demasiado antigua; esperando un par nuevo</string>
<string name="spatial_fit_ready">Ajustar %1$.2f px. Mida la distancia seleccionada desde la cámara izquierda hasta el centro del objetivo de calibración y luego valide.</string>
<string name="spatial_opencv_error">OpenCV no disponible: %1$s</string>
<string name="spatial_restored">%1$d vistas guardadas restauradas. Calcule la calibración para volver a intentarlo o comience un nuevo conjunto para capturar más.</string>
<string name="spatial_captured">Capturado %1$d / 24 vistas. Mantenga fijo el objetivo de calibración; Mueva las gafas inteligentes para la siguiente vista.</string>
<string name="spatial_capture_corners">Captura rechazada: %1$d / 12 esquinas requeridas visibles en ambas cámaras. Muestre más del objetivo de calibración, quédese quieto y vuelva a intentarlo.</string>
<string name="spatial_validation_error">La verificación de distancia no pudo finalizar: %1$s. Intentar otra vez.</string>
<string name="spatial_inference_error">Detección de manos no disponible: %1$s. Cambie la CPU/GPU o cambie los controladores operados con las manos desnudas para volver a intentarlo.</string>
<string name="spatial_hand_timeout">La detección de manos superó los %1$d ms; observación descartada</string>
<string name="spatial_wrists_tracked">%1$d / 2 posiciones de muñeca Joy-Con medidas por estéreo</string>
<string name="spatial_palms_tracked">Palmas seguidas: %1$d / 2; gesto de pinza listo: %2$d; agarre listo: %3$d</string>
<string name="spatial_capture_saved">Captura de diagnóstico guardada en %1$s</string>
<string name="spatial_hand_error">Seguimiento de manos detenido: %1$s</string>
<string name="spatial_stereo_loaded">Estéreo %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">El seguimiento de la cabeza y el objetivo de calibración difieren en %1$s (límite de 5 cm/2 pulgadas). Mantenga fijo el objetivo de calibración y espere un seguimiento constante antes de comenzar una nueva serie. Gire e incline suavemente las gafas inteligentes; pausa antes de cada captura.</string>
<string name="spatial_need_views">Capture al menos 24 vistas variadas del objetivo de calibración estacionario.</string>
<string name="spatial_need_rotation">Gire las gafas inteligentes hacia la izquierda y hacia la derecha, e inclínelas hacia arriba y hacia abajo; el movimiento sin rotación no puede calibrar la alineación de la cámara.</string>
<string name="spatial_bad_baseline">Línea de base de cámara inverosímil</string>
<string name="spatial_need_orientations">Mueve la cabeza en orientaciones más variadas.</string>
<string name="spatial_bad_rotation">Rotación de cámara a cabezal no válida</string>
<string name="spatial_bad_alignment">La alineación entre la cámara y el cabezal es inconsistente.</string>
<string name="spatial_calibration_error">La calibración no pudo finalizar. %1$s Tus vistas capturadas se guardan.</string>
<string name="spatial_reprojection_error">El error de reproyección retenida %1$.2f px supera los 2 px. Recalibrar.</string>
<string name="spatial_distance_corners">%1$d / 24 esquinas de tablero de ajedrez detectadas en ambas cámaras. Mantenga visible el objetivo de calibración completo y manténgalo quieto. Si ya es visible, seleccione 12 pulgadas o 30 cm y mida esa distancia más cercana antes de volver a intentarlo.</string>
<string name="spatial_distance_depth">El objetivo de calibración es visible, pero su profundidad no se pudo medir de manera confiable. Mantenga las gafas inteligentes quietas y mantenga el objetivo de calibración plano y bien iluminado, luego vuelva a intentarlo.</string>
<string name="spatial_distance_invalid">La ​​medición de la cámara no fue válida. Quédate quieto y vuelve a intentarlo.</string>
<string name="spatial_distance_passed">Se pasó la verificación de distancia.</string>
<string name="spatial_distance_retry">Vuelva a verificar la distancia desde la cámara izquierda al centro del objetivo de calibración y luego vuelva a intentarlo.</string>
<string name="spatial_distance_result">Las cámaras midieron %1$s; distancia seleccionada %2$s. %3$s</string>
<string name="spatial_wait_previews">Espere las vistas previas de ambas cámaras antes de comenzar la calibración.</string>
<string name="spatial_saved_views_hint">Las vistas guardadas se pueden recalcular. Inicie una nueva calibración para capturar en el sistema de coordenadas de seguimiento actual.</string>
<string name="spatial_model_checksum_error">La suma de comprobación del modelo de mano no coincide</string>
<string name="spatial_opencv_load_error">OpenCV no se pudo cargar</string>
</resources>
+214 -10
View File
@@ -2366,14 +2366,14 @@
<string name="glasses_display">Näyttö</string>
<string name="glasses_refresh">Virkistystaajuus</string>
<string name="glasses_3d">3D (rinnakkain)</string>
<string name="glasses_3d_hint">Kumpikin silmä saa oman puoliskonsa 3840×1080-kuvasta. Jos kuva katoaa, pidä lasien 2D/3D-painiketta pohjassa.</string>
<string name="glasses_3d_hint">Vaihda älylasit vierekkäiseen 3D-tilaan. Pään seurantaa ohjataan erikseen.</string>
<string name="glasses_3d_failed">3D-signaalia ei voitu vahvistaa. Yritä uudelleen, kun 2D-kuva palautuu.</string>
<string name="glasses_no_signal">Ei kuvaa: lasit ovat tilassa, jota tämä puhelin ei pysty lähettämään. Pidä niiden 2D/3D-painiketta pohjassa tai irrota ja kytke ne uudelleen.</string>
<string name="glasses_3d_refresh">3D-virkistystaajuus</string>
<string name="glasses_auto3d">3D VR-peleille</string>
<string name="glasses_auto3d_hint">Vaihtaa 3D:hen SteamVR:n ollessa käynnissä ja takaisin sen jälkeen.</string>
<string name="glasses_picture">3D-kuva</string>
<string name="glasses_picture_hint">Automaattinen: SteamVR täyttää näkymän; muut pelit leijuvat näyttönä 3D:ssä ja pysyvät litteinä 2D:ssä.</string>
<string name="glasses_picture">Kuva</string>
<string name="glasses_picture_hint">Automaattinen toiminto näyttää tasaisen sisällön molemmissa silmissä ja tunnistaa SteamVR:n. Valitse SBS-sisällölle Vierekkäin-peli.</string>
<string name="glasses_picture_auto">Automaattinen</string>
<string name="glasses_picture_screen">Virtuaalinen näyttö</string>
<string name="glasses_picture_sbs">Pelin rinnakkais-3D</string>
@@ -2383,20 +2383,20 @@
<string name="glasses_screen_no_output">Nämä asetukset ovat käytössä, kun Peli laseissa on päällä ja peli näkyy niissä.</string>
<string name="glasses_screen_flat_hint">Automaattinen pitää tämän 2D-pelin koko näytön tilassa. Käytä virtuaalista näyttöä, jos haluat säätää sen leveyttä, etäisyyttä ja pään seurantaa.</string>
<string name="glasses_screen_use">Käytä virtuaalista näyttöä</string>
<string name="glasses_screen_direct_hint">SteamVR täyttää näkymän. Säädä sen näkökenttää alla; leveys ja etäisyys koskevat vain virtuaalista näyttöä.</string>
<string name="glasses_screen_direct_hint">SteamVR täyttää näkymän. Säädä sen näkökenttää VR:ssä; leveys ja etäisyys koskevat vain virtuaalinäyttöä.</string>
<string name="glasses_screen_sbs_hint">Rinnakkais-3D-pelit täyttävät kummankin silmän näkymän. Leveys ja etäisyys koskevat vain virtuaalista näyttöä.</string>
<string name="glasses_tracking_flat_hint">Automaattinen pitää tämän 2D-pelin paikallaan näkymässä. Valitse Näyttö-kohdasta Virtuaalinen näyttö, jos haluat käyttää pään seurantaa.</string>
<string name="glasses_tracking_sbs_hint">Pään seuranta koskee virtuaalista näyttöä tai käynnissä olevaa SteamVR-peliä.</string>
<string name="glasses_hardware">Lasit</string>
<string name="glasses_hardware">Laite</string>
<string name="glasses_brightness">Kirkkaus</string>
<string name="glasses_volume">Äänenvoimakkuus</string>
<string name="glasses_sunblock">Tummennus</string>
<string name="glasses_sunblock_hint">Tummentaa etulinssit.</string>
<string name="glasses_tracking">Pään seuranta</string>
<string name="glasses_tracking">Seuranta</string>
<string name="glasses_head_tracking">Pään seuranta</string>
<string name="glasses_head_tracking_hint">Lasien omat anturit kääntävät virtuaalista näyttöä ja SteamVR:ää pääsi mukana.</string>
<string name="glasses_recenter">Keskitä näkymä</string>
<string name="glasses_recenter_button">Keskitä</string>
<string name="glasses_recenter_button">Keskitä näyttö uudelleen</string>
<string name="glasses_vr_fov">SteamVR:n näkökenttä</string>
<string name="glasses_vr_fov_hint">Pienempi tuo SteamVR:n lähemmäs ja täyttää lasit; suurempi näyttää enemmän ympäriltäsi.</string>
<string name="glasses_playing">Pelataan laitteella %1$s</string>
@@ -2417,8 +2417,8 @@
<string name="glasses_calibration_reset_hint">Poistetaanko tallennettu kalibrointi ja palautetaanko mallin oletukset? Keskitä näkymä, kun olet laittanut lasit takaisin päähän.</string>
<string name="glasses_calibration_reset_button">Nollaa</string>
<string name="hand_camera_group">Käden seuranta</string>
<string name="hand_camera_prompt_title">Seurataanko käsiäsi kameralla?</string>
<string name="hand_camera_prompt_body">Kamera näkee kätesi. Kun ohjainta ei ole yhdistetty, paljaista käsistäsi tulee VR-kädet: napsauta nipistämällä peukalo ja etusormi yhteen, tartu puristamalla käsi nyrkkiin ja avaa valikko kääntämällä kämmen itseäsi kohti ja nipistämällä. Joy-Conien kanssa kamera sijoittaa ne sinne, missä kätesi oikeasti ovat. Kuvat käsitellään tällä laitteella eikä niitä koskaan tallenneta tai lähetetä.</string>
<string name="hand_camera_prompt_title">Seurataanko käsiä vai Joy-Con-ohjaimia puhelimen kameralla?</string>
<string name="hand_camera_prompt_body">Kun ohjainta ei ole kytketty, puhelimen kamera seuraa paljaita käsiäsi VR:ssä: purista peukaloa ja etusormea ​​napsauttamalla, sulje kätesi tarttuaksesi ja käännä kämmentäsi itseäsi kohti ja nipistä avataksesi valikon. Paritettujen Joy-Con-ohjainten kanssa kamera seuraa niiden sijaintia, kun taas Joy-Con-ohjaimet tarjoavat pyöritystä ja painikesyötteitä. Kuvia käsitellään tällä laitteella eikä niitä koskaan tallenneta tai lähetetä.</string>
<string name="hand_camera_rear">Takakamera</string>
<string name="hand_camera_rear_hint">Suositellaan. Yleensä tarkempi ja laajempi.</string>
<string name="hand_camera_front">Etukamera</string>
@@ -2426,7 +2426,7 @@
<string name="hand_camera_allow">Käytä kameraa</string>
<string name="hand_camera_not_now">Ei nyt</string>
<string name="hand_camera_setting">Käden seuranta kameralla</string>
<string name="hand_camera_setting_hint">Kun ohjainta ei ole yhdistetty, käsistäsi tulee VR-kädet: napsauta nipistämällä ja avaa valikko nipistämällä kämmen itseäsi kohti. Joy-Conien kanssa se sijoittaa ne sinne, missä kamera ne näkee. Kuvat pysyvät tällä laitteella.</string>
<string name="hand_camera_setting_hint">Käytä puhelimen kameraa paljain käsin tehtäviin eleisiin tai pariliitettyjen Joy-Con-ohjainten asemointiin VR:ssä. Joy-Con-ohjaimet tarjoavat kierto- ja painikesyötteitä. Seuranta keskeytyy, kun älylasit seuraavat käsiäsi tai Joy-Con-ohjaimiasi. Kuvia käsitellään vain tässä laitteessa.</string>
<string name="hand_camera_choice">Kamera</string>
<string name="hand_camera_access">Kameran käyttöoikeus</string>
<string name="hand_camera_access_hint">Salli kameran käyttö, jotta käden seuranta voi käynnistyä.</string>
@@ -2680,4 +2680,208 @@
<string name="boot_movie_converting_started">Muunnetaan %1$s Steamille. Pidä DroidDeck auki.</string>
<string name="boot_movie_unsupported">Laite ei pysty purkamaan videota.</string>
<string name="boot_movie_import_failed">Videon tuonti epäonnistui</string>
<string name="glasses_experimental">Luma Ultra -kamerat — Kokeellinen</string>
<string name="glasses_joycons">Joy-Con-paikan seuranta</string>
<string name="glasses_joycons_hint">Käytä älylasien kameroita pariliitettyjen Joy-Con-ohjainten asemointiin. Kierto- ja painikesyötteet tulevat Joy-Con-ohjaimista. Sijainti arvioidaan, kun ranteesi ovat piilossa. Korvaa paljain käsin käytettävät ohjaimet ja keskeyttää puhelimen kameran seurannan. Osoita Joy-Con-ohjaimet eteenpäin, kun yhdistät tai nollaat seurantakeskuksen.</string>
<string name="glasses_joycons_requires_position">Ota ensin käyttöön sijainnin seuranta. Vaatii liiketunnistuksella varustetut Joy-Con-ohjaimet Android 12:ssa tai uudemmassa käyttöjärjestelmässä. Oletusarvoisesti pois päältä.</string>
<string name="setup_tab_glasses">Älylasit</string>
<string name="setup_glasses_scope">Näyttö- ja päänseuranta-asetukset jaetaan DroidDeck-valikoiden, pelien ja työpöytäistuntojen välillä.</string>
<string name="setup_glasses_connect">Yhdistä älylasit muuttaaksesi niiden näyttö- ja seuranta-asetuksia. Tallennetut asetuksesi säilyvät, kun irrotat yhteyden.</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">Näyttötila</string>
<string name="glasses_output_2d">2D-ulostulo</string>
<string name="glasses_output_3d">3D-ulostulo</string>
<string name="glasses_picture_group">Kuva ja virtuaalinäyttö</string>
<string name="glasses_position_group">Sijainti ja lattiataso</string>
<string name="glasses_hands_group">Käsien seuranta — Kokeellinen</string>
<string name="glasses_tracking_status">Seurannan tila</string>
<string name="glasses_resolution_2d">2D-resoluutio</string>
<string name="glasses_resolution_3d">3D-resoluutio</string>
<string name="glasses_resolution_vr">VR-resoluutio</string>
<string name="glasses_resolution_auto">Automaattinen</string>
<string name="glasses_resolution_inherit">Sama kuin 2D / 3D</string>
<string name="glasses_resolution_hint">Lähtöresoluutio silmää kohden.</string>
<string name="glasses_refresh_30_hint">Ota käyttöön älylasien pelitoiminto valitaksesi näytön natiivin 30 Hz:n tilan.</string>
<string name="glasses_3dof">3DoF-virtuaalinäyttö</string>
<string name="glasses_3dof_hint">Ankkuroi tasaiset pelit eteesi, jotta voit katsella ympärillesi. Mahdollistaa älylasien pelitoiminnon ilman 3D-tilaan vaihtamista.</string>
<string name="glasses_screen_app_wide">Leveys ja etäisyys päivittävät seuratun virtuaalinäytön DroidDeck-valikoissa, peleissä ja työpöytäistunnoissa välittömästi.</string>
<string name="glasses_head_tracking_spatial_vr_hint">Sijainnin seuranta sisältää pään kierron SteamVR:ssä. Jos haluat vain kierron seurannan, poista sijainnin seuranta käytöstä.</string>
<string name="glasses_head_tracking_spatial_hint">Ohjaa pään liikettä virtuaalinäytöllä. Sijainnin seuranta sisältää pään kierron SteamVR:ssä.</string>
<string name="glasses_quick_actions">Pikatoiminnot</string>
<string name="glasses_recenter_screen_hint">Katso, mihin haluat näytön sijoittuvan, ja nollaa sitten seurantakeskus asettaaksesi sen korkeuden ja suunnan.</string>
<string name="glasses_recenter_joycons_button">Keskitä Joy-Con-ohjaimet uudelleen</string>
<string name="phone_camera_tracking_group">Puhelimen kamera — Kokeellinen</string>
<string name="phone_camera_tracking_setting">Käsien tai Joy-Con-ohjainten seuranta</string>
<string name="glasses_6dof">6DoF-virtuaalinäyttö</string>
<string name="glasses_position">Sijainnin seuranta — kokeellinen</string>
<string name="glasses_position_hint">Fyysinen liike VR:ssä ja virtuaalinäyttöjen ympärillä. Seurannan uudelleenkäynnistäminen kestää hetken.</string>
<string name="glasses_eye_height">Nykyinen silmien korkeus lattiasta</string>
<string name="glasses_floor">Aseta lattiataso nykyisestä korkeudesta</string>
<string name="glasses_floor_set">Aseta lattiataso</string>
<string name="glasses_spatial_setup">Stereokameran asetukset ja diagnostiikka</string>
<string name="glasses_spatial_setup_hint">Tarkista kameran kalibrointi ja käsien seuranta ennen pelaamista.</string>
<string name="glasses_spatial_open">Avaa asetukset</string>
<string name="glasses_hands">Paljain käsin käytettävät ohjaimet</string>
<string name="glasses_hands_hint">Käytä älylasien kameroita tähtäämiseen käsilläsi, nipistämällä klikkaamiseen ja sulkemalla kätesi tarttumiseen VR:ssä. Peliohjaimen sauvat liikkuvat ja kääntyvät. Korvaa Joy-Con-paikan seurannan. Seuranta voi viiveellä tai kadota pelien aikana.</string>
<string name="glasses_hands_ready_hint">Ota käyttöön paljain käsin käytettävät ohjaimet Luma Ultra -kameroiden alla Seurannassa. Näytä avoimet kädet ensin.</string>
<string name="glasses_hands_base">Jaettu Luma Ultra -kalibrointi</string>
<string name="glasses_hands_base_hint">Jaetun Luma Ultra -jalustan käyttö. Kalibroi omat älylasisi varmistaaksesi niiden tarkkuuden.</string>
<string name="glasses_hands_calibration_verified">Mitattu kalibrointi vahvistettu</string>
<string name="glasses_hands_calibration_required">Vahvistus vaaditaan</string>
<string name="glasses_hands_calibration_required_hint">Paljain käsin käytettävät ohjaimet tulevat saataville, kun mitattu etäisyystarkistus on suoritettu.</string>
<string name="tracking_recovery_lost">Seuranta menetetty</string>
<string name="tracking_recovery_look">Katsele hitaasti ympärillesi hyvin valaistussa huoneessa.\nPidä seurantakameroiden näkymä esteettömänä ja katso huonekaluja tai muita näkyviä yksityiskohtia kohti.</string>
<string name="tracking_recovery_restart_help">Oletko edelleen jumissa? Puhelimessa: älylasit → Seuranta → Avaa asetukset → Näytä diagnostiikka → Aloita seuranta uudelleen.</string>
<string name="tracking_recovery_disconnected">Älylasit irrotettu</string>
<string name="tracking_recovery_connect">Tarkista USB-yhteys ja yhdistä älylasit uudelleen.\nHyväksy puhelimesi USB-käyttökehote, jos se tulee näkyviin.</string>
<string name="tracking_recovery_usb_help">Kun yhteys on muodostettu, katso hitaasti ympärillesi hyvin valaistussa huoneessa jäljityksen palauttamiseksi.</string>
<string name="tracking_recovery_recenter">Uudelleenkeskitys tarvitaan</string>
<string name="tracking_recovery_recenter_steps">Seuranta on palannut uuteen sijaintiin.\nKatso eteenpäin ja napauta sitten \"nollaa seurantakeskus\" puhelimessasi.</string>
<string name="tracking_recovery_recenter_help">Seurantakeskuksen nollaus löytyy myös kohdasta älylasit → Seuranta → Avaa asetukset. Tallennettu kameran kalibrointi säilytetään.</string>
<string name="tracking_recovery_stabilizing">Pysy paikallaan hetki</string>
<string name="tracking_recovery_hold">Seurantakeskuksen nollaus on käynnissä.\nPidä älylasit paikoillaan seurannan vakiintumisen ajan.</string>
<string name="tracking_recovery_automatic">Tämä näyttö sulkeutuu automaattisesti, kun pään seuranta on valmis.</string>
<string name="glasses_steady_hand_aim">Stabiloitu tähtäys käsillä</string>
<string name="glasses_steady_hand_aim_hint">Tähtää olkapäältä kämmenen läpi. Sammuta ranteella ohjattua pyöritystä varten.</string>
<string name="spatial_title">Tila-asetukset</string>
<string name="spatial_wait_tracking">Odotetaan seurantaa…</string>
<string name="spatial_cameras_description">Vasen ja oikea seurantakamera</string>
<string name="spatial_camera_sync">Kameran synkronointi</string>
<string name="spatial_ready">Valmis</string>
<string name="spatial_sync_hint">Odotetaan kameran ja päänseurannan linjaamista. Pidä älylasit kytkettynä.</string>
<string name="spatial_prepare">1. Valmistele kalibrointikohde</string>
<string name="spatial_print_actual">Tulosta todellisessa koossa</string>
<string name="spatial_print_hint">Jokaisen neliön on oltava kooltaan 30 mm (1,181 tuumaa). Asenna arkki tasaisesti jäykälle alustalle hyvin valaistulle alueelle.</string>
<string name="spatial_print">Tulosta kalibrointikuvio</string>
<string name="spatial_capture_group">2. Tallenna erilaisia näkymiä</string>
<string name="spatial_board_stationary">Pidä kalibrointikohde paikallaan</string>
<string name="spatial_capture_hint">Siirrä älylaseja lähemmäs, kauemmas ja sivuttain. Käännä niitä vasemmalle/oikealle, kallista niitä ylös/alas ja kallista sivuttain. Pidä kalibrointikohde molemmissa kameroissa ja keskeytä hetki ennen jokaista kuvausta.</string>
<string name="spatial_start">Aloita uusi kalibrointi</string>
<string name="spatial_wait_sync">Odotetaan kameran synkronointia.</string>
<string name="spatial_start_hint">Aloita uusi kalibrointi näkymien keräämiseksi.</string>
<string name="spatial_views_hint">Pidä kalibrointitavoite näkyvissä molemmissa esikatseluissa. Tallenna vähintään 24 terävää näkymää eri kulmista.</string>
<string name="spatial_capturing">Kuvataan…</string>
<string name="spatial_capture">Kaappaa kalibroinnin kohdenäkymä</string>
<string name="spatial_alignment">Laske kameran kohdistus</string>
<string name="spatial_calculating">Lasketaan…</string>
<string name="spatial_calculate">Laske kalibrointi</string>
<string name="spatial_distance_group">3. Tarkista etäisyys</string>
<string name="spatial_distance">Mitattu etäisyys</string>
<string name="spatial_distance_hint">Aloita 12 tuumasta tai 30 cm:stä, jotta painetut merkit on helpompi havaita. Valitse tarkka mitta, jota käytät.</string>
<string name="spatial_camera_board_distance">Vasemman kameran ja kalibrointikohteen keskipisteen välinen etäisyys</string>
<string name="spatial_checking_distance">Tarkistetaan etäisyyttä…</string>
<string name="spatial_validate">Vahvista etäisyys</string>
<string name="spatial_diagnostics">Diagnostiikka</string>
<string name="spatial_show_diagnostics">Näytä diagnostiikka</string>
<string name="spatial_diagnostics_hint">Kameran ajoituksen ja seurannan palautustyökalut.</string>
<string name="spatial_camera_timing">Kameran ajoitus</string>
<string name="spatial_hand_input">Syöttö käsien seurannasta</string>
<string name="spatial_wait_timing">Odotetaan kameran ajoitusta.</string>
<string name="spatial_frames">Kamerakuvat</string>
<string name="spatial_gpu">GPU-käsien tunnistus</string>
<string name="spatial_gpu_hint">CPU on oletusarvo. GPU on kokeellinen ja voi saada SteamVR:n käynnistymään uudelleen. Tämä valinta nollautuu, kun seuranta käynnistetään uudelleen.</string>
<string name="spatial_lite">Käsimallien kevyt tunnistus</string>
<string name="spatial_lite_hint">Käytä Lite-kämmenilmaisinta ja Lite-avainpisteitä käsissä. Tarkista kohdistus, puristus ja tartuntatarkkuus. Nollautuu, kun seuranta käynnistyy uudelleen.</string>
<string name="spatial_adaptive">Automaattinen syöttötaajuus käsien seurantaan</string>
<string name="spatial_adaptive_hint">Säädä tulo prosessointinopeuden mukaan. Nollautuu, kun seuranta käynnistyy uudelleen.</string>
<string name="spatial_half_rate">Rajoita tulotaajuutta käsien seurannassa</string>
<string name="spatial_half_rate_hint">Käytä puolet kameran kuvista. Sammuta, jos haluat verrata enimmäistaajuutta. Nollautuu, kun seuranta käynnistyy uudelleen.</string>
<string name="spatial_diagnostic_capture">Diagnostinen sieppaus</string>
<string name="spatial_diagnostic_capture_hint">Tallenna yksi stereokuvapari paikallisesti. Kuvat tallennetaan vain pyydettäessä.</string>
<string name="spatial_save_frame">Tallenna kamerakuva</string>
<string name="spatial_restart_tracking">Aloita seuranta uudelleen</string>
<string name="spatial_restart_hint">Käytä, jos seuranta ei palaudu hyvin valaistulla alueella. Uudelleenkäynnistys vaatii uuden seurantalähteen.</string>
<string name="spatial_restart">Käynnistä uudelleen</string>
<string name="spatial_restart_title">Aloitetaanko seuranta uudelleen?</string>
<string name="spatial_restart_confirm">Tallentamattomat kalibrointinäkymät menetetään. Pidä älylasit paikoillaan tämän jälkeen ja nollaa seurantakeskus, kun seuranta on palautettu.</string>
<string name="spatial_views_count">%1$d / 24 katselukertaa</string>
<string name="spatial_capture_paused">Kuvaus keskeytetty: %1$s</string>
<string name="spatial_measure_hint">Toimenpide %1$s. Näytä täydellinen kalibrointikohde molemmissa esikatseluissa ja pidä sitten paikallaan.</string>
<string name="spatial_timing_values">%1$.1f Hz • kuvan ikä %2$.1f ms • käsittely %3$.1f ms</string>
<string name="spatial_input_values">Tuloraja %1$.1f Hz • %2$.0f%% kamerakuvia. Pään seuranta jatkuu maksimitaajuudellaan.</string>
<string name="spatial_frame_counts">%1$d käsitelty • %2$d pudonnut • %3$d ohitettu syöttörajan mukaan</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck – Luma Ultra -kalibrointi</string>
<string name="spatial_print_scale" formatted="false">Tulosta 100% / todellinen koko. Jokainen neliö: 30 mm (1,181 tuumaa).</string>
<string name="spatial_print_stationary">Pidä kalibrointikohde tasaisena ja paikallaan; siirrä älylaseja kuvausten välillä.</string>
<string name="spatial_print_check">Mittakaavatarkistus: 100 mm (3,937 tuumaa) punkkien välillä</string>
<string name="spatial_distance_12in">12 tuumaa (30,48 cm)</string>
<string name="spatial_distance_30cm">30 cm (11,81 tuumaa)</string>
<string name="spatial_distance_20in">20 tuumaa (50,8 cm)</string>
<string name="spatial_distance_50cm">50 cm (19,69 tuumaa)</string>
<string name="spatial_disabled">Ei käytössä</string>
<string name="spatial_wait_stable">Odotetaan vakaata seurantaa</string>
<string name="spatial_disconnected">Yhteys katkaistu</string>
<string name="spatial_hands_off">Paljain käsin ohjattavat ohjaimet ovat pois päältä</string>
<string name="spatial_tracking_off">Paikanseuranta on pois päältä tai älylasit on irrotettu</string>
<string name="spatial_recenter_pending">Seurantakeskuksen nollausta pyydetty – pidä paikallasi, kun seuranta vakautuu.</string>
<string name="spatial_tracking_lost">Seuranta menetetty – kohtaa hyvin valaistu alue, jossa on näkyviä yksityiskohtia</string>
<string name="spatial_origin_changed">Seurannan lähtöpiste muuttunut – napauta seurantakeskuksen nollausta ja pidä sitten paikallasi.</string>
<string name="spatial_active">6DoF aktiivinen</string>
<string name="spatial_connect_hint">Liitä Luma Ultra ja ota sijainnin seuranta käyttöön.</string>
<string name="spatial_validation_session">Tämän yhteyden kalibrointi on vahvistettu.</string>
<string name="spatial_validation_saved">Kalibrointi vahvistettu ja tallennettu.</string>
<string name="spatial_wait_cameras">Odotetaan stereokameroita</string>
<string name="spatial_wait_hands">Odotetaan käsien seurantaa</string>
<string name="spatial_collect_instructions">Pidä 5 × 7 -kalibrointitavoite paikallaan. Tallenna yli 24 näkymää liikuttamalla ja pyörittämällä älylaseja.</string>
<string name="spatial_capture_pending">Odotetaan seuraavaa kamerakuvaa. Pidä älylasit paikallaan.</string>
<string name="spatial_validation_pending">Tarkistetaan seuraavaa kamerakuvaa. Pidä koko kalibrointitavoite näkyvissä ja pidä paikallaan.</string>
<string name="spatial_start_first">Aloita kalibrointi ensin</string>
<string name="spatial_no_camera_frame">Uusi kamerakuva ei saapunut. Tarkista älylasien yhteys ja kameran esikatselu ja yritä sitten uudelleen.</string>
<string name="spatial_clock_reset">Kameran kellon nollaus</string>
<string name="spatial_frame_changed">Seurantakoordinaatisto muuttunut. Näkymäsi tallennetaan uudelleenlaskentaa varten. Nollaa seurantakeskus ja aloita uusi sarja useampien näkymien tallentamiseksi.</string>
<string name="spatial_device_loaded">laitteen kalibrointi ladattu</string>
<string name="spatial_base_loaded">Luma Ultra -peruskalibrointi ladattu</string>
<string name="spatial_calibration_needed">kalibrointi vaaditaan</string>
<string name="spatial_tap_start">Napauta Aloita uusi kalibrointi ensin.</string>
<string name="spatial_capture_recenter_pending">Seurantakeskuksen nollaus odottaa vakaata seurantaa. Pysy paikallasi hetki.</string>
<string name="spatial_capture_recenter">Seuranta muuttunut. Napauta seurantakeskuksen nollausta kerran ja pidä sitten paikallasi ennen kuvaamista.</string>
<string name="spatial_capture_unsynchronized">Kuvaus hylätty: pään seuranta ei ollut synkronoitu tai se muuttui kuvauksen aikana. Pysy paikallasi ja yritä uudelleen.</string>
<string name="spatial_align_first">Laske kameran kohdistus ennen etäisyyden tarkistamista.</string>
<string name="spatial_processing_failed">Kameran käsittely epäonnistui</string>
<string name="spatial_hand_head_changed">Pään seuranta muuttui käsien havaitsemisen aikana</string>
<string name="spatial_hand_wait_sync">Odotetaan kameran ja pään synkronointia</string>
<string name="spatial_no_wrists">Ranteita ei havaittu. Pidä Joy-Con-ohjaimesi kahvat älylasien edessä</string>
<string name="spatial_no_hands">Käsiä ei havaittu. Näytä avoimet kämmenet älylasien edessä</string>
<string name="spatial_one_camera_wrists">Ranteet näkyvät vain yhdessä kamerassa; pidä ohjaimen kahvat jaetussa näkymässä</string>
<string name="spatial_one_camera_hands">Kädet näkyvät vain yhdessä kamerassa; pidä molemmat kämmenet yhteisessä näkymässä</string>
<string name="spatial_ambiguous_hands">Molemmat kamerat näkevät kädet, mutta vasen/oikea vastaavuus on epäselvä</string>
<string name="spatial_inconsistent_depth">Kädet havaittu, mutta stereosyvyys on epäjohdonmukainen</string>
<string name="spatial_rejected_wrist">Ranteen asento hylätty stereo- tai liiketarkistuksella</string>
<string name="spatial_inconsistent_palm">Stereopisteet havaittu, mutta kämmengeometria on epäjohdonmukainen</string>
<string name="spatial_hands_need_calibration">Suorita kalibrointi ja etäisyyden tarkistus käsien mahdollistamiseksi</string>
<string name="spatial_hands_wait_stable">Odotetaan vakaata pään seurantaa; nollaa seurantakeskus pyydettäessä.</string>
<string name="spatial_old_frame">Kamerahavainto on liian vanha; uutta paria odotellessa</string>
<string name="spatial_fit_ready">Sovita %1$.2f px. Mittaa valitsemasi etäisyys vasemmasta kamerasta kalibrointikohteen keskustaan ​​ja vahvista sitten.</string>
<string name="spatial_opencv_error">OpenCV ei saatavilla: %1$s</string>
<string name="spatial_restored">%1$d tallennettua näkymää palautettu. Laske kalibrointi yrittääksesi uudelleen tai aloita uusi sarja, jos haluat ottaa lisää.</string>
<string name="spatial_captured">Kuvattu %1$d / 24 katselukertaa. Pidä kalibrointitavoite kiinteänä; siirrä älylaseja seuraavaa näkymää varten.</string>
<string name="spatial_capture_corners">Kuvaus hylätty: %1$d / 12 vaadittua kulmaa näkyvissä molemmissa kameroissa. Näytä lisää kalibrointikohdetta, pidä paikallaan ja yritä sitten uudelleen.</string>
<string name="spatial_validation_error">Etäisyyden tarkistusta ei voitu suorittaa loppuun: %1$s. Yritä uudelleen.</string>
<string name="spatial_inference_error">Käsien tunnistus ei ole käytettävissä: %1$s. Vaihda CPU/GPU tai vaihda ohjaimia, joita käytetään paljain käsin, jotta voit yrittää uudelleen.</string>
<string name="spatial_hand_timeout">Käsien tunnistus ylitti %1$d ms; havainto hylätty</string>
<string name="spatial_wrists_tracked">%1$d / 2 Joy-Con-ranteen asentoa stereolla mitattuna</string>
<string name="spatial_palms_tracked">Seuratut kämmenet: %1$d / 2; nipistysele valmis: %2$d; tarttumaele valmis: %3$d</string>
<string name="spatial_capture_saved">Tallennettu diagnostiikkakaappaus kohteeseen %1$s</string>
<string name="spatial_hand_error">Käsien seuranta pysäytetty: %1$s</string>
<string name="spatial_stereo_loaded">Stereo %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">Pään seuranta ja kalibrointitavoite eroavat toisistaan %1$s:lla (raja 5 cm / 2 tuumaa). Pidä kalibrointitavoite kiinteänä ja odota tasaista seurantaa ennen uuden sarjan aloittamista. Käännä ja kallista älylaseja varovasti; tauko ennen jokaista sieppausta.</string>
<string name="spatial_need_views">Tallenna vähintään 24 erilaista näkymää paikallaan olevasta kalibrointikohteesta.</string>
<string name="spatial_need_rotation">Käännä älylaseja vasemmalle ja oikealle ja kallista niitä ylös ja alas; liike ilman pyöritystä ei voi kalibroida kameran kohdistusta.</string>
<string name="spatial_bad_baseline">Epäuskottava kameran perusviiva</string>
<string name="spatial_need_orientations">Liikuta päätäsi monipuolisemmissa asennoissa.</string>
<string name="spatial_bad_rotation">Virheellinen kameran ja pään välinen kierto</string>
<string name="spatial_bad_alignment">Kameran ja pään välinen kohdistus on epäjohdonmukainen.</string>
<string name="spatial_calibration_error">Kalibrointia ei voitu suorittaa loppuun. %1$s Tallennetut näkymät tallennetaan.</string>
<string name="spatial_reprojection_error">Pidätetty toistovirhe %1$.2f px ylittää 2 pikseliä. Kalibroi uudelleen.</string>
<string name="spatial_distance_corners">%1$d / 24 shakkilaudan kulmaa havaittu molemmissa kameroissa. Pidä koko kalibrointitavoite näkyvissä ja pidä paikallaan. Jos se on jo näkyvissä, valitse 12 tuumaa tai 30 cm ja mittaa lähempi etäisyys ennen kuin yrität uudelleen.</string>
<string name="spatial_distance_depth">Kalibrointikohde on näkyvissä, mutta sen syvyyttä ei voitu mitata luotettavasti. Pidä älylaseja paikallaan ja pidä kalibrointikohde tasaisena ja hyvin valaistuna. Yritä sitten uudelleen.</string>
<string name="spatial_distance_invalid">Kameran mittaus oli virheellinen. Pysy paikallaan ja yritä uudelleen.</string>
<string name="spatial_distance_passed">Etäisyystarkastus läpäisty.</string>
<string name="spatial_distance_retry">Tarkista etäisyys vasemmasta kamerasta kalibrointikohteen keskustaan ​​ja yritä sitten uudelleen.</string>
<string name="spatial_distance_result">Kamerat mitattu %1$s; valittu etäisyys %2$s. %3$s</string>
<string name="spatial_wait_previews">Odota molempien kameroiden esikatselua ennen kalibroinnin aloittamista.</string>
<string name="spatial_saved_views_hint">Tallennetut näkymät voidaan laskea uudelleen. Aloita uusi kalibrointi kaapataksesi nykyiseen seurantakoordinaattijärjestelmään.</string>
<string name="spatial_model_checksum_error">Käsimallin tarkistussumman epäsuhta</string>
<string name="spatial_opencv_load_error">OpenCV:tä ei voitu ladata</string>
</resources>
+214 -10
View File
@@ -2385,14 +2385,14 @@
<string name="glasses_display">Affichage</string>
<string name="glasses_refresh">Fréquence d\'affichage</string>
<string name="glasses_3d">3D (côte à côte)</string>
<string name="glasses_3d_hint">Chaque œil reçoit sa propre moitié d\'une image de 3840×1080. Si l\'image disparaît, maintenez le bouton 2D/3D des lunettes.</string>
<string name="glasses_3d_hint">Basculer les lunettes intelligentes en mode 3D côte à côte. Le suivi de la tête est contrôlé séparément.</string>
<string name="glasses_3d_failed">Le signal 3D n\'a pas été confirmé. Réessayez une fois l\'image 2D rétablie.</string>
<string name="glasses_no_signal">Pas d\'image : les lunettes sont dans un mode que ce téléphone ne peut pas envoyer. Maintenez leur bouton 2D/3D, ou débranchez-les puis rebranchez-les.</string>
<string name="glasses_3d_refresh">Fréquence en 3D</string>
<string name="glasses_auto3d">3D pour les jeux VR</string>
<string name="glasses_auto3d_hint">Passe en 3D pendant que SteamVR tourne, puis revient ensuite.</string>
<string name="glasses_picture">Image 3D</string>
<string name="glasses_picture_hint">Automatique : SteamVR remplit la vue ; les autres jeux flottent comme un écran en 3D et restent plats en 2D.</string>
<string name="glasses_picture">Image</string>
<string name="glasses_picture_hint">Affichage automatique du contenu plat pour les deux yeux et détection de SteamVR. Choisissez « Jeu côte à côte » pour le contenu SBS.</string>
<string name="glasses_picture_auto">Automatique</string>
<string name="glasses_picture_screen">Écran virtuel</string>
<string name="glasses_picture_sbs">Jeu côte à côte</string>
@@ -2402,20 +2402,20 @@
<string name="glasses_screen_no_output">Ces réglages s\'appliquent lorsque Jeu sur les lunettes est activé et que le jeu y est affiché.</string>
<string name="glasses_screen_flat_hint">Automatique garde ce jeu 2D en plein écran. Utilisez un écran virtuel pour régler sa largeur, sa distance et le suivi de la tête.</string>
<string name="glasses_screen_use">Utiliser l\'écran virtuel</string>
<string name="glasses_screen_direct_hint">SteamVR remplit la vue. Réglez son champ de vision ci-dessous ; la largeur et la distance ne s\'appliquent qu\'à un écran virtuel.</string>
<string name="glasses_screen_direct_hint">SteamVR remplit le champ de vision. Ajustez son champ de vision en VR ; la largeur et la distance s’appliquent uniquement à l’écran virtuel.</string>
<string name="glasses_screen_sbs_hint">Les jeux côte à côte remplissent chaque œil. La largeur et la distance ne s\'appliquent qu\'à un écran virtuel.</string>
<string name="glasses_tracking_flat_hint">Automatique garde ce jeu 2D fixe dans la vue. Choisissez Écran virtuel dans Affichage pour utiliser le suivi de la tête.</string>
<string name="glasses_tracking_sbs_hint">Le suivi de la tête s\'applique à un écran virtuel ou à un jeu SteamVR en cours.</string>
<string name="glasses_hardware">Lunettes</string>
<string name="glasses_hardware">Appareil</string>
<string name="glasses_brightness">Luminosité</string>
<string name="glasses_volume">Volume</string>
<string name="glasses_sunblock">Pare-soleil</string>
<string name="glasses_sunblock_hint">Assombrit les verres avant.</string>
<string name="glasses_tracking">Suivi de la tête</string>
<string name="glasses_tracking">Suivi</string>
<string name="glasses_head_tracking">Suivi de la tête</string>
<string name="glasses_head_tracking_hint">Les capteurs des lunettes font tourner l\'écran virtuel et SteamVR avec votre tête.</string>
<string name="glasses_recenter">Recentrer la vue</string>
<string name="glasses_recenter_button">Recentrer</string>
<string name="glasses_recenter_button">Recentrer l’écran</string>
<string name="glasses_vr_fov">Champ de vision SteamVR</string>
<string name="glasses_vr_fov_hint">Plus bas rapproche SteamVR et remplit les lunettes ; plus haut montre davantage autour de vous.</string>
<string name="glasses_playing">En jeu sur %1$s</string>
@@ -2436,8 +2436,8 @@
<string name="glasses_calibration_reset_hint">Supprimer le calibrage enregistré et rétablir les valeurs par défaut du modèle ? Recentrez après avoir remis les lunettes.</string>
<string name="glasses_calibration_reset_button">Réinitialiser</string>
<string name="hand_camera_group">Suivi des mains</string>
<string name="hand_camera_prompt_title">Suivre vos mains avec la caméra ?</string>
<string name="hand_camera_prompt_body">La caméra peut voir vos mains. Sans manette connectée, vos mains nues deviennent les mains VR : pincez le pouce et l\'index pour cliquer, fermez la main pour saisir, et tournez la paume vers vous puis pincez pour ouvrir le menu. Avec des Joy-Con, elle les place là où se trouvent vraiment vos mains. Les images sont traitées sur cet appareil et ne sont jamais enregistrées ni envoyées.</string>
<string name="hand_camera_prompt_title">Suivi des mains ou des manettes Joy-Con avec la caméra du téléphone ?</string>
<string name="hand_camera_prompt_body">Sans manette connectée, la caméra du téléphone suit vos mains nues en VR : pincez votre pouce et votre index pour cliquer, fermez la main pour saisir un objet, et tournez la paume vers vous et pincez pour ouvrir le menu. Avec des manettes Joy-Con connectées, la caméra suit leur position tandis que les manettes permettent la rotation et l\'utilisation des boutons. Les images sont traitées sur cet appareil et ne sont ni enregistrées ni envoyées.</string>
<string name="hand_camera_rear">Caméra arrière</string>
<string name="hand_camera_rear_hint">Recommandée. Généralement plus nette et plus large.</string>
<string name="hand_camera_front">Caméra avant</string>
@@ -2445,7 +2445,7 @@
<string name="hand_camera_allow">Utiliser la caméra</string>
<string name="hand_camera_not_now">Pas maintenant</string>
<string name="hand_camera_setting">Suivi des mains par caméra</string>
<string name="hand_camera_setting_hint">Sans manette connectée, vos mains deviennent les mains VR : pincez pour cliquer, paume vers vous et pincez pour le menu. Avec des Joy-Con, elle les place là où la caméra les voit. Les images restent sur cet appareil.</string>
<string name="hand_camera_setting_hint">Utilisez la caméra du téléphone pour les gestes à mains nues ou pour positionner les manettes Joy-Con appariées en VR. Les manettes Joy-Con permettent la rotation et les entrées des boutons. Interrompt le suivi pendant que les lunettes connectées suivent vos mains ou les manettes Joy-Con. Les images sont traitées uniquement sur cet appareil.</string>
<string name="hand_camera_choice">Caméra</string>
<string name="hand_camera_access">Accès à la caméra</string>
<string name="hand_camera_access_hint">Autorisez l\'accès à la caméra pour démarrer le suivi des mains.</string>
@@ -2705,4 +2705,208 @@
<string name="boot_movie_converting_started">Conversion de %1$s pour Steam. Gardez DroidDeck ouvert.</string>
<string name="boot_movie_unsupported">Cet appareil ne peut pas décoder cette vidéo.</string>
<string name="boot_movie_import_failed">Impossible d\'importer cette vidéo</string>
<string name="glasses_experimental">Caméras Luma Ultra — Expérimental</string>
<string name="glasses_joycons">Suivi de la position des Joy-Con</string>
<string name="glasses_joycons_hint">Utilisez les caméras des lunettes connectées pour positionner vos manettes Joy-Con appariées. La rotation et les entrées des boutons proviennent des manettes Joy-Con. La position est estimée lorsque vos poignets sont cachés. Remplace les manettes utilisées à mains nues et interrompt le suivi par la caméra du téléphone. Pointez les manettes Joy-Con vers l\'avant lors de la connexion ou de la réinitialisation du centre de suivi.</string>
<string name="glasses_joycons_requires_position">Activez d\'abord le suivi de position. Nécessite des manettes Joy-Con compatibles avec la détection de mouvements sous Android 12 ou version ultérieure. Désactivé par défaut.</string>
<string name="setup_tab_glasses">Lunettes connectées</string>
<string name="setup_glasses_scope">Les paramètres d\'affichage et de suivi de la tête sont partagés entre les menus DroidDeck, les jeux et les sessions de bureau.</string>
<string name="setup_glasses_connect">Connectez vos lunettes connectées pour modifier leurs paramètres d\'affichage et de suivi. Vos paramètres enregistrés sont conservés lors de la déconnexion.</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">Mode d\'affichage</string>
<string name="glasses_output_2d">Sortie 2D</string>
<string name="glasses_output_3d">Sortie 3D</string>
<string name="glasses_picture_group">Image et écran virtuel</string>
<string name="glasses_position_group">Position et niveau du sol</string>
<string name="glasses_hands_group">Suivi des mains — Expérimental</string>
<string name="glasses_tracking_status">État du suivi</string>
<string name="glasses_resolution_2d">Résolution 2D</string>
<string name="glasses_resolution_3d">Résolution 3D</string>
<string name="glasses_resolution_vr">Résolution VR</string>
<string name="glasses_resolution_auto">Automatique</string>
<string name="glasses_resolution_inherit">Identique à 2D/3D</string>
<string name="glasses_resolution_hint">Résolution de sortie par œil.</string>
<string name="glasses_refresh_30_hint">Activer le mode Jeu sur lunettes intelligentes pour sélectionner le mode 30 Hz natif de l\'écran.</string>
<string name="glasses_3dof">Écran virtuel 3DoF</string>
<string name="glasses_3dof_hint">Ancrer les jeux plats devant vous pour vous permettre de regarder autour de vous. Active le mode Jeu sur lunettes intelligentes sans passer en 3D.</string>
<string name="glasses_screen_app_wide">Largeur et distance : mise à jour immédiate de l’écran virtuel suivi pour les menus DroidDeck, les jeux et les sessions de bureau.</string>
<string name="glasses_head_tracking_spatial_vr_hint">Le suivi de position inclut la rotation de la tête dans SteamVR. Pour un suivi de la rotation uniquement, désactivez le suivi de position.</string>
<string name="glasses_head_tracking_spatial_hint">Contrôle les mouvements de la tête sur l’écran virtuel. Le suivi de position inclut la rotation de la tête dans SteamVR.</string>
<string name="glasses_quick_actions">Actions rapides</string>
<string name="glasses_recenter_screen_hint">Regardez où vous souhaitez que l\'écran apparaisse, puis réinitialisez le centre de suivi pour définir sa hauteur et sa direction.</string>
<string name="glasses_recenter_joycons_button">Recentrer les Joy-Con</string>
<string name="phone_camera_tracking_group">Caméra du téléphone — Expérimental</string>
<string name="phone_camera_tracking_setting">Suivi des mains ou des manettes Joy-Con</string>
<string name="glasses_6dof">Écran virtuel 6DoF</string>
<string name="glasses_position">Suivi de position — expérimental</string>
<string name="glasses_position_hint">Mouvements physiques en VR et autour des écrans virtuels. Le redémarrage du suivi peut prendre quelques instants.</string>
<string name="glasses_eye_height">Hauteur actuelle des yeux par rapport au sol</string>
<string name="glasses_floor">Définir le niveau du sol à partir de la hauteur actuelle</string>
<string name="glasses_floor_set">Définir le niveau du sol</string>
<string name="glasses_spatial_setup">Configuration et diagnostic de la caméra stéréo</string>
<string name="glasses_spatial_setup_hint">Vérifiez l\'étalonnage de la caméra et le suivi des mains avant de jouer.</string>
<string name="glasses_spatial_open">Ouvrir la configuration</string>
<string name="glasses_hands">Manettes utilisées à mains nues</string>
<string name="glasses_hands_hint">Utilisez les caméras des lunettes intelligentes pour viser avec vos mains, pincer pour cliquer et fermer la main pour saisir en VR. Les joysticks de la manette bougent et tournent. Remplace le suivi de position des Joy-Con. Peut entraîner des latences ou des pertes de suivi pendant les jeux.</string>
<string name="glasses_hands_ready_hint">Activer l\'utilisation des manettes à mains nues sous les caméras Luma Ultra dans Suivi. Afficher d\'abord les mains ouvertes.</string>
<string name="glasses_hands_base">Étalonnage Luma Ultra partagé</string>
<string name="glasses_hands_base_hint">Utilisation de la base Luma Ultra partagée. Calibrez vos lunettes connectées pour vérifier leur précision.</string>
<string name="glasses_hands_calibration_verified">Calibrage mesuré vérifié</string>
<string name="glasses_hands_calibration_required">Validation requise</string>
<string name="glasses_hands_calibration_required_hint">Les manettes utilisables à mains nues sont disponibles une fois la vérification de distance effectuée.</string>
<string name="tracking_recovery_lost">Suivi perdu</string>
<string name="tracking_recovery_look">Regardez lentement autour de vous dans une pièce bien éclairée.\nAssurez-vous que les caméras de suivi sont dégagées et que vous êtes face à des meubles ou à d\'autres éléments visibles.</string>
<string name="tracking_recovery_restart_help">Toujours bloqué ? Sur votre téléphone : lunettes intelligentes → Suivi → Ouvrir la configuration → Afficher les diagnostics → Redémarrer le suivi.</string>
<string name="tracking_recovery_disconnected">Lunettes intelligentes déconnectées</string>
<string name="tracking_recovery_connect">Vérifiez la connexion USB et reconnectez les lunettes intelligentes.\nAcceptez l\'invite d\'accès USB sur votre téléphone si elle apparaît.</string>
<string name="tracking_recovery_usb_help">Une fois connecté, regardez lentement autour de vous dans une pièce bien éclairée pour rétablir le suivi.</string>
<string name="tracking_recovery_recenter">Recentrage nécessaire</string>
<string name="tracking_recovery_recenter_steps">Le suivi a repris dans une nouvelle position.\nRegardez devant vous, puis appuyez sur « Réinitialiser le centre de suivi » sur votre téléphone.</string>
<string name="tracking_recovery_recenter_help">Vous pouvez également réinitialiser le centre de suivi dans Lunettes connectées → Suivi → Ouvrir la configuration. Votre étalonnage de caméra enregistré est conservé.</string>
<string name="tracking_recovery_stabilizing">Restez immobile un instant</string>
<string name="tracking_recovery_hold">Réinitialisation du centre de suivi en cours.\nGardez les lunettes connectées immobiles pendant la stabilisation du suivi.</string>
<string name="tracking_recovery_automatic">Cet écran se ferme automatiquement lorsque le suivi de la tête est prêt.</string>
<string name="glasses_steady_hand_aim">Visée stabilisée avec les mains</string>
<string name="glasses_steady_hand_aim_hint">Visez depuis votre épaule à travers votre paume. Éteignez-le pour une rotation contrôlée par le poignet.</string>
<string name="spatial_title">Configuration spatiale</string>
<string name="spatial_wait_tracking">En attente de suivi…</string>
<string name="spatial_cameras_description">Caméras de suivi gauche et droite</string>
<string name="spatial_camera_sync">Synchronisation de la caméra</string>
<string name="spatial_ready">Prêt</string>
<string name="spatial_sync_hint">En attente que la caméra et le suivi de la tête s\'alignent. Gardez les lunettes intelligentes connectées.</string>
<string name="spatial_prepare">1. Préparez la cible d\'étalonnage</string>
<string name="spatial_print_actual">Imprimer à taille réelle</string>
<string name="spatial_print_hint">Chaque carré doit mesurer 30 mm (1,181 po). Montez la feuille à plat sur une surface rigide dans un endroit bien éclairé.</string>
<string name="spatial_print">Imprimer la mire d’étalonnage</string>
<string name="spatial_capture_group">2. Capturer différentes vues</string>
<string name="spatial_board_stationary">Maintenir la cible d\'étalonnage stationnaire</string>
<string name="spatial_capture_hint">Rapprochez, éloignez et latéralement les lunettes intelligentes. Tournez-les à gauche/droite, inclinez-les vers le haut/bas et inclinez-les sur le côté. Conservez la cible d\'étalonnage dans les deux caméras et faites une brève pause avant chaque capture.</string>
<string name="spatial_start">Démarrer un nouvel étalonnage</string>
<string name="spatial_wait_sync">En attente de synchronisation de la caméra.</string>
<string name="spatial_start_hint">Démarrez un nouveau calibrage pour collecter des vues.</string>
<string name="spatial_views_hint">Gardez la cible d\'étalonnage visible dans les deux aperçus. Capturez au moins 24 vues nettes sous différents angles.</string>
<string name="spatial_capturing">Capturer…</string>
<string name="spatial_capture">Capturer la vue cible d\'étalonnage</string>
<string name="spatial_alignment">Calculer l\'alignement de la caméra</string>
<string name="spatial_calculating">Calcul…</string>
<string name="spatial_calculate">Calculer l\'étalonnage</string>
<string name="spatial_distance_group">3. Vérifiez la distance</string>
<string name="spatial_distance">Distance mesurée</string>
<string name="spatial_distance_hint">Commencez par 12 pouces ou 30 cm pour que les marqueurs imprimés soient plus faciles à détecter. Choisissez la mesure exacte que vous utiliserez.</string>
<string name="spatial_camera_board_distance">Caméra gauche vers le centre de la cible d\'étalonnage</string>
<string name="spatial_checking_distance">Vérification de la distance…</string>
<string name="spatial_validate">Valider la distance</string>
<string name="spatial_diagnostics">Diagnostic</string>
<string name="spatial_show_diagnostics">Afficher les diagnostics</string>
<string name="spatial_diagnostics_hint">Outils de synchronisation et de récupération de suivi de la caméra.</string>
<string name="spatial_camera_timing">Synchronisation de la caméra</string>
<string name="spatial_hand_input">Entrée du suivi des mains</string>
<string name="spatial_wait_timing">En attente du timing de la caméra.</string>
<string name="spatial_frames">Images de la caméra</string>
<string name="spatial_gpu">Détection GPU des mains</string>
<string name="spatial_gpu_hint">Le CPU est utilisé par défaut. Le GPU est expérimental et peut provoquer un redémarrage de SteamVR. Ce choix est réinitialisé au redémarrage du suivi.</string>
<string name="spatial_lite">Détection légère des modèles de mains</string>
<string name="spatial_lite_hint">Utilisez le détecteur de paume Lite et les points clés Lite sur les mains. Vérifiez la précision de la visée, du pincement et de la saisie. Se réinitialise lors du redémarrage du suivi.</string>
<string name="spatial_adaptive">Fréquence d\'entrée automatique pour le suivi des mains</string>
<string name="spatial_adaptive_hint">Ajustez l\'entrée à la vitesse de traitement. Se réinitialise lors du redémarrage du suivi.</string>
<string name="spatial_half_rate">Limiter la fréquence d\'entrée pour le suivi des mains</string>
<string name="spatial_half_rate_hint">Utilisez la moitié des images de la caméra. Éteignez pour comparer la fréquence maximale. Se réinitialise lors du redémarrage du suivi.</string>
<string name="spatial_diagnostic_capture">Capture diagnostique</string>
<string name="spatial_diagnostic_capture_hint">Enregistrez localement une paire d’images stéréo. Les images sont enregistrées uniquement sur demande.</string>
<string name="spatial_save_frame">Enregistrer l\'image de la caméra</string>
<string name="spatial_restart_tracking">Redémarrer le suivi</string>
<string name="spatial_restart_hint">À utiliser si le suivi ne se rétablit pas dans une zone bien éclairée. Un redémarrage nécessite une nouvelle origine de suivi.</string>
<string name="spatial_restart">Redémarrer</string>
<string name="spatial_restart_title">Redémarrer le suivi ?</string>
<string name="spatial_restart_confirm">Les vues d\'étalonnage non enregistrées seront perdues. Gardez les lunettes connectées immobiles ensuite et réinitialisez le centre de suivi une fois le suivi rétabli.</string>
<string name="spatial_views_count">%1$d / 24 vues capturées</string>
<string name="spatial_capture_paused">Capture interrompue : %1$s</string>
<string name="spatial_measure_hint">Mesure %1$s. Affichez la cible d\'étalonnage complète dans les deux aperçus, puis restez immobile.</string>
<string name="spatial_timing_values">%1$.1f Hz • âge de l\'image %2$.1f ms • traitement %3$.1f ms</string>
<string name="spatial_input_values">Limite d\'entrée %1$.1f Hz • %2$.0f%% des images de la caméra. Le suivi de la tête continue à sa fréquence maximale.</string>
<string name="spatial_frame_counts">%1$d traité • %2$d supprimé • %3$d ignoré par limite d\'entrée</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck - Calibrage Luma Ultra</string>
<string name="spatial_print_scale" formatted="false">Imprimer à 100% / Taille réelle. Chaque carré : 30 mm (1,181 po).</string>
<string name="spatial_print_stationary">Gardez la cible d\'étalonnage à plat et stationnaire ; déplacez les lunettes intelligentes entre les captures.</string>
<string name="spatial_print_check">Vérification de l\'échelle : 100 mm (3,937 in) entre les graduations</string>
<string name="spatial_distance_12in">12 pouces (30,48 cm)</string>
<string name="spatial_distance_30cm">30 cm (11,81 pouces)</string>
<string name="spatial_distance_20in">20 po (50,8 cm)</string>
<string name="spatial_distance_50cm">50 cm (19,69 pouces)</string>
<string name="spatial_disabled">Désactivé</string>
<string name="spatial_wait_stable">En attente d\'un suivi stable</string>
<string name="spatial_disconnected">Déconnecté</string>
<string name="spatial_hands_off">Les contrôleurs actionnés à mains nues sont éteints</string>
<string name="spatial_tracking_off">Le suivi de position est désactivé ou les lunettes intelligentes sont déconnectées</string>
<string name="spatial_recenter_pending">Réinitialisation du centre de suivi demandée — restez immobile pendant la stabilisation du suivi.</string>
<string name="spatial_tracking_lost">Suivi perdu – faites face à une zone bien éclairée avec des détails visibles</string>
<string name="spatial_origin_changed">Origine du suivi modifiée — appuyez sur Réinitialiser le centre de suivi, puis restez immobile.</string>
<string name="spatial_active">6DoF actif</string>
<string name="spatial_connect_hint">Connectez Luma Ultra et activez le suivi de position.</string>
<string name="spatial_validation_session">Calibrage vérifié pour cette connexion.</string>
<string name="spatial_validation_saved">Calibrage vérifié et enregistré.</string>
<string name="spatial_wait_cameras">En attente des caméras stéréo</string>
<string name="spatial_wait_hands">En attente du suivi des mains</string>
<string name="spatial_collect_instructions">Gardez la cible d\'étalonnage 5 × 7 stationnaire. Capturez plus de 24 vues tout en déplaçant et en faisant pivoter les lunettes intelligentes.</string>
<string name="spatial_capture_pending">En attente de la prochaine image de la caméra. Tenez immobile les lunettes intelligentes.</string>
<string name="spatial_validation_pending">Vérification de l\'image suivante de la caméra. Gardez la cible d\'étalonnage complète visible et restez immobile.</string>
<string name="spatial_start_first">Commencer d\'abord l\'étalonnage</string>
<string name="spatial_no_camera_frame">Aucune nouvelle image de caméra n’est arrivée. Vérifiez la connexion des lunettes intelligentes et les aperçus de l\'appareil photo, puis réessayez.</string>
<string name="spatial_clock_reset">Réinitialisation de l\'horloge de l\'appareil photo</string>
<string name="spatial_frame_changed">Système de coordonnées de suivi modifié. Vos vues sont enregistrées pour recalcul. Réinitialisez le centre de suivi, puis lancez une nouvelle série pour capturer davantage de vues.</string>
<string name="spatial_device_loaded">étalonnage de l\'appareil chargé</string>
<string name="spatial_base_loaded">Calibrage de base Luma Ultra chargé</string>
<string name="spatial_calibration_needed">étalonnage requis</string>
<string name="spatial_tap_start">Appuyez d\'abord sur Démarrer un nouvel étalonnage.</string>
<string name="spatial_capture_recenter_pending">La réinitialisation du centre de suivi attend un suivi stable. Restez immobile un instant.</string>
<string name="spatial_capture_recenter">Suivi modifié. Appuyez une fois sur Réinitialiser le centre de suivi, puis restez immobile avant de capturer.</string>
<string name="spatial_capture_unsynchronized">Capture rejetée : le suivi de la tête était désynchronisé ou a changé pendant la capture. Restez immobile et réessayez.</string>
<string name="spatial_align_first">Calculez l\'alignement de la caméra avant de vérifier la distance.</string>
<string name="spatial_processing_failed">Échec du traitement de la caméra</string>
<string name="spatial_hand_head_changed">Le suivi de la tête a changé lors de la détection des mains</string>
<string name="spatial_hand_wait_sync">En attente de synchronisation de la caméra et de la tête</string>
<string name="spatial_no_wrists">Aucun poignet détecté. Gardez les poignées de votre contrôleur Joy-Con devant les lunettes intelligentes</string>
<string name="spatial_no_hands">Aucune main détectée. Montrez les paumes ouvertes devant les lunettes intelligentes</string>
<string name="spatial_one_camera_wrists">Poignets visibles dans une seule caméra ; gardez les poignées de votre contrôleur dans la vue partagée</string>
<string name="spatial_one_camera_hands">Mains visibles dans une seule caméra ; garder les deux paumes dans la vue partagée</string>
<string name="spatial_ambiguous_hands">Les deux caméras voient les mains, mais la correspondance gauche/droite est ambiguë</string>
<string name="spatial_inconsistent_depth">Mains détectées, mais la profondeur stéréo est incohérente</string>
<string name="spatial_rejected_wrist">Position du poignet rejetée par les contrôles stéréo ou de mouvement</string>
<string name="spatial_inconsistent_palm">Points stéréo détectés, mais la géométrie de la paume est incohérente</string>
<string name="spatial_hands_need_calibration">Terminer l\'étalonnage et la vérification de la distance pour permettre aux mains</string>
<string name="spatial_hands_wait_stable">Attente d\'un suivi de tête stable ; réinitialisation du centre de suivi si demandé.</string>
<string name="spatial_old_frame">L\'observation par caméra est trop ancienne ; en attendant une nouvelle paire</string>
<string name="spatial_fit_ready">Ajuster %1$.2f px. Mesurez la distance sélectionnée entre la caméra gauche et le centre de la cible de calibrage, puis validez.</string>
<string name="spatial_opencv_error">OpenCV indisponible : %1$s</string>
<string name="spatial_restored">%1$d vues enregistrées restaurées. Calculez l’étalonnage pour réessayer ou démarrez un nouvel ensemble pour en capturer davantage.</string>
<string name="spatial_captured">Capturé %1$d / 24 vues. Gardez la cible d\'étalonnage fixe ; déplacez les lunettes intelligentes pour la vue suivante.</string>
<string name="spatial_capture_corners">Capture rejetée : %1$d / 12 coins requis visibles dans les deux caméras. Affichez davantage la cible d\'étalonnage, restez immobile, puis réessayez.</string>
<string name="spatial_validation_error">La vérification de distance n\'a pas pu se terminer : %1$s. Essayer à nouveau.</string>
<string name="spatial_inference_error">Détection de mains indisponibles : %1$s. Changez de CPU/GPU ou basculez les contrôleurs utilisés à mains nues pour réessayer.</string>
<string name="spatial_hand_timeout">Détection des mains dépassée %1$d ms ; observation rejetée</string>
<string name="spatial_wrists_tracked">%1$d / 2 positions du poignet du Joy-Con mesurées en stéréo</string>
<string name="spatial_palms_tracked">Paumes suivies : %1$d / 2 ; pincement prêt : %2$d ; saisie prête : %3$d</string>
<string name="spatial_capture_saved">Capture de diagnostic enregistrée dans %1$s</string>
<string name="spatial_hand_error">Suivi des mains arrêté : %1$s</string>
<string name="spatial_stereo_loaded">Stéréo %1$d×%2$d ; %3$s</string>
<string name="spatial_alignment_error">Le suivi de la tête et la cible d\'étalonnage sont en désaccord de %1$s (limite de 5 cm / 2 po). Gardez la cible d\'étalonnage fixe et attendez un suivi stable avant de commencer un nouvel ensemble. Tournez et inclinez doucement les lunettes intelligentes ; faites une pause avant chaque capture.</string>
<string name="spatial_need_views">Capturez au moins 24 vues variées de la cible d\'étalonnage stationnaire.</string>
<string name="spatial_need_rotation">Tournez les lunettes intelligentes à gauche et à droite et inclinez-les de haut en bas ; un mouvement sans rotation ne peut pas calibrer l’alignement de la caméra.</string>
<string name="spatial_bad_baseline">Base de référence de la caméra invraisemblable</string>
<string name="spatial_need_orientations">Déplacez votre tête dans des orientations plus variées.</string>
<string name="spatial_bad_rotation">Rotation caméra-tête non valide</string>
<string name="spatial_bad_alignment">L\'alignement caméra-tête est incohérent.</string>
<string name="spatial_calibration_error">L\'étalonnage n\'a pas pu se terminer. %1$s Vos vues capturées sont enregistrées.</string>
<string name="spatial_reprojection_error">L\'erreur de reprojection suspendue %1$.2f px dépasse 2 px. Recalibrer.</string>
<string name="spatial_distance_corners">%1$d / 24 coins en damier détectés dans les deux caméras. Gardez la cible d\'étalonnage complète visible et restez immobile. S\'il est déjà visible, sélectionnez 12 pouces ou 30 cm et mesurez cette distance la plus proche avant de réessayer.</string>
<string name="spatial_distance_depth">La cible d\'étalonnage est visible, mais sa profondeur n\'a pas pu être mesurée de manière fiable. Maintenez les lunettes intelligentes immobiles et gardez la cible d\'étalonnage plate et bien éclairée, puis réessayez.</string>
<string name="spatial_distance_invalid">Les mesures de la caméra n\'étaient pas valides. Restez immobile et réessayez.</string>
<string name="spatial_distance_passed">Contrôle de distance réussi.</string>
<string name="spatial_distance_retry">Revérifiez la distance entre la caméra gauche et le centre de la cible d\'étalonnage, puis réessayez.</string>
<string name="spatial_distance_result">Caméras mesurées %1$s ; distance sélectionnée %2$s. %3$s</string>
<string name="spatial_wait_previews">Attendez les deux aperçus de la caméra avant de commencer l\'étalonnage.</string>
<string name="spatial_saved_views_hint">Les vues enregistrées peuvent être recalculées. Démarrez un nouvel étalonnage pour capturer dans le système de coordonnées de suivi actuel.</string>
<string name="spatial_model_checksum_error">Incohérence de la somme de contrôle du modèle manuel</string>
<string name="spatial_opencv_load_error">OpenCV n\'a pas pu charger</string>
</resources>
+214 -10
View File
@@ -2366,14 +2366,14 @@
<string name="glasses_display">डिस्प्ले</string>
<string name="glasses_refresh">रिफ्रेश रेट</string>
<string name="glasses_3d">3D (साइड-बाय-साइड)</string>
<string name="glasses_3d_hint">हर आंख को 3840×1080 तस्वीर का अपना आधा हिस्सा मिलता है। अगर तस्वीर चली जाए, तो चश्मे का 2D/3D बटन दबाकर रखें।</string>
<string name="glasses_3d_hint">स्मार्ट ग्लास को साइड-बाय-साइड 3D पर स्विच करें। हेड ट्रैकिंग को अलग से कंट्रोल किया जाता है।</string>
<string name="glasses_3d_failed">3D सिग्नल की पुष्टि नहीं हुई। 2D तस्वीर वापस आने पर फिर कोशिश करें।</string>
<string name="glasses_no_signal">कोई तस्वीर नहीं: चश्मा ऐसे मोड में है जिसे यह फ़ोन नहीं भेज सकता। उसका 2D/3D बटन दबाकर रखें, या केबल निकालकर फिर से लगाएं।</string>
<string name="glasses_3d_refresh">3D रिफ्रेश रेट</string>
<string name="glasses_auto3d">VR गेम के लिए 3D</string>
<string name="glasses_auto3d_hint">SteamVR चलने के दौरान 3D पर चला जाता है, और बाद में वापस आ जाता है।</string>
<string name="glasses_picture">3D तस्वीर</string>
<string name="glasses_picture_hint">ऑटोमैटिक: SteamVR पूरा व्यू भर देता है; दूसरे गेम 3D में स्क्रीन की तरह हवा में दिखते हैं और 2D में सपाट रहते हैं।</string>
<string name="glasses_picture">पिक्चर</string>
<string name="glasses_picture_hint">ऑटोमैटिक दोनों आँखों में फ़्लैट कंटेंट दिखाता है और SteamVR को डिटेक्ट करता है। SBS कंटेंट के लिए साइड-बाय-साइड गेम चुनें।</string>
<string name="glasses_picture_auto">ऑटोमैटिक</string>
<string name="glasses_picture_screen">वर्चुअल स्क्रीन</string>
<string name="glasses_picture_sbs">साइड-बाय-साइड गेम</string>
@@ -2383,20 +2383,20 @@
<string name="glasses_screen_no_output">ये सेटिंग तब लागू होती हैं जब चश्मे पर गेम चालू हो और गेम वहीं दिख रहा हो।</string>
<string name="glasses_screen_flat_hint">ऑटोमैटिक इस 2D गेम को फ़ुल-स्क्रीन रखता है। इसकी चौड़ाई, दूरी और हेड ट्रैकिंग बदलने के लिए वर्चुअल स्क्रीन इस्तेमाल करें।</string>
<string name="glasses_screen_use">वर्चुअल स्क्रीन इस्तेमाल करें</string>
<string name="glasses_screen_direct_hint">SteamVR पूरा व्यू भर देता है। नीचे इसका फ़ील्ड ऑफ़ व्यू बदलें; चौड़ाई और दूरी सिर्फ़ वर्चुअल स्क्रीन पर लागू होती हैं।</string>
<string name="glasses_screen_direct_hint">SteamVR व्यू को भरता है। VR में इसके फ़ील्ड ऑफ़ व्यू को एडजस्ट करें; चौड़ाई और दूरी सिर्फ़ वर्चुअल स्क्रीन पर लागू होती है।</string>
<string name="glasses_screen_sbs_hint">साइड-बाय-साइड गेम हर आंख का व्यू भर देते हैं। चौड़ाई और दूरी सिर्फ़ वर्चुअल स्क्रीन पर लागू होती हैं।</string>
<string name="glasses_tracking_flat_hint">ऑटोमैटिक इस 2D गेम को व्यू में एक जगह टिकाए रखता है। हेड ट्रैकिंग इस्तेमाल करने के लिए डिस्प्ले में वर्चुअल स्क्रीन चुनें।</string>
<string name="glasses_tracking_sbs_hint">हेड ट्रैकिंग वर्चुअल स्क्रीन या चल रहे SteamVR गेम पर लागू होती है।</string>
<string name="glasses_hardware">चश्मा</string>
<string name="glasses_hardware">डिवाइस</string>
<string name="glasses_brightness">ब्राइटनेस</string>
<string name="glasses_volume">वॉल्यूम</string>
<string name="glasses_sunblock">सनब्लॉक</string>
<string name="glasses_sunblock_hint">आगे के लेंस गहरे करता है।</string>
<string name="glasses_tracking">हेड ट्रैकिंग</string>
<string name="glasses_tracking">ट्रैकिंग</string>
<string name="glasses_head_tracking">हेड ट्रैकिंग</string>
<string name="glasses_head_tracking_hint">चश्मे के अपने सेंसर वर्चुअल स्क्रीन और SteamVR को आपके सिर के साथ घुमाते हैं।</string>
<string name="glasses_recenter">व्यू को फिर से बीच में लाएं</string>
<string name="glasses_recenter_button">बीच में लाएं</string>
<string name="glasses_recenter_button">स्क्रीन फिर से केंद्रित करें</string>
<string name="glasses_vr_fov">SteamVR फ़ील्ड ऑफ़ व्यू</string>
<string name="glasses_vr_fov_hint">कम करने पर SteamVR पास आता है और चश्मा भर देता है; ज़्यादा करने पर आपके आस-पास ज़्यादा दिखता है।</string>
<string name="glasses_playing">%1$s पर खेल रहे हैं</string>
@@ -2417,8 +2417,8 @@
<string name="glasses_calibration_reset_hint">सेव किया गया कैलिब्रेशन हटाकर मॉडल का डिफ़ॉल्ट वापस लाएं? चश्मा दोबारा पहनने के बाद व्यू बीच में लाएं।</string>
<string name="glasses_calibration_reset_button">रीसेट करें</string>
<string name="hand_camera_group">हाथ ट्रैकिंग</string>
<string name="hand_camera_prompt_title">क्या कैमरे से आपके हाथों को ट्रैक करें?</string>
<string name="hand_camera_prompt_body">कैमरा आपके हाथ देख सकता है। कोई कंट्रोलर कनेक्ट न होने पर आपके खाली हाथ ही VR हाथ बन जाते हैं: क्लिक करने के लिए अंगूठे और तर्जनी को मिलाएं, पकड़ने के लिए मुट्ठी बंद करें, और मेनू खोलने के लिए हथेली अपनी ओर करके उंगलियां मिलाएं। Joy-Con के साथ, यह उन्हें वहीं रखता है जहां आपके हाथ सच में हैं। तस्वीरें इसी डिवाइस पर प्रोसेस होती हैं और कभी सेव या भेजी नहीं जातीं।</string>
<string name="hand_camera_prompt_title">फ़ोन कैमरे से हाथ या Joy-Con कंट्रोलर ट्रैक करें?</string>
<string name="hand_camera_prompt_body">बिना कंट्रोलर कनेक्ट किए, फ़ोन कैमरा VR में आपके खाली हाथों को ट्रैक करता है: क्लिक करने के लिए अपने अंगूठे और इंडेक्स फिंगर को पिंच करें, पकड़ने के लिए अपना हाथ बंद करें, और अपनी हथेली को अपनी ओर घुमाएं और मेनू खोलने के लिए पिंच करें। पेयर्ड Joy-Con कंट्रोलर के साथ, कैमरा उनकी पोज़िशन को ट्रैक करता है जबकि Joy-Con कंट्रोलर रोटेशन और बटन इनपुट देते हैं। पिक्चर इस डिवाइस पर प्रोसेस की जाती हैं और कभी सेव या भेजी नहीं जातीं।</string>
<string name="hand_camera_rear">रियर कैमरा</string>
<string name="hand_camera_rear_hint">सुझाया गया। आमतौर पर ज़्यादा शार्प और चौड़ा।</string>
<string name="hand_camera_front">फ्रंट कैमरा</string>
@@ -2426,7 +2426,7 @@
<string name="hand_camera_allow">कैमरा इस्तेमाल करें</string>
<string name="hand_camera_not_now">अभी नहीं</string>
<string name="hand_camera_setting">कैमरे से हाथ ट्रैकिंग</string>
<string name="hand_camera_setting_hint">कोई कंट्रोलर कनेक्ट न होने पर आपके हाथ ही VR हाथ बन जाते हैं: क्लिक के लिए उंगलियां मिलाएं, मेनू के लिए हथेली अपनी ओर करके उंगलियां मिलाएं। Joy-Con के साथ, यह उन्हें वहां रखता है जहां कैमरा उन्हें देखता है। तस्वीरें इसी डिवाइस पर रहती हैं।</string>
<string name="hand_camera_setting_hint">नंगे हाथों से जेस्चर के लिए या VR में पेयर्ड Joy-Con कंट्रोलर्स को सही जगह पर लगाने के लिए फ़ोन कैमरे का इस्तेमाल करें। Joy-Con कंट्रोलर्स रोटेशन और बटन इनपुट देते हैं। जब स्मार्ट ग्लास आपके हाथों या Joy-Con कंट्रोलर्स को ट्रैक करते हैं, तो यह पॉज़ करता है। इमेज सिर्फ़ इसी डिवाइस पर प्रोसेस की जाती हैं।</string>
<string name="hand_camera_choice">कैमरा</string>
<string name="hand_camera_access">कैमरा एक्सेस</string>
<string name="hand_camera_access_hint">हाथ ट्रैकिंग शुरू करने के लिए कैमरा एक्सेस की अनुमति दें।</string>
@@ -2680,4 +2680,208 @@
<string name="boot_movie_converting_started">%1$s को Steam के लिए बदला जा रहा है। DroidDeck खुला रखें।</string>
<string name="boot_movie_unsupported">यह डिवाइस उस वीडियो को डिकोड नहीं कर सकता।</string>
<string name="boot_movie_import_failed">वीडियो इंपोर्ट नहीं हो सका</string>
<string name="glasses_experimental">Luma Ultra कैमरे — एक्सपेरिमेंटल</string>
<string name="glasses_joycons">Joy-Con पोजीशन ट्रैकिंग</string>
<string name="glasses_joycons_hint">अपने पेयर्ड Joy-Con कंट्रोलर्स को सही जगह पर लगाने के लिए स्मार्ट ग्लास के कैमरे का इस्तेमाल करें। रोटेशन और बटन इनपुट Joy-Con कंट्रोलर्स से आते हैं। जब आपकी कलाइयां छिपी होती हैं, तो पोजीशन का अंदाज़ा लगाया जाता है। नंगे हाथों से चलने वाले कंट्रोलर्स को बदलता है और फ़ोन-कैमरा ट्रैकिंग को पॉज़ करता है। ट्रैकिंग सेंटर को कनेक्ट या रीसेट करते समय Joy-Con कंट्रोलर्स को आगे की ओर पॉइंट करें।</string>
<string name="glasses_joycons_requires_position">पहले पोजीशनल ट्रैकिंग इनेबल करें। Android 12 या उसके बाद के वर्शन पर मोशन-कैपेबल Joy-Con कंट्रोलर्स की ज़रूरत है। डिफ़ॉल्ट रूप से बंद।</string>
<string name="setup_tab_glasses">स्मार्ट ग्लास</string>
<string name="setup_glasses_scope">डिस्प्ले और हेड-ट्रैकिंग सेटिंग्स DroidDeck मेनू, गेम्स और डेस्कटॉप सेशन में शेयर की जाती हैं।</string>
<string name="setup_glasses_connect">स्मार्ट ग्लास को उनकी डिस्प्ले और ट्रैकिंग सेटिंग्स बदलने के लिए कनेक्ट करें। जब आप डिस्कनेक्ट करते हैं तो आपकी सेव की गई सेटिंग्स रखी जाती हैं।</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">डिस्प्ले मोड</string>
<string name="glasses_output_2d">2D आउटपुट</string>
<string name="glasses_output_3d">3D आउटपुट</string>
<string name="glasses_picture_group">पिक्चर और वर्चुअल स्क्रीन</string>
<string name="glasses_position_group">पोजीशन और फ्लोर लेवल</string>
<string name="glasses_hands_group">हाथों की ट्रैकिंग — एक्सपेरिमेंटल</string>
<string name="glasses_tracking_status">ट्रैकिंग स्टेटस</string>
<string name="glasses_resolution_2d">2D रिज़ॉल्यूशन</string>
<string name="glasses_resolution_3d">3D रिज़ॉल्यूशन</string>
<string name="glasses_resolution_vr">VR रिज़ॉल्यूशन</string>
<string name="glasses_resolution_auto">ऑटोमैटिक</string>
<string name="glasses_resolution_inherit">2D / 3D जैसा ही</string>
<string name="glasses_resolution_hint">हर आंख के लिए आउटपुट रिज़ॉल्यूशन।</string>
<string name="glasses_refresh_30_hint">डिस्प्ले का नेटिव 30 Hz मोड चुनने के लिए स्मार्ट ग्लास पर गेम चालू करें।</string>
<string name="glasses_3dof">3DoF वर्चुअल स्क्रीन</string>
<string name="glasses_3dof_hint">अपने सामने फ्लैट गेम्स को एंकर करें ताकि आप चारों ओर देख सकें। 3D पर स्विच किए बिना स्मार्ट ग्लास पर गेम चालू करता है।</string>
<string name="glasses_screen_app_wide">चौड़ाई और दूरी, DroidDeck मेन्यू, गेम और डेस्कटॉप सेशन के लिए ट्रैक की गई वर्चुअल स्क्रीन को तुरंत अपडेट करते हैं।</string>
<string name="glasses_head_tracking_spatial_vr_hint">SteamVR में पोज़िशनल ट्रैकिंग में हेड रोटेशन शामिल है। सिर्फ़ रोटेशन ट्रैकिंग के लिए, पोज़िशनल ट्रैकिंग को बंद करें।</string>
<string name="glasses_head_tracking_spatial_hint">वर्चुअल स्क्रीन पर हेड मूवमेंट को कंट्रोल करता है। SteamVR में पोज़िशनल ट्रैकिंग में हेड रोटेशन शामिल है।</string>
<string name="glasses_quick_actions">क्विक एक्शन</string>
<string name="glasses_recenter_screen_hint">स्क्रीन को जहां आप चाहते हैं वहां देखें, फिर उसकी ऊंचाई और दिशा सेट करने के लिए ट्रैकिंग सेंटर को रीसेट करें।</string>
<string name="glasses_recenter_joycons_button">Joy-Con फिर से केंद्रित करें</string>
<string name="phone_camera_tracking_group">फ़ोन कैमरा — एक्सपेरिमेंटल</string>
<string name="phone_camera_tracking_setting">हाथ या Joy-Con कंट्रोलर ट्रैक करें</string>
<string name="glasses_6dof">6DoF वर्चुअल स्क्रीन</string>
<string name="glasses_position">पोज़िशनल ट्रैकिंग — एक्सपेरिमेंटल</string>
<string name="glasses_position_hint">VR में और वर्चुअल स्क्रीन के आस-पास फिजिकल मूवमेंट। ट्रैकिंग को फिर से शुरू करने में थोड़ा समय लगता है।</string>
<string name="glasses_eye_height">फ्लोर से अभी की आंख की ऊंचाई</string>
<string name="glasses_floor">अभी की ऊंचाई से फ्लोर लेवल सेट करें</string>
<string name="glasses_floor_set">फ्लोर लेवल सेट करें</string>
<string name="glasses_spatial_setup">स्टीरियो कैमरा सेटअप और डायग्नोस्टिक्स</string>
<string name="glasses_spatial_setup_hint">खेलने से पहले कैमरा कैलिब्रेशन और हाथों की ट्रैकिंग चेक करें।</string>
<string name="glasses_spatial_open">सेटअप खोलें</string>
<string name="glasses_hands">कंट्रोलर नंगे हाथों से ऑपरेट होते हैं</string>
<string name="glasses_hands_hint">VR में अपने हाथों से निशाना लगाने, क्लिक करने के लिए पिंच करने और पकड़ने के लिए अपना हाथ बंद करने के लिए स्मार्ट ग्लास कैमरे का इस्तेमाल करें। गेमपैड स्टिक हिलते और मुड़ते हैं। Joy-Con पोज़िशन ट्रैकिंग की जगह लेता है। गेम के दौरान ट्रैकिंग लैग हो सकती है या खो सकती है।</string>
<string name="glasses_hands_ready_hint">ट्रैकिंग में Luma Ultra कैमरों के तहत नंगे हाथों से ऑपरेट होने वाले कंट्रोलर चालू करें। पहले खुले हाथ दिखाएं।</string>
<string name="glasses_hands_base">शेयर्ड Luma Ultra कैलिब्रेशन</string>
<string name="glasses_hands_base_hint">शेयर्ड Luma Ultra बेस का इस्तेमाल करना। अपने स्मार्ट ग्लास की सटीकता को वेरिफ़ाई करने के लिए उन्हें खुद कैलिब्रेट करें।</string>
<string name="glasses_hands_calibration_verified">मापे गए कैलिब्रेशन को वेरिफ़ाई किया गया</string>
<string name="glasses_hands_calibration_required">वैलिडेशन ज़रूरी है</string>
<string name="glasses_hands_calibration_required_hint">मापी गई दूरी की जाँच पास होने के बाद, नंगे हाथों से ऑपरेट किए जाने वाले कंट्रोलर उपलब्ध हो जाते हैं।</string>
<string name="tracking_recovery_lost">ट्रैकिंग खो गई</string>
<string name="tracking_recovery_look">अच्छी रोशनी वाले कमरे में धीरे-धीरे चारों ओर देखें।\nट्रैकिंग कैमरे साफ़ रखें और फ़र्नीचर या दूसरी दिखने वाली डिटेल की ओर मुँह करके रखें।</string>
<string name="tracking_recovery_restart_help">अभी भी अटके हुए हैं? आपके फ़ोन पर: स्मार्ट चश्मा → ट्रैकिंग → सेटअप खोलें → डायग्नोस्टिक्स दिखाएँ → ट्रैकिंग पुनरारंभ करें।</string>
<string name="tracking_recovery_disconnected">स्मार्ट चश्मा डिस्कनेक्ट हो गया</string>
<string name="tracking_recovery_connect">USB कनेक्शन की जांच करें और स्मार्ट चश्मे को फिर से कनेक्ट करें।\nयदि आपके फोन पर USB-एक्सेस प्रॉम्प्ट दिखाई दे तो उसे स्वीकार करें।</string>
<string name="tracking_recovery_usb_help">एक बार कनेक्ट होने के बाद, ट्रैकिंग बहाल करने के लिए अच्छी रोशनी वाले कमरे में धीरे-धीरे चारों ओर देखें।</string>
<string name="tracking_recovery_recenter">फिर से केंद्रित करना ज़रूरी है</string>
<string name="tracking_recovery_recenter_steps">ट्रैकिंग नई जगह पर वापस आ गई है.\nआगे देखें, फिर अपने फ़ोन पर ट्रैकिंग सेंटर रीसेट करें पर टैप करें.</string>
<string name="tracking_recovery_recenter_help">आप स्मार्ट ग्लास → ट्रैकिंग → ओपन सेटअप में भी ट्रैकिंग सेंटर रीसेट कर सकते हैं. आपका सेव किया गया कैमरा कैलिब्रेशन रखा जाता है.</string>
<string name="tracking_recovery_stabilizing">एक पल के लिए रुकें</string>
<string name="tracking_recovery_hold">ट्रैकिंग सेंटर रीसेट किया जा रहा है.\nट्रैकिंग के सेटल होने तक स्मार्ट ग्लास को स्थिर रखें.</string>
<string name="tracking_recovery_automatic">हेड ट्रैकिंग तैयार होने पर यह स्क्रीन स्वचालित रूप से बंद हो जाती है।</string>
<string name="glasses_steady_hand_aim">हाथों से स्थिर निशाना लगाना</string>
<string name="glasses_steady_hand_aim_hint">अपने कंधे से हथेली के माध्यम से निशाना लगाओ। कलाई-नियंत्रित घुमाव के लिए बंद करें।</string>
<string name="spatial_title">स्थानिक सेटअप</string>
<string name="spatial_wait_tracking">ट्रैकिंग की प्रतीक्षा में...</string>
<string name="spatial_cameras_description">बाएँ और दाएँ ट्रैकिंग कैमरे</string>
<string name="spatial_camera_sync">कैमरा सिंक्रनाइज़ेशन</string>
<string name="spatial_ready">तैयार</string>
<string name="spatial_sync_hint">कैमरा और हेड ट्रैकिंग के लाइन में लगने की प्रतीक्षा की जा रही है। स्मार्ट ग्लास कनेक्ट रखें.</string>
<string name="spatial_prepare">1. अंशांकन लक्ष्य तैयार करें</string>
<string name="spatial_print_actual">वास्तविक आकार में प्रिंट करें</string>
<string name="spatial_print_hint">प्रत्येक वर्ग की माप 30 मिमी (1.181 इंच) होनी चाहिए। अच्छी रोशनी वाले क्षेत्र में एक सख्त सतह पर शीट को सपाट स्थापित करें।</string>
<string name="spatial_print">कैलिब्रेशन बोर्ड प्रिंट करें</string>
<string name="spatial_capture_group">2. विभिन्न दृश्यों को कैप्चर करें</string>
<string name="spatial_board_stationary">अंशांकन लक्ष्य को स्थिर रखें</string>
<string name="spatial_capture_hint">स्मार्ट चश्मे को पास, दूर और बग़ल में ले जाएँ। उन्हें बाएँ/दाएँ मोड़ें, उन्हें ऊपर/नीचे झुकाएँ और उन्हें बग़ल में झुकाएँ। दोनों कैमरों में अंशांकन लक्ष्य रखें और प्रत्येक कैप्चर से पहले थोड़ी देर रुकें।</string>
<string name="spatial_start">नया अंशांकन प्रारंभ करें</string>
<string name="spatial_wait_sync">कैमरा सिंक्रनाइज़ेशन की प्रतीक्षा की जा रही है।</string>
<string name="spatial_start_hint">दृश्य एकत्र करने के लिए एक नया अंशांकन प्रारंभ करें।</string>
<string name="spatial_views_hint">अंशांकन लक्ष्य को दोनों पूर्वावलोकनों में दृश्यमान रखें। विभिन्न कोणों से कम से कम 24 तीव्र दृश्य कैप्चर करें।</string>
<string name="spatial_capturing">कैप्चरिंग...</string>
<string name="spatial_capture">अंशांकन लक्ष्य दृश्य कैप्चर करें</string>
<string name="spatial_alignment">कैमरा संरेखण की गणना करें</string>
<string name="spatial_calculating">गणना कर रहा हूं...</string>
<string name="spatial_calculate">अंशांकन की गणना करें</string>
<string name="spatial_distance_group">3. दूरी की जाँच करें</string>
<string name="spatial_distance">मापी गई दूरी</string>
<string name="spatial_distance_hint">12 इंच या 30 सेमी से शुरू करें ताकि मुद्रित मार्करों का पता लगाना आसान हो। वह सटीक माप चुनें जिसका आप उपयोग करेंगे.</string>
<string name="spatial_camera_board_distance">बाएँ कैमरे से कैलिब्रेशन लक्ष्य के केंद्र तक की दूरी</string>
<string name="spatial_checking_distance">दूरी की जाँच हो रही है...</string>
<string name="spatial_validate">दूरी मान्य करें</string>
<string name="spatial_diagnostics">डायग्नोस्टिक्स</string>
<string name="spatial_show_diagnostics">डायग्नोस्टिक्स दिखाएं</string>
<string name="spatial_diagnostics_hint">कैमरा टाइमिंग और ट्रैकिंग रिकवरी टूल।</string>
<string name="spatial_camera_timing">कैमरा टाइमिंग</string>
<string name="spatial_hand_input">हाथों की ट्रैकिंग से इनपुट</string>
<string name="spatial_wait_timing">कैमरे की टाइमिंग का इंतजार है।</string>
<string name="spatial_frames">कैमरा छवियाँ</string>
<string name="spatial_gpu">हाथों की GPU पहचान</string>
<string name="spatial_gpu_hint">CPU डिफ़ॉल्ट है। GPU प्रायोगिक है और SteamVR को पुनः आरंभ करने का कारण बन सकता है। ट्रैकिंग पुनः प्रारंभ होने पर यह विकल्प रीसेट हो जाता है।</string>
<string name="spatial_lite">हाथों के मॉडलों का लाइट डिटेक्शन</string>
<string name="spatial_lite_hint">हाथों पर लाइट पाम डिटेक्टर और लाइट कुंजी बिंदुओं का उपयोग करें। लक्ष्य, चुटकी और पकड़ने की सटीकता की जाँच करें। ट्रैकिंग पुनरारंभ होने पर रीसेट हो जाता है।</string>
<string name="spatial_adaptive">हाथों की ट्रैकिंग के लिए स्वचालित इनपुट आवृत्ति</string>
<string name="spatial_adaptive_hint">इनपुट को प्रसंस्करण गति के अनुसार समायोजित करें। ट्रैकिंग पुनरारंभ होने पर रीसेट हो जाता है।</string>
<string name="spatial_half_rate">हाथों की ट्रैकिंग के लिए इनपुट आवृत्ति सीमित करें</string>
<string name="spatial_half_rate_hint">कैमरे की आधी छवियों का उपयोग करें। अधिकतम आवृत्ति की तुलना करने के लिए बंद करें. ट्रैकिंग पुनरारंभ होने पर रीसेट हो जाता है।</string>
<string name="spatial_diagnostic_capture">डायग्नोस्टिक कैप्चर</string>
<string name="spatial_diagnostic_capture_hint">एक स्टीरियो छवि जोड़ी को स्थानीय रूप से सहेजें। अनुरोध किए जाने पर ही छवियाँ सहेजी जाती हैं।</string>
<string name="spatial_save_frame">कैमरा छवि सहेजें</string>
<string name="spatial_restart_tracking">ट्रैकिंग पुनः प्रारंभ करें</string>
<string name="spatial_restart_hint">यदि अच्छी रोशनी वाले क्षेत्र में ट्रैकिंग ठीक नहीं होती है तो इसका उपयोग करें। पुनरारंभ के लिए एक नए ट्रैकिंग मूल की आवश्यकता होती है।</string>
<string name="spatial_restart">पुनः आरंभ करें</string>
<string name="spatial_restart_title">ट्रैकिंग पुनः प्रारंभ करें?</string>
<string name="spatial_restart_confirm">सेव न किए गए कैलिब्रेशन व्यू खो जाएँगे. इसके बाद स्मार्ट ग्लास को स्थिर रखें और ट्रैकिंग के ठीक होने पर ट्रैकिंग सेंटर रीसेट करें.</string>
<string name="spatial_views_count">%1$d / 24 बार देखा गया</string>
<string name="spatial_capture_paused">कैप्चर रोका गया: %1$s</string>
<string name="spatial_measure_hint">माप %1$s। दोनों पूर्वावलोकनों में पूर्ण अंशांकन लक्ष्य दिखाएँ, फिर स्थिर रखें।</string>
<string name="spatial_timing_values">%1$.1f हर्ट्ज़ • छवि आयु %2$.1f एमएस • प्रसंस्करण %3$.1f एमएस</string>
<string name="spatial_input_values">कैमरा छवियों की इनपुट सीमा %1$.1f हर्ट्ज़ • %2$.0f %%। हेड ट्रैकिंग अपनी अधिकतम आवृत्ति पर जारी रहती है।</string>
<string name="spatial_frame_counts">%1$d संसाधित हुआ • %2$d हटा दिया गया • %3$d इनपुट सीमा के कारण छोड़ दिया गया</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck - Luma Ultra कैलिब्रेशन</string>
<string name="spatial_print_scale" formatted="false">100%/वास्तविक आकार पर प्रिंट करें। प्रत्येक वर्ग: 30 मिमी (1.181 इंच)।</string>
<string name="spatial_print_stationary">अंशांकन लक्ष्य को सपाट और स्थिर रखें; कैप्चर के बीच स्मार्ट चश्मे को घुमाएँ।</string>
<string name="spatial_print_check">स्केल जांच: टिकों के बीच 100 मिमी (3.937 इंच)।</string>
<string name="spatial_distance_12in">12 इंच (30.48 सेमी)</string>
<string name="spatial_distance_30cm">30 सेमी (11.81 इंच)</string>
<string name="spatial_distance_20in">20 इंच (50.8 सेमी)</string>
<string name="spatial_distance_50cm">50 सेमी (19.69 इंच)</string>
<string name="spatial_disabled">बंद</string>
<string name="spatial_wait_stable">स्थिर ट्रैकिंग की प्रतीक्षा में</string>
<string name="spatial_disconnected">डिसकनेक्ट किया गया</string>
<string name="spatial_hands_off">नंगे हाथों से संचालित होने वाले नियंत्रक बंद हैं</string>
<string name="spatial_tracking_off">पोजिशनल ट्रैकिंग बंद है या स्मार्ट चश्मा डिस्कनेक्ट हो गया है</string>
<string name="spatial_recenter_pending">ट्रैकिंग सेंटर रीसेट करने का अनुरोध किया गया — ट्रैकिंग के स्थिर होने तक स्थिर रखें</string>
<string name="spatial_tracking_lost">ट्रैकिंग खो गई - दृश्यमान विवरण के साथ एक अच्छी रोशनी वाले क्षेत्र का सामना करें</string>
<string name="spatial_origin_changed">ट्रैकिंग ओरिजिन बदल गया — ट्रैकिंग सेंटर रीसेट करें पर टैप करें, फिर स्थिर रखें</string>
<string name="spatial_active">6DoF सक्रिय</string>
<string name="spatial_connect_hint">Luma Ultra को कनेक्ट करें और पोजिशनल ट्रैकिंग सक्षम करें।</string>
<string name="spatial_validation_session">इस कनेक्शन के लिए अंशांकन सत्यापित।</string>
<string name="spatial_validation_saved">अंशांकन सत्यापित और सहेजा गया।</string>
<string name="spatial_wait_cameras">स्टीरियो कैमरे की प्रतीक्षा है</string>
<string name="spatial_wait_hands">हाथों की ट्रैकिंग का इंतजार</string>
<string name="spatial_collect_instructions">5 × 7 अंशांकन लक्ष्य को स्थिर रखें। स्मार्ट चश्मे को घुमाते और घुमाते हुए 24+ दृश्य कैप्चर करें।</string>
<string name="spatial_capture_pending">अगली कैमरा छवि की प्रतीक्षा है। स्मार्ट चश्मे को स्थिर रखें।</string>
<string name="spatial_validation_pending">अगली कैमरा छवि की जाँच कर रहा हूँ। पूर्ण अंशांकन लक्ष्य को दृश्यमान रखें और स्थिर रखें।</string>
<string name="spatial_start_first">पहले अंशांकन प्रारंभ करें</string>
<string name="spatial_no_camera_frame">कोई ताज़ा कैमरा छवि नहीं आई। स्मार्ट ग्लास कनेक्शन और कैमरा पूर्वावलोकन जांचें, फिर पुनः प्रयास करें।</string>
<string name="spatial_clock_reset">कैमरा घड़ी रीसेट</string>
<string name="spatial_frame_changed">ट्रैकिंग कोऑर्डिनेट सिस्टम बदल गया है. आपके व्यू रीकैलकुलेशन के लिए सेव किए गए हैं. ट्रैकिंग सेंटर रीसेट करें, फिर ज़्यादा व्यू कैप्चर करने के लिए नया सेट शुरू करें.</string>
<string name="spatial_device_loaded">डिवाइस अंशांकन लोड किया गया</string>
<string name="spatial_base_loaded">Luma Ultra बेस कैलिब्रेशन लोड किया गया</string>
<string name="spatial_calibration_needed">अंशांकन आवश्यक है</string>
<string name="spatial_tap_start">सबसे पहले स्टार्ट न्यू कैलिब्रेशन पर टैप करें।</string>
<string name="spatial_capture_recenter_pending">ट्रैकिंग सेंटर रीसेट करने के लिए ट्रैकिंग के स्थिर होने का इंतज़ार किया जा रहा है. थोड़ी देर के लिए स्थिर रखें.</string>
<string name="spatial_capture_recenter">ट्रैकिंग बदल गई है. ट्रैकिंग सेंटर को रीसेट करने के लिए एक बार टैप करें, फिर कैप्चर करने से पहले उसे स्थिर रखें.</string>
<string name="spatial_capture_unsynchronized">कैप्चर अस्वीकृत: कैप्चर के दौरान हेड ट्रैकिंग सिंक्रोनाइज़ नहीं थी या उसमें बदलाव आया। स्थिर रहें और पुनः प्रयास करें।</string>
<string name="spatial_align_first">दूरी की जांच करने से पहले कैमरा संरेखण की गणना करें।</string>
<string name="spatial_processing_failed">कैमरा प्रोसेसिंग विफल रही</string>
<string name="spatial_hand_head_changed">हाथों का पता लगाने के दौरान हेड ट्रैकिंग बदल गई</string>
<string name="spatial_hand_wait_sync">कैमरा और हेड सिंक्रोनाइज़ेशन की प्रतीक्षा की जा रही है</string>
<string name="spatial_no_wrists">किसी कलाई का पता नहीं चला। अपने Joy-Con कंट्रोलर हैंडल को स्मार्ट चश्मे के सामने रखें</string>
<string name="spatial_no_hands">कोई हाथ नहीं मिला। स्मार्ट चश्मे के सामने खुली हथेलियाँ दिखाएँ</string>
<string name="spatial_one_camera_wrists">कलाइयाँ केवल एक कैमरे में दिखाई देती हैं; अपने नियंत्रक हैंडल को साझा दृश्य में रखें</string>
<string name="spatial_one_camera_hands">केवल एक कैमरे में हाथ दिखाई दे रहे हैं; दोनों हथेलियों को साझा दृश्य में रखें</string>
<string name="spatial_ambiguous_hands">दोनों कैमरे हाथ देखते हैं, लेकिन बाएँ/दाएँ मिलान अस्पष्ट है</string>
<string name="spatial_inconsistent_depth">हाथों का पता चला, लेकिन स्टीरियो गहराई असंगत है</string>
<string name="spatial_rejected_wrist">कलाई की स्थिति को स्टीरियो या मोशन जांच द्वारा अस्वीकार कर दिया गया</string>
<string name="spatial_inconsistent_palm">स्टीरियो बिंदुओं का पता चला, लेकिन हथेली की ज्यामिति असंगत है</string>
<string name="spatial_hands_need_calibration">हाथों को सक्षम करने के लिए पूर्ण अंशांकन और दूरी की जांच</string>
<string name="spatial_hands_wait_stable">स्थिर हेड ट्रैकिंग की प्रतीक्षा कर रहा है; अनुरोध किए जाने पर ट्रैकिंग सेंटर को रीसेट करें</string>
<string name="spatial_old_frame">कैमरा अवलोकन बहुत पुराना है; एक नई जोड़ी का इंतजार है</string>
<string name="spatial_fit_ready">फ़िट %1$.2f पीएक्स। बाएं कैमरे से अंशांकन लक्ष्य केंद्र तक अपनी चयनित दूरी को मापें, फिर सत्यापित करें।</string>
<string name="spatial_opencv_error">OpenCV अनुपलब्ध: %1$s</string>
<string name="spatial_restored">%1$d सहेजे गए दृश्य पुनर्स्थापित किए गए। पुनः प्रयास करने के लिए अंशांकन की गणना करें, या अधिक कैप्चर करने के लिए एक नया सेट प्रारंभ करें।</string>
<string name="spatial_captured">कैप्चर किया गया %1$d / 24 बार देखा गया। अंशांकन लक्ष्य निश्चित रखें; अगले दृश्य के लिए स्मार्ट चश्मा हटाएँ।</string>
<string name="spatial_capture_corners">कैप्चर अस्वीकृत: %1$d / दोनों कैमरों में 12 आवश्यक कोने दिखाई दे रहे हैं। अंशांकन लक्ष्य को और अधिक दिखाएं, स्थिर रखें, फिर पुनः प्रयास करें।</string>
<string name="spatial_validation_error">दूरी की जांच पूरी नहीं हो सकी: %1$s। पुनः प्रयास करें।</string>
<string name="spatial_inference_error">हाथों का पता लगाना अनुपलब्ध: %1$s। पुनः प्रयास करने के लिए CPU/GPU बदलें या नंगे हाथों से संचालित नियंत्रकों को टॉगल करें।</string>
<string name="spatial_hand_timeout">हाथों का पता लगाना %1$d एमएस से अधिक हो गया; अवलोकन त्याग दिया गया</string>
<string name="spatial_wrists_tracked">%1$d / 2 Joy-Con कलाई की स्थिति स्टीरियो द्वारा मापी गई</string>
<string name="spatial_palms_tracked">ट्रैक की गई हथेलियाँ: %1$d / 2; पिंच के लिए तैयार: %2$d; पकड़ने के लिए तैयार: %3$d</string>
<string name="spatial_capture_saved">डायग्नोस्टिक कैप्चर को %1$s में सहेजा गया</string>
<string name="spatial_hand_error">हाथों का पता लगाना बंद कर दिया गया: %1$s</string>
<string name="spatial_stereo_loaded">स्टीरियो %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">हेड ट्रैकिंग और अंशांकन लक्ष्य %1$s (सीमा 5 सेमी/2 इंच) से असहमत हैं। अंशांकन लक्ष्य को निश्चित रखें और नया सेट शुरू करने से पहले स्थिर ट्रैकिंग की प्रतीक्षा करें। स्मार्ट चश्मे को धीरे से घुमाएँ और झुकाएँ; प्रत्येक कैप्चर से पहले रुकें।</string>
<string name="spatial_need_views">स्थिर अंशांकन लक्ष्य के कम से कम 24 विविध दृश्य कैप्चर करें।</string>
<string name="spatial_need_rotation">स्मार्ट चश्मे को बाएँ और दाएँ घुमाएँ, और उन्हें ऊपर और नीचे झुकाएँ; रोटेशन के बिना गति कैमरा संरेखण को कैलिब्रेट नहीं कर सकती।</string>
<string name="spatial_bad_baseline">अविश्वसनीय कैमरा बेसलाइन</string>
<string name="spatial_need_orientations">अपने सिर को अधिक विविध दिशाओं में घुमाएँ।</string>
<string name="spatial_bad_rotation">अमान्य कैमरा-टू-हेड रोटेशन</string>
<string name="spatial_bad_alignment">कैमरा-टू-हेड संरेखण असंगत है।</string>
<string name="spatial_calibration_error">अंशांकन समाप्त नहीं हो सका। %1$s आपके कैप्चर किए गए दृश्य सहेजे गए हैं।</string>
<string name="spatial_reprojection_error">हेल्ड-आउट रिप्रोजेक्शन त्रुटि %1$.2f px 2 px से अधिक है। पुन: अंशांकित करें.</string>
<string name="spatial_distance_corners">%1$d / दोनों कैमरों में 24 चेकरबोर्ड कोनों का पता चला। पूर्ण अंशांकन लक्ष्य को दृश्यमान रखें और स्थिर रखें। यदि यह पहले से ही दिखाई दे रहा है, तो 12 इंच या 30 सेमी का चयन करें और पुनः प्रयास करने से पहले उस निकटतम दूरी को मापें।</string>
<string name="spatial_distance_depth">अंशांकन लक्ष्य दृश्यमान है, लेकिन इसकी गहराई को विश्वसनीय रूप से नहीं मापा जा सका। स्मार्ट ग्लास को स्थिर रखें और अंशांकन लक्ष्य को सपाट और अच्छी रोशनी में रखें, फिर पुनः प्रयास करें।</string>
<string name="spatial_distance_invalid">कैमरे का माप अमान्य था। स्थिर रहें और पुनः प्रयास करें।</string>
<string name="spatial_distance_passed">दूरी जांच पास हो गई।</string>
<string name="spatial_distance_retry">बाएं कैमरे से अंशांकन लक्ष्य केंद्र तक की दूरी दोबारा जांचें, फिर पुनः प्रयास करें।</string>
<string name="spatial_distance_result">कैमरों की माप %1$s हुई; चयनित दूरी %2$s. %3$s</string>
<string name="spatial_wait_previews">अंशांकन शुरू करने से पहले दोनों कैमरा पूर्वावलोकन की प्रतीक्षा करें।</string>
<string name="spatial_saved_views_hint">सहेजे गए दृश्यों की पुनर्गणना की जा सकती है। वर्तमान ट्रैकिंग समन्वय प्रणाली में कैप्चर करने के लिए एक नया अंशांकन प्रारंभ करें।</string>
<string name="spatial_model_checksum_error">हैंड मॉडल चेकसम बेमेल</string>
<string name="spatial_opencv_load_error">OpenCV लोड नहीं हो सका</string>
</resources>
+214 -10
View File
@@ -2366,14 +2366,14 @@
<string name="glasses_display">Kijelző</string>
<string name="glasses_refresh">Frissítési frekvencia</string>
<string name="glasses_3d">3D (egymás melletti)</string>
<string name="glasses_3d_hint">Mindkét szem egy 3840×1080-as kép saját felét kapja. Ha a kép eltűnik, tartsa lenyomva a szemüveg 2D/3D gombját.</string>
<string name="glasses_3d_hint">Váltsa az okosszemüveget egymás melletti 3D-re. A fejkövetés külön vezérelhető.</string>
<string name="glasses_3d_failed">Nem sikerült megerősíteni a 3D-jelet. Próbáld újra, ha visszatér a 2D-kép.</string>
<string name="glasses_no_signal">Nincs kép: a szemüveg olyan módban van, amelyet ez a telefon nem tud küldeni. Tartsa lenyomva a 2D/3D gombját, vagy húzza ki, és dugja vissza.</string>
<string name="glasses_3d_refresh">3D frissítési frekvencia</string>
<string name="glasses_auto3d">3D a VR-játékokhoz</string>
<string name="glasses_auto3d_hint">3D-re vált, amíg a SteamVR fut, utána vissza.</string>
<string name="glasses_picture">3D kép</string>
<string name="glasses_picture_hint">Automatikus: a SteamVR kitölti a látóteret; más játékok 3D-ben képernyőként lebegnek, 2D-ben laposak maradnak.</string>
<string name="glasses_picture">Kép</string>
<string name="glasses_picture_hint">Az Automatikus mód lapos tartalmat jelenít meg mindkét szemben, és érzékeli a SteamVR-t. SBS tartalomhoz válassza az Egymás melletti játék lehetőséget.</string>
<string name="glasses_picture_auto">Automatikus</string>
<string name="glasses_picture_screen">Virtuális képernyő</string>
<string name="glasses_picture_sbs">Egymás melletti 3D játék</string>
@@ -2383,20 +2383,20 @@
<string name="glasses_screen_no_output">Ezek a beállítások akkor érvényesek, ha a Játék a szemüvegen be van kapcsolva, és a játék ott jelenik meg.</string>
<string name="glasses_screen_flat_hint">Az Automatikus mód teljes képernyőn tartja ezt a 2D-s játékot. Használjon virtuális képernyőt a szélesség, a távolság és a fejkövetés beállításához.</string>
<string name="glasses_screen_use">Virtuális képernyő használata</string>
<string name="glasses_screen_direct_hint">A SteamVR kitölti a látóteret. A látómezőt lent állíthatja; a szélesség és a távolság csak virtuális képernyőre vonatkozik.</string>
<string name="glasses_screen_direct_hint">A SteamVR kitölti a nézetet. Állítsa be a látómezőt VR-ben; a szélesség és a távolság csak egy virtuális képernyőre vonatkozik.</string>
<string name="glasses_screen_sbs_hint">Az egymás melletti 3D-s játékok mindkét szem látóterét kitöltik. A szélesség és a távolság csak virtuális képernyőre vonatkozik.</string>
<string name="glasses_tracking_flat_hint">Az Automatikus mód rögzítve tartja ezt a 2D-s játékot a látótérben. A fejkövetéshez válassza a Kijelző részben a Virtuális képernyő lehetőséget.</string>
<string name="glasses_tracking_sbs_hint">A fejkövetés virtuális képernyőre vagy futó SteamVR-játékra vonatkozik.</string>
<string name="glasses_hardware">Szemüveg</string>
<string name="glasses_hardware">Eszköz</string>
<string name="glasses_brightness">Fényerő</string>
<string name="glasses_volume">Hangerő</string>
<string name="glasses_sunblock">Elsötétítés</string>
<string name="glasses_sunblock_hint">Elsötétíti az elülső lencséket.</string>
<string name="glasses_tracking">Fejkövetés</string>
<string name="glasses_tracking">Követés</string>
<string name="glasses_head_tracking">Fejkövetés</string>
<string name="glasses_head_tracking_hint">A szemüveg saját érzékelői a fejével együtt forgatják a virtuális képernyőt és a SteamVR-t.</string>
<string name="glasses_recenter">Nézet középre igazítása</string>
<string name="glasses_recenter_button">Középre</string>
<string name="glasses_recenter_button">Képernyő újraközpontosítása</string>
<string name="glasses_vr_fov">SteamVR látómező</string>
<string name="glasses_vr_fov_hint">Kisebb érték közelebb hozza a SteamVR-t és kitölti a szemüveget; nagyobb többet mutat a környezetéből.</string>
<string name="glasses_playing">Játék ezen: %1$s</string>
@@ -2417,8 +2417,8 @@
<string name="glasses_calibration_reset_hint">Törli a mentett kalibrációt, és visszaállítja a modell alapértékeit? A szemüveg visszavétele után igazítsa középre a nézetet.</string>
<string name="glasses_calibration_reset_button">Visszaállítás</string>
<string name="hand_camera_group">Kézkövetés</string>
<string name="hand_camera_prompt_title">Nyomon kövesse a kamera a kezeit?</string>
<string name="hand_camera_prompt_body">A kamera látja a kezeit. Ha nincs csatlakoztatva kontroller, a puszta kezei lesznek a VR-kezek: kattintáshoz érintse össze a hüvelyk- és mutatóujját, megragadáshoz zárja ökölbe a kezét, a menühöz pedig fordítsa maga felé a tenyerét, és érintse össze az ujjait. Joy-Conokkal oda helyezi őket, ahol a kezei valóban vannak. A képeket ez az eszköz dolgozza fel, és soha nem menti el vagy küldi el őket.</string>
<string name="hand_camera_prompt_title">Kezek vagy Joy-Con kontrollerek követése a telefonkamerával?</string>
<string name="hand_camera_prompt_body">Ha nincs csatlakoztatva kontroller, a telefon kamerája követi a kezeidet VR-ban: kattintáshoz csípd össze a hüvelykujjadat és a mutatóujjadat, megfogáshoz zárd össze a kezed, a menü megnyitásához pedig fordítsd magad felé a tenyeredet, majd csípd össze őket. Párosított Joy-Con kontrollerek esetén a kamera követi a pozíciójukat, miközben a Joy-Con kontrollerek forgatást és gombbevitelt biztosítanak. A képeket ezen az eszközön dolgozzák fel, és soha nem mentik vagy küldik el.</string>
<string name="hand_camera_rear">Hátlapi kamera</string>
<string name="hand_camera_rear_hint">Ajánlott. Általában élesebb és szélesebb látószögű.</string>
<string name="hand_camera_front">Előlapi kamera</string>
@@ -2426,7 +2426,7 @@
<string name="hand_camera_allow">Kamera használata</string>
<string name="hand_camera_not_now">Most nem</string>
<string name="hand_camera_setting">Kézkövetés kamerával</string>
<string name="hand_camera_setting_hint">Ha nincs csatlakoztatva kontroller, a kezei lesznek a VR-kezek: kattintáshoz érintse össze az ujjait, a menühöz tenyérrel maga felé tegye ugyanezt. Joy-Conokkal oda helyezi őket, ahol a kamera látja. A képek ezen az eszközön maradnak.</string>
<string name="hand_camera_setting_hint">Használd a telefon kameráját puszta kézzel végzett mozdulatokhoz vagy a párosított Joy-Con kontrollerek pozicionálásához VR-ban. A Joy-Con kontrollerek forgatást és gombbemeneteket biztosítanak. Szünetel, amíg az okosszemüveg követi a kezeidet vagy a Joy-Con kontrollereket. A képek feldolgozása csak ezen az eszközön történik.</string>
<string name="hand_camera_choice">Kamera</string>
<string name="hand_camera_access">Kamerahozzáférés</string>
<string name="hand_camera_access_hint">Engedélyezze a kamerahozzáférést, hogy elindulhasson a kézkövetés.</string>
@@ -2680,4 +2680,208 @@
<string name="boot_movie_converting_started">%1$s átalakítása a Steamhez. Hagyd nyitva a DroidDecket.</string>
<string name="boot_movie_unsupported">Ez az eszköz nem tudja dekódolni a videót.</string>
<string name="boot_movie_import_failed">Nem sikerült importálni a videót</string>
<string name="glasses_experimental">Luma Ultra kamerák — Kísérleti</string>
<string name="glasses_joycons">Joy-Con pozíciókövetés</string>
<string name="glasses_joycons_hint">Használd az okosszemüveg kameráit a párosított Joy-Con kontrollerek pozicionálásához. A forgatás és a gombbemenetek a Joy-Con kontrollerekről származnak. A pozíció akkor becsült, amikor a csuklód rejtve van. Helyettesíti a puszta kézzel működtetett kontrollereket, és szünetelteti a telefonkamera követést. Irányítsd a Joy-Con kontrollereket előre a követőközpont csatlakoztatásakor vagy visszaállításakor.</string>
<string name="glasses_joycons_requires_position">Először engedélyezd a pozíciókövetést. Mozgásérzékelős Joy-Con kontrollereket igényel Android 12-es vagy újabb rendszeren. Alapértelmezés szerint ki van kapcsolva.</string>
<string name="setup_tab_glasses">Okosszemüveg</string>
<string name="setup_glasses_scope">A kijelző és a fejkövetés beállításai megoszlanak a DroidDeck menükben, játékokban és asztali munkamenetekben.</string>
<string name="setup_glasses_connect">Csatlakoztasd az okosszemüveget a kijelző és a követés beállításainak módosításához. A mentett beállítások a leválasztáskor is megmaradnak.</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">Kijelző mód</string>
<string name="glasses_output_2d">2D kimenet</string>
<string name="glasses_output_3d">3D kimenet</string>
<string name="glasses_picture_group">Kép és virtuális képernyő</string>
<string name="glasses_position_group">Pozíció és padlószint</string>
<string name="glasses_hands_group">Kezek követése — Kísérleti</string>
<string name="glasses_tracking_status">Követési állapot</string>
<string name="glasses_resolution_2d">2D felbontás</string>
<string name="glasses_resolution_3d">3D felbontás</string>
<string name="glasses_resolution_vr">VR felbontás</string>
<string name="glasses_resolution_auto">Automatikus</string>
<string name="glasses_resolution_inherit">Ugyanaz, mint a 2D / 3D</string>
<string name="glasses_resolution_hint">Kimeneti felbontás szemenként.</string>
<string name="glasses_refresh_30_hint">Engedélyezze a Játék okosszemüvegen funkciót a kijelző natív 30 Hz-es módjának kiválasztásához.</string>
<string name="glasses_3dof_hint">Lapos játékok rögzítése előtted, hogy körülnézhess. Engedélyezi a Játék okosszemüvegen funkciót 3D-re váltás nélkül.</string>
<string name="glasses_screen_app_wide">A szélesség és távolság azonnal frissíti a követett virtuális képernyőt a DroidDeck menükben, játékokban és asztali munkamenetekben.</string>
<string name="glasses_head_tracking_spatial_vr_hint">A pozíciókövetés magában foglalja a fej elforgatását a SteamVR-ben. Csak elforgatásos követéshez kapcsolja ki a pozíciókövetést.</string>
<string name="glasses_head_tracking_spatial_hint">A fej mozgását vezérli a virtuális képernyőn. A pozíciókövetés magában foglalja a fej elforgatását a SteamVR-ben.</string>
<string name="glasses_quick_actions">Gyors műveletek</string>
<string name="glasses_recenter_screen_hint">Nézz oda, ahová a képernyőt szeretnéd helyezni, majd állítsd vissza a követőközpontot a magasság és az irány beállításához.</string>
<string name="glasses_recenter_joycons_button">Joy-Con újraközpontosítása</string>
<string name="phone_camera_tracking_group">Telefonkamera — Kísérleti</string>
<string name="phone_camera_tracking_setting">Kezek vagy Joy-Con kontrollerek követése</string>
<string name="glasses_position">Pozíciókövetés — kísérleti</string>
<string name="glasses_position_hint">Fizikai mozgás VR-ban és virtuális képernyők körül. A követés újraindítása eltart egy ideig.</string>
<string name="glasses_eye_height">Aktuális szemmagasság a padló felett</string>
<string name="glasses_floor">Padlószint beállítása az aktuális magasság alapján</string>
<string name="glasses_floor_set">Padlószint beállítása</string>
<string name="glasses_spatial_setup">Sztereó kamera beállítása és diagnosztika</string>
<string name="glasses_spatial_setup_hint">Játék előtt ellenőrizze a kamera kalibrálását és a kézkövetést.</string>
<string name="glasses_spatial_open">Beállítás megnyitása</string>
<string name="glasses_hands">Puszta kézzel működtetett kontrollerek</string>
<string name="glasses_hands_hint">Használja az okosszemüveg kameráit a kezeivel való célzáshoz, csíptesse össze a kattintáshoz és zárja össze a kezét a megfogáshoz VR-ban. A gamepad botjai mozognak és forognak. Helyettesíti a Joy-Con pozíciókövetést. Játék közben késleltetheti vagy elveszítheti a követést.</string>
<string name="glasses_hands_ready_hint">Engedélyezze a puszta kézzel működtetett kontrollereket a Luma Ultra kamerák alatt a Követés módban. Először mutassa a nyitott kezeket.</string>
<string name="glasses_hands_base">Megosztott Luma Ultra kalibrálás</string>
<string name="glasses_hands_base_hint">A megosztott Luma Ultra talp használata. Kalibrálja saját okosszemüvegét a pontosság ellenőrzéséhez.</string>
<string name="glasses_hands_calibration_verified">Mért kalibráció ellenőrizve</string>
<string name="glasses_hands_calibration_required">Érvényesítés szükséges</string>
<string name="glasses_hands_calibration_required_hint">A puszta kézzel működtetett kontrollerek a mért távolság ellenőrzésének sikeres elvégzése után válnak elérhetővé.</string>
<string name="tracking_recovery_lost">Elveszett a követés</string>
<string name="tracking_recovery_look">Nézzen körül lassan egy jól megvilágított szobában.\nTartsa távol a követőkamerákat, és nézzen szembe a bútorokkal vagy más látható részletekkel.</string>
<string name="tracking_recovery_restart_help">Még mindig elakad? A telefonon: okosszemüveg → Követés → Beállítás megnyitása → Diagnosztika megjelenítése → Nyomon követés újraindítása.</string>
<string name="tracking_recovery_disconnected">Az intelligens szemüvegek leválasztva</string>
<string name="tracking_recovery_connect">Ellenőrizze az USB-kapcsolatot, és csatlakoztassa újra az okosszemüveget.\nHa megjelenik, fogadja el az USB-hozzáférési felszólítást a telefonon.</string>
<string name="tracking_recovery_usb_help">Csatlakozás után lassan nézzen körül egy jól megvilágított szobában a követés helyreállításához.</string>
<string name="tracking_recovery_recenter">Újraközpontosítás szükséges</string>
<string name="tracking_recovery_recenter_steps">A követés visszatért egy új pozícióba.\nNézz előre, majd koppints a Követőközpont visszaállítása gombra a telefonodon.</string>
<string name="tracking_recovery_recenter_help">A követőközpont visszaállítása funkciót az okosszemüveg → Követés → Beállítás megnyitása menüpontban is megtalálhatja. A mentett kamerakalibráció megmarad.</string>
<string name="tracking_recovery_stabilizing">Maradjon mozdulatlanul egy pillanatra</string>
<string name="tracking_recovery_hold">A követőközpont visszaállítása folyamatban van.\nTartsa az okosszemüveget mozdulatlanul, amíg a követés beáll.</string>
<string name="tracking_recovery_automatic">Ez a képernyő automatikusan bezárul, amikor a fejkövetés készen áll.</string>
<string name="glasses_steady_hand_aim">Stabilizált célzás kézzel</string>
<string name="glasses_steady_hand_aim_hint">Célozzon a válláról a tenyerén keresztül. Kapcsolja ki a csuklóval vezérelt forgatáshoz.</string>
<string name="spatial_title">Térbeli beállítások</string>
<string name="spatial_wait_tracking">Várakozás a követésre…</string>
<string name="spatial_cameras_description">Bal és jobb követési kamera</string>
<string name="spatial_camera_sync">Kamera szinkronizálás</string>
<string name="spatial_ready">Kész</string>
<string name="spatial_sync_hint">Várakozás, amíg a kamera és a fejkövetés összeáll. Tartsa csatlakoztatva az okosszemüveget.</string>
<string name="spatial_prepare">1. Készítse elő a kalibrációs célt</string>
<string name="spatial_print_actual">Nyomtatás tényleges méretben</string>
<string name="spatial_print_hint">Minden négyzetnek 30 mm-nek (1,181 hüvelyk) kell lennie. Szerelje fel a lapot merev felületre, jól megvilágított helyen.</string>
<string name="spatial_print">Kalibrációs minta nyomtatása</string>
<string name="spatial_capture_group">2. Különböző nézetek rögzítése</string>
<string name="spatial_board_stationary">Tartsa mozdulatlanul a kalibrációs célt</string>
<string name="spatial_capture_hint">Vigye közelebb az intelligens szemüveget, távolabb és oldalra. Forgassa balra/jobbra, döntse fel/le, és döntse oldalra. Tartsa a kalibrációs célt mindkét kamerában, és minden rögzítés előtt álljon meg egy rövid ideig.</string>
<string name="spatial_start">Indítsa el az új kalibrálást</string>
<string name="spatial_wait_sync">Várakozás a kamera szinkronizálására.</string>
<string name="spatial_start_hint">Indítson el egy új kalibrálást a nézetek összegyűjtéséhez.</string>
<string name="spatial_views_hint">Tartsa láthatóan a kalibrációs célt mindkét előnézetben. Rögzítsen legalább 24 éles képet különböző szögekből.</string>
<string name="spatial_capturing">Rögzítés…</string>
<string name="spatial_capture">Kalibrációs célnézet rögzítése</string>
<string name="spatial_alignment">Számítsa ki a kamera beállítását</string>
<string name="spatial_calculating">Számítás…</string>
<string name="spatial_calculate">Számítsa ki a kalibrációt</string>
<string name="spatial_distance_group">3. Ellenőrizze a távolságot</string>
<string name="spatial_distance">Mért távolság</string>
<string name="spatial_distance_hint">Kezdje 12 hüvelykkel vagy 30 cm-rel, hogy a nyomtatott jelölők könnyebben felismerhetők legyenek. Válassza ki a használni kívánt pontos méretet.</string>
<string name="spatial_camera_board_distance">A bal kamera és a kalibrációs céltábla középpontja közötti távolság</string>
<string name="spatial_checking_distance">Távolság ellenőrzése…</string>
<string name="spatial_validate">Távolság érvényesítése</string>
<string name="spatial_diagnostics">Diagnosztika</string>
<string name="spatial_show_diagnostics">Diagnosztika megjelenítése</string>
<string name="spatial_diagnostics_hint">Kamera időzítési és nyomkövetési helyreállítási eszközök.</string>
<string name="spatial_camera_timing">Kamera időzítése</string>
<string name="spatial_hand_input">Bemenet a kezek követéséből</string>
<string name="spatial_wait_timing">Várakozás a kameraidőzítésre.</string>
<string name="spatial_frames">Kameraképek</string>
<string name="spatial_gpu">A kezek GPU-felismerése</string>
<string name="spatial_gpu_hint">A CPU az alapértelmezett. A GPU kísérleti jellegű, és a SteamVR újraindítását okozhatja. Ez a választás visszaáll a követés újraindításakor.</string>
<string name="spatial_lite">Kézmodellek egyszerű észlelése</string>
<string name="spatial_lite_hint">Használja a Lite tenyérérzékelőt és a Lite kulcspontokat a kezeken. Ellenőrizze a célzás, a szorítás és a megfogás pontosságát. Visszaállítja a követés újraindulásakor.</string>
<string name="spatial_adaptive">Automatikus bemeneti frekvencia a kezek nyomon követéséhez</string>
<string name="spatial_adaptive_hint">Állítsa be a bemenetet a feldolgozási sebességhez. Visszaállítja a követés újraindulásakor.</string>
<string name="spatial_half_rate">Korlátozza a bemeneti frekvenciát a kezek követéséhez</string>
<string name="spatial_half_rate_hint">A kameraképek felét használja. A maximális frekvencia összehasonlításához kapcsolja ki. Visszaállítja a követés újraindulásakor.</string>
<string name="spatial_diagnostic_capture">Diagnosztikai rögzítés</string>
<string name="spatial_diagnostic_capture_hint">Mentse el egy sztereó képpárt helyileg. A képek csak kérésre kerülnek mentésre.</string>
<string name="spatial_save_frame">Kamerakép mentése</string>
<string name="spatial_restart_tracking">Indítsa újra a követést</string>
<string name="spatial_restart_hint">Akkor használja, ha a követés nem áll helyre jól megvilágított területen. Az újraindításhoz új nyomkövetési eredetre van szükség.</string>
<string name="spatial_restart">Indítsa újra</string>
<string name="spatial_restart_title">Újraindítja a követést?</string>
<string name="spatial_restart_confirm">A nem mentett kalibrációs nézetek elvesznek. Tartsa mozdulatlanul az okosszemüveget, és állítsa vissza a követőközpontot, amikor a követés helyreállt.</string>
<string name="spatial_views_count">%1$d / 24 megtekintés</string>
<string name="spatial_capture_paused">A rögzítés szünetel: %1$s</string>
<string name="spatial_measure_hint">%1$s. intézkedés. Jelenítse meg a teljes kalibrációs célt mindkét előnézetben, majd tartsa mozdulatlanul.</string>
<string name="spatial_timing_values">%1$.1f Hz • kép kora %2$.1f ms • feldolgozás %3$.1f ms</string>
<string name="spatial_input_values">Bemeneti korlát %1$.1f Hz • %2$.0f%% kamerakép. A fejkövetés a maximális frekvencián folytatódik.</string>
<string name="spatial_frame_counts">%1$d feldolgozva • %2$d elvetve • %3$d kihagyva a beviteli korlát miatt</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck – Luma Ultra kalibrálás</string>
<string name="spatial_print_scale" formatted="false">Nyomtatás 100%/Tényleges méretben. Mindegyik négyzet: 30 mm (1,181 hüvelyk).</string>
<string name="spatial_print_stationary">Tartsa a kalibrációs célt síkban és álló helyzetben; mozgassa az okosszemüveget a felvételek között.</string>
<string name="spatial_print_check">Skálaellenőrzés: 100 mm (3,937 hüvelyk) a jelölések között</string>
<string name="spatial_distance_12in">12 hüvelyk (30,48 cm)</string>
<string name="spatial_distance_30cm">30 cm (11,81 hüvelyk)</string>
<string name="spatial_distance_20in">20 hüvelyk (50,8 cm)</string>
<string name="spatial_distance_50cm">50 cm (19,69 hüvelyk)</string>
<string name="spatial_disabled">Letiltva</string>
<string name="spatial_wait_stable">Várakozás a stabil követésre</string>
<string name="spatial_disconnected">Lekapcsolva</string>
<string name="spatial_hands_off">A puszta kézzel működtetett vezérlők ki vannak kapcsolva</string>
<string name="spatial_tracking_off">A helyzetkövetés ki van kapcsolva, vagy az intelligens szemüvegek le vannak választva</string>
<string name="spatial_recenter_pending">Követőközpont visszaállítása kérve – tartsa mozdulatlanul, amíg a követés stabilizálódik.</string>
<string name="spatial_tracking_lost">A követés elveszett – nézzen szembe egy jól megvilágított területtel, látható részletekkel</string>
<string name="spatial_origin_changed">A követés kezdőpontja megváltozott – koppintson a követőközpont visszaállítására, majd tartsa mozdulatlanul.</string>
<string name="spatial_active">6DoF aktív</string>
<string name="spatial_connect_hint">Csatlakoztassa a Luma Ultra-t és engedélyezze a helyzetkövetést.</string>
<string name="spatial_validation_session">Ehhez a kapcsolathoz a kalibráció ellenőrizve.</string>
<string name="spatial_validation_saved">A kalibráció ellenőrizve és mentve.</string>
<string name="spatial_wait_cameras">Várakozás a sztereó kamerákra</string>
<string name="spatial_wait_hands">Várakozás a kezek nyomon követésére</string>
<string name="spatial_collect_instructions">Tartsa mozdulatlanul az 5 × 7 kalibrációs célt. Rögzítsen 24+ nézetet az okosszemüveg mozgatása és forgatása közben.</string>
<string name="spatial_capture_pending">Várakozás a következő kameraképre. Tartsa mozdulatlanul az okosszemüveget.</string>
<string name="spatial_validation_pending">A következő kamerakép ellenőrzése. Tartsa láthatóan a teljes kalibrációs célt, és tartsa mozdulatlanul.</string>
<string name="spatial_start_first">Először indítsa el a kalibrálást</string>
<string name="spatial_no_camera_frame">Nem érkezett friss kamerakép. Ellenőrizze az okosszemüveg csatlakozását és a kamera előnézetét, majd próbálkozzon újra.</string>
<string name="spatial_clock_reset">A kamera órájának visszaállítása</string>
<string name="spatial_frame_changed">A követési koordinátarendszer megváltozott. A nézetek mentésre kerültek az újraszámításhoz. Állítsa vissza a követőközpontot, majd indítson új sorozatot további nézetek rögzítéséhez.</string>
<string name="spatial_device_loaded">eszközkalibráció betöltve</string>
<string name="spatial_base_loaded">A Luma Ultra alapkalibráció betöltve</string>
<string name="spatial_calibration_needed">kalibrálás szükséges</string>
<string name="spatial_tap_start">Érintse meg az Új kalibrálás indítása elemet először.</string>
<string name="spatial_capture_recenter_pending">A követőközpont visszaállítása funkció stabil követést vár. Tartsa mozdulatlanul egy pillanatig.</string>
<string name="spatial_capture_recenter">A követés megváltozott. Koppintson egyszer a követőközpont visszaállítására, majd tartsa mozdulatlanul a rögzítés előtt.</string>
<string name="spatial_capture_unsynchronized">Rögzítés elutasítva: a fejkövetés nem lett szinkronizálva vagy módosult a rögzítés során. Maradjon nyugton, és próbálja újra.</string>
<string name="spatial_align_first">Számítsa ki a kamera beállítását a távolság ellenőrzése előtt.</string>
<string name="spatial_processing_failed">A kamera feldolgozása nem sikerült</string>
<string name="spatial_hand_head_changed">A fejkövetés megváltozott a kezek észlelése során</string>
<string name="spatial_hand_wait_sync">Várakozás a kamera és a fej szinkronizálására</string>
<string name="spatial_no_wrists">Nem észleltek csuklót. Tartsa a Joy-Con kontroller fogantyúit az okosszemüveg előtt</string>
<string name="spatial_no_hands">Nem észleltek kezet. Mutasson nyitott tenyeret az okosszemüveg előtt</string>
<string name="spatial_one_camera_wrists">A csukló csak egy kamerában látható; tartsa a vezérlő fogantyúit a megosztott nézetben</string>
<string name="spatial_one_camera_hands">A kezek csak egy kamerában láthatók; tartsa mindkét tenyerét megosztott nézetben</string>
<string name="spatial_ambiguous_hands">Mindkét kamera látja a kezeket, de a bal/jobb párosítás kétértelmű</string>
<string name="spatial_inconsistent_depth">Kezek észlelve, de a sztereó mélység nem következetes</string>
<string name="spatial_rejected_wrist">Sztereó vagy mozgásellenőrzések által elutasított csuklóhelyzet</string>
<string name="spatial_inconsistent_palm">Sztereópontok észlelve, de a tenyér geometriája inkonzisztens</string>
<string name="spatial_hands_need_calibration">Végezze el a kalibrálást és a távolságellenőrzést a kezek engedélyezéséhez</string>
<string name="spatial_hands_wait_stable">Várakozás stabil fejkövetésre; szükség esetén állítsa vissza a követőközpontot.</string>
<string name="spatial_old_frame">A kamerás megfigyelés túl régi; friss párra vár</string>
<string name="spatial_fit_ready">Fit %1$.2f px. Mérje meg a kiválasztott távolságot a bal oldali kamera és a kalibrációs célközép között, majd érvényesítse.</string>
<string name="spatial_opencv_error">Az OpenCV nem érhető el: %1$s</string>
<string name="spatial_restored">%1$d mentett nézet visszaállítva. Számítsa ki a kalibrációt az újrapróbálkozáshoz, vagy indítson új sorozatot, hogy többet rögzítsen.</string>
<string name="spatial_captured">Rögzített %1$d / 24 megtekintés. Tartsa rögzítve a kalibrációs célt; mozgassa az okosszemüveget a következő nézethez.</string>
<string name="spatial_capture_corners">Rögzítés elutasítva: %1$d / 12 szükséges sarok mindkét kamerán látható. Mutasson többet a kalibrációs célból, tartsa mozdulatlanul, majd próbálja újra.</string>
<string name="spatial_validation_error">A távolságellenőrzést nem sikerült befejezni: %1$s. Próbáld újra.</string>
<string name="spatial_inference_error">A kezek észlelése nem érhető el: %1$s. Az újrapróbálkozáshoz cserélje ki a CPU/GPU-t, vagy váltson puszta kézzel működtetett vezérlőket.</string>
<string name="spatial_hand_timeout">A kezek észlelése meghaladja a %1$d ms-ot; a megfigyelést elvetették</string>
<string name="spatial_wrists_tracked">%1$d / 2 Joy-Con csuklópozíció sztereóval mérve</string>
<string name="spatial_palms_tracked">Követett tenyerek: %1$d / 2; csippentésre kész: %2$d; megfogásra kész: %3$d</string>
<string name="spatial_capture_saved">Diagnosztikai rögzítés mentve ide: %1$s</string>
<string name="spatial_hand_error">Leállított kezek követése: %1$s</string>
<string name="spatial_stereo_loaded">Sztereó %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">A fejkövetés és a kalibrálási cél %1$s-kal (korlátozás: 5 cm) nem egyezik. Tartsa rögzítve a kalibrációs célt, és várja meg a folyamatos követést, mielőtt új sorozatot kezdene. Finoman fordítsa és döntse meg az intelligens szemüveget; szünetet minden rögzítés előtt.</string>
<string name="spatial_need_views">Rögzítsen legalább 24 különböző nézetet az álló kalibrációs célpontról.</string>
<string name="spatial_need_rotation">Forgassa balra és jobbra az intelligens szemüveget, majd döntse fel és le; a forgatás nélküli mozgás nem tudja kalibrálni a kamera beállítását.</string>
<string name="spatial_bad_baseline">Valószínűtlen kamera alapvonal</string>
<string name="spatial_need_orientations">Mozgassa fejét változatosabb tájolásokon.</string>
<string name="spatial_bad_rotation">Érvénytelen kamera-fej elforgatás</string>
<string name="spatial_bad_alignment">A kamera-fej igazítás nem következetes.</string>
<string name="spatial_calibration_error">A kalibrálást nem sikerült befejezni. %1$s A rögzített nézetek mentésre kerülnek.</string>
<string name="spatial_reprojection_error">Kitartott újravetítési hiba %1$.2f px meghaladja a 2 képpontot. Újrakalibrálás.</string>
<string name="spatial_distance_corners">%1$d / 24 sakktábla-sarkot észleltünk mindkét kamerában. Tartsa láthatóan a teljes kalibrációs célt, és tartsa mozdulatlanul. Ha már látható, válassza ki a 12 hüvelyk vagy a 30 cm-t, és mérje meg a közelebbi távolságot, mielőtt újra próbálkozna.</string>
<string name="spatial_distance_depth">A kalibrációs cél látható, de a mélysége nem mérhető megbízhatóan. Tartsa mozdulatlanul az intelligens szemüveget, és tartsa a kalibrációs célt síkban és jól megvilágítva, majd próbálkozzon újra.</string>
<string name="spatial_distance_invalid">A kamera mérése érvénytelen volt. Maradjon nyugton, és próbálja újra.</string>
<string name="spatial_distance_passed">A távolság ellenőrzése sikeres.</string>
<string name="spatial_distance_retry">Ellenőrizze újra a bal oldali kamera és a kalibrációs célpont közötti távolságot, majd próbálkozzon újra.</string>
<string name="spatial_distance_result">Mért kamerák: %1$s; kiválasztott távolság %2$s. %3$s</string>
<string name="spatial_wait_previews">A kalibrálás megkezdése előtt várja meg mindkét kamera előnézetét.</string>
<string name="spatial_saved_views_hint">Az elmentett nézetek újraszámíthatók. Indítson el egy új kalibrálást, hogy rögzítse az aktuális követési koordináta-rendszerben.</string>
<string name="spatial_model_checksum_error">A kézi modell ellenőrzőösszegének eltérése</string>
<string name="spatial_opencv_load_error">Az OpenCV betöltése nem sikerült</string>
<string name="glasses_3dof">3DoF virtuális képernyő</string>
<string name="glasses_6dof">6DoF virtuális képernyő</string>
</resources>
+214 -10
View File
@@ -2341,14 +2341,14 @@
<string name="glasses_display">Tampilan</string>
<string name="glasses_refresh">Refresh rate</string>
<string name="glasses_3d">3D (berdampingan)</string>
<string name="glasses_3d_hint">Setiap mata mendapat separuhnya sendiri dari gambar 3840×1080. Jika gambar hilang, tahan tombol 2D/3D kacamata.</string>
<string name="glasses_3d_hint">Alihkan kacamata pintar ke 3D berdampingan. Pelacakan kepala dikontrol secara terpisah.</string>
<string name="glasses_3d_failed">Sinyal 3D belum terkonfirmasi. Coba lagi setelah gambar 2D kembali.</string>
<string name="glasses_no_signal">Tidak ada gambar: kacamata dalam mode yang tidak bisa dikirim ponsel ini. Tahan tombol 2D/3D-nya, atau cabut lalu colokkan lagi.</string>
<string name="glasses_3d_refresh">Refresh rate 3D</string>
<string name="glasses_auto3d">3D untuk game VR</string>
<string name="glasses_auto3d_hint">Beralih ke 3D saat SteamVR berjalan, lalu kembali setelahnya.</string>
<string name="glasses_picture">Gambar 3D</string>
<string name="glasses_picture_hint">Otomatis: SteamVR memenuhi pandangan; game lain melayang sebagai layar di 3D dan tetap datar di 2D.</string>
<string name="glasses_picture">Gambar</string>
<string name="glasses_picture_hint">Otomatis menampilkan konten datar di kedua mata dan mendeteksi SteamVR. Pilih game berdampingan untuk konten SBS.</string>
<string name="glasses_picture_auto">Otomatis</string>
<string name="glasses_picture_screen">Layar virtual</string>
<string name="glasses_picture_sbs">Game 3D berdampingan</string>
@@ -2358,20 +2358,20 @@
<string name="glasses_screen_no_output">Setelan ini berlaku saat Game di kacamata aktif dan game ditampilkan di sana.</string>
<string name="glasses_screen_flat_hint">Otomatis menjaga game 2D ini tetap layar penuh. Gunakan layar virtual untuk mengatur lebar, jarak, dan pelacakan kepalanya.</string>
<string name="glasses_screen_use">Gunakan layar virtual</string>
<string name="glasses_screen_direct_hint">SteamVR memenuhi pandangan. Atur bidang pandangnya di bawah; lebar dan jarak hanya berlaku untuk layar virtual.</string>
<string name="glasses_screen_direct_hint">SteamVR memenuhi tampilan. Sesuaikan bidang pandangnya di VR; lebar dan jarak hanya berlaku untuk layar virtual.</string>
<string name="glasses_screen_sbs_hint">Game berdampingan memenuhi pandangan setiap mata. Lebar dan jarak hanya berlaku untuk layar virtual.</string>
<string name="glasses_tracking_flat_hint">Otomatis menjaga game 2D ini tetap diam dalam pandangan. Pilih Layar virtual di Tampilan untuk menggunakan pelacakan kepala.</string>
<string name="glasses_tracking_sbs_hint">Pelacakan kepala berlaku untuk layar virtual atau game SteamVR yang sedang berjalan.</string>
<string name="glasses_hardware">Kacamata</string>
<string name="glasses_hardware">Perangkat</string>
<string name="glasses_brightness">Kecerahan</string>
<string name="glasses_volume">Volume</string>
<string name="glasses_sunblock">Peredup lensa</string>
<string name="glasses_sunblock_hint">Menggelapkan lensa depan.</string>
<string name="glasses_tracking">Pelacakan kepala</string>
<string name="glasses_tracking">Pelacakan</string>
<string name="glasses_head_tracking">Pelacakan kepala</string>
<string name="glasses_head_tracking_hint">Sensor milik kacamata memutar layar virtual dan SteamVR mengikuti kepala Anda.</string>
<string name="glasses_recenter">Posisikan ulang tampilan</string>
<string name="glasses_recenter_button">Posisikan ulang</string>
<string name="glasses_recenter_button">Pusatkan ulang layar</string>
<string name="glasses_vr_fov">Bidang pandang SteamVR</string>
<string name="glasses_vr_fov_hint">Lebih rendah membawa SteamVR lebih dekat dan memenuhi kacamata; lebih tinggi menampilkan lebih banyak di sekitar Anda.</string>
<string name="glasses_playing">Bermain di %1$s</string>
@@ -2392,8 +2392,8 @@
<string name="glasses_calibration_reset_hint">Hapus kalibrasi tersimpan dan pulihkan default model? Posisikan ulang tampilan setelah memakai kacamata kembali.</string>
<string name="glasses_calibration_reset_button">Reset</string>
<string name="hand_camera_group">Pelacakan tangan</string>
<string name="hand_camera_prompt_title">Lacak tangan Anda dengan kamera?</string>
<string name="hand_camera_prompt_body">Kamera dapat melihat tangan Anda. Tanpa pengontrol yang terhubung, tangan kosong Anda menjadi tangan VR: satukan ibu jari dan telunjuk untuk mengeklik, kepalkan tangan untuk menggenggam, dan hadapkan telapak tangan ke arah Anda lalu satukan jari untuk membuka menu. Dengan Joy-Con, kamera menempatkannya di posisi tangan Anda yang sebenarnya. Gambar diproses di perangkat ini dan tidak pernah disimpan atau dikirim.</string>
<string name="hand_camera_prompt_title">Lacak tangan atau pengontrol Joy-Con dengan kamera ponsel?</string>
<string name="hand_camera_prompt_body">Tanpa pengontrol yang terhubung, kamera ponsel melacak tangan Anda di VR: jepit ibu jari dan jari telunjuk untuk mengklik, tutup tangan untuk meraih, dan putar telapak tangan ke arah Anda dan jepit untuk membuka menu. Dengan pengontrol Joy-Con yang terpasang, kamera melacak posisinya sementara pengontrol Joy-Con menyediakan input rotasi dan tombol. Gambar diproses di perangkat ini dan tidak pernah disimpan atau dikirim.</string>
<string name="hand_camera_rear">Kamera belakang</string>
<string name="hand_camera_rear_hint">Direkomendasikan. Biasanya lebih tajam dan lebih lebar.</string>
<string name="hand_camera_front">Kamera depan</string>
@@ -2401,7 +2401,7 @@
<string name="hand_camera_allow">Gunakan kamera</string>
<string name="hand_camera_not_now">Nanti saja</string>
<string name="hand_camera_setting">Pelacakan tangan dengan kamera</string>
<string name="hand_camera_setting_hint">Tanpa pengontrol yang terhubung, tangan Anda menjadi tangan VR: satukan jari untuk mengeklik, hadapkan telapak tangan ke arah Anda dan satukan jari untuk menu. Dengan Joy-Con, kamera menempatkannya di posisi yang dilihatnya. Gambar tetap di perangkat ini.</string>
<string name="hand_camera_setting_hint">Gunakan kamera ponsel untuk gerakan tangan kosong atau untuk memposisikan pengontrol Joy-Con yang terpasang di VR. Pengontrol Joy-Con menyediakan rotasi dan input tombol. Berhenti sementara kacamata pintar melacak tangan atau pengontrol Joy-Con Anda. Gambar hanya diproses pada perangkat ini.</string>
<string name="hand_camera_choice">Kamera</string>
<string name="hand_camera_access">Akses kamera</string>
<string name="hand_camera_access_hint">Izinkan akses kamera agar pelacakan tangan bisa dimulai.</string>
@@ -2655,4 +2655,208 @@
<string name="boot_movie_converting_started">Mengonversi %1$s untuk Steam. Biarkan DroidDeck tetap terbuka.</string>
<string name="boot_movie_unsupported">Perangkat ini tidak dapat mendekode video itu.</string>
<string name="boot_movie_import_failed">Tidak dapat mengimpor video itu</string>
<string name="glasses_experimental">Kamera Luma Ultra — Eksperimental</string>
<string name="glasses_joycons">Pelacakan posisi Joy-Con</string>
<string name="glasses_joycons_hint">Gunakan kamera kacamata pintar untuk memposisikan pengontrol Joy-Con yang terpasang. Rotasi dan input tombol berasal dari pengontrol Joy-Con. Posisi diperkirakan saat pergelangan tangan Anda tersembunyi. Menggantikan pengontrol yang dioperasikan dengan tangan kosong dan menghentikan pelacakan kamera ponsel. Arahkan pengontrol Joy-Con ke depan saat menghubungkan atau mengatur ulang pusat pelacakan.</string>
<string name="glasses_joycons_requires_position">Aktifkan pelacakan posisi terlebih dahulu. Membutuhkan pengontrol Joy-Con yang mampu mendeteksi gerakan pada Android 12 atau yang lebih baru. Nonaktif secara default.</string>
<string name="setup_tab_glasses">Kacamata pintar</string>
<string name="setup_glasses_scope">Pengaturan tampilan dan pelacakan kepala dibagikan di seluruh menu DroidDeck, game, dan sesi desktop.</string>
<string name="setup_glasses_connect">Hubungkan kacamata pintar untuk mengubah pengaturan tampilan dan pelacakannya. Pengaturan yang Anda simpan akan tetap ada saat Anda memutuskan sambungan.</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">Mode tampilan</string>
<string name="glasses_output_2d">Output 2D</string>
<string name="glasses_output_3d">Output 3D</string>
<string name="glasses_picture_group">Gambar dan layar virtual</string>
<string name="glasses_position_group">Posisi dan level lantai</string>
<string name="glasses_hands_group">Pelacakan tangan — Eksperimental</string>
<string name="glasses_tracking_status">Status pelacakan</string>
<string name="glasses_resolution_2d">Resolusi 2D</string>
<string name="glasses_resolution_3d">Resolusi 3D</string>
<string name="glasses_resolution_vr">Resolusi VR</string>
<string name="glasses_resolution_auto">Otomatis</string>
<string name="glasses_resolution_inherit">Sama seperti 2D / 3D</string>
<string name="glasses_resolution_hint">Resolusi output per mata.</string>
<string name="glasses_refresh_30_hint">Aktifkan Game pada kacamata pintar untuk memilih mode 30 Hz asli layar.</string>
<string name="glasses_3dof">Layar virtual 3DoF</string>
<string name="glasses_3dof_hint">Pasang game datar di depan Anda sehingga Anda dapat melihat sekeliling. Mengaktifkan Game pada kacamata pintar tanpa beralih ke 3D.</string>
<string name="glasses_screen_app_wide">Lebar dan jarak memperbarui layar virtual yang dilacak untuk menu DroidDeck, game, dan sesi desktop secara instan.</string>
<string name="glasses_head_tracking_spatial_vr_hint">Pelacakan posisi mencakup rotasi kepala di SteamVR. Untuk pelacakan hanya rotasi, matikan pelacakan posisi.</string>
<string name="glasses_head_tracking_spatial_hint">Mengontrol pergerakan kepala di layar virtual. Pelacakan posisi mencakup rotasi kepala di SteamVR.</string>
<string name="glasses_quick_actions">Tindakan cepat</string>
<string name="glasses_recenter_screen_hint">Lihat ke arah yang Anda inginkan untuk layar, lalu atur ulang pusat pelacakan untuk mengatur ketinggian dan arahnya.</string>
<string name="glasses_recenter_joycons_button">Pusatkan ulang Joy-Con</string>
<string name="phone_camera_tracking_group">Kamera Ponsel — Eksperimental</string>
<string name="phone_camera_tracking_setting">Lacak tangan atau pengontrol Joy-Con</string>
<string name="glasses_6dof">Layar virtual 6DoF</string>
<string name="glasses_position">Pelacakan posisi — eksperimental</string>
<string name="glasses_position_hint">Pergerakan fisik di VR dan di sekitar layar virtual. Memulai ulang pelacakan membutuhkan waktu sejenak.</string>
<string name="glasses_eye_height">Tinggi mata saat ini di atas lantai</string>
<string name="glasses_floor">Atur level lantai dari tinggi saat ini</string>
<string name="glasses_floor_set">Atur level lantai</string>
<string name="glasses_spatial_setup">Pengaturan dan diagnostik kamera stereo</string>
<string name="glasses_spatial_setup_hint">Periksa kalibrasi kamera dan pelacakan tangan sebelum bermain.</string>
<string name="glasses_spatial_open">Buka pengaturan</string>
<string name="glasses_hands">Pengontrol dioperasikan dengan tangan kosong</string>
<string name="glasses_hands_hint">Gunakan kamera kacamata pintar untuk membidik dengan tangan Anda, cubit untuk mengklik dan tutup tangan Anda untuk meraih di VR. Stik gamepad bergerak dan berputar. Menggantikan pelacakan posisi Joy-Con. Mungkin mengalami lag atau kehilangan pelacakan selama permainan.</string>
<string name="glasses_hands_ready_hint">Aktifkan pengontrol yang dioperasikan dengan tangan kosong di bawah kamera Luma Ultra di Pelacakan. Tunjukkan tangan terbuka terlebih dahulu.</string>
<string name="glasses_hands_base">Kalibrasi Luma Ultra bersama</string>
<string name="glasses_hands_base_hint">Menggunakan basis Luma Ultra bersama. Kalibrasi kacamata pintar Anda sendiri untuk memverifikasi keakuratannya.</string>
<string name="glasses_hands_calibration_verified">Kalibrasi terukur terverifikasi</string>
<string name="glasses_hands_calibration_required">Validasi diperlukan</string>
<string name="glasses_hands_calibration_required_hint">Pengontrol yang dioperasikan dengan tangan kosong tersedia setelah pemeriksaan jarak terukur berhasil.</string>
<string name="tracking_recovery_lost">Pelacakan hilang</string>
<string name="tracking_recovery_look">Lihat sekeliling perlahan di ruangan yang terang.\nJaga agar kamera pelacak tetap bersih dan menghadap ke furnitur atau detail lain yang terlihat.</string>
<string name="tracking_recovery_restart_help">Masih buntu? Di ponsel Anda: kacamata pintar → Pelacakan → Buka pengaturan → Tampilkan diagnostik → Mulai ulang pelacakan.</string>
<string name="tracking_recovery_disconnected">Kacamata pintar terputus</string>
<string name="tracking_recovery_connect">Periksa sambungan USB dan sambungkan kembali kacamata pintar.\nTerima perintah akses USB di ponsel Anda jika muncul.</string>
<string name="tracking_recovery_usb_help">Setelah terhubung, lihat sekeliling secara perlahan di ruangan yang cukup terang untuk memulihkan pelacakan.</string>
<string name="tracking_recovery_recenter">Perlu pemusatan ulang</string>
<string name="tracking_recovery_recenter_steps">Pelacakan telah kembali ke posisi baru.\nLihat ke depan, lalu ketuk atur ulang pusat pelacakan di ponsel Anda.</string>
<string name="tracking_recovery_recenter_help">Anda juga dapat menemukan reset pusat pelacakan di kacamata pintar → Pelacakan → Buka pengaturan. Kalibrasi kamera yang Anda simpan akan tetap tersimpan.</string>
<string name="tracking_recovery_stabilizing">Diam sejenak</string>
<string name="tracking_recovery_hold">Reset pusat pelacakan sedang berlangsung.\nJaga agar kacamata pintar tetap stabil saat pelacakan stabil.</string>
<string name="tracking_recovery_automatic">Layar ini tertutup secara otomatis ketika pelacakan kepala sudah siap.</string>
<string name="glasses_steady_hand_aim">Membidik dengan tangan dengan stabil</string>
<string name="glasses_steady_hand_aim_hint">Bidik dari bahumu hingga telapak tanganmu. Matikan untuk rotasi yang dikontrol pergelangan tangan.</string>
<string name="spatial_title">Pengaturan spasial</string>
<string name="spatial_wait_tracking">Menunggu pelacakan…</string>
<string name="spatial_cameras_description">Kamera pelacak kiri dan kanan</string>
<string name="spatial_camera_sync">Sinkronisasi kamera</string>
<string name="spatial_ready">Siap</string>
<string name="spatial_sync_hint">Menunggu kamera dan pelacakan kepala berbaris. Jaga agar kacamata pintar tetap terhubung.</string>
<string name="spatial_prepare">1. Siapkan target kalibrasi</string>
<string name="spatial_print_actual">Cetak dengan ukuran sebenarnya</string>
<string name="spatial_print_hint">Setiap persegi harus berukuran 30 mm (1,181 in). Pasang lembaran secara mendatar pada permukaan yang kaku di area yang cukup terang.</string>
<string name="spatial_print">Cetak pola kalibrasi</string>
<string name="spatial_capture_group">2. Menangkap tampilan berbeda</string>
<string name="spatial_board_stationary">Jaga agar target kalibrasi tetap diam</string>
<string name="spatial_capture_hint">Pindahkan kacamata pintar lebih dekat, lebih jauh dan ke samping. Putar ke kiri/kanan, miringkan ke atas/bawah, dan miringkan ke samping. Simpan target kalibrasi di kedua kamera dan jeda sebentar sebelum setiap pengambilan.</string>
<string name="spatial_start">Mulai kalibrasi baru</string>
<string name="spatial_wait_sync">Menunggu sinkronisasi kamera.</string>
<string name="spatial_start_hint">Mulai kalibrasi baru untuk mengumpulkan tampilan.</string>
<string name="spatial_views_hint">Jaga agar target kalibrasi tetap terlihat di kedua pratinjau. Tangkap setidaknya 24 tampilan tajam dari berbagai sudut.</string>
<string name="spatial_capturing">Menangkap…</string>
<string name="spatial_capture">Menangkap tampilan target kalibrasi</string>
<string name="spatial_alignment">Hitung penyelarasan kamera</string>
<string name="spatial_calculating">Menghitung…</string>
<string name="spatial_calculate">Hitung kalibrasi</string>
<string name="spatial_distance_group">3. Periksa jaraknya</string>
<string name="spatial_distance">Jarak terukur</string>
<string name="spatial_distance_hint">Mulailah dengan 12 inci atau 30 cm agar penanda yang dicetak lebih mudah dideteksi. Pilih pengukuran tepat yang akan Anda gunakan.</string>
<string name="spatial_camera_board_distance">Kamera kiri ke pusat target kalibrasi</string>
<string name="spatial_checking_distance">Memeriksa jarak…</string>
<string name="spatial_validate">Validasi jarak</string>
<string name="spatial_diagnostics">Diagnostik</string>
<string name="spatial_show_diagnostics">Tampilkan diagnostik</string>
<string name="spatial_diagnostics_hint">Alat pemulihan pengaturan waktu dan pelacakan kamera.</string>
<string name="spatial_camera_timing">Pengaturan waktu kamera</string>
<string name="spatial_hand_input">Masukan dari pelacakan tangan</string>
<string name="spatial_wait_timing">Menunggu timing kamera.</string>
<string name="spatial_frames">Gambar kamera</string>
<string name="spatial_gpu">Deteksi GPU pada tangan</string>
<string name="spatial_gpu_hint">CPU adalah defaultnya. GPU bersifat eksperimental dan dapat menyebabkan SteamVR dimulai ulang. Pilihan ini disetel ulang saat pelacakan dimulai ulang.</string>
<string name="spatial_lite">Deteksi ringan model tangan</string>
<string name="spatial_lite_hint">Gunakan detektor telapak tangan Lite dan titik kunci Lite di tangan. Periksa akurasi membidik, mencubit, dan meraih. Direset saat pelacakan dimulai ulang.</string>
<string name="spatial_adaptive">Frekuensi input otomatis untuk pelacakan tangan</string>
<string name="spatial_adaptive_hint">Sesuaikan input dengan kecepatan pemrosesan. Direset saat pelacakan dimulai ulang.</string>
<string name="spatial_half_rate">Batasi frekuensi input untuk pelacakan tangan</string>
<string name="spatial_half_rate_hint">Gunakan separuh gambar kamera. Matikan untuk membandingkan frekuensi maksimum. Direset saat pelacakan dimulai ulang.</string>
<string name="spatial_diagnostic_capture">Pengambilan diagnostik</string>
<string name="spatial_diagnostic_capture_hint">Simpan satu pasangan gambar stereo secara lokal. Gambar disimpan hanya jika diminta.</string>
<string name="spatial_save_frame">Simpan gambar kamera</string>
<string name="spatial_restart_tracking">Mulai ulang pelacakan</string>
<string name="spatial_restart_hint">Gunakan jika pelacakan tidak pulih di area yang cukup terang. Memulai ulang memerlukan asal pelacakan baru.</string>
<string name="spatial_restart">Mulai ulang</string>
<string name="spatial_restart_title">Mulai ulang pelacakan?</string>
<string name="spatial_restart_confirm">Tampilan kalibrasi yang belum disimpan akan hilang. Jaga agar kacamata pintar tetap diam setelahnya dan reset pusat pelacakan saat pelacakan dipulihkan.</string>
<string name="spatial_views_count">%1$d / 24 tayangan diambil</string>
<string name="spatial_capture_paused">Pengambilan dijeda: %1$s</string>
<string name="spatial_measure_hint">Ukur %1$s. Tampilkan target kalibrasi lengkap di kedua pratinjau, lalu tahan.</string>
<string name="spatial_timing_values">%1$.1f Hz • umur gambar %2$.1f ms • pemrosesan %3$.1f ms</string>
<string name="spatial_input_values">Batas input %1$.1f Hz • %2$.0f%% gambar kamera. Pelacakan kepala berlanjut pada frekuensi maksimumnya.</string>
<string name="spatial_frame_counts">%1$d diproses • %2$d dihilangkan • %3$d dilewati berdasarkan batas masukan</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck - Kalibrasi Luma Ultra</string>
<string name="spatial_print_scale" formatted="false">Cetak pada ukuran 100% / Ukuran sebenarnya. Setiap kotak: 30 mm (1,181 in).</string>
<string name="spatial_print_stationary">Jaga agar target kalibrasi tetap datar dan tidak bergerak; pindahkan kacamata pintar di antara pengambilan.</string>
<string name="spatial_print_check">Pemeriksaan skala: 100 mm (3,937 in) di antara tick</string>
<string name="spatial_distance_12in">12 inci (30,48 cm)</string>
<string name="spatial_distance_30cm">30 cm (11,81 inci)</string>
<string name="spatial_distance_20in">20 inci (50,8 cm)</string>
<string name="spatial_distance_50cm">50 cm (19,69 inci)</string>
<string name="spatial_disabled">Dinonaktifkan</string>
<string name="spatial_wait_stable">Menunggu pelacakan stabil</string>
<string name="spatial_disconnected">Terputus</string>
<string name="spatial_hands_off">Pengendali yang dioperasikan dengan tangan kosong dimatikan</string>
<string name="spatial_tracking_off">Pelacakan posisi tidak aktif atau kacamata pintar dilepas</string>
<string name="spatial_recenter_pending">Reset pusat pelacakan diminta — diamkan saat pelacakan stabil</string>
<string name="spatial_tracking_lost">Pelacakan hilang — hadapi area yang cukup terang dengan detail yang terlihat</string>
<string name="spatial_origin_changed">Asal pelacakan berubah — ketuk reset pusat pelacakan, lalu diamkan</string>
<string name="spatial_active">6DoF aktif</string>
<string name="spatial_connect_hint">Hubungkan Luma Ultra dan aktifkan pelacakan posisi.</string>
<string name="spatial_validation_session">Kalibrasi diverifikasi untuk koneksi ini.</string>
<string name="spatial_validation_saved">Kalibrasi diverifikasi dan disimpan.</string>
<string name="spatial_wait_cameras">Menunggu kamera stereo</string>
<string name="spatial_wait_hands">Menunggu pelacakan tangan</string>
<string name="spatial_collect_instructions">Jaga agar target kalibrasi 5 × 7 tidak bergerak. Tangkap 24+ tampilan sambil menggerakkan dan memutar kacamata pintar.</string>
<string name="spatial_capture_pending">Menunggu gambar kamera selanjutnya. Pegang kacamata pintar dengan tenang.</string>
<string name="spatial_validation_pending">Memeriksa gambar kamera selanjutnya. Jaga agar target kalibrasi penuh tetap terlihat dan diam.</string>
<string name="spatial_start_first">Mulai kalibrasi terlebih dahulu</string>
<string name="spatial_no_camera_frame">Tidak ada gambar kamera baru yang tiba. Periksa sambungan kacamata pintar dan pratinjau kamera, lalu coba lagi.</string>
<string name="spatial_clock_reset">Reset jam kamera</string>
<string name="spatial_frame_changed">Sistem koordinat pelacakan berubah. Tampilan Anda disimpan untuk perhitungan ulang. Reset pusat pelacakan, lalu mulai set baru untuk menangkap lebih banyak tampilan.</string>
<string name="spatial_device_loaded">kalibrasi perangkat dimuat</string>
<string name="spatial_base_loaded">Kalibrasi dasar Luma Ultra dimuat</string>
<string name="spatial_calibration_needed">kalibrasi diperlukan</string>
<string name="spatial_tap_start">Ketuk Mulai kalibrasi baru terlebih dahulu.</string>
<string name="spatial_capture_recenter_pending">Reset pusat pelacakan sedang menunggu pelacakan stabil. Diamkan sebentar.</string>
<string name="spatial_capture_recenter">Pelacakan berubah. Ketuk reset pusat pelacakan sekali, lalu diamkan sebelum menangkap.</string>
<string name="spatial_capture_unsynchronized">Pengambilan ditolak: pelacakan kepala tidak sinkron atau berubah selama pengambilan. Tetap diam dan coba lagi.</string>
<string name="spatial_align_first">Hitung keselarasan kamera sebelum memeriksa jarak.</string>
<string name="spatial_processing_failed">pemrosesan kamera gagal</string>
<string name="spatial_hand_head_changed">Pelacakan kepala berubah selama deteksi tangan</string>
<string name="spatial_hand_wait_sync">Menunggu sinkronisasi kamera dan kepala</string>
<string name="spatial_no_wrists">Tidak ada pergelangan tangan yang terdeteksi. Letakkan pegangan pengontrol Joy-Con Anda di depan kacamata pintar</string>
<string name="spatial_no_hands">Tidak ada tangan yang terdeteksi. Tunjukkan telapak tangan terbuka di depan kacamata pintar</string>
<string name="spatial_one_camera_wrists">Pergelangan tangan hanya terlihat di satu kamera; pertahankan pegangan pengontrol Anda dalam tampilan bersama</string>
<string name="spatial_one_camera_hands">Tangan hanya terlihat di satu kamera; jaga kedua telapak tangan dalam pandangan bersama</string>
<string name="spatial_ambiguous_hands">Kedua kamera melihat tangan, tetapi pencocokan kiri/kanan tidak jelas</string>
<string name="spatial_inconsistent_depth">Tangan terdeteksi, tetapi kedalaman stereo tidak konsisten</string>
<string name="spatial_rejected_wrist">Posisi pergelangan tangan ditolak dengan pemeriksaan stereo atau gerakan</string>
<string name="spatial_inconsistent_palm">Titik stereo terdeteksi, tetapi geometri telapak tangan tidak konsisten</string>
<string name="spatial_hands_need_calibration">Kalibrasi lengkap dan pemeriksaan jarak untuk mengaktifkan tangan</string>
<string name="spatial_hands_wait_stable">Menunggu pelacakan kepala yang stabil; reset pusat pelacakan jika diminta</string>
<string name="spatial_old_frame">Pengamatan kamera terlalu tua; menunggu pasangan baru</string>
<string name="spatial_fit_ready">Cocok dengan %1$.2f piksel. Ukur jarak yang Anda pilih dari kamera kiri ke pusat target kalibrasi, lalu validasi.</string>
<string name="spatial_opencv_error">OpenCV tidak tersedia: %1$s</string>
<string name="spatial_restored">%1$d tampilan tersimpan dipulihkan. Hitung kalibrasi untuk mencoba ulang, atau mulai rangkaian baru untuk menangkap lebih banyak.</string>
<string name="spatial_captured">Diambil %1$d / 24 penayangan. Jaga agar target kalibrasi tetap; pindahkan kacamata pintar untuk tampilan selanjutnya.</string>
<string name="spatial_capture_corners">Pengambilan gambar ditolak: %1$d / 12 sudut yang diperlukan terlihat di kedua kamera. Tampilkan lebih banyak target kalibrasi, diamkan, lalu coba lagi.</string>
<string name="spatial_validation_error">Pemeriksaan jarak tidak dapat diselesaikan: %1$s. Coba lagi.</string>
<string name="spatial_inference_error">Deteksi tangan tidak tersedia: %1$s. Ubah CPU/GPU atau pengontrol sakelar yang dioperasikan dengan tangan kosong untuk mencoba lagi.</string>
<string name="spatial_hand_timeout">Deteksi tangan melebihi %1$d ms; observasi dibuang</string>
<string name="spatial_wrists_tracked">%1$d / 2 posisi pergelangan tangan Joy-Con diukur dengan stereo</string>
<string name="spatial_palms_tracked">Telapak tangan terlacak: %1$d / 2; gestur cubit siap: %2$d; gestur genggam siap: %3$d</string>
<string name="spatial_capture_saved">Pengambilan diagnostik tersimpan ke %1$s</string>
<string name="spatial_hand_error">Pelacakan tangan dihentikan: %1$s</string>
<string name="spatial_stereo_loaded">Stereo %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">Pelacakan kepala dan target kalibrasi berbeda sebesar %1$s (batas 5 cm / 2 inci). Pertahankan target kalibrasi tetap dan tunggu pelacakan stabil sebelum memulai set baru. Putar dan miringkan kacamata pintar dengan lembut; jeda sebelum setiap pengambilan.</string>
<string name="spatial_need_views">Menangkap setidaknya 24 tampilan bervariasi dari target kalibrasi stasioner.</string>
<string name="spatial_need_rotation">Putar kacamata pintar ke kiri dan ke kanan, lalu miringkan ke atas dan ke bawah; gerakan tanpa rotasi tidak dapat mengkalibrasi kesejajaran kamera.</string>
<string name="spatial_bad_baseline">Garis dasar kamera yang tidak masuk akal</string>
<string name="spatial_need_orientations">Gerakkan kepala Anda ke arah yang lebih bervariasi.</string>
<string name="spatial_bad_rotation">Rotasi kamera-ke-kepala tidak valid</string>
<string name="spatial_bad_alignment">Penyelarasan kamera-ke-kepala tidak konsisten.</string>
<string name="spatial_calibration_error">Kalibrasi tidak dapat diselesaikan. %1$s Tampilan yang Anda ambil disimpan.</string>
<string name="spatial_reprojection_error">Kesalahan proyeksi ulang yang tertunda %1$.2f px melebihi 2 px. Kalibrasi ulang.</string>
<string name="spatial_distance_corners">%1$d / 24 sudut kotak-kotak terdeteksi di kedua kamera. Jaga agar target kalibrasi penuh tetap terlihat dan diam. Jika sudah terlihat, pilih 12 inci atau 30 cm dan ukur jarak terdekatnya sebelum mencoba lagi.</string>
<string name="spatial_distance_depth">Target kalibrasi terlihat, namun kedalamannya tidak dapat diukur dengan andal. Pegang kacamata pintar dengan tenang dan jaga target kalibrasi tetap datar dan terang, lalu coba lagi.</string>
<string name="spatial_distance_invalid">Pengukuran kamera tidak valid. Tahan dan coba lagi.</string>
<string name="spatial_distance_passed">Pemeriksaan jarak berhasil.</string>
<string name="spatial_distance_retry">Periksa kembali jarak dari kamera kiri ke pusat target kalibrasi, lalu coba lagi.</string>
<string name="spatial_distance_result">Kamera diukur %1$s; jarak yang dipilih %2$s. %3$s</string>
<string name="spatial_wait_previews">Tunggu pratinjau kedua kamera sebelum memulai kalibrasi.</string>
<string name="spatial_saved_views_hint">Tampilan yang disimpan dapat dihitung ulang. Mulai kalibrasi baru untuk menangkap sistem koordinat pelacakan saat ini.</string>
<string name="spatial_model_checksum_error">Ketidakcocokan checksum model tangan</string>
<string name="spatial_opencv_load_error">OpenCV tidak dapat memuat</string>
</resources>
+214 -10
View File
@@ -2377,14 +2377,14 @@
<string name="glasses_display">Schermo</string>
<string name="glasses_refresh">Frequenza di aggiornamento</string>
<string name="glasses_3d">3D (affiancato)</string>
<string name="glasses_3d_hint">Ogni occhio riceve la propria metà di un\'immagine 3840×1080. Se l\'immagine scompare, tieni premuto il pulsante 2D/3D degli occhiali.</string>
<string name="glasses_3d_hint">Passa gli occhiali intelligenti alla modalità 3D side-by-side. Il tracciamento della testa è controllato separatamente.</string>
<string name="glasses_3d_failed">Il segnale 3D non è stato confermato. Riprova quando torna l\'immagine 2D.</string>
<string name="glasses_no_signal">Nessuna immagine: gli occhiali sono in una modalità che questo telefono non può inviare. Tieni premuto il loro pulsante 2D/3D, oppure scollegali e ricollegali.</string>
<string name="glasses_3d_refresh">Frequenza di aggiornamento 3D</string>
<string name="glasses_auto3d">3D per i giochi VR</string>
<string name="glasses_auto3d_hint">Passa al 3D mentre SteamVR è in esecuzione, poi torna indietro.</string>
<string name="glasses_picture">Immagine 3D</string>
<string name="glasses_picture_hint">Automatico: SteamVR riempie la vista; gli altri giochi appaiono come uno schermo sospeso in 3D e restano piatti in 2D.</string>
<string name="glasses_picture">Immagine</string>
<string name="glasses_picture_hint">La ​​modalità automatica mostra contenuti piatti in entrambi gli occhi e rileva SteamVR. Scegli Gioco Side-by-Side per contenuti SBS.</string>
<string name="glasses_picture_auto">Automatico</string>
<string name="glasses_picture_screen">Schermo virtuale</string>
<string name="glasses_picture_sbs">Gioco affiancato</string>
@@ -2394,20 +2394,20 @@
<string name="glasses_screen_no_output">Queste impostazioni si applicano quando Gioco sugli occhiali è attivo e il gioco è mostrato sugli occhiali.</string>
<string name="glasses_screen_flat_hint">Con Automatico questo gioco 2D resta a schermo intero. Usa uno schermo virtuale per regolarne larghezza, distanza e tracciamento della testa.</string>
<string name="glasses_screen_use">Usa schermo virtuale</string>
<string name="glasses_screen_direct_hint">SteamVR riempie la vista. Regola qui sotto il suo campo visivo; larghezza e distanza si applicano solo a uno schermo virtuale.</string>
<string name="glasses_screen_direct_hint">SteamVR riempie la visuale. Regola il campo visivo in VR; larghezza e distanza si applicano solo allo schermo virtuale.</string>
<string name="glasses_screen_sbs_hint">I giochi affiancati riempiono ciascun occhio. Larghezza e distanza si applicano solo a uno schermo virtuale.</string>
<string name="glasses_tracking_flat_hint">Con Automatico questo gioco 2D resta fisso nella vista. Scegli Schermo virtuale nella sezione Schermo per usare il tracciamento della testa.</string>
<string name="glasses_tracking_sbs_hint">Il tracciamento della testa si applica a uno schermo virtuale o a un gioco SteamVR in esecuzione.</string>
<string name="glasses_hardware">Occhiali</string>
<string name="glasses_hardware">Dispositivo</string>
<string name="glasses_brightness">Luminosità</string>
<string name="glasses_volume">Volume</string>
<string name="glasses_sunblock">Oscuramento</string>
<string name="glasses_sunblock_hint">Scurisce le lenti frontali.</string>
<string name="glasses_tracking">Tracciamento della testa</string>
<string name="glasses_tracking">Tracciamento</string>
<string name="glasses_head_tracking">Tracciamento della testa</string>
<string name="glasses_head_tracking_hint">I sensori degli occhiali ruotano lo schermo virtuale e SteamVR insieme alla tua testa.</string>
<string name="glasses_recenter">Ricentra vista</string>
<string name="glasses_recenter_button">Ricentra</string>
<string name="glasses_recenter_button">Ricentra lo schermo</string>
<string name="glasses_vr_fov">Campo visivo di SteamVR</string>
<string name="glasses_vr_fov_hint">Più basso avvicina SteamVR e riempie gli occhiali; più alto mostra di più intorno a te.</string>
<string name="glasses_playing">Gioco in corso su %1$s</string>
@@ -2428,8 +2428,8 @@
<string name="glasses_calibration_reset_hint">Rimuovere la calibrazione salvata e ripristinare i valori predefiniti del modello? Ricentra dopo aver rimesso gli occhiali.</string>
<string name="glasses_calibration_reset_button">Ripristina</string>
<string name="hand_camera_group">Tracciamento delle mani</string>
<string name="hand_camera_prompt_title">Tracciare le mani con la fotocamera?</string>
<string name="hand_camera_prompt_body">La fotocamera vede le tue mani. Senza controller collegati, le tue mani nude diventano le mani VR: unisci pollice e indice per fare clic, chiudi la mano per afferrare e, per aprire il menu, gira il palmo verso di te e unisci le dita. Con i Joy-Con, li posiziona dove si trovano davvero le tue mani. Le immagini vengono elaborate su questo dispositivo e non vengono mai salvate né inviate.</string>
<string name="hand_camera_prompt_title">Tracciare le mani o i controller Joy-Con con la fotocamera del telefono?</string>
<string name="hand_camera_prompt_body">Senza controller collegato, la fotocamera del telefono traccia le tue mani nude in VR: pizzica il pollice e l\'indice per cliccare, chiudi la mano per afferrare e ruota il palmo verso di te e pizzica per aprire il menu. Con i controller Joy-Con abbinati, la fotocamera ne traccia la posizione mentre i controller Joy-Con forniscono input di rotazione e pulsanti. Le immagini vengono elaborate su questo dispositivo e non vengono mai salvate o inviate.</string>
<string name="hand_camera_rear">Fotocamera posteriore</string>
<string name="hand_camera_rear_hint">Consigliata. Di solito più nitida e ampia.</string>
<string name="hand_camera_front">Fotocamera anteriore</string>
@@ -2437,7 +2437,7 @@
<string name="hand_camera_allow">Usa la fotocamera</string>
<string name="hand_camera_not_now">Non ora</string>
<string name="hand_camera_setting">Tracciamento mani con fotocamera</string>
<string name="hand_camera_setting_hint">Senza controller collegati, le tue mani diventano le mani VR: unisci le dita per fare clic, palmo verso di te e dita unite per il menu. Con i Joy-Con, li posiziona dove li vede la fotocamera. Le immagini restano su questo dispositivo.</string>
<string name="hand_camera_setting_hint">Utilizza la fotocamera del telefono per i gesti a mani nude o per posizionare i controller Joy-Con associati in VR. I controller Joy-Con forniscono la rotazione e gli input dei pulsanti. Mette in pausa mentre gli occhiali smart tracciano le tue mani o i controller Joy-Con. Le immagini vengono elaborate solo su questo dispositivo.</string>
<string name="hand_camera_choice">Fotocamera</string>
<string name="hand_camera_access">Accesso alla fotocamera</string>
<string name="hand_camera_access_hint">Consenti l\'accesso alla fotocamera per avviare il tracciamento delle mani.</string>
@@ -2705,4 +2705,208 @@
<string name="boot_movie_converting_started">Conversione di %1$s per Steam. Tieni aperto DroidDeck.</string>
<string name="boot_movie_unsupported">Questo dispositivo non può decodificare il video.</string>
<string name="boot_movie_import_failed">Impossibile importare il video</string>
<string name="glasses_experimental">Fotocamere Luma Ultra — Sperimentale</string>
<string name="glasses_joycons">Tracciamento della posizione dei Joy-Con</string>
<string name="glasses_joycons_hint">Utilizza le fotocamere degli occhiali smart per posizionare i controller Joy-Con associati. La rotazione e gli input dei pulsanti provengono dai controller Joy-Con. La posizione viene stimata quando i polsi sono nascosti. Sostituisce i controller utilizzati a mani nude e mette in pausa il tracciamento tramite la fotocamera del telefono. Punta i controller Joy-Con in avanti quando li connetti o reimposti il ​​centro di tracciamento.</string>
<string name="glasses_joycons_requires_position">Abilita prima il tracciamento della posizione. Richiede controller Joy-Con compatibili con il movimento su Android 12 o versioni successive. Disattivato per impostazione predefinita.</string>
<string name="setup_tab_glasses">Occhiali smart</string>
<string name="setup_glasses_connect">Collega gli occhiali smart per modificarne le impostazioni di visualizzazione e tracciamento. Le impostazioni salvate vengono mantenute anche dopo la disconnessione.</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">Modalità di visualizzazione</string>
<string name="glasses_output_2d">Uscita 2D</string>
<string name="glasses_output_3d">Uscita 3D</string>
<string name="glasses_picture_group">Immagine e schermo virtuale</string>
<string name="glasses_position_group">Posizione e livello del pavimento</string>
<string name="glasses_hands_group">Tracciamento delle mani - Sperimentale</string>
<string name="glasses_tracking_status">Stato del tracciamento</string>
<string name="glasses_resolution_2d">Risoluzione 2D</string>
<string name="glasses_resolution_3d">Risoluzione 3D</string>
<string name="glasses_resolution_vr">Risoluzione VR</string>
<string name="glasses_resolution_auto">Automatica</string>
<string name="glasses_resolution_inherit">Uguale a 2D/3D</string>
<string name="glasses_resolution_hint">Risoluzione di output per occhio.</string>
<string name="glasses_refresh_30_hint">Abilita Gioco su occhiali intelligenti per selezionare la modalità nativa a 30 Hz del display.</string>
<string name="glasses_3dof">Schermo virtuale 3DoF</string>
<string name="glasses_3dof_hint">Ancora i giochi piatti di fronte a te per poterti guardare intorno. Abilita Gioco su occhiali intelligenti senza passare al 3D.</string>
<string name="glasses_screen_app_wide">Larghezza e distanza aggiornano immediatamente lo schermo virtuale tracciato per i menu di DroidDeck, i giochi e le sessioni desktop.</string>
<string name="glasses_head_tracking_spatial_vr_hint">Il tracciamento posizionale include la rotazione della testa in SteamVR. Per il tracciamento della sola rotazione, disattiva il tracciamento posizionale.</string>
<string name="glasses_head_tracking_spatial_hint">Controlla il movimento della testa sullo schermo virtuale. Il tracciamento posizionale include la rotazione della testa in SteamVR.</string>
<string name="glasses_quick_actions">Azioni rapide</string>
<string name="glasses_recenter_screen_hint">Guarda dove desideri che appaia lo schermo, quindi reimposta il centro di tracciamento per impostarne altezza e direzione.</string>
<string name="glasses_recenter_joycons_button">Ricentra i Joy-Con</string>
<string name="phone_camera_tracking_group">Fotocamera del telefono - Sperimentale</string>
<string name="phone_camera_tracking_setting">Tracciare mani o controller Joy-Con</string>
<string name="glasses_6dof">Schermo virtuale 6DoF</string>
<string name="glasses_position">Tracciamento posizionale - sperimentale</string>
<string name="glasses_position_hint">Movimento fisico in VR e intorno agli schermi virtuali. Il riavvio del tracciamento richiede un attimo.</string>
<string name="glasses_eye_height">Altezza attuale degli occhi rispetto al pavimento</string>
<string name="glasses_floor">Imposta il livello del pavimento dall\'altezza attuale</string>
<string name="glasses_floor_set">Imposta il livello del pavimento</string>
<string name="glasses_spatial_setup">Configurazione e diagnostica della telecamera stereo</string>
<string name="glasses_spatial_setup_hint">Verifica la calibrazione della telecamera e il tracciamento delle mani prima di giocare.</string>
<string name="glasses_spatial_open">Apri la configurazione</string>
<string name="glasses_hands">Controller utilizzati a mani nude</string>
<string name="glasses_hands_hint">Usa le telecamere degli occhiali smart per mirare con le mani, pizzica per cliccare e chiudi la mano per afferrare in VR. Gli stick del gamepad si muovono e ruotano. Sostituisce il tracciamento della posizione dei Joy-Con. Potrebbe verificarsi un ritardo o la perdita del tracciamento durante le partite.</string>
<string name="glasses_hands_ready_hint">Abilita i controller utilizzati a mani nude sotto le telecamere Luma Ultra in Tracciamento. Mostra prima le mani aperte.</string>
<string name="glasses_hands_base">Calibrazione Luma Ultra condivisa</string>
<string name="glasses_hands_base_hint">Utilizzo della base Luma Ultra condivisa. Calibra i tuoi occhiali intelligenti per verificarne la precisione.</string>
<string name="glasses_hands_calibration_verified">Calibrazione misurata verificata</string>
<string name="glasses_hands_calibration_required">Validazione richiesta</string>
<string name="glasses_hands_calibration_required_hint">I controller azionati a mani nude saranno disponibili dopo il superamento del controllo della distanza misurata.</string>
<string name="tracking_recovery_lost">Tracciamento perso</string>
<string name="tracking_recovery_look">Guardati intorno lentamente in una stanza ben illuminata.\nMantieni le telecamere di tracciamento libere e rivolte verso mobili o altri dettagli visibili.</string>
<string name="tracking_recovery_restart_help">Ancora bloccato? Sul tuo telefono: occhiali intelligenti → Monitoraggio → Apri configurazione → Mostra diagnostica → Riavvia monitoraggio.</string>
<string name="tracking_recovery_disconnected">Occhiali intelligenti disconnessi</string>
<string name="tracking_recovery_connect">Controlla la connessione USB e ricollega gli occhiali intelligenti.\nAccetta la richiesta di accesso USB sul telefono, se visualizzata.</string>
<string name="tracking_recovery_usb_help">Una volta connesso, guardati intorno lentamente in una stanza ben illuminata per ripristinare il tracciamento.</string>
<string name="tracking_recovery_recenter">È necessario ricentrare</string>
<string name="tracking_recovery_recenter_steps">Il tracciamento è tornato in una nuova posizione.\nGuarda in avanti, quindi tocca \"Ricentra\" sul telefono.</string>
<string name="tracking_recovery_recenter_help">Puoi trovare \"Ricentra\" anche in Occhiali smart → Tracciamento → Apri la configurazione. La calibrazione salvata della fotocamera viene mantenuta.</string>
<string name="tracking_recovery_stabilizing">Resta fermo per un momento</string>
<string name="tracking_recovery_hold">Ripristino del centro di tracciamento in corso.\nMantieni gli Smart Glasses fermi mentre il tracciamento si stabilizza.</string>
<string name="tracking_recovery_automatic">Questa schermata si chiude automaticamente quando il tracciamento della testa è pronto.</string>
<string name="glasses_steady_hand_aim">Mira stabilizzata con le mani</string>
<string name="glasses_steady_hand_aim_hint">Mira dalla spalla attraverso il palmo della mano. Disattivare per la rotazione controllata dal polso.</string>
<string name="spatial_title">Configurazione spaziale</string>
<string name="spatial_wait_tracking">In attesa del tracciamento…</string>
<string name="spatial_cameras_description">Telecamere di tracciamento sinistra e destra</string>
<string name="spatial_camera_sync">Sincronizzazione della fotocamera</string>
<string name="spatial_ready">Pronto</string>
<string name="spatial_sync_hint">In attesa che la telecamera e il rilevamento della testa si allineino. Mantieni gli occhiali intelligenti collegati.</string>
<string name="spatial_prepare">1. Preparare il target di calibrazione</string>
<string name="spatial_print_actual">Stampa nelle dimensioni reali</string>
<string name="spatial_print_hint">Ciascun quadrato deve misurare 30 mm (1,181 pollici). Montare il foglio in piano su una superficie rigida in un\'area ben illuminata.</string>
<string name="spatial_print">Stampa il pannello di calibrazione</string>
<string name="spatial_capture_group">2. Cattura punti di vista diversi</string>
<string name="spatial_board_stationary">Mantenere fermo il target di calibrazione</string>
<string name="spatial_capture_hint">Avvicinare, allontanare e lateralmente gli occhiali intelligenti. Girali a sinistra/destra, inclinali verso l\'alto/il basso e inclinali lateralmente. Conservare il target di calibrazione in entrambe le fotocamere e fare una breve pausa prima di ogni acquisizione.</string>
<string name="spatial_start">Avvia una nuova calibrazione</string>
<string name="spatial_wait_sync">In attesa della sincronizzazione della fotocamera.</string>
<string name="spatial_start_hint">Avviare una nuova calibrazione per raccogliere visualizzazioni.</string>
<string name="spatial_views_hint">Mantenere il target di calibrazione visibile in entrambe le anteprime. Cattura almeno 24 viste nitide da diverse angolazioni.</string>
<string name="spatial_capturing">Cattura…</string>
<string name="spatial_capture">Cattura la vista del target di calibrazione</string>
<string name="spatial_alignment">Calcolare l\'allineamento della telecamera</string>
<string name="spatial_calculating">Calcolo…</string>
<string name="spatial_calculate">Calcolare la calibrazione</string>
<string name="spatial_distance_group">3. Controllare la distanza</string>
<string name="spatial_distance">Distanza misurata</string>
<string name="spatial_distance_hint">Iniziare con 12 pollici o 30 cm in modo che gli indicatori stampati siano più facili da rilevare. Scegli la misura esatta che utilizzerai.</string>
<string name="spatial_camera_board_distance">Telecamera sinistra al centro del target di calibrazione</string>
<string name="spatial_checking_distance">Controllo della distanza…</string>
<string name="spatial_validate">Convalida la distanza</string>
<string name="spatial_diagnostics">Diagnostica</string>
<string name="spatial_show_diagnostics">Mostra diagnostica</string>
<string name="spatial_diagnostics_hint">Strumenti di recupero della temporizzazione e tracciamento della fotocamera.</string>
<string name="spatial_camera_timing">Temporizzazione della telecamera</string>
<string name="spatial_hand_input">Input dal tracciamento delle mani</string>
<string name="spatial_wait_timing">In attesa dei tempi della telecamera.</string>
<string name="spatial_frames">Immagini della telecamera</string>
<string name="spatial_gpu">Rilevamento delle mani tramite GPU</string>
<string name="spatial_gpu_hint">La CPU è l\'impostazione predefinita. La GPU è sperimentale e può causare il riavvio di SteamVR. Questa scelta si reimposta al riavvio del monitoraggio.</string>
<string name="spatial_lite">Rilevamento rapido dei modelli di mani</string>
<string name="spatial_lite_hint">Utilizzare il rilevatore di palma Lite e i punti chiave Lite sulle mani. Controlla la precisione della mira, del pizzicotto e della presa. Si reimposta al riavvio del monitoraggio.</string>
<string name="spatial_adaptive">Frequenza di ingresso automatica per il tracciamento delle mani</string>
<string name="spatial_adaptive_hint">Regola l\'input alla velocità di elaborazione. Si reimposta al riavvio del monitoraggio.</string>
<string name="spatial_half_rate">Limita la frequenza di ingresso per il tracciamento delle mani</string>
<string name="spatial_half_rate_hint">Utilizzare la metà delle immagini della telecamera. Spegnere per confrontare la frequenza massima. Si reimposta al riavvio del monitoraggio.</string>
<string name="spatial_diagnostic_capture">Cattura diagnostica</string>
<string name="spatial_diagnostic_capture_hint">Salva localmente una coppia di immagini stereo. Le immagini vengono salvate solo quando richiesto.</string>
<string name="spatial_save_frame">Salva l\'immagine della telecamera</string>
<string name="spatial_restart_tracking">Riavviare il tracciamento</string>
<string name="spatial_restart_hint">Utilizzare se il tracciamento non viene ripristinato in un\'area ben illuminata. Un riavvio richiede una nuova origine di tracciamento.</string>
<string name="spatial_restart">Riavvio</string>
<string name="spatial_restart_title">Riavviare il monitoraggio?</string>
<string name="spatial_restart_confirm">Le visualizzazioni di calibrazione non salvate andranno perse. Mantieni gli Smart Glasses fermi in seguito e ripristina il centro di tracciamento quando il tracciamento viene ripristinato.</string>
<string name="spatial_views_count">%1$d / 24 visualizzazioni acquisite</string>
<string name="spatial_capture_paused">Cattura in pausa: %1$s</string>
<string name="spatial_measure_hint">Misura %1$s. Mostra il target di calibrazione completo in entrambe le anteprime, quindi resta fermo.</string>
<string name="spatial_timing_values">%1$.1f Hz • età dell\'immagine %2$.1f ms • elaborazione %3$.1f ms</string>
<string name="spatial_input_values">Limite di ingresso %1$.1f Hz • %2$.0f%% delle immagini della telecamera. Il tracciamento della testa continua alla sua frequenza massima.</string>
<string name="spatial_frame_counts">%1$d elaborato • %2$d eliminato • %3$d saltato per limite di input</string>
<string name="spatial_print_title">DroidDeck - Calibrazione Luma Ultra</string>
<string name="spatial_print_scale" formatted="false">Stampa a 100% / Dimensioni effettive. Ogni quadrato: 30 mm (1,181 pollici).</string>
<string name="spatial_print_stationary">Mantenere il target di calibrazione piatto e fermo; spostare gli occhiali intelligenti tra le acquisizioni.</string>
<string name="spatial_print_check">Controllo scala: 100 mm (3,937 pollici) tra i tick</string>
<string name="spatial_distance_12in">12 pollici (30,48 cm)</string>
<string name="spatial_distance_30cm">30 cm (11,81 pollici)</string>
<string name="spatial_distance_20in">20 pollici (50,8 cm)</string>
<string name="spatial_distance_50cm">50 cm (19,69 pollici)</string>
<string name="spatial_disabled">Disabilitato</string>
<string name="spatial_wait_stable">In attesa del tracciamento stabile</string>
<string name="spatial_disconnected">Disconnesso</string>
<string name="spatial_hands_off">I controller azionati a mani nude sono spenti</string>
<string name="spatial_tracking_off">Il rilevamento della posizione è disattivato o gli occhiali intelligenti sono disconnessi</string>
<string name="spatial_recenter_pending">Richiesta di ripristino del centro di tracciamento: tieni fermi gli occhiali mentre il tracciamento si stabilizza.</string>
<string name="spatial_tracking_lost">Tracciamento perso: rivolgiti verso un\'area ben illuminata con dettagli visibili</string>
<string name="spatial_origin_changed">Origine del tracciamento modificata: tocca \"Ricentra\", quindi tieni fermi gli occhiali.</string>
<string name="spatial_active">6DoF attivo</string>
<string name="spatial_connect_hint">Collegare Luma Ultra e abilitare il tracciamento posizionale.</string>
<string name="spatial_validation_session">Calibrazione verificata per questa connessione.</string>
<string name="spatial_validation_saved">Calibrazione verificata e salvata.</string>
<string name="spatial_wait_cameras">In attesa delle telecamere stereo</string>
<string name="spatial_wait_hands">In attesa del tracciamento delle mani</string>
<string name="spatial_collect_instructions">Mantenere fermo il target di calibrazione 5 × 7. Cattura oltre 24 visualizzazioni spostando e ruotando gli occhiali intelligenti.</string>
<string name="spatial_capture_pending">In attesa dell\'immagine successiva della telecamera. Tieni fermi gli occhiali intelligenti.</string>
<string name="spatial_validation_pending">Controllo dell\'immagine successiva della telecamera. Mantenere visibile l\'intero target di calibrazione e restare fermi.</string>
<string name="spatial_start_first">Avviare prima la calibrazione</string>
<string name="spatial_no_camera_frame">Non è arrivata alcuna nuova immagine della fotocamera. Controlla la connessione degli occhiali intelligenti e le anteprime della fotocamera, quindi riprova.</string>
<string name="spatial_clock_reset">Reimpostazione dell\'orologio della fotocamera</string>
<string name="spatial_frame_changed">Sistema di coordinate di tracciamento modificato. Le visualizzazioni sono state salvate per il ricalcolo. Ripristina il centro di tracciamento, quindi avvia una nuova configurazione per acquisire altre visualizzazioni.</string>
<string name="spatial_device_loaded">calibrazione del dispositivo caricata</string>
<string name="spatial_base_loaded">Calibrazione base Luma Ultra caricata</string>
<string name="spatial_calibration_needed">calibrazione richiesta</string>
<string name="spatial_tap_start">Toccare prima Avvia nuova calibrazione.</string>
<string name="spatial_capture_recenter_pending">Il ripristino del centro di tracciamento è in attesa di un tracciamento stabile. Tieni fermi gli occhiali per un momento.</string>
<string name="spatial_capture_recenter">Tracciamento modificato. Tocca \"Ricentra\" una volta, quindi tieni fermi gli occhiali prima di acquisire.</string>
<string name="spatial_capture_unsynchronized">Acquisizione rifiutata: il tracciamento della testa non era sincronizzato oppure è cambiato durante l’acquisizione. Rimani fermo e riprova.</string>
<string name="spatial_align_first">Calcolare l\'allineamento della telecamera prima di controllare la distanza.</string>
<string name="spatial_processing_failed">elaborazione della fotocamera non riuscita</string>
<string name="spatial_hand_head_changed">Il rilevamento della testa è stato modificato durante il rilevamento delle mani</string>
<string name="spatial_hand_wait_sync">In attesa della sincronizzazione della fotocamera e della testa</string>
<string name="spatial_no_wrists">Nessun polso rilevato. Tieni le maniglie del controller Joy-Con davanti agli occhiali intelligenti</string>
<string name="spatial_no_hands">Nessuna mano rilevata. Mostra i palmi aperti davanti agli occhiali intelligenti</string>
<string name="spatial_one_camera_wrists">Polsi visibili in una sola telecamera; mantieni gli handle del controller nella vista condivisa</string>
<string name="spatial_one_camera_hands">Mani visibili in una sola telecamera; mantieni entrambi i palmi nella vista condivisa</string>
<string name="spatial_ambiguous_hands">Entrambe le fotocamere vedono le mani, ma la corrispondenza sinistra/destra è ambigua</string>
<string name="spatial_inconsistent_depth">Mani rilevate, ma la profondità stereo non è coerente</string>
<string name="spatial_rejected_wrist">Posizione del polso rifiutata dai controlli stereo o di movimento</string>
<string name="spatial_inconsistent_palm">Sono stati rilevati punti stereo, ma la geometria del palmo non è coerente</string>
<string name="spatial_hands_need_calibration">Calibrazione completa e controllo della distanza per abilitare le mani</string>
<string name="spatial_hands_wait_stable">In attesa di un tracciamento stabile della testa; ripristina il centro di tracciamento se richiesto.</string>
<string name="spatial_old_frame">L’osservazione tramite telecamera è troppo vecchia; in attesa di un nuovo paio</string>
<string name="spatial_fit_ready">Adatta %1$.2f px. Misura la distanza selezionata dalla telecamera sinistra al centro del target di calibrazione, quindi convalida.</string>
<string name="spatial_opencv_error">OpenCV non disponibile: %1$s</string>
<string name="spatial_restored">%1$d visualizzazioni salvate ripristinate. Calcola la calibrazione per riprovare o avvia un nuovo set per acquisirne di più.</string>
<string name="spatial_captured">Acquisito %1$d / 24 visualizzazioni. Mantenere fisso il target di calibrazione; sposta gli occhiali intelligenti per la visualizzazione successiva.</string>
<string name="spatial_capture_corners">Acquisizione rifiutata: %1$d / 12 angoli obbligatori visibili in entrambe le telecamere. Mostra una parte maggiore del target di calibrazione, resta fermo e riprova.</string>
<string name="spatial_validation_error">Impossibile terminare il controllo della distanza: %1$s. Riprova.</string>
<string name="spatial_inference_error">Rilevamento delle mani non disponibile: %1$s. Cambia CPU/GPU o attiva/disattiva i controller azionati a mani nude per riprovare.</string>
<string name="spatial_hand_timeout">Il rilevamento delle mani ha superato %1$d ms; osservazione scartata</string>
<string name="spatial_wrists_tracked">%1$d / 2 Posizioni del polso dei Joy-Con misurate tramite stereo</string>
<string name="spatial_palms_tracked">Palmi tracciati: %1$d / 2; gesto di pizzico pronto: %2$d; presa pronta: %3$d</string>
<string name="spatial_capture_saved">Acquisizione diagnostica salvata in %1$s</string>
<string name="spatial_hand_error">Tracciamento delle mani interrotto: %1$s</string>
<string name="spatial_stereo_loaded">Stereo %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">Il rilevamento della testa e il target di calibrazione non sono d\'accordo di %1$s (limite 5 cm/2 pollici). Mantenere fisso il target di calibrazione e attendere un tracciamento costante prima di iniziare una nuova serie. Ruota e inclina delicatamente gli occhiali intelligenti; fare una pausa prima di ogni acquisizione.</string>
<string name="spatial_need_views">Cattura almeno 24 viste diverse del target di calibrazione stazionario.</string>
<string name="spatial_need_rotation">Gira gli occhiali intelligenti a sinistra e a destra e inclinali su e giù; il movimento senza rotazione non può calibrare l\'allineamento della fotocamera.</string>
<string name="spatial_bad_baseline">Linea di base della fotocamera non plausibile</string>
<string name="spatial_need_orientations">Muovi la testa attraverso orientamenti più diversi.</string>
<string name="spatial_bad_rotation">Rotazione telecamera-testa non valida</string>
<string name="spatial_bad_alignment">L\'allineamento telecamera-testa non è coerente.</string>
<string name="spatial_calibration_error">Impossibile terminare la calibrazione. %1$s Le tue visualizzazioni acquisite vengono salvate.</string>
<string name="spatial_reprojection_error">L\'errore di riproiezione trattenuta %1$.2f px supera i 2 px. Ricalibrare.</string>
<string name="spatial_distance_corners">%1$d / 24 angoli della scacchiera rilevati in entrambe le fotocamere. Mantenere visibile l\'intero target di calibrazione e restare fermi. Se è già visibile, seleziona 12 pollici o 30 cm e misura la distanza più vicina prima di riprovare.</string>
<string name="spatial_distance_depth">Il target di calibrazione è visibile, ma la sua profondità non può essere misurata in modo affidabile. Tieni fermi gli occhiali intelligenti e mantieni il target di calibrazione piatto e ben illuminato, quindi riprova.</string>
<string name="spatial_distance_invalid">La ​​misurazione della fotocamera non era valida. Stai fermo e riprova.</string>
<string name="spatial_distance_passed">Controllo della distanza superato.</string>
<string name="spatial_distance_retry">Ricontrollare la distanza dalla telecamera sinistra al centro del target di calibrazione, quindi riprovare.</string>
<string name="spatial_distance_result">Telecamere misurate %1$s; distanza selezionata %2$s. %3$s</string>
<string name="spatial_wait_previews">Attendere le anteprime di entrambe le telecamere prima di avviare la calibrazione.</string>
<string name="spatial_saved_views_hint">Le visualizzazioni salvate possono essere ricalcolate. Avvia una nuova calibrazione per acquisire il sistema di coordinate di tracciamento corrente.</string>
<string name="spatial_model_checksum_error">Mancata corrispondenza del checksum del modello di mano</string>
<string name="spatial_opencv_load_error">Impossibile caricare OpenCV</string>
<string name="setup_glasses_scope">Le impostazioni dello schermo e del tracciamento della testa sono condivise tra menu, giochi e sessioni desktop di DroidDeck.</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
</resources>
+214 -10
View File
@@ -2341,14 +2341,14 @@
<string name="glasses_display">表示</string>
<string name="glasses_refresh">リフレッシュレート</string>
<string name="glasses_3d">3D(サイドバイサイド)</string>
<string name="glasses_3d_hint">左右の目に 3840×1080 の画像の半分ずつを表示します。映像が消えたら、グラスの 2D/3D ボタンを長押ししてください。</string>
<string name="glasses_3d_hint">スマートグラスをサイドバイサイド3Dに切り替えます。ヘッドトラッキングは個別に制御されます。</string>
<string name="glasses_3d_failed">3D 信号を確認できませんでした。2D 映像が戻ってから再試行してください。</string>
<string name="glasses_no_signal">映像なし: グラスがこのスマートフォンで出力できないモードになっています。2D/3D ボタンを長押しするか、抜き差ししてください。</string>
<string name="glasses_3d_refresh">3D のリフレッシュレート</string>
<string name="glasses_auto3d">VR ゲームで 3D</string>
<string name="glasses_auto3d_hint">SteamVR の実行中は 3D に切り替え、終了後に戻します。</string>
<string name="glasses_picture">3D の映像</string>
<string name="glasses_picture_hint">自動: SteamVR は視界全体に表示し、ほかのゲームは 3D ではスクリーンとして浮かび、2D では平面のままです。</string>
<string name="glasses_picture">画像</string>
<string name="glasses_picture_hint">自動設定では、両眼にフラットコンテンツを表示し、SteamVRを検出します。サイドバイサイドゲーム(SBS)コンテンツの場合は、サイドバイサイドゲームを選択してください。</string>
<string name="glasses_picture_auto">自動</string>
<string name="glasses_picture_screen">仮想スクリーン</string>
<string name="glasses_picture_sbs">サイドバイサイドのゲーム</string>
@@ -2358,20 +2358,20 @@
<string name="glasses_screen_no_output">これらの設定は、「グラスにゲームを表示」がオンで、ゲームがグラスに表示されているときに適用されます。</string>
<string name="glasses_screen_flat_hint">「自動」では、この 2D ゲームは全画面のままです。幅、距離、ヘッドトラッキングを調整するには、仮想スクリーンを使用してください。</string>
<string name="glasses_screen_use">仮想スクリーンを使用</string>
<string name="glasses_screen_direct_hint">SteamVR は視界全体に表示されます。視野角は下で調整できます。幅と距離は仮想スクリーンにのみ適用されます。</string>
<string name="glasses_screen_direct_hint">SteamVRは画面全体に表示されます。VR内で視野角を調整してください。幅と距離は仮想画面にのみ適用されます。</string>
<string name="glasses_screen_sbs_hint">サイドバイサイドのゲームは、左右の目の視界いっぱいに表示されます。幅と距離は仮想スクリーンにのみ適用されます。</string>
<string name="glasses_tracking_flat_hint">「自動」では、この 2D ゲームは視界の中で固定されます。ヘッドトラッキングを使うには、「表示」で「仮想スクリーン」を選んでください。</string>
<string name="glasses_tracking_sbs_hint">ヘッドトラッキングは、仮想スクリーンまたは実行中の SteamVR ゲームに適用されます。</string>
<string name="glasses_hardware">グラス</string>
<string name="glasses_hardware">デバイス</string>
<string name="glasses_brightness">明るさ</string>
<string name="glasses_volume">音量</string>
<string name="glasses_sunblock">サンブロック</string>
<string name="glasses_sunblock_hint">前面のレンズを暗くします。</string>
<string name="glasses_tracking">ヘッドトラッキング</string>
<string name="glasses_tracking">トラッキング</string>
<string name="glasses_head_tracking">ヘッドトラッキング</string>
<string name="glasses_head_tracking_hint">グラスのセンサーで、頭の動きに合わせて仮想スクリーンと SteamVR を動かします。</string>
<string name="glasses_recenter">視点をリセット</string>
<string name="glasses_recenter_button">中央に戻す</string>
<string name="glasses_recenter_button">画面の中心をリセット</string>
<string name="glasses_vr_fov">SteamVR の視野角</string>
<string name="glasses_vr_fov_hint">小さくすると SteamVR が近くなりグラスいっぱいに広がり、大きくすると周囲が広く見えます。</string>
<string name="glasses_playing">%1$s でプレイ中</string>
@@ -2392,8 +2392,8 @@
<string name="glasses_calibration_reset_hint">保存したキャリブレーションを削除して、モデルの初期設定に戻しますか?グラスを装着し直したら、中央に戻してください。</string>
<string name="glasses_calibration_reset_button">リセット</string>
<string name="hand_camera_group">ハンドトラッキング</string>
<string name="hand_camera_prompt_title">カメラで手をトラッキングしますか?</string>
<string name="hand_camera_prompt_body">カメラで手を認識できます。コントローラーが接続されていないときは、素手がそのまま VR の手になります。親指と人差し指をつまむとクリック、手を握るとつかむ、手のひらを自分に向けてつまむとメニューが開きます。Joy-Con を使うときは、手が実際にある位置に Joy-Con を配置します。映像はこのデバイス上で処理され、保存や送信は一切行われません。</string>
<string name="hand_camera_prompt_title">スマートフォンカメラで手やJoy-Conコントローラーをトラッキングしますか?</string>
<string name="hand_camera_prompt_body">コントローラーが接続されていない場合、スマートフォンカメラはVR内で素手の動きをトラッキングします。親指と人差し指でつまむとクリック、手を閉じると掴む、手のひらを自分の方に向け、つまむとメニューが開きます。Joy-Conコントローラーがペアリングされている場合、カメラはコントローラーの位置をトラッキングし、Joy-Conコントローラーは回転とボタン入力を行います。画像はこのデバイスで処理され、保存または送信されることはありません。</string>
<string name="hand_camera_rear">背面カメラ</string>
<string name="hand_camera_rear_hint">おすすめです。通常はより鮮明で広角です。</string>
<string name="hand_camera_front">前面カメラ</string>
@@ -2401,7 +2401,7 @@
<string name="hand_camera_allow">カメラを使う</string>
<string name="hand_camera_not_now">後で</string>
<string name="hand_camera_setting">カメラハンドトラッキング</string>
<string name="hand_camera_setting_hint">コントローラーが接続されていないときは、手が VR の手になります。つまむとクリック、手のひらを自分に向けてつまむとメニューです。Joy-Con はカメラが見た位置に配置します。映像はこのデバイスの外に出ません。</string>
<string name="hand_camera_setting_hint">スマートフォンのカメラを使って、素手でのジェスチャー操作や、ペアリングしたJoy-ConコントローラーをVR空間に配置することができます。Joy-Conコントローラーは回転とボタン入力を提供します。スマートグラスが手やJoy-Conコントローラーをトラッキングしている間、一時停止します。画像はこのデバイスでのみ処理されます。</string>
<string name="hand_camera_choice">カメラ</string>
<string name="hand_camera_access">カメラの権限</string>
<string name="hand_camera_access_hint">ハンドトラッキングを開始するには、カメラへのアクセスを許可してください。</string>
@@ -2655,4 +2655,208 @@
<string name="boot_movie_converting_started">%1$s を Steam 用に変換しています。DroidDeck を開いたままにしてください。</string>
<string name="boot_movie_unsupported">この端末ではその動画をデコードできません。</string>
<string name="boot_movie_import_failed">ムービーをインポートできませんでした</string>
<string name="setup_tab_glasses">スマートグラス</string>
<string name="setup_glasses_connect">スマートグラスを接続して、ディスプレイと追跡の設定を変更してください。接続を解除しても、保存された設定は保持されます。</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">表示モード</string>
<string name="glasses_output_2d">2D出力</string>
<string name="glasses_output_3d">3D出力</string>
<string name="glasses_picture_group">画像と仮想スクリーン</string>
<string name="glasses_position_group">位置と床面レベル</string>
<string name="glasses_hands_group">ハンドトラッキング - 実験的</string>
<string name="glasses_tracking_status">トラッキング状態</string>
<string name="glasses_resolution_2d">2D解像度</string>
<string name="glasses_resolution_3d">3D解像度</string>
<string name="glasses_resolution_vr">VR解像度</string>
<string name="glasses_resolution_auto">自動</string>
<string name="glasses_resolution_inherit">2D/3Dと同じ</string>
<string name="glasses_resolution_hint">片目あたりの出力解像度</string>
<string name="glasses_refresh_30_hint">スマートグラスでゲームを有効にすると、ディスプレイのネイティブ30Hzモードが選択されます。</string>
<string name="glasses_3dof_hint">平面ゲームを目の前に固定して、周囲を見渡せるようにします。3Dに切り替えずにスマートグラスでゲームを有効にします。</string>
<string name="glasses_quick_actions">クイックアクション</string>
<string name="glasses_recenter_screen_hint">画面を表示したい場所を見てから、トラッキングセンターをリセットして画面の高さと方向を設定します。</string>
<string name="phone_camera_tracking_group">スマートフォンカメラ - 実験的機能</string>
<string name="glasses_position">位置トラッキング - 実験的機能</string>
<string name="glasses_position_hint">VR内および仮想スクリーン周辺での身体の動き。トラッキングの再開には少々時間がかかります。</string>
<string name="glasses_eye_height">床面からの現在の目の高さ</string>
<string name="glasses_floor">現在の高さから床面レベルを設定</string>
<string name="glasses_floor_set">床面レベルを設定</string>
<string name="glasses_spatial_setup">ステレオカメラの設定と診断</string>
<string name="glasses_spatial_setup_hint">プレイ前にカメラのキャリブレーションと手のトラッキングを確認してください。</string>
<string name="glasses_spatial_open">セットアップを開く</string>
<string name="glasses_hands">素手で操作するコントローラー</string>
<string name="glasses_hands_calibration_verified">測定されたキャリブレーションが検証されました</string>
<string name="glasses_hands_calibration_required">検証が必要です</string>
<string name="glasses_hands_calibration_required_hint">測定距離チェックに合格すると、素手で操作できるコントローラーが使用可能になります。</string>
<string name="tracking_recovery_lost">トラッキングが失われました</string>
<string name="glasses_experimental">Luma Ultraカメラ - 実験的機能</string>
<string name="glasses_joycons">Joy-Conの位置追跡</string>
<string name="glasses_joycons_hint">スマートグラスのカメラを使って、ペアリングしたJoy-Conコントローラーの位置を調整します。回転とボタン入力はJoy-Conコントローラーから行われます。手首が隠れている状態では位置が推定されます。素手で操作するコントローラーの代わりに、スマートフォンのカメラによるトラッキングを一時停止します。接続時またはトラッキングセンターをリセットする際は、Joy-Conコントローラーを前方に向けてください。</string>
<string name="glasses_joycons_requires_position">まず位置追跡を有効にしてください。Android 12以降を搭載したモーション対応のJoy-Conコントローラーが必要です。デフォルトでは無効になっています。</string>
<string name="setup_glasses_scope">ディスプレイとヘッドトラッキングの設定は、DroidDeckのメニュー、ゲーム、デスクトップセッション間で共有されます。</string>
<string name="glasses_3dof">3DoF仮想スクリーン</string>
<string name="glasses_screen_app_wide">幅と距離を設定すると、DroidDeckのメニュー、ゲーム、デスクトップセッションのトラッキングされた仮想画面が即座に更新されます。</string>
<string name="glasses_head_tracking_spatial_vr_hint">位置トラッキングには、SteamVRでの頭部回転が含まれます。回転のみのトラッキングを行うには、位置トラッキングをオフにしてください。</string>
<string name="glasses_head_tracking_spatial_hint">仮想画面上の頭部の動きを制御します。位置トラッキングには、SteamVRでの頭部回転が含まれます。</string>
<string name="glasses_recenter_joycons_button">Joy-Conの中心をリセット</string>
<string name="phone_camera_tracking_setting">手やJoy-Conコントローラーのトラッキング</string>
<string name="glasses_6dof">6DoF仮想スクリーン</string>
<string name="glasses_hands_hint">スマートグラスのカメラを使用して、VR内で手で照準を合わせ、ピンチ操作でクリック、手を閉じて掴みます。ゲームパッドのスティックは移動および回転します。Joy-Conの位置トラッキングを置き換えます。ゲーム中にラグが発生したり、トラッキングが失われる場合があります。</string>
<string name="glasses_hands_ready_hint">トラッキングのLuma Ultraカメラで、素手で操作するコントローラーを有効にします。最初に開いた手を表示します。</string>
<string name="glasses_hands_base">Luma Ultraの共有キャリブレーション</string>
<string name="glasses_hands_base_hint">共有Luma Ultraベースを使用します。スマートグラスの精度を確認するために、ご自身のスマートグラスをキャリブレーションしてください。</string>
<string name="tracking_recovery_restart_help">まだ行き詰まっていますか?携帯電話で: スマートグラス → 追跡 → セットアップを開く → 診断を表示 → 追跡を再開します。</string>
<string name="tracking_recovery_disconnected">スマートグラスが切断されました</string>
<string name="tracking_recovery_connect">USB 接続を確認し、スマート グラスを再接続します。\nスマートフォンに USB アクセス プロンプトが表示されたら、これを受け入れます。</string>
<string name="tracking_recovery_usb_help">接続したら、明るい部屋でゆっくり周りを見回して追跡を復元します。</string>
<string name="tracking_recovery_recenter">中心のリセットが必要です</string>
<string name="tracking_recovery_recenter_steps">トラッキングが新しい位置で再開しました。\n正面を向いてから、スマートフォンの「トラッキングセンターのリセット」をタップしてください。</string>
<string name="tracking_recovery_recenter_help">スマートグラスの「トラッキング」→「セットアップを開く」からも「トラッキングセンターのリセット」が可能です。保存されたカメラキャリブレーションは保持されます。</string>
<string name="tracking_recovery_stabilizing">ちょっと静止してください</string>
<string name="tracking_recovery_hold">トラッキングセンターのリセット処理中です。\nトラッキングが安定するまで、スマートグラスを動かさないでください。</string>
<string name="tracking_recovery_automatic">ヘッドトラッキングの準備が完了すると、この画面は自動的に閉じます。</string>
<string name="glasses_steady_hand_aim">手による照準の安定化</string>
<string name="glasses_steady_hand_aim_hint">肩から手のひらを通って狙います。手首で回転を制御する場合はオフにします。</string>
<string name="spatial_title">空間設定</string>
<string name="spatial_wait_tracking">追跡を待っています…</string>
<string name="spatial_cameras_description">左右の追跡カメラ</string>
<string name="spatial_camera_sync">カメラ同期</string>
<string name="spatial_ready">準備完了</string>
<string name="spatial_sync_hint">カメラとヘッドトラッキングが揃うのを待っています。スマートグラスを接続したままにしてください。</string>
<string name="spatial_prepare">1. キャリブレーションターゲットを準備します</string>
<string name="spatial_print_actual">実寸で印刷する</string>
<string name="spatial_print_hint">各正方形の寸法は 30 mm (1.181 インチ) でなければなりません。明るい場所の硬い面にシートを平らに取り付けます。</string>
<string name="spatial_print">キャリブレーションボードを印刷</string>
<string name="spatial_capture_group">2. さまざまなビューをキャプチャする</string>
<string name="spatial_board_stationary">キャリブレーションターゲットを静止させます</string>
<string name="spatial_capture_hint">スマートグラスを近づけたり、遠ざけたり、横に動かしたりします。左右に回転させたり、上下に角度を付けたり、横に傾けたりします。キャリブレーション ターゲットを両方のカメラに保持し、各キャプチャの前に少し停止します。</string>
<string name="spatial_start">新しいキャリブレーションを開始する</string>
<string name="spatial_wait_sync">カメラの同期を待っています。</string>
<string name="spatial_start_hint">新しいキャリブレーションを開始してビューを収集します。</string>
<string name="spatial_views_hint">両方のプレビューでキャリブレーションターゲットを表示したままにします。さまざまな角度から少なくとも 24 の鮮明なビューを撮影します。</string>
<string name="spatial_capturing">キャプチャ中…</string>
<string name="spatial_capture">キャリブレーションターゲットビューのキャプチャ</string>
<string name="spatial_alignment">カメラアライメントを計算する</string>
<string name="spatial_calculating">計算中…</string>
<string name="spatial_calculate">キャリブレーションを計算する</string>
<string name="spatial_distance_group">3.距離を確認する</string>
<string name="spatial_distance">測定距離</string>
<string name="spatial_distance_hint">印刷されたマーカーが検出しやすいように、12 インチまたは 30 cm から始めます。使用する正確な測定値を選択してください。</string>
<string name="spatial_camera_board_distance">左カメラからキャリブレーションターゲット中心まで</string>
<string name="spatial_checking_distance">距離を確認中…</string>
<string name="spatial_validate">距離の検証</string>
<string name="spatial_diagnostics">診断</string>
<string name="spatial_show_diagnostics">診断を表示する</string>
<string name="spatial_diagnostics_hint">カメラのタイミングおよび追跡回復ツール。</string>
<string name="spatial_camera_timing">カメラのタイミング</string>
<string name="spatial_hand_input">手の追跡からの入力</string>
<string name="spatial_wait_timing">カメラのタイミングを待っています。</string>
<string name="spatial_frames">カメラ画像</string>
<string name="spatial_gpu">手のGPU検出</string>
<string name="spatial_gpu_hint">CPU がデフォルトです。 GPU は実験的なものであるため、SteamVR が再起動する可能性があります。この選択は、追跡が再開されるとリセットされます。</string>
<string name="spatial_lite">手モデルのライト検出</string>
<string name="spatial_lite_hint">Lite 手のひら検出器と手の Lite キー ポイントを使用します。照準、ピンチ、グラブの精度を確認します。トラッキングを再開するとリセットされます。</string>
<string name="spatial_adaptive">手の追跡のための自動入力周波数</string>
<string name="spatial_adaptive_hint">入力を処理速度に調整します。トラッキングを再開するとリセットされます。</string>
<string name="spatial_half_rate">手の追跡のための入力周波数を制限する</string>
<string name="spatial_half_rate_hint">カメラ画像の半分を使用します。最大周波数を比較する場合はオフにします。トラッキングを再開するとリセットされます。</string>
<string name="spatial_diagnostic_capture">診断キャプチャ</string>
<string name="spatial_diagnostic_capture_hint">1 つのステレオ画像ペアをローカルに保存します。画像は要求された場合にのみ保存されます。</string>
<string name="spatial_save_frame">カメラ画像の保存</string>
<string name="spatial_restart_tracking">追跡を再開する</string>
<string name="spatial_restart_hint">明るい場所でトラッキングが回復しない場合に使用します。再起動には新しい追跡起点が必要です。</string>
<string name="spatial_restart">再起動</string>
<string name="spatial_restart_title">追跡を再開しますか?</string>
<string name="spatial_restart_confirm">保存されていないキャリブレーションビューは失われます。その後、スマートグラスを動かさずに、トラッキングが復元されたらトラッキングセンターをリセットしてください。</string>
<string name="spatial_views_count">%1$d / 24 ビューがキャプチャされました</string>
<string name="spatial_capture_paused">キャプチャが一時停止されました: %1$s</string>
<string name="spatial_measure_hint">%1$s を測定します。両方のプレビューで完全なキャリブレーション ターゲットを表示し、静止したままにします。</string>
<string name="spatial_timing_values">%1$.1f Hz • 画像期間 %2$.1f ms • 処理時間 %3$.1f ms</string>
<string name="spatial_input_values">カメラ画像の入力制限 %1$.1f Hz • %2$.0f%%。ヘッドトラッキングは最大周波数で継続します。</string>
<string name="spatial_frame_counts">%1$d が処理されました • %2$d がドロップされました • %3$d は入力制限によりスキップされました</string>
<string name="spatial_print_title">DroidDeck - Luma Ultra キャリブレーション</string>
<string name="spatial_print_scale" formatted="false">100% / 実際のサイズで印刷します。各正方形: 30 mm (1.181 インチ)。</string>
<string name="spatial_print_stationary">キャリブレーションターゲットを平らで静止した状態に保ちます。キャプチャ間でスマート グラスを移動します。</string>
<string name="spatial_print_check">スケール チェック: 目盛間 100 mm (3.937 インチ)</string>
<string name="spatial_distance_12in">12 インチ (30.48 センチメートル)</string>
<string name="spatial_distance_30cm">30cm (11.81インチ)</string>
<string name="spatial_distance_20in">20 インチ (50.8 センチメートル)</string>
<string name="spatial_distance_50cm">50cm (19.69インチ)</string>
<string name="spatial_disabled">無効</string>
<string name="spatial_wait_stable">安定した追跡を待っています</string>
<string name="spatial_disconnected">切断されました</string>
<string name="spatial_hands_off">素手で操作するコントローラーはオフです</string>
<string name="spatial_tracking_off">位置追跡がオフになっているか、スマートグラスが接続されていません</string>
<string name="spatial_recenter_pending">トラッキングセンターのリセットが要求されました。トラッキングが安定するまで動かないでください。</string>
<string name="spatial_tracking_lost">追跡が失われました — 細部が見える明るい場所に面してください</string>
<string name="spatial_origin_changed">トラッキングの原点が変更されました。「トラッキングセンターのリセット」をタップしてから、動かないでください。</string>
<string name="spatial_active">6DoF アクティブ</string>
<string name="spatial_connect_hint">Luma Ultra を接続し、位置追跡を有効にします。</string>
<string name="spatial_validation_session">この接続のキャリブレーションが検証されました。</string>
<string name="spatial_validation_saved">キャリブレーションが検証され、保存されました。</string>
<string name="spatial_wait_cameras">ステレオカメラ待ち</string>
<string name="spatial_wait_hands">手の追跡を待っています</string>
<string name="spatial_collect_instructions">5 × 7 キャリブレーション ターゲットを静止したままにします。スマートグラスを動かしたり回転させたりしながら、24 以上のビューをキャプチャします。</string>
<string name="spatial_capture_pending">次のカメラ画像を待っています。スマートグラスを動かさないでください。</string>
<string name="spatial_validation_pending">次のカメラ画像を確認します。完全なキャリブレーションターゲットが見える状態を維持し、静止してください。</string>
<string name="spatial_start_first">まずキャリブレーションを開始します</string>
<string name="spatial_no_camera_frame">新しいカメラ画像が到着しませんでした。スマートグラスの接続とカメラのプレビューを確認してから、再試行してください。</string>
<string name="spatial_clock_reset">カメラクロックリセット</string>
<string name="spatial_frame_changed">トラッキング座標系が変更されました。ビューは再計算のために保存されます。トラッキングセンターをリセットしてから、新しいセットを開始して、さらに多くのビューをキャプチャしてください。</string>
<string name="spatial_device_loaded">デバイスキャリブレーションがロードされました</string>
<string name="spatial_base_loaded">Luma Ultra ベース キャリブレーションをロードしました</string>
<string name="spatial_calibration_needed">校正が必要です</string>
<string name="spatial_tap_start">まず「新しいキャリブレーションを開始」をタップします。</string>
<string name="spatial_capture_recenter_pending">トラッキングセンターのリセットは、安定したトラッキングを待っています。しばらく動かないでください。</string>
<string name="spatial_capture_recenter">トラッキングが変更されました。トラッキングセンターのリセットを一度タップし、静止してから撮影してください。</string>
<string name="spatial_capture_unsynchronized">キャプチャが拒否されました: ヘッド トラッキングが同期されていないか、キャプチャ中に変更されました。静止してもう一度試してください。</string>
<string name="spatial_align_first">距離を確認する前にカメラの位置を計算します。</string>
<string name="spatial_processing_failed">カメラ処理に失敗しました</string>
<string name="spatial_hand_head_changed">手の検出中にヘッドトラッキングが変更されました</string>
<string name="spatial_hand_wait_sync">カメラとヘッドの同期待ち</string>
<string name="spatial_no_wrists">手首は検出されませんでした。 Joy-Con コントローラーのハンドルをスマートグラスの前に置いてください</string>
<string name="spatial_no_hands">手が検出されませんでした。スマートグラスの前で開いた手のひらを見せる</string>
<string name="spatial_one_camera_wrists">手首は 1 台のカメラでのみ表示されます。コントローラーのハンドルを共有ビューに保持します</string>
<string name="spatial_one_camera_hands">手は 1 台のカメラでのみ表示されます。両方の手のひらを共有ビューに保ちます</string>
<string name="spatial_ambiguous_hands">両方のカメラで手は見えますが、左右の一致があいまいです</string>
<string name="spatial_inconsistent_depth">手は検出されましたが、ステレオの深度が一貫していません</string>
<string name="spatial_rejected_wrist">手首の位置がステレオまたはモーション チェックで拒否されました</string>
<string name="spatial_inconsistent_palm">ステレオ ポイントが検出されましたが、手のひらのジオメトリが一貫していません</string>
<string name="spatial_hands_need_calibration">手のトラッキングを有効にするには、キャリブレーションと距離チェックを完了してください</string>
<string name="spatial_hands_wait_stable">安定したヘッドトラッキングを待機中。必要に応じてトラッキングセンターをリセットします。</string>
<string name="spatial_old_frame">カメラによる観察は古すぎる。新しいペアを待っています</string>
<string name="spatial_fit_ready">%1$.2f ピクセルにフィットします。左側のカメラからキャリブレーション ターゲットの中心までの選択した距離を測定し、検証します。</string>
<string name="spatial_opencv_error">OpenCV は利用できません: %1$s</string>
<string name="spatial_restored">%1$d 個の保存されたビューが復元されました。キャリブレーションを計算して再試行するか、新しいセットを開始してさらにキャプチャします。</string>
<string name="spatial_captured">%1$d / 24 ビューをキャプチャしました。キャリブレーションターゲットを固定してください。次のビューのためにスマート グラスを移動します。</string>
<string name="spatial_capture_corners">キャプチャが拒否されました: %1$d / 12 個の必須コーナーが両方のカメラに表示されます。キャリブレーション ターゲットをさらに表示し、静止してからもう一度試してください。</string>
<string name="spatial_validation_error">距離チェックを完了できませんでした: %1$s。もう一度やり直してください。</string>
<string name="spatial_inference_error">手の検出が利用できません: %1$s。 CPU/GPUを変更するか、素手で操作するコントローラーを切り替えて再試行してください。</string>
<string name="spatial_hand_timeout">手の検出が %1$d ミリ秒を超えました。観察は破棄されました</string>
<string name="spatial_wrists_tracked">%1$d / 2 Joy-Con の手首の位置をステレオで測定</string>
<string name="spatial_palms_tracked">追跡中の手のひら: %1$d / 2、ピンチ可能: %2$d、つかむ操作が可能: %3$d</string>
<string name="spatial_capture_saved">診断キャプチャを %1$s に保存しました</string>
<string name="spatial_hand_error">停止した手の追跡: %1$s</string>
<string name="spatial_stereo_loaded">ステレオ %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">ヘッドトラッキングとキャリブレーションターゲットは、%1$s (制限 5 cm / 2 インチ) によって一致しません。新しいセットを開始する前に、キャリブレーションターゲットを固定したままにして、安定した追跡を待ちます。スマートグラスをゆっくり回転させたり傾けたりしてください。各キャプチャの前に一時停止します。</string>
<string name="spatial_need_views">静止した校正ターゲットの少なくとも 24 のさまざまなビューをキャプチャします。</string>
<string name="spatial_need_rotation">スマートグラスを左右に回し、上下に傾けます。回転を伴わない動きではカメラの位置を調整できません。</string>
<string name="spatial_bad_baseline">信じられないカメラのベースライン</string>
<string name="spatial_need_orientations">頭をより多様な方向に動かします。</string>
<string name="spatial_bad_rotation">カメラから頭への回転が無効です</string>
<string name="spatial_bad_alignment">カメラとヘッドの位置合わせが一貫していません。</string>
<string name="spatial_calibration_error">キャリブレーションを完了できませんでした。 %1$s キャプチャしたビューは保存されます。</string>
<string name="spatial_reprojection_error">保留された再投影エラー %1$.2f ピクセルが 2 ピクセルを超えています。再調整します。</string>
<string name="spatial_distance_corners">%1$d / 24 個のチェッカーボード コーナーが両方のカメラで検出されました。完全なキャリブレーションターゲットが見える状態を維持し、静止してください。すでに表示されている場合は、12 インチまたは 30 cm を選択し、再試行する前にさらに近い距離を測定します。</string>
<string name="spatial_distance_depth">校正ターゲットは目に見えますが、その深さを確実に測定することはできませんでした。スマート グラスを動かさずに保持し、キャリブレーション ターゲットを平らで明るい状態に保ってから、再試行してください。</string>
<string name="spatial_distance_invalid">カメラ測定が無効でした。静止してもう一度試してください。</string>
<string name="spatial_distance_passed">距離チェックに合格しました。</string>
<string name="spatial_distance_retry">左カメラからキャリブレーションターゲット中心までの距離を再確認し、再試行してください。</string>
<string name="spatial_distance_result">カメラは %1$s を測定しました。選択された距離 %2$s。 %3$s</string>
<string name="spatial_wait_previews">キャリブレーションを開始する前に、両方のカメラのプレビューを待ちます。</string>
<string name="spatial_saved_views_hint">保存されたビューは再計算できます。新しいキャリブレーションを開始して、現在のトラッキング座標系でキャプチャします。</string>
<string name="spatial_model_checksum_error">ハンドモデルのチェックサムの不一致</string>
<string name="spatial_opencv_load_error">OpenCVをロードできませんでした</string>
<string name="tracking_recovery_look">明るい部屋でゆっくりと周囲を見回してください。\nトラッキングカメラを遮るものがないように、家具などの見えるものに顔を向けてください。</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
</resources>
+214 -10
View File
@@ -2341,14 +2341,14 @@
<string name="glasses_display">디스플레이</string>
<string name="glasses_refresh">주사율</string>
<string name="glasses_3d">3D (사이드 바이 사이드)</string>
<string name="glasses_3d_hint">각 눈이 3840×1080 화면의 절반씩을 봅니다. 화면이 끊기면 안경의 2D/3D 버튼을 길게 누르세요.</string>
<string name="glasses_3d_hint">스마트 안경을 사이드 바이 사이드 3D 모드로 전환합니다. 헤드 추적은 별도로 제어됩니다.</string>
<string name="glasses_3d_failed">3D 신호를 확인하지 못했습니다. 2D 화면이 돌아오면 다시 시도하세요.</string>
<string name="glasses_no_signal">화면 없음: 안경이 이 휴대폰에서 보낼 수 없는 모드에 있습니다. 2D/3D 버튼을 길게 누르거나 안경을 뺐다가 다시 연결하세요.</string>
<string name="glasses_3d_refresh">3D 주사율</string>
<string name="glasses_auto3d">VR 게임에서 3D</string>
<string name="glasses_auto3d_hint">SteamVR이 실행되는 동안 3D로 전환하고 끝나면 되돌립니다.</string>
<string name="glasses_picture">3D 화면</string>
<string name="glasses_picture_hint">자동: SteamVR은 시야 전체를 채우고, 다른 게임은 3D에서는 화면으로 떠 있고 2D에서는 평면으로 보입니다.</string>
<string name="glasses_picture">화면</string>
<string name="glasses_picture_hint">자동 모드는 양쪽 눈에 평면 콘텐츠를 표시하고 SteamVR을 감지합니다. SBS 콘텐츠의 경우 \'Side-by-side 게임\'을 선택하세요.</string>
<string name="glasses_picture_auto">자동</string>
<string name="glasses_picture_screen">가상 화면</string>
<string name="glasses_picture_sbs">사이드 바이 사이드 게임</string>
@@ -2358,20 +2358,20 @@
<string name="glasses_screen_no_output">이 설정은 “안경에 게임 표시”가 켜져 있고 게임이 안경에 표시될 때 적용됩니다.</string>
<string name="glasses_screen_flat_hint">“자동”에서는 이 2D 게임이 전체 화면으로 유지됩니다. 너비, 거리, 헤드 트래킹을 조정하려면 가상 화면을 사용하세요.</string>
<string name="glasses_screen_use">가상 화면 사용</string>
<string name="glasses_screen_direct_hint">SteamVR이 시야를 가득 채웁니다. 아래에서 시야각을 조정하세요. 너비와 거리는 가상 화면에만 적용됩니다.</string>
<string name="glasses_screen_direct_hint">SteamVR은 화면을 가득 채웁니다. VR 환경에서 시야각을 조정하세요. 너비와 거리는 가상 화면에만 적용됩니다.</string>
<string name="glasses_screen_sbs_hint">사이드 바이 사이드 게임은 양쪽 눈의 시야를 각각 가득 채웁니다. 너비와 거리는 가상 화면에만 적용됩니다.</string>
<string name="glasses_tracking_flat_hint">“자동”에서는 이 2D 게임이 시야에 고정됩니다. 헤드 트래킹을 사용하려면 “디스플레이”에서 “가상 화면”을 선택하세요.</string>
<string name="glasses_tracking_sbs_hint">헤드 트래킹은 가상 화면이나 실행 중인 SteamVR 게임에 적용됩니다.</string>
<string name="glasses_hardware">안경</string>
<string name="glasses_hardware">장치</string>
<string name="glasses_brightness">밝기</string>
<string name="glasses_volume">볼륨</string>
<string name="glasses_sunblock">선블록</string>
<string name="glasses_sunblock_hint">앞쪽 렌즈를 어둡게 합니다.</string>
<string name="glasses_tracking">헤드 트래킹</string>
<string name="glasses_tracking">추적</string>
<string name="glasses_head_tracking">헤드 트래킹</string>
<string name="glasses_head_tracking_hint">안경의 센서가 머리 움직임에 맞춰 가상 화면과 SteamVR을 돌립니다.</string>
<string name="glasses_recenter">시점 재설정</string>
<string name="glasses_recenter_button">중앙 맞추기</string>
<string name="glasses_recenter_button">화면 중심 재설정</string>
<string name="glasses_vr_fov">SteamVR 시야각</string>
<string name="glasses_vr_fov_hint">낮추면 SteamVR이 가까워져 안경을 가득 채우고, 높이면 주변이 더 넓게 보입니다.</string>
<string name="glasses_playing">%1$s에서 플레이 중</string>
@@ -2392,8 +2392,8 @@
<string name="glasses_calibration_reset_hint">저장된 보정을 삭제하고 모델 기본값으로 되돌릴까요? 안경을 다시 쓴 후 중앙을 맞추세요.</string>
<string name="glasses_calibration_reset_button">초기화</string>
<string name="hand_camera_group">핸드 트래킹</string>
<string name="hand_camera_prompt_title">카메라로 손을 트래킹할까요?</string>
<string name="hand_camera_prompt_body">카메라가 손을 볼 수 있습니다. 컨트롤러가 연결되어 있지 않으면 맨손이 그대로 VR 손이 됩니다. 엄지와 검지를 맞대면 클릭, 손을 쥐면 잡기, 손바닥을 자신 쪽으로 향하고 맞대면 메뉴가 열립니다. Joy-Con을 쓸 때는 손이 실제로 있는 위치에 배치합니다. 영상은 이 기기에서 처리되며 저장되거나 전송되지 않습니다.</string>
<string name="hand_camera_prompt_title">스마트폰 카메라로 손 또는 Joy-Con 컨트롤러 추적?</string>
<string name="hand_camera_prompt_body">컨트롤러가 연결되지 않은 경우, 스마트폰 카메라가 VR 환경에서 맨손을 추적합니다. 엄지와 검지를 오므려 클릭, 손을 오므려 잡기, 손바닥을 자신 쪽으로 돌리고 오므려 메뉴를 엽니다. Joy-Con 컨트롤러가 연결된 경우, 카메라가 컨트롤러의 위치를 ​​추적하는 동안 Joy-Con 컨트롤러는 회전 및 버튼 입력을 제공합니다. 사진은 이 기기에서 처리되며 저장되거나 전송되지 않습니다.</string>
<string name="hand_camera_rear">후면 카메라</string>
<string name="hand_camera_rear_hint">권장합니다. 보통 더 선명하고 화각이 넓습니다.</string>
<string name="hand_camera_front">전면 카메라</string>
@@ -2401,7 +2401,7 @@
<string name="hand_camera_allow">카메라 사용</string>
<string name="hand_camera_not_now">나중에</string>
<string name="hand_camera_setting">카메라 핸드 트래킹</string>
<string name="hand_camera_setting_hint">컨트롤러가 연결되어 있지 않으면 손이 VR 손이 됩니다. 손가락을 맞대면 클릭, 손바닥을 자신 쪽으로 향하고 맞대면 메뉴입니다. Joy-Con은 카메라가 보는 위치에 배치합니다. 영상은 이 기기 밖으로 나가지 않습니다.</string>
<string name="hand_camera_setting_hint">휴대폰 카메라를 사용하여 맨손 제스처를 사용하거나 VR에서 페어링된 Joy-Con 컨트롤러의 위치를 ​​조정합니다. Joy-Con 컨트롤러는 회전 및 버튼 입력을 제공합니다. 스마트 안경이 손이나 Joy-Con 컨트롤러를 추적하는 동안 일시 중지됩니다. 이미지는 이 기기에서만 처리됩니다.</string>
<string name="hand_camera_choice">카메라</string>
<string name="hand_camera_access">카메라 권한</string>
<string name="hand_camera_access_hint">핸드 트래킹을 시작하려면 카메라 권한을 허용하세요.</string>
@@ -2655,4 +2655,208 @@
<string name="boot_movie_converting_started">Steam용으로 %1$s 변환 중입니다. DroidDeck을 열어 두세요.</string>
<string name="boot_movie_unsupported">이 기기에서 해당 동영상을 디코딩할 수 없습니다.</string>
<string name="boot_movie_import_failed">동영상을 가져오지 못했습니다</string>
<string name="glasses_experimental">Luma Ultra 카메라 — 실험적 기능</string>
<string name="glasses_joycons">Joy-Con 위치 추적</string>
<string name="glasses_joycons_hint">스마트 안경 카메라를 사용하여 페어링된 Joy-Con 컨트롤러의 위치를 ​​조정합니다. 회전 및 버튼 입력은 Joy-Con 컨트롤러에서 이루어집니다. 손목이 가려진 경우 위치가 추정됩니다. 맨손으로 조작하는 컨트롤러를 대체하며, 휴대폰 카메라 추적이 일시 중지됩니다. 추적 센터를 연결하거나 재설정할 때는 Joy-Con 컨트롤러를 앞쪽으로 향하게 하십시오.</string>
<string name="glasses_joycons_requires_position">위치 추적을 먼저 활성화해야 합니다. Android 12 이상에서 모션 기능을 지원하는 Joy-Con 컨트롤러가 필요합니다. 기본적으로 비활성화되어 있습니다.</string>
<string name="setup_tab_glasses">스마트 안경</string>
<string name="setup_glasses_scope">디스플레이 및 헤드 트래킹 설정은 DroidDeck 메뉴, 게임 및 데스크톱 세션에서 공유됩니다.</string>
<string name="setup_glasses_connect">스마트 안경을 연결하여 디스플레이 및 추적 설정을 변경하십시오. 연결을 해제해도 저장된 설정이 유지됩니다.</string>
<string name="glasses_vr">VR</string>
<string name="glasses_display_mode">표시 모드</string>
<string name="glasses_output_2d">2D 출력</string>
<string name="glasses_output_3d">3D 출력</string>
<string name="glasses_picture_group">화면 및 가상 화면</string>
<string name="glasses_position_group">위치 및 층수</string>
<string name="glasses_hands_group">손 추적 - 실험적 기능</string>
<string name="glasses_tracking_status">추적 상태</string>
<string name="glasses_resolution_2d">2D 해상도</string>
<string name="glasses_resolution_3d">3D 해상도</string>
<string name="glasses_resolution_vr">VR 해상도</string>
<string name="glasses_resolution_auto">자동</string>
<string name="glasses_resolution_inherit">2D/3D 동일</string>
<string name="glasses_resolution_hint">눈별 출력 해상도</string>
<string name="glasses_refresh_30_hint">스마트 안경에서 게임 기능을 활성화하여 디스플레이의 기본 30Hz 모드를 선택합니다.</string>
<string name="glasses_3dof">3DoF 가상 화면</string>
<string name="glasses_3dof_hint">플랫 게임을 정면에 고정하여 주변을 둘러볼 수 있습니다. 3D 모드로 전환하지 않고도 스마트 안경에서 게임 기능을 활성화할 수 있습니다.</string>
<string name="glasses_screen_app_wide">너비와 거리는 DroidDeck 메뉴, 게임 및 데스크톱 세션에 대해 추적된 가상 화면을 즉시 업데이트합니다.</string>
<string name="glasses_head_tracking_spatial_vr_hint">위치 추적은 SteamVR에서 머리 회전을 포함합니다. 회전만 추적하려면 위치 추적을 끄세요.</string>
<string name="glasses_head_tracking_spatial_hint">가상 화면에서 머리 움직임을 제어합니다. 위치 추적은 SteamVR에서 머리 회전을 포함합니다.</string>
<string name="glasses_quick_actions">빠른 실행</string>
<string name="glasses_recenter_screen_hint">화면을 표시할 위치를 바라본 다음 추적 센터를 재설정하여 높이와 방향을 설정합니다.</string>
<string name="glasses_recenter_joycons_button">Joy-Con 중심 재설정</string>
<string name="phone_camera_tracking_group">스마트폰 카메라 - 실험적 기능</string>
<string name="phone_camera_tracking_setting">손 또는 Joy-Con 컨트롤러 추적</string>
<string name="glasses_position">위치 추적 - 실험적 기능</string>
<string name="glasses_position_hint">VR 및 가상 화면 주변에서의 물리적 움직임. 추적을 다시 시작하면 잠시 시간이 걸립니다.</string>
<string name="glasses_eye_height">현재 눈높이(바닥 위)</string>
<string name="glasses_floor">현재 높이에서 바닥 높이 설정</string>
<string name="glasses_floor_set">바닥 높이 설정</string>
<string name="glasses_spatial_setup">스테레오 카메라 설정 및 진단</string>
<string name="glasses_spatial_setup_hint">게임 시작 전 카메라 보정 및 손 추적을 확인하세요.</string>
<string name="glasses_spatial_open">설정 열기</string>
<string name="glasses_hands">맨손으로 조작하는 컨트롤러</string>
<string name="glasses_hands_hint">스마트 안경 카메라를 사용하여 VR에서 손으로 조준하고, 엄지와 검지를 오므려 클릭하고, 손을 오므려 잡으세요. 게임패드 스틱으로 이동하고 방향을 바꿀 수 있습니다. Joy-Con 위치 추적을 대체합니다. 게임 중 지연이 발생하거나 추적이 끊길 수 있습니다.</string>
<string name="glasses_hands_ready_hint">추적 설정에서 Luma Ultra 카메라 아래에 맨손으로 조작하는 컨트롤러를 활성화합니다. 먼저 손을 펼친 상태를 표시합니다.</string>
<string name="glasses_hands_base">Luma Ultra 공유 보정</string>
<string name="glasses_hands_base_hint">Luma Ultra 공유 베이스 사용 스마트 안경의 정확도를 확인하려면 직접 보정하십시오.</string>
<string name="glasses_hands_calibration_verified">측정된 보정 확인됨</string>
<string name="glasses_hands_calibration_required">검증 필요</string>
<string name="glasses_hands_calibration_required_hint">측정 거리 확인이 완료되면 맨손으로 조작하는 컨트롤러를 사용할 수 있습니다.</string>
<string name="tracking_recovery_lost">추적 실패</string>
<string name="tracking_recovery_disconnected">스마트 안경 연결이 끊어졌습니다.</string>
<string name="spatial_capturing">캡처하는 중…</string>
<string name="spatial_camera_board_distance">왼쪽 카메라에서 보정 대상 중심까지의 거리</string>
<string name="spatial_checking_distance">거리 확인 중…</string>
<string name="spatial_validate">거리 확인</string>
<string name="spatial_diagnostics">진단</string>
<string name="spatial_camera_timing">카메라 타이밍</string>
<string name="spatial_frames">카메라 이미지</string>
<string name="spatial_lite">Lite 손 추적 모델</string>
<string name="spatial_lite_hint">Lite 손바닥 감지 모델과 Lite 손 랜드마크 모델을 사용합니다. 조준, 집기 및 잡기 동작의 정확도를 확인하세요. 추적을 다시 시작하면 초기화됩니다.</string>
<string name="spatial_adaptive">손 추적을 위한 자동 입력 주파수</string>
<string name="spatial_adaptive_hint">입력을 처리 속도로 조정합니다. 추적이 다시 시작되면 재설정됩니다.</string>
<string name="spatial_save_frame">카메라 이미지 저장</string>
<string name="spatial_restart_tracking">추적 다시 시작</string>
<string name="spatial_restart_hint">조명이 밝은 곳에서 추적이 복구되지 않는 경우 사용합니다. 다시 시작하려면 새로운 추적 원본이 필요합니다.</string>
<string name="spatial_restart">재시작</string>
<string name="spatial_restart_title">추적을 다시 시작하시겠습니까?</string>
<string name="spatial_restart_confirm">저장하지 않은 보정 이미지가 삭제됩니다. 다시 시작한 뒤에는 안경을 움직이지 말고, 추적이 복구되면 중심을 재설정하세요.</string>
<string name="spatial_views_count">%1$d / 24개 보정 이미지 촬영됨</string>
<string name="spatial_timing_values">%1$.1fHz • 이미지 수명 %2$.1fms • 처리 중 %3$.1fms</string>
<string name="spatial_input_values">카메라 이미지의 입력 제한 %1$.1fHz • %2$.0f%%. 머리 추적은 최대 빈도로 계속됩니다.</string>
<string name="spatial_frame_counts">%1$d 처리됨 • %2$d 삭제됨 • 입력 제한으로 인해 %3$d 건너뛰음</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_print_title">DroidDeck - Luma Ultra 보정</string>
<string name="spatial_print_check">눈금 확인: 눈금 간 100mm(3.937인치)</string>
<string name="spatial_distance_12in">30.48cm(12인치)</string>
<string name="spatial_distance_30cm">30cm(11.81인치)</string>
<string name="spatial_distance_20in">50.8cm(20인치)</string>
<string name="spatial_distance_50cm">50cm(19.69인치)</string>
<string name="spatial_disabled">비활성화됨</string>
<string name="spatial_wait_stable">안정적인 추적을 기다리는 중</string>
<string name="spatial_disconnected">연결 끊김</string>
<string name="spatial_hands_off">맨손으로 조작하는 컨트롤러는 꺼짐</string>
<string name="spatial_tracking_off">위치 추적이 꺼져 있거나 스마트 안경이 연결되지 않은 경우</string>
<string name="spatial_recenter_pending">추적 중심 재설정 요청됨 - 추적이 안정될 때까지 움직이지 마세요.</string>
<string name="spatial_tracking_lost">추적 손실 - 눈에 보이는 세부 사항이 있는 밝은 조명 영역을 향합니다.</string>
<string name="spatial_origin_changed">추적 원점 변경됨 - 추적 중심 재설정을 탭한 다음 움직이지 마세요.</string>
<string name="spatial_validation_session">이 연결에 대한 교정이 확인되었습니다.</string>
<string name="spatial_validation_saved">교정이 확인되고 저장되었습니다.</string>
<string name="spatial_wait_cameras">스테레오 카메라를 기다리는 중</string>
<string name="spatial_clock_reset">카메라 시계 재설정</string>
<string name="spatial_frame_changed">추적 좌표계가 변경되었습니다. 뷰가 재계산을 위해 저장됩니다. 추적 중심을 재설정한 다음 새 세트를 시작하여 더 많은 뷰를 캡처하세요.</string>
<string name="spatial_device_loaded">장치 교정 로드됨</string>
<string name="spatial_base_loaded">Luma Ultra 기본 보정 로드됨</string>
<string name="spatial_calibration_needed">교정이 필요함</string>
<string name="spatial_capture_recenter">추적이 변경되었습니다. 캡처하기 전에 추적 중심 재설정을 한 번 탭한 다음 움직이지 마세요.</string>
<string name="spatial_processing_failed">카메라 처리 실패</string>
<string name="spatial_hand_head_changed">손 감지 시 헤드 트래킹이 변경됨</string>
<string name="spatial_ambiguous_hands">두 카메라 모두 손은 보이나 좌우 매칭이 모호함</string>
<string name="spatial_inconsistent_depth">손이 감지되었으나 스테레오 깊이가 일치하지 않습니다.</string>
<string name="spatial_rejected_wrist">스테레오 또는 모션 검사에 의해 손목 위치가 거부됨</string>
<string name="spatial_inconsistent_palm">스테레오 포인트가 감지되었지만 손바닥 형상이 일치하지 않습니다.</string>
<string name="spatial_fit_ready">%1$.2f픽셀에 맞춥니다. 왼쪽 카메라에서 보정 대상 중심까지 선택한 거리를 측정한 다음 확인합니다.</string>
<string name="spatial_opencv_error">OpenCV를 사용할 수 없습니다: %1$s</string>
<string name="spatial_restored">%1$d 저장된 뷰가 복원되었습니다. 다시 시도하려면 보정을 계산하고, 더 많은 것을 캡처하려면 새 세트를 시작하세요.</string>
<string name="spatial_captured">캡쳐 %1$d / 조회수 24입니다. 교정 대상을 고정된 상태로 유지하십시오. 다음 보기를 위해 스마트 안경을 이동하세요.</string>
<string name="spatial_capture_corners">캡처 거부됨: %1$d / 두 카메라 모두에 12개의 필수 모서리가 표시됩니다. 보정 대상을 더 많이 표시하고 가만히 있다가 다시 시도하세요.</string>
<string name="spatial_validation_error">거리 확인을 완료할 수 없습니다: %1$s. 다시 시도해 보세요.</string>
<string name="spatial_inference_error">손 감지 불가: %1$s. CPU/GPU를 변경하거나 맨손으로 조작하는 컨트롤러를 전환하여 다시 시도하세요.</string>
<string name="spatial_hand_timeout">손 감지가 %1$dms를 초과했습니다. 관찰이 폐기됨</string>
<string name="spatial_wrists_tracked">%1$d / 스테레오로 측정한 Joy-Con 손목 위치 2개</string>
<string name="spatial_palms_tracked">추적 중인 손바닥: %1$d / 2; 집기 준비: %2$d; 잡기 준비: %3$d</string>
<string name="spatial_capture_saved">진단 캡처를 %1$s에 저장했습니다.</string>
<string name="spatial_bad_rotation">잘못된 카메라 대 머리 회전</string>
<string name="spatial_calibration_error">교정을 완료할 수 없습니다. %1$s 캡처한 보기가 저장되었습니다.</string>
<string name="spatial_reprojection_error">보류 재투영 오류 %1$.2f px가 2px를 초과합니다. 재보정하세요.</string>
<string name="spatial_distance_invalid">카메라 측정이 잘못되었습니다. 가만히 있다가 다시 시도해 보세요.</string>
<string name="spatial_distance_passed">거리 확인이 통과되었습니다.</string>
<string name="spatial_model_checksum_error">손 모델 체크섬 불일치</string>
<string name="spatial_opencv_load_error">OpenCV를 로드할 수 없습니다.</string>
<string name="glasses_6dof">6DoF 가상 화면</string>
<string name="tracking_recovery_look">조명이 밝은 방에서 천천히 주변을 둘러보세요.\n추적 카메라를 가리지 말고 가구처럼 특징이 뚜렷한 곳을 바라보세요.</string>
<string name="tracking_recovery_restart_help">아직도 막혔나요? 휴대폰에서: 스마트 안경 → 추적 → 설정 열기 → 진단 표시 → 추적 다시 시작.</string>
<string name="tracking_recovery_connect">USB 연결을 확인하고 스마트 안경을 다시 연결하세요.\n휴대전화에 USB 액세스 메시지가 나타나면 수락하세요.</string>
<string name="tracking_recovery_usb_help">일단 연결되면, 조명이 밝은 방에서 천천히 주위를 둘러보며 추적을 복원하십시오.</string>
<string name="tracking_recovery_recenter">중심 재설정 필요</string>
<string name="tracking_recovery_recenter_steps">새로운 위치에서 추적이 재개되었습니다.\n앞을 바라본 다음 휴대폰에서 추적 센터 재설정을 탭하세요.</string>
<string name="tracking_recovery_recenter_help">스마트 안경 → 추적 → 설정 열기에서 추적 중심 재설정을 찾을 수도 있습니다. 저장된 카메라 보정값이 유지됩니다.</string>
<string name="tracking_recovery_stabilizing">잠시만 가만히 있어주세요</string>
<string name="tracking_recovery_hold">추적 중심 재설정이 진행 중입니다.\n추적이 안정될 때까지 스마트 안경을 움직이지 마세요.</string>
<string name="tracking_recovery_automatic">이 화면은 머리 추적이 준비되면 자동으로 닫힙니다.</string>
<string name="glasses_steady_hand_aim">손으로 안정화된 조준</string>
<string name="glasses_steady_hand_aim_hint">어깨부터 손바닥까지 조준하세요. 손목으로 제어되는 회전을 사용하려면 끄세요.</string>
<string name="spatial_title">공간 설정</string>
<string name="spatial_wait_tracking">추적을 기다리는 중…</string>
<string name="spatial_cameras_description">왼쪽 및 오른쪽 추적 카메라</string>
<string name="spatial_camera_sync">카메라 동기화</string>
<string name="spatial_ready">준비</string>
<string name="spatial_sync_hint">카메라와 머리 추적이 정렬되기를 기다리는 중입니다. 스마트 안경을 연결된 상태로 유지하세요.</string>
<string name="spatial_prepare">1. 교정 타겟 준비</string>
<string name="spatial_print_actual">실제 크기로 인쇄</string>
<string name="spatial_print_hint">각 정사각형의 크기는 30mm(1.181인치)여야 합니다. 조명이 밝은 곳의 단단한 표면에 시트를 평평하게 장착합니다.</string>
<string name="spatial_print">보정 패턴 인쇄</string>
<string name="spatial_capture_group">2. 다양한 뷰 캡처</string>
<string name="spatial_board_stationary">교정 타겟을 고정된 상태로 유지하세요.</string>
<string name="spatial_capture_hint">스마트 안경을 더 가까이, 더 멀리, 옆으로 움직여 보세요. 왼쪽/오른쪽으로 돌리고, 위쪽/아래쪽 각도를 맞추고, 옆쪽으로 기울입니다. 두 카메라 모두에서 보정 대상을 유지하고 각 캡처 전에 잠시 일시 중지합니다.</string>
<string name="spatial_start">새로운 교정 시작</string>
<string name="spatial_wait_sync">카메라 동기화를 기다리는 중입니다.</string>
<string name="spatial_start_hint">뷰를 수집하기 위해 새로운 교정을 시작합니다.</string>
<string name="spatial_views_hint">두 미리보기 모두에서 교정 타겟이 표시되도록 유지합니다. 다양한 각도에서 최소 24개의 선명한 뷰를 캡처하세요.</string>
<string name="spatial_capture">캡쳐 캘리브레이션 타겟 화면</string>
<string name="spatial_alignment">카메라 정렬 계산</string>
<string name="spatial_calculating">계산 중…</string>
<string name="spatial_calculate">교정 계산</string>
<string name="spatial_distance_group">3. 거리 확인</string>
<string name="spatial_distance">측정된 거리</string>
<string name="spatial_distance_hint">인쇄된 마커를 더 쉽게 감지할 수 있도록 12인치 또는 30cm로 시작하세요. 사용할 정확한 측정값을 선택하세요.</string>
<string name="spatial_show_diagnostics">진단 표시</string>
<string name="spatial_diagnostics_hint">카메라 타이밍 및 추적 복구 도구.</string>
<string name="spatial_hand_input">손 추적을 통한 입력</string>
<string name="spatial_wait_timing">카메라 타이밍을 기다리는 중입니다.</string>
<string name="spatial_gpu">손의 GPU 감지</string>
<string name="spatial_gpu_hint">CPU가 기본값이다. GPU는 실험적이므로 SteamVR이 다시 시작될 수 있습니다. 이 선택은 추적이 다시 시작되면 재설정됩니다.</string>
<string name="spatial_half_rate">손 추적을 위한 입력 빈도 제한</string>
<string name="spatial_half_rate_hint">카메라 이미지의 절반을 사용합니다. 최대 주파수를 비교하려면 끄십시오. 추적이 다시 시작되면 재설정됩니다.</string>
<string name="spatial_diagnostic_capture">진단 캡처</string>
<string name="spatial_diagnostic_capture_hint">하나의 스테레오 이미지 쌍을 로컬에 저장합니다. 이미지는 요청한 경우에만 저장됩니다.</string>
<string name="spatial_capture_paused">캡처가 일시 중지되었습니다: %1$s</string>
<string name="spatial_measure_hint">%1$s을 측정한다. 두 미리보기 모두에 전체 보정 대상을 표시한 다음 가만히 기다리세요.</string>
<string name="spatial_print_scale" formatted="false">100% / 실제 크기로 인쇄합니다. 각 정사각형: 30mm(1.181인치).</string>
<string name="spatial_print_stationary">교정 대상을 평평하고 고정된 상태로 유지하십시오. 캡처 사이에 스마트 안경을 이동합니다.</string>
<string name="spatial_active">6DoF 활성</string>
<string name="spatial_connect_hint">Luma Ultra를 연결하고 위치 추적을 활성화합니다.</string>
<string name="spatial_wait_hands">손 추적을 기다리는 중</string>
<string name="spatial_collect_instructions">5 × 7 교정 대상을 고정된 상태로 유지하십시오. 스마트 안경을 이동하고 회전하면서 24개 이상의 뷰를 캡처하세요.</string>
<string name="spatial_capture_pending">다음 카메라 영상을 기다리는 중입니다. 스마트 안경을 가만히 잡으세요.</string>
<string name="spatial_validation_pending">다음 카메라 영상을 확인하는 중입니다. 전체 교정 대상이 보이도록 유지하고 가만히 기다리십시오.</string>
<string name="spatial_start_first">먼저 캘리브레이션을 시작하세요</string>
<string name="spatial_no_camera_frame">새로운 카메라 이미지가 도착하지 않았습니다. 스마트 안경 연결 및 카메라 미리보기를 확인한 후 다시 시도하세요.</string>
<string name="spatial_tap_start">먼저 새 보정 시작을 탭합니다.</string>
<string name="spatial_capture_recenter_pending">추적 중심 재설정이 안정적인 추적을 기다리고 있습니다. 잠시 움직이지 마세요.</string>
<string name="spatial_capture_unsynchronized">캡처 거부됨: 머리 추적이 동기화되지 않았거나 캡처 중에 변경되었습니다. 움직이지 말고 다시 시도하세요.</string>
<string name="spatial_align_first">거리를 확인하기 전에 카메라 정렬을 계산하십시오.</string>
<string name="spatial_hand_wait_sync">카메라와 헤드의 동기화를 기다리는 중</string>
<string name="spatial_no_wrists">손목이 감지되지 않았습니다. Joy-Con 컨트롤러 핸들을 스마트 안경 앞에 두십시오.</string>
<string name="spatial_no_hands">손이 감지되지 않습니다. 스마트 안경 앞에 손바닥을 펼쳐 보여주세요.</string>
<string name="spatial_one_camera_wrists">하나의 카메라에서만 손목이 보입니다. 컨트롤러 핸들을 공유 보기에 유지하세요</string>
<string name="spatial_one_camera_hands">하나의 카메라에서만 손이 보입니다. 두 손바닥을 공유 보기에 유지</string>
<string name="spatial_hands_need_calibration">손이 활성화되도록 보정 및 거리 확인을 완료합니다.</string>
<string name="spatial_hands_wait_stable">안정적인 헤드 추적을 기다리는 중; 요청 시 추적 중심을 재설정하세요.</string>
<string name="spatial_old_frame">카메라 관찰이 너무 오래되었습니다. 새로운 한 쌍을 기다리고 있어요</string>
<string name="spatial_hand_error">손 추적이 중지됨: %1$s</string>
<string name="spatial_stereo_loaded">스테레오 %1$d×%2$d; %3$s</string>
<string name="spatial_alignment_error">머리 추적과 보정 대상이 %1$s(5cm/2인치 제한)만큼 일치하지 않습니다. 교정 대상을 고정된 상태로 유지하고 새 세트를 시작하기 전에 안정적인 추적을 기다립니다. 스마트 안경을 부드럽게 돌리고 기울이세요. 각 캡처 전에 일시 중지합니다.</string>
<string name="spatial_need_views">고정된 교정 대상에 대해 최소 24개의 다양한 뷰를 캡처합니다.</string>
<string name="spatial_need_rotation">스마트 안경을 좌우로 돌리고, 상하로 기울이는 단계; 회전 없는 움직임은 카메라 정렬을 교정할 수 없습니다.</string>
<string name="spatial_bad_baseline">믿기지 않는 카메라 기준선</string>
<string name="spatial_need_orientations">좀 더 다양한 방향으로 머리를 움직여 보세요.</string>
<string name="spatial_bad_alignment">카메라와 머리의 정렬이 일관되지 않습니다.</string>
<string name="spatial_distance_corners">%1$d / 두 카메라 모두에서 24개의 체커보드 모서리가 감지되었습니다. 전체 교정 대상이 보이도록 유지하고 가만히 기다리십시오. 이미 보이는 경우 12인치 또는 30cm를 선택하고 다시 시도하기 전에 더 가까운 거리를 측정합니다.</string>
<string name="spatial_distance_depth">교정대상은 보이지만 깊이를 확실하게 측정할 수 없었다. 스마트 안경을 가만히 잡고 보정 대상을 평평하게 유지하고 조명을 밝게 유지한 후 다시 시도하세요.</string>
<string name="spatial_distance_retry">왼쪽 카메라에서 캘리브레이션 대상 중심까지의 거리를 다시 확인하고 재시도하세요.</string>
<string name="spatial_distance_result">카메라 측정 %1$s; 선택한 거리 %2$s. %3$s</string>
<string name="spatial_wait_previews">보정을 시작하기 전에 두 카메라 미리보기를 모두 기다리세요.</string>
<string name="spatial_saved_views_hint">저장된 뷰는 다시 계산될 수 있습니다. 현재 추적 좌표계에서 캡처하려면 새 보정을 시작하세요.</string>
</resources>
Loaded 100 of 213 files, more files were not shown because too many files have changed in this diff. Show more