mirror of
https://github.com/SlimeVR/SlimeVR-Server.git
synced 2026-04-06 02:01:58 +02:00
Improvements on the Autobone GUI (#776)
Co-authored-by: Butterscotch! <bscotchvanilla@gmail.com>
This commit is contained in:
@@ -29,10 +29,11 @@
|
||||
"react-modal": "3.15.1",
|
||||
"react-responsive": "^9.0.2",
|
||||
"react-router-dom": "^6.2.2",
|
||||
"semver": "^7.5.0",
|
||||
"semver": "^7.5.3",
|
||||
"solarxr-protocol": "file:../solarxr-protocol",
|
||||
"three": "^0.148.0",
|
||||
"typescript": "^4.6.3"
|
||||
"ts-pattern": "^5.0.1",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "vite --force",
|
||||
@@ -64,30 +65,30 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/forms": "^0.5.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tauri-apps/cli": "^1.4.0",
|
||||
"@types/file-saver": "^2.0.5",
|
||||
"@types/react": "18.0.25",
|
||||
"@types/react-dom": "^18.0.5",
|
||||
"@types/react-modal": "3.13.1",
|
||||
"@types/three": "^0.148.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.6",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
||||
"@typescript-eslint/parser": "^5.60.1",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint": "^8.18.0",
|
||||
"eslint": "^8.44.0",
|
||||
"eslint-config-airbnb": "^19.0.4",
|
||||
"eslint-import-resolver-typescript": "^3.1.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-plugin-jsx-a11y": "^6.6.0",
|
||||
"eslint-plugin-react": "^7.30.1",
|
||||
"eslint-import-resolver-typescript": "^3.5.5",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsx-a11y": "^6.7.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"postcss": "^8.4.12",
|
||||
"prettier": "^2.7.1",
|
||||
"postcss": "^8.4.24",
|
||||
"prettier": "^2.8.8",
|
||||
"pretty-quick": "^3.1.3",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"tailwind-gradient-mask-image": "^1.0.0",
|
||||
"tailwindcss": "^3.3.1",
|
||||
"vite": "^4.0.3"
|
||||
"tailwindcss": "^3.3.2",
|
||||
"vite": "^4.3.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -730,7 +730,10 @@ onboarding-choose_proportions-description = Body proportions are used to know th
|
||||
onboarding-choose_proportions-auto_proportions = Automatic proportions
|
||||
# Italized text
|
||||
onboarding-choose_proportions-auto_proportions-subtitle = Recommended
|
||||
onboarding-choose_proportions-auto_proportions-description = This will guess your proportions by recording a sample of your movements and passing it through an algorithm
|
||||
onboarding-choose_proportions-auto_proportions-descriptionv2 =
|
||||
This will guess your proportions by recording a sample of your movements and passing it through an algorithm.
|
||||
|
||||
<b>It requires having your HMD connected to SlimeVR!</b>
|
||||
onboarding-choose_proportions-manual_proportions = Manual proportions
|
||||
# Italized text
|
||||
onboarding-choose_proportions-manual_proportions-subtitle = For small touches
|
||||
@@ -764,6 +767,18 @@ onboarding-automatic_proportions-requirements-description =
|
||||
Your trackers and headset are working properly within the SlimeVR server.
|
||||
Your headset is reporting positional data to the SlimeVR server (this generally means having SteamVR running and connected to SlimeVR using SlimeVR's SteamVR driver).
|
||||
onboarding-automatic_proportions-requirements-next = I have read the requirements
|
||||
onboarding-automatic_proportions-check_height-title = Check your height
|
||||
onboarding-automatic_proportions-check_height-description = We use your height as a basis of our measurements by using the HMD's height as an approximation of your actual height, but it's better to check if they are right yourself!
|
||||
# All the text is in bold!
|
||||
onboarding-automatic_proportions-check_height-calculation_warning = Please press the button while standing <u>upright</u> to calculate your height. You have 3 seconds after you press the button!
|
||||
onboarding-automatic_proportions-check_height-fetch_height = I'm standing!
|
||||
# Context is that the height is unknown
|
||||
onboarding-automatic_proportions-check_height-unknown = Unknown
|
||||
# Shows an element below it
|
||||
onboarding-automatic_proportions-check_height-height = Your height is
|
||||
# Shows an element below it
|
||||
onboarding-automatic_proportions-check_height-hmd_height = And HMD height is
|
||||
onboarding-automatic_proportions-check_height-next_step = They are fine
|
||||
onboarding-automatic_proportions-start_recording-title = Get ready to move
|
||||
onboarding-automatic_proportions-start_recording-description = We're now going to record some specific poses and moves. These will be prompted in the next screen. Be ready to start when the button is pressed!
|
||||
onboarding-automatic_proportions-start_recording-next = Start Recording
|
||||
@@ -792,6 +807,10 @@ onboarding-automatic_proportions-verify_results-redo = Redo recording
|
||||
onboarding-automatic_proportions-verify_results-confirm = They're correct
|
||||
onboarding-automatic_proportions-done-title = Body measured and saved.
|
||||
onboarding-automatic_proportions-done-description = Your body proportions' calibration is complete!
|
||||
onboarding-automatic_proportions-error_modal =
|
||||
<b>Warning:</b> An error was found while estimating proportions!
|
||||
Please <docs>check the docs</docs> or join our <discord>Discord</discord> for help ^_^
|
||||
onboarding-automatic_proportions-error_modal-confirm = Understood!
|
||||
|
||||
## Home
|
||||
home-no_trackers = No trackers detected or assigned
|
||||
|
||||
@@ -55,7 +55,8 @@ import { error, log } from './utils/logging';
|
||||
|
||||
export const GH_REPO = 'SlimeVR/SlimeVR-Server';
|
||||
export const VersionContext = createContext('');
|
||||
export const DOCS_SITE = 'https://docs.slimevr.dev/';
|
||||
export const DOCS_SITE = 'https://docs.slimevr.dev';
|
||||
export const SLIMEVR_DISCORD = 'https://discord.gg/slimevr';
|
||||
|
||||
function Layout() {
|
||||
const { loading } = useConfig();
|
||||
|
||||
14
gui/src/components/commons/A.tsx
Normal file
14
gui/src/components/commons/A.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { open } from '@tauri-apps/api/shell';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
export function A({ href, children }: { href: string; children?: ReactNode }) {
|
||||
return (
|
||||
<a
|
||||
href="javascript:void(0)"
|
||||
onClick={() => open(href).catch(() => window.open(href, '_blank'))}
|
||||
className="underline"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ export function NumberSelector({
|
||||
min,
|
||||
max,
|
||||
step,
|
||||
disabled = false,
|
||||
}: {
|
||||
label: string;
|
||||
valueLabelFormat?: (value: number) => string;
|
||||
@@ -18,6 +19,7 @@ export function NumberSelector({
|
||||
min: number;
|
||||
max: number;
|
||||
step: number | ((value: number, add: boolean) => number);
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const stepFn =
|
||||
typeof step === 'function'
|
||||
@@ -38,7 +40,7 @@ export function NumberSelector({
|
||||
variant="tertiary"
|
||||
rounded
|
||||
onClick={() => onChange(stepFn(value, false))}
|
||||
disabled={stepFn(value, false) < min}
|
||||
disabled={stepFn(value, false) < min || disabled}
|
||||
>
|
||||
-
|
||||
</Button>
|
||||
@@ -51,7 +53,7 @@ export function NumberSelector({
|
||||
variant="tertiary"
|
||||
rounded
|
||||
onClick={() => onChange(stepFn(value, true))}
|
||||
disabled={stepFn(value, true) > max}
|
||||
disabled={stepFn(value, true) > max || disabled}
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
|
||||
@@ -16,6 +16,7 @@ import { Recording } from './autobone-steps/Recording';
|
||||
import { StartRecording } from './autobone-steps/StartRecording';
|
||||
import { VerifyResultsStep } from './autobone-steps/VerifyResults';
|
||||
import { useCountdown } from '../../../../hooks/countdown';
|
||||
import { CheckHeight } from './autobone-steps/СheckHeight';
|
||||
|
||||
export function AutomaticProportionsPage() {
|
||||
const { l10n } = useLocalization();
|
||||
@@ -53,6 +54,7 @@ export function AutomaticProportionsPage() {
|
||||
steps={[
|
||||
{ type: 'numbered', component: PutTrackersOnStep },
|
||||
{ type: 'numbered', component: RequirementsStep },
|
||||
{ type: 'numbered', component: CheckHeight },
|
||||
{ type: 'numbered', component: StartRecording },
|
||||
{ type: 'fullsize', component: Recording },
|
||||
{ type: 'numbered', component: VerifyResultsStep },
|
||||
|
||||
@@ -60,7 +60,7 @@ export function BodyProportions({
|
||||
unit: 'centimeter',
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
const percentageFormat = Intl.NumberFormat(currentLocales, {
|
||||
const percentageFormat = new Intl.NumberFormat(currentLocales, {
|
||||
style: 'percent',
|
||||
maximumFractionDigits: 1,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useOnboarding } from '../../../../hooks/onboarding';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { useState } from 'react';
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Typography } from '../../../commons/Typography';
|
||||
import { Button } from '../../../commons/Button';
|
||||
@@ -14,14 +14,39 @@ import saveAs from 'file-saver';
|
||||
import { save } from '@tauri-apps/api/dialog';
|
||||
import { writeTextFile } from '@tauri-apps/api/fs';
|
||||
import { useIsTauri } from '../../../../hooks/breakpoint';
|
||||
import { useAppContext } from '../../../../hooks/app';
|
||||
import { error } from '../../../../utils/logging';
|
||||
|
||||
export const MIN_HEIGHT = 0.4;
|
||||
export const MAX_HEIGHT = 4;
|
||||
export const DEFAULT_HEIGHT = 1.5;
|
||||
|
||||
export function ProportionsChoose() {
|
||||
const isTauri = useIsTauri();
|
||||
const { l10n } = useLocalization();
|
||||
const { applyProgress, state } = useOnboarding();
|
||||
const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();
|
||||
const [animated, setAnimated] = useState(false);
|
||||
const { computedTrackers } = useAppContext();
|
||||
|
||||
const hmdTracker = useMemo(
|
||||
() =>
|
||||
computedTrackers.find(
|
||||
(tracker) =>
|
||||
tracker.tracker.trackerId?.trackerNum === 1 &&
|
||||
tracker.tracker.trackerId.deviceId?.id === undefined
|
||||
),
|
||||
[computedTrackers]
|
||||
);
|
||||
|
||||
const beneathFloor = useMemo(
|
||||
() =>
|
||||
!(
|
||||
hmdTracker?.tracker.position &&
|
||||
hmdTracker.tracker.position.y >= MIN_HEIGHT
|
||||
),
|
||||
[hmdTracker?.tracker.position?.y]
|
||||
);
|
||||
|
||||
useRPCPacket(
|
||||
RpcMessage.SkeletonConfigResponse,
|
||||
@@ -145,15 +170,23 @@ export function ProportionsChoose() {
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(
|
||||
'onboarding-choose_proportions-auto_proportions-description'
|
||||
)}
|
||||
</Typography>
|
||||
<Localized
|
||||
id="onboarding-choose_proportions-auto_proportions-descriptionv2"
|
||||
elems={{ b: <b></b> }}
|
||||
>
|
||||
<Typography
|
||||
color="secondary"
|
||||
whitespace="whitespace-pre-line"
|
||||
>
|
||||
Description for autobone
|
||||
</Typography>
|
||||
</Localized>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
// Check if we are in dev mode and just let it be used
|
||||
disabled={beneathFloor && import.meta.env.PROD}
|
||||
to="/onboarding/body-proportions/auto"
|
||||
className="self-start mt-auto"
|
||||
state={{ alonePage: state.alonePage }}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import ReactModal from 'react-modal';
|
||||
import { BaseModal } from '../../../../commons/BaseModal';
|
||||
import { WarningBox } from '../../../../commons/TipBox';
|
||||
import { Button } from '../../../../commons/Button';
|
||||
import { A } from '../../../../commons/A';
|
||||
import { DOCS_SITE, SLIMEVR_DISCORD } from '../../../../../App';
|
||||
|
||||
export function AutoboneErrorModal({
|
||||
isOpen = true,
|
||||
onClose,
|
||||
...props
|
||||
}: {
|
||||
/**
|
||||
* Is the parent/sibling component opened?
|
||||
*/
|
||||
isOpen: boolean;
|
||||
/**
|
||||
* Function to trigger when closed or accepted
|
||||
*/
|
||||
onClose: () => void;
|
||||
} & ReactModal.Props) {
|
||||
const { l10n } = useLocalization();
|
||||
|
||||
return (
|
||||
<BaseModal
|
||||
isOpen={isOpen}
|
||||
shouldCloseOnOverlayClick
|
||||
shouldCloseOnEsc
|
||||
onRequestClose={onClose}
|
||||
className={props.className}
|
||||
overlayClassName={props.overlayClassName}
|
||||
>
|
||||
<div className="flex w-full h-full flex-col ">
|
||||
<div className="flex w-full flex-col flex-grow items-center gap-3">
|
||||
<Localized
|
||||
id="onboarding-automatic_proportions-error_modal"
|
||||
elems={{
|
||||
b: <b></b>,
|
||||
docs: (
|
||||
<A
|
||||
href={`${DOCS_SITE}/server/body-config.html#common-issues--debugging`}
|
||||
></A>
|
||||
),
|
||||
discord: <A href={SLIMEVR_DISCORD}></A>,
|
||||
}}
|
||||
>
|
||||
<WarningBox>
|
||||
<b>Warning:</b> An autobone error happened!
|
||||
</WarningBox>
|
||||
</Localized>
|
||||
|
||||
<div className="flex flex-row gap-3 pt-5 place-content-center">
|
||||
<Button variant="primary" onClick={onClose}>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_proportions-error_modal-confirm'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BaseModal>
|
||||
);
|
||||
}
|
||||
@@ -1,22 +1,49 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useAutobone } from '../../../../../hooks/autobone';
|
||||
import { ReactNode, useEffect, useState } from 'react';
|
||||
import { ProcessStatus, useAutobone } from '../../../../../hooks/autobone';
|
||||
import { ProgressBar } from '../../../../commons/ProgressBar';
|
||||
import { TipBox } from '../../../../commons/TipBox';
|
||||
import { Typography } from '../../../../commons/Typography';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { P, match } from 'ts-pattern';
|
||||
import { AutoboneErrorModal } from './AutoboneErrorModal';
|
||||
|
||||
export function Recording({ nextStep }: { nextStep: () => void }) {
|
||||
export function Recording({
|
||||
nextStep,
|
||||
resetSteps,
|
||||
}: {
|
||||
nextStep: () => void;
|
||||
resetSteps: () => void;
|
||||
}) {
|
||||
const { l10n } = useLocalization();
|
||||
const { progress, hasCalibration, hasRecording } = useAutobone();
|
||||
const { progress, hasCalibration, hasRecording, eta } = useAutobone();
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (progress === 1 && hasCalibration) {
|
||||
if (
|
||||
hasRecording === ProcessStatus.REJECTED ||
|
||||
hasCalibration === ProcessStatus.REJECTED
|
||||
) {
|
||||
setModalOpen(true);
|
||||
}
|
||||
if (progress !== 1) return;
|
||||
|
||||
if (
|
||||
hasRecording === ProcessStatus.FULFILLED &&
|
||||
hasCalibration === ProcessStatus.FULFILLED
|
||||
) {
|
||||
nextStep();
|
||||
}
|
||||
}, [progress, hasCalibration]);
|
||||
}, [progress, hasCalibration, hasRecording]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center w-full justify-between">
|
||||
<AutoboneErrorModal
|
||||
isOpen={modalOpen}
|
||||
onClose={() => {
|
||||
setModalOpen(false);
|
||||
resetSteps();
|
||||
}}
|
||||
></AutoboneErrorModal>
|
||||
<div className="flex gap-1 flex-col justify-center items-center">
|
||||
<div className="flex text-status-critical justify-center items-center gap-1">
|
||||
<div className="w-2 h-2 rounded-lg bg-status-critical"></div>
|
||||
@@ -51,19 +78,39 @@ export function Recording({ nextStep }: { nextStep: () => void }) {
|
||||
<TipBox>{l10n.getString('tips-do_not_move_heels')}</TipBox>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 items-center w-full max-w-[150px]">
|
||||
<ProgressBar progress={progress} height={2}></ProgressBar>
|
||||
<ProgressBar
|
||||
progress={progress}
|
||||
height={2}
|
||||
colorClass={match([hasCalibration, hasRecording])
|
||||
.returnType<string | undefined>()
|
||||
.with(
|
||||
P.union(
|
||||
[ProcessStatus.REJECTED, P._],
|
||||
[P._, ProcessStatus.REJECTED]
|
||||
),
|
||||
() => 'bg-status-critical'
|
||||
)
|
||||
.with(
|
||||
[ProcessStatus.FULFILLED, ProcessStatus.FULFILLED],
|
||||
() => 'bg-status-success'
|
||||
)
|
||||
.otherwise(() => undefined)}
|
||||
></ProgressBar>
|
||||
<Typography color="secondary">
|
||||
{!hasCalibration && hasRecording
|
||||
? l10n.getString(
|
||||
{match([hasCalibration, hasRecording])
|
||||
.returnType<ReactNode>()
|
||||
.with([ProcessStatus.PENDING, ProcessStatus.FULFILLED], () =>
|
||||
l10n.getString(
|
||||
'onboarding-automatic_proportions-recording-processing'
|
||||
)
|
||||
: l10n.getString(
|
||||
)
|
||||
.with([ProcessStatus.PENDING, ProcessStatus.PENDING], () =>
|
||||
l10n.getString(
|
||||
'onboarding-automatic_proportions-recording-timer',
|
||||
{
|
||||
// TODO: The progress should be communicated by the server in SolarXR
|
||||
time: Math.round(20 * (1 - progress)),
|
||||
}
|
||||
)}
|
||||
{ time: Math.round(eta) }
|
||||
)
|
||||
)
|
||||
.otherwise(() => '')}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import classNames from 'classnames';
|
||||
import { useAutobone } from '../../../../../hooks/autobone';
|
||||
import { ProcessStatus, useAutobone } from '../../../../../hooks/autobone';
|
||||
import { Button } from '../../../../commons/Button';
|
||||
import { Typography } from '../../../../commons/Typography';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
@@ -69,13 +69,14 @@ export function VerifyResultsStep({
|
||||
<Typography bold>{(value * 100).toFixed(2)} CM</Typography>
|
||||
</div>
|
||||
))}
|
||||
{!hasCalibration && hasRecording && (
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic-proportions-verify-results-processing'
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
{hasCalibration === ProcessStatus.PENDING &&
|
||||
hasRecording === ProcessStatus.FULFILLED && (
|
||||
<Typography>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic-proportions-verify-results-processing'
|
||||
)}
|
||||
</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
import {
|
||||
AutoBoneSettingsT,
|
||||
ChangeSettingsRequestT,
|
||||
HeightRequestT,
|
||||
HeightResponseT,
|
||||
RpcMessage,
|
||||
} from 'solarxr-protocol';
|
||||
import { useWebsocketAPI } from '../../../../../hooks/websocket-api';
|
||||
import { Button } from '../../../../commons/Button';
|
||||
import { Typography } from '../../../../commons/Typography';
|
||||
import { Localized, useLocalization } from '@fluent/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { NumberSelector } from '../../../../commons/NumberSelector';
|
||||
import { DEFAULT_HEIGHT, MIN_HEIGHT } from '../ProportionsChoose';
|
||||
import { useLocaleConfig } from '../../../../../i18n/config';
|
||||
import { useCountdown } from '../../../../../hooks/countdown';
|
||||
|
||||
interface HeightForm {
|
||||
height: number;
|
||||
hmdHeight: number;
|
||||
}
|
||||
|
||||
export function CheckHeight({
|
||||
nextStep,
|
||||
prevStep,
|
||||
variant,
|
||||
}: {
|
||||
nextStep: () => void;
|
||||
prevStep: () => void;
|
||||
variant: 'onboarding' | 'alone';
|
||||
}) {
|
||||
const { l10n } = useLocalization();
|
||||
const { control, handleSubmit, setValue } = useForm<HeightForm>();
|
||||
const [fetchedHeight, setFetchedHeight] = useState(false);
|
||||
const { sendRPCPacket, useRPCPacket } = useWebsocketAPI();
|
||||
const { timer, isCounting, startCountdown } = useCountdown({
|
||||
duration: 3,
|
||||
onCountdownEnd: () => {
|
||||
setFetchedHeight(true);
|
||||
sendRPCPacket(RpcMessage.HeightRequest, new HeightRequestT());
|
||||
},
|
||||
});
|
||||
const { currentLocales } = useLocaleConfig();
|
||||
|
||||
const mFormat = useMemo(
|
||||
() =>
|
||||
new Intl.NumberFormat(currentLocales, {
|
||||
style: 'unit',
|
||||
unit: 'meter',
|
||||
maximumFractionDigits: 2,
|
||||
}),
|
||||
[currentLocales]
|
||||
);
|
||||
|
||||
const sFormat = useMemo(
|
||||
() => new Intl.RelativeTimeFormat(currentLocales, { style: 'short' }),
|
||||
[currentLocales]
|
||||
);
|
||||
|
||||
useRPCPacket(
|
||||
RpcMessage.HeightResponse,
|
||||
({ hmdHeight, estimatedFullHeight }: HeightResponseT) => {
|
||||
setValue('height', estimatedFullHeight || DEFAULT_HEIGHT);
|
||||
setValue('hmdHeight', hmdHeight);
|
||||
}
|
||||
);
|
||||
|
||||
const onSubmit = (values: HeightForm) => {
|
||||
const changeSettings = new ChangeSettingsRequestT();
|
||||
const autobone = new AutoBoneSettingsT();
|
||||
autobone.targetFullHeight = values.height;
|
||||
autobone.targetHmdHeight = values.hmdHeight;
|
||||
changeSettings.autoBoneSettings = autobone;
|
||||
|
||||
sendRPCPacket(RpcMessage.ChangeSettingsRequest, changeSettings);
|
||||
nextStep();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col flex-grow">
|
||||
<div className="flex flex-grow flex-col gap-4">
|
||||
<Typography variant="main-title" bold>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_proportions-check_height-title'
|
||||
)}
|
||||
</Typography>
|
||||
<div>
|
||||
<Typography color="secondary">
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_proportions-check_height-description'
|
||||
)}
|
||||
</Typography>
|
||||
<Localized
|
||||
id="onboarding-automatic_proportions-check_height-calculation_warning"
|
||||
elems={{ u: <span className="underline"></span> }}
|
||||
>
|
||||
<Typography color="secondary" bold>
|
||||
Press the button to get your height!
|
||||
</Typography>
|
||||
</Localized>
|
||||
|
||||
<Button
|
||||
variant="primary"
|
||||
className="mt-2"
|
||||
onClick={startCountdown}
|
||||
disabled={isCounting}
|
||||
>
|
||||
{isCounting
|
||||
? sFormat.format(timer, 'second')
|
||||
: l10n.getString(
|
||||
'onboarding-automatic_proportions-check_height-fetch_height'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
<form className="flex flex-col self-center items-center justify-center">
|
||||
<NumberSelector
|
||||
control={control}
|
||||
name="height"
|
||||
label={l10n.getString(
|
||||
'onboarding-automatic_proportions-check_height-height'
|
||||
)}
|
||||
valueLabelFormat={(value) =>
|
||||
isNaN(value)
|
||||
? l10n.getString(
|
||||
'onboarding-automatic_proportions-check_height-unknown'
|
||||
)
|
||||
: mFormat.format(value)
|
||||
}
|
||||
min={MIN_HEIGHT}
|
||||
max={4}
|
||||
step={0.01}
|
||||
/>
|
||||
<NumberSelector
|
||||
control={control}
|
||||
name="hmdHeight"
|
||||
label={l10n.getString(
|
||||
'onboarding-automatic_proportions-check_height-hmd_height'
|
||||
)}
|
||||
valueLabelFormat={(value) =>
|
||||
isNaN(value)
|
||||
? l10n.getString(
|
||||
'onboarding-automatic_proportions-check_height-unknown'
|
||||
)
|
||||
: mFormat.format(value)
|
||||
}
|
||||
min={MIN_HEIGHT}
|
||||
max={4}
|
||||
step={0.01}
|
||||
disabled={true}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 mobile:justify-between">
|
||||
<Button
|
||||
variant={variant === 'onboarding' ? 'secondary' : 'tertiary'}
|
||||
onClick={prevStep}
|
||||
>
|
||||
{l10n.getString('onboarding-automatic_proportions-prev_step')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleSubmit(onSubmit)}
|
||||
disabled={!fetchedHeight}
|
||||
>
|
||||
{l10n.getString(
|
||||
'onboarding-automatic_proportions-check_height-next_step'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -125,7 +125,7 @@ export function GeneralSettings() {
|
||||
const { currentLocales } = useLocaleConfig();
|
||||
// const pageRef = useRef<HTMLFormElement | null>(null);
|
||||
|
||||
const percentageFormat = Intl.NumberFormat(currentLocales, {
|
||||
const percentageFormat = new Intl.NumberFormat(currentLocales, {
|
||||
style: 'percent',
|
||||
maximumFractionDigits: 0,
|
||||
});
|
||||
|
||||
@@ -42,6 +42,7 @@ export interface AppContext {
|
||||
trackers: FlatDeviceTracker[];
|
||||
dispatch: Dispatch<AppStateAction>;
|
||||
bones: BoneT[];
|
||||
computedTrackers: FlatDeviceTracker[];
|
||||
}
|
||||
|
||||
export function reducer(state: AppState, action: AppStateAction) {
|
||||
@@ -89,6 +90,11 @@ export function useProvideAppContext(): AppContext {
|
||||
[state]
|
||||
);
|
||||
|
||||
const computedTrackers: FlatDeviceTracker[] = useMemo(
|
||||
() => (state.datafeed?.syntheticTrackers || []).map((tracker) => ({ tracker })),
|
||||
[state]
|
||||
);
|
||||
|
||||
const bones = useMemo(() => state.datafeed?.bones || [], [state]);
|
||||
|
||||
useDataFeedPacket(DataFeedMessage.DataFeedUpdate, (packet: DataFeedUpdateT) => {
|
||||
@@ -114,6 +120,7 @@ export function useProvideAppContext(): AppContext {
|
||||
trackers,
|
||||
dispatch,
|
||||
bones,
|
||||
computedTrackers,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,30 @@
|
||||
import { createContext, useContext, useMemo, useState } from 'react';
|
||||
import {
|
||||
AutoBoneApplyRequestT,
|
||||
AutoBoneEpochResponseT,
|
||||
AutoBoneProcessRequestT,
|
||||
AutoBoneProcessStatusResponseT,
|
||||
AutoBoneProcessType,
|
||||
RpcMessage,
|
||||
SkeletonBone,
|
||||
SkeletonConfigRequestT,
|
||||
SkeletonPartT,
|
||||
} from 'solarxr-protocol';
|
||||
import { useWebsocketAPI } from './websocket-api';
|
||||
import { useLocalization } from '@fluent/react';
|
||||
import { log } from '../utils/logging';
|
||||
|
||||
export enum ProcessStatus {
|
||||
PENDING,
|
||||
FULFILLED,
|
||||
REJECTED,
|
||||
}
|
||||
|
||||
export interface AutoboneContext {
|
||||
hasRecording: boolean;
|
||||
hasCalibration: boolean;
|
||||
hasRecording: ProcessStatus;
|
||||
hasCalibration: ProcessStatus;
|
||||
progress: number;
|
||||
bodyParts: { bone: SkeletonBone; label: string; value: number }[] | null;
|
||||
eta: number;
|
||||
startRecording: () => void;
|
||||
startProcessing: () => void;
|
||||
applyProcessing: () => void;
|
||||
@@ -26,9 +33,10 @@ export interface AutoboneContext {
|
||||
export function useProvideAutobone(): AutoboneContext {
|
||||
const { l10n } = useLocalization();
|
||||
const { useRPCPacket, sendRPCPacket } = useWebsocketAPI();
|
||||
const [hasRecording, setHasRecording] = useState(false);
|
||||
const [hasCalibration, setHasCalibration] = useState(false);
|
||||
const [hasRecording, setHasRecording] = useState(ProcessStatus.PENDING);
|
||||
const [hasCalibration, setHasCalibration] = useState(ProcessStatus.PENDING);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const [eta, setEta] = useState(-1);
|
||||
const [skeletonParts, setSkeletonParts] = useState<SkeletonPartT[] | null>(null);
|
||||
|
||||
const bodyParts = useMemo(() => {
|
||||
@@ -48,6 +56,7 @@ export function useProvideAutobone(): AutoboneContext {
|
||||
// }
|
||||
|
||||
setProgress(0);
|
||||
setEta(-1);
|
||||
|
||||
const processRequest = new AutoBoneProcessRequestT();
|
||||
processRequest.processType = processType;
|
||||
@@ -56,19 +65,19 @@ export function useProvideAutobone(): AutoboneContext {
|
||||
};
|
||||
|
||||
const startRecording = () => {
|
||||
setHasCalibration(false);
|
||||
setHasRecording(false);
|
||||
setHasCalibration(ProcessStatus.PENDING);
|
||||
setHasRecording(ProcessStatus.PENDING);
|
||||
setSkeletonParts(null);
|
||||
startProcess(AutoBoneProcessType.RECORD);
|
||||
};
|
||||
|
||||
const startProcessing = () => {
|
||||
setHasCalibration(false);
|
||||
setHasCalibration(ProcessStatus.PENDING);
|
||||
startProcess(AutoBoneProcessType.PROCESS);
|
||||
};
|
||||
|
||||
const applyProcessing = () => {
|
||||
startProcess(AutoBoneProcessType.APPLY);
|
||||
sendRPCPacket(RpcMessage.AutoBoneApplyRequest, new AutoBoneApplyRequestT());
|
||||
};
|
||||
|
||||
useRPCPacket(
|
||||
@@ -79,35 +88,36 @@ export function useProvideAutobone(): AutoboneContext {
|
||||
}
|
||||
|
||||
if (data.processType) {
|
||||
if (data.message) {
|
||||
log(AutoBoneProcessType[data.processType], ': ', data.message);
|
||||
}
|
||||
|
||||
if (data.total > 0 && data.current >= 0) {
|
||||
setProgress(data.current / data.total);
|
||||
}
|
||||
|
||||
setEta(data.eta);
|
||||
|
||||
if (data.completed) {
|
||||
log('Process ', AutoBoneProcessType[data.processType], ' has completed');
|
||||
log(`Process ${AutoBoneProcessType[data.processType]} has completed`);
|
||||
|
||||
switch (data.processType) {
|
||||
case AutoBoneProcessType.RECORD:
|
||||
setHasRecording(data.success);
|
||||
setHasRecording(
|
||||
data.success ? ProcessStatus.FULFILLED : ProcessStatus.REJECTED
|
||||
);
|
||||
startProcessing();
|
||||
break;
|
||||
|
||||
case AutoBoneProcessType.PROCESS:
|
||||
setHasCalibration(data.success);
|
||||
|
||||
break;
|
||||
|
||||
case AutoBoneProcessType.APPLY:
|
||||
// Update skeleton config when applied
|
||||
sendRPCPacket(
|
||||
RpcMessage.SkeletonConfigRequest,
|
||||
new SkeletonConfigRequestT()
|
||||
setHasCalibration(
|
||||
data.success ? ProcessStatus.FULFILLED : ProcessStatus.REJECTED
|
||||
);
|
||||
break;
|
||||
|
||||
// case AutoBoneProcessType.APPLY:
|
||||
// // Update skeleton config when applied
|
||||
// sendRPCPacket(
|
||||
// RpcMessage.SkeletonConfigRequest,
|
||||
// new SkeletonConfigRequestT()
|
||||
// );
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,6 +145,7 @@ export function useProvideAutobone(): AutoboneContext {
|
||||
hasCalibration,
|
||||
hasRecording,
|
||||
progress,
|
||||
eta,
|
||||
bodyParts,
|
||||
startProcessing,
|
||||
startRecording,
|
||||
|
||||
714
package-lock.json
generated
714
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,8 +15,6 @@
|
||||
"prepare": "husky install && npm run update-solarxr"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/parser": "^5.59.6",
|
||||
"husky": "^8.0.2",
|
||||
"typescript": "^5.0.4"
|
||||
"husky": "^8.0.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import dev.slimevr.autobone.errors.*
|
||||
import dev.slimevr.config.AutoBoneConfig
|
||||
import dev.slimevr.poseframeformat.PoseFrameIO
|
||||
import dev.slimevr.poseframeformat.PoseFrames
|
||||
import dev.slimevr.tracking.processor.BoneType
|
||||
import dev.slimevr.tracking.processor.HumanPoseManager
|
||||
import dev.slimevr.tracking.processor.config.SkeletonConfigManager
|
||||
import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets
|
||||
@@ -20,45 +19,43 @@ import io.github.axisangles.ktmath.Vector3
|
||||
import org.apache.commons.lang3.tuple.Pair
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.function.BiConsumer
|
||||
import java.util.function.Consumer
|
||||
import java.util.function.Function
|
||||
|
||||
class AutoBone(server: VRServer) {
|
||||
// This is filled by reloadConfigValues()
|
||||
val offsets = EnumMap<BoneType, Float>(
|
||||
BoneType::class.java
|
||||
// This is filled by loadConfigValues()
|
||||
val offsets = EnumMap<SkeletonConfigOffsets, Float>(
|
||||
SkeletonConfigOffsets::class.java
|
||||
)
|
||||
val adjustOffsets = FastList(
|
||||
arrayOf(
|
||||
BoneType.HEAD,
|
||||
BoneType.NECK,
|
||||
BoneType.UPPER_CHEST,
|
||||
BoneType.CHEST,
|
||||
BoneType.WAIST,
|
||||
BoneType.HIP, // This now works when using body proportion error! It's not the
|
||||
SkeletonConfigOffsets.HEAD,
|
||||
SkeletonConfigOffsets.NECK,
|
||||
SkeletonConfigOffsets.UPPER_CHEST,
|
||||
SkeletonConfigOffsets.CHEST,
|
||||
SkeletonConfigOffsets.WAIST,
|
||||
SkeletonConfigOffsets.HIP, // This now works when using body proportion error! It's not the
|
||||
// best still, but it is somewhat functional
|
||||
BoneType.LEFT_HIP,
|
||||
BoneType.LEFT_UPPER_LEG,
|
||||
BoneType.LEFT_LOWER_LEG
|
||||
SkeletonConfigOffsets.HIPS_WIDTH,
|
||||
SkeletonConfigOffsets.UPPER_LEG,
|
||||
SkeletonConfigOffsets.LOWER_LEG
|
||||
)
|
||||
)
|
||||
val heightOffsets = FastList(
|
||||
arrayOf(
|
||||
BoneType.NECK,
|
||||
BoneType.UPPER_CHEST,
|
||||
BoneType.CHEST,
|
||||
BoneType.WAIST,
|
||||
BoneType.HIP,
|
||||
BoneType.LEFT_UPPER_LEG,
|
||||
BoneType.RIGHT_UPPER_LEG,
|
||||
BoneType.LEFT_LOWER_LEG,
|
||||
BoneType.RIGHT_LOWER_LEG
|
||||
)
|
||||
val heightOffsetDefaults = EnumMap<SkeletonConfigOffsets, Float>(
|
||||
SkeletonConfigOffsets::class.java
|
||||
)
|
||||
|
||||
val legacyConfigs = EnumMap<SkeletonConfigOffsets, Float>(
|
||||
SkeletonConfigOffsets::class.java
|
||||
// This is filled by loadConfigValues()
|
||||
val heightOffsets = FastList(
|
||||
arrayOf(
|
||||
SkeletonConfigOffsets.NECK,
|
||||
SkeletonConfigOffsets.UPPER_CHEST,
|
||||
SkeletonConfigOffsets.CHEST,
|
||||
SkeletonConfigOffsets.WAIST,
|
||||
SkeletonConfigOffsets.HIP,
|
||||
SkeletonConfigOffsets.UPPER_LEG,
|
||||
SkeletonConfigOffsets.LOWER_LEG
|
||||
)
|
||||
)
|
||||
|
||||
private val server: VRServer
|
||||
@@ -83,34 +80,6 @@ class AutoBone(server: VRServer) {
|
||||
loadConfigValues()
|
||||
}
|
||||
|
||||
fun computeBoneOffset(
|
||||
bone: BoneType,
|
||||
getOffset: Function<SkeletonConfigOffsets, Float>,
|
||||
): Float {
|
||||
return when (bone) {
|
||||
BoneType.HEAD -> getOffset.apply(SkeletonConfigOffsets.HEAD)
|
||||
BoneType.NECK -> getOffset.apply(SkeletonConfigOffsets.NECK)
|
||||
BoneType.UPPER_CHEST -> getOffset.apply(SkeletonConfigOffsets.UPPER_CHEST)
|
||||
BoneType.CHEST -> getOffset.apply(SkeletonConfigOffsets.CHEST)
|
||||
BoneType.WAIST -> getOffset.apply(SkeletonConfigOffsets.WAIST)
|
||||
BoneType.HIP -> getOffset.apply(SkeletonConfigOffsets.HIP)
|
||||
BoneType.LEFT_HIP, BoneType.RIGHT_HIP -> (
|
||||
getOffset.apply(SkeletonConfigOffsets.HIPS_WIDTH) /
|
||||
2f
|
||||
)
|
||||
|
||||
BoneType.LEFT_UPPER_LEG, BoneType.RIGHT_UPPER_LEG ->
|
||||
getOffset
|
||||
.apply(SkeletonConfigOffsets.UPPER_LEG)
|
||||
|
||||
BoneType.LEFT_LOWER_LEG, BoneType.RIGHT_LOWER_LEG ->
|
||||
getOffset
|
||||
.apply(SkeletonConfigOffsets.LOWER_LEG)
|
||||
|
||||
else -> -1f
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadConfigValues() {
|
||||
// Remove all previous values
|
||||
offsets.clear()
|
||||
@@ -127,143 +96,83 @@ class AutoBone(server: VRServer) {
|
||||
}
|
||||
}
|
||||
for (bone in adjustOffsets) {
|
||||
val offset = computeBoneOffset(bone, getOffset)
|
||||
val offset = getOffset.apply(bone)
|
||||
if (offset > 0f) {
|
||||
offsets[bone] = offset
|
||||
}
|
||||
}
|
||||
for (bone in heightOffsets) {
|
||||
val offset = getOffset.apply(bone)
|
||||
if (offset > 0f) {
|
||||
heightOffsetDefaults[bone] = offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getBoneDirection(
|
||||
skeleton: HumanPoseManager,
|
||||
node: BoneType,
|
||||
configOffset: SkeletonConfigOffsets,
|
||||
rightSide: Boolean,
|
||||
): Vector3 {
|
||||
var node = when (node) {
|
||||
BoneType.LEFT_HIP, BoneType.RIGHT_HIP -> if (rightSide) BoneType.RIGHT_HIP else BoneType.LEFT_HIP
|
||||
BoneType.LEFT_UPPER_LEG, BoneType.RIGHT_UPPER_LEG ->
|
||||
if (rightSide) BoneType.RIGHT_UPPER_LEG else BoneType.LEFT_UPPER_LEG
|
||||
BoneType.LEFT_LOWER_LEG, BoneType.RIGHT_LOWER_LEG ->
|
||||
if (rightSide) BoneType.RIGHT_LOWER_LEG else BoneType.LEFT_LOWER_LEG
|
||||
else -> node
|
||||
// IMPORTANT: This assumption for acquiring BoneType only works if
|
||||
// SkeletonConfigOffsets is set up to only affect one BoneType, make sure no
|
||||
// changes to SkeletonConfigOffsets goes against this assumption, please!
|
||||
val boneType = when (configOffset) {
|
||||
SkeletonConfigOffsets.HIPS_WIDTH, SkeletonConfigOffsets.SHOULDERS_WIDTH,
|
||||
SkeletonConfigOffsets.SHOULDERS_DISTANCE, SkeletonConfigOffsets.UPPER_ARM,
|
||||
SkeletonConfigOffsets.LOWER_ARM, SkeletonConfigOffsets.UPPER_LEG,
|
||||
SkeletonConfigOffsets.LOWER_LEG, SkeletonConfigOffsets.FOOT_LENGTH,
|
||||
->
|
||||
if (rightSide) configOffset.affectedOffsets[1] else configOffset.affectedOffsets[0]
|
||||
else -> configOffset.affectedOffsets[0]
|
||||
}
|
||||
val relevantTransform = skeleton.getTailNodeOfBone(node)
|
||||
return (relevantTransform.worldTransform.translation - relevantTransform.parent!!.worldTransform.translation).unit()
|
||||
val relevantTransform = skeleton.getTailNodeOfBone(boneType)
|
||||
return (
|
||||
relevantTransform.worldTransform.translation -
|
||||
relevantTransform.parent!!.worldTransform.translation
|
||||
).unit()
|
||||
}
|
||||
|
||||
fun getDotProductDiff(
|
||||
skeleton1: HumanPoseManager,
|
||||
skeleton2: HumanPoseManager,
|
||||
node: BoneType,
|
||||
configOffset: SkeletonConfigOffsets,
|
||||
rightSide: Boolean,
|
||||
offset: Vector3,
|
||||
): Float {
|
||||
val normalizedOffset = offset.unit()
|
||||
val dot1 = normalizedOffset.dot(getBoneDirection(skeleton1, node, rightSide))
|
||||
val dot2 = normalizedOffset.dot(getBoneDirection(skeleton2, node, rightSide))
|
||||
val dot1 = normalizedOffset.dot(getBoneDirection(skeleton1, configOffset, rightSide))
|
||||
val dot2 = normalizedOffset.dot(getBoneDirection(skeleton2, configOffset, rightSide))
|
||||
return dot2 - dot1
|
||||
}
|
||||
|
||||
fun applyConfig(
|
||||
configConsumer: BiConsumer<SkeletonConfigOffsets, Float>,
|
||||
offsets: Map<BoneType, Float> = this.offsets,
|
||||
): Boolean {
|
||||
return try {
|
||||
val headOffset = offsets[BoneType.HEAD]
|
||||
if (headOffset != null) {
|
||||
configConsumer.accept(SkeletonConfigOffsets.HEAD, headOffset)
|
||||
}
|
||||
val neckOffset = offsets[BoneType.NECK]
|
||||
if (neckOffset != null) {
|
||||
configConsumer.accept(SkeletonConfigOffsets.NECK, neckOffset)
|
||||
}
|
||||
val upperChestOffset = offsets[BoneType.UPPER_CHEST]
|
||||
val chestOffset = offsets[BoneType.CHEST]
|
||||
val waistOffset = offsets[BoneType.WAIST]
|
||||
val hipOffset = offsets[BoneType.HIP]
|
||||
if (upperChestOffset != null) {
|
||||
configConsumer
|
||||
.accept(SkeletonConfigOffsets.UPPER_CHEST, upperChestOffset)
|
||||
}
|
||||
if (chestOffset != null) {
|
||||
configConsumer
|
||||
.accept(SkeletonConfigOffsets.CHEST, chestOffset)
|
||||
}
|
||||
if (waistOffset != null) {
|
||||
configConsumer.accept(SkeletonConfigOffsets.WAIST, waistOffset)
|
||||
}
|
||||
if (hipOffset != null) {
|
||||
configConsumer.accept(SkeletonConfigOffsets.HIP, hipOffset)
|
||||
}
|
||||
var hipWidthOffset = offsets[BoneType.LEFT_HIP]
|
||||
if (hipWidthOffset == null) {
|
||||
hipWidthOffset = offsets[BoneType.RIGHT_HIP]
|
||||
}
|
||||
if (hipWidthOffset != null) {
|
||||
configConsumer
|
||||
.accept(SkeletonConfigOffsets.HIPS_WIDTH, hipWidthOffset * 2f)
|
||||
}
|
||||
var upperLegOffset = offsets[BoneType.LEFT_UPPER_LEG]
|
||||
if (upperLegOffset == null) {
|
||||
upperLegOffset = offsets[BoneType.RIGHT_UPPER_LEG]
|
||||
}
|
||||
var lowerLegOffset = offsets[BoneType.LEFT_LOWER_LEG]
|
||||
if (lowerLegOffset == null) {
|
||||
lowerLegOffset = offsets[BoneType.RIGHT_LOWER_LEG]
|
||||
}
|
||||
if (upperLegOffset != null) {
|
||||
configConsumer
|
||||
.accept(SkeletonConfigOffsets.UPPER_LEG, upperLegOffset)
|
||||
}
|
||||
if (lowerLegOffset != null) {
|
||||
configConsumer.accept(SkeletonConfigOffsets.LOWER_LEG, lowerLegOffset)
|
||||
}
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fun applyConfig(
|
||||
skeletonConfig: MutableMap<SkeletonConfigOffsets, Float>,
|
||||
offsets: Map<BoneType, Float> = this.offsets,
|
||||
): Boolean {
|
||||
return applyConfig({ key: SkeletonConfigOffsets, value: Float -> skeletonConfig[key] = value }, offsets)
|
||||
}
|
||||
|
||||
fun applyConfig(
|
||||
humanPoseManager: HumanPoseManager,
|
||||
offsets: Map<BoneType, Float> = this.offsets,
|
||||
): Boolean {
|
||||
return applyConfig({ key: SkeletonConfigOffsets?, newLength: Float? ->
|
||||
humanPoseManager.setOffset(
|
||||
key,
|
||||
newLength
|
||||
)
|
||||
}, offsets)
|
||||
offsets: Map<SkeletonConfigOffsets, Float> = this.offsets,
|
||||
) {
|
||||
for ((offset, value) in offsets) {
|
||||
humanPoseManager.setOffset(offset, value)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun applyAndSaveConfig(humanPoseManager: HumanPoseManager? = this.server.humanPoseManager): Boolean {
|
||||
if (humanPoseManager == null) return false
|
||||
if (!applyConfig(humanPoseManager)) return false
|
||||
applyConfig(humanPoseManager)
|
||||
humanPoseManager.saveConfig()
|
||||
server.configManager.saveConfig()
|
||||
LogManager.info("[AutoBone] Configured skeleton bone lengths")
|
||||
return true
|
||||
}
|
||||
|
||||
fun getConfig(config: BoneType): Float? {
|
||||
return offsets[config]
|
||||
}
|
||||
|
||||
fun <T> sumSelectConfigs(
|
||||
selection: List<T>,
|
||||
configs: Function<T, Float?>,
|
||||
configs: Map<T, Float>,
|
||||
configsAlt: Map<T, Float>? = null,
|
||||
): Float {
|
||||
var sum = 0f
|
||||
for (config in selection) {
|
||||
val length = configs.apply(config)
|
||||
val length = configs[config] ?: configsAlt?.get(config)
|
||||
if (length != null) {
|
||||
sum += length
|
||||
}
|
||||
@@ -271,27 +180,17 @@ class AutoBone(server: VRServer) {
|
||||
return sum
|
||||
}
|
||||
|
||||
fun <T> sumSelectConfigs(
|
||||
selection: List<T>,
|
||||
configs: Map<T, Float>,
|
||||
): Float {
|
||||
return sumSelectConfigs(selection) { key: T -> configs[key] }
|
||||
fun calcHeight(): Float {
|
||||
return sumSelectConfigs(heightOffsets, offsets, heightOffsetDefaults)
|
||||
}
|
||||
|
||||
fun sumSelectConfigs(
|
||||
selection: List<SkeletonConfigOffsets>,
|
||||
humanPoseManager: HumanPoseManager,
|
||||
): Float {
|
||||
return sumSelectConfigs(selection) { key: SkeletonConfigOffsets? -> humanPoseManager.getOffset(key) }
|
||||
}
|
||||
|
||||
fun getLengthSum(configs: Map<BoneType, Float>): Float {
|
||||
fun getLengthSum(configs: Map<SkeletonConfigOffsets, Float>): Float {
|
||||
return getLengthSum(configs, null)
|
||||
}
|
||||
|
||||
fun getLengthSum(
|
||||
configs: Map<BoneType, Float>,
|
||||
configsAlt: Map<BoneType, Float>?,
|
||||
configs: Map<SkeletonConfigOffsets, Float>,
|
||||
configsAlt: Map<SkeletonConfigOffsets, Float>?,
|
||||
): Float {
|
||||
var length = 0f
|
||||
if (configsAlt != null) {
|
||||
@@ -327,7 +226,7 @@ class AutoBone(server: VRServer) {
|
||||
// Otherwise if there is no skeleton available, attempt to get the
|
||||
// max HMD height from the recording
|
||||
val hmdHeight = frames.maxHmdHeight
|
||||
if (hmdHeight <= 0.50f) {
|
||||
if (hmdHeight <= 0.4f) {
|
||||
LogManager
|
||||
.warning(
|
||||
"[AutoBone] Max headset height detected (Value seems too low, did you not stand up straight while measuring?): $hmdHeight"
|
||||
@@ -346,7 +245,7 @@ class AutoBone(server: VRServer) {
|
||||
fun processFrames(
|
||||
frames: PoseFrames,
|
||||
config: AutoBoneConfig = globalConfig,
|
||||
epochCallback: Consumer<Epoch?>? = null,
|
||||
epochCallback: Consumer<Epoch>? = null,
|
||||
): AutoBoneResults {
|
||||
// Load current values for adjustable configs
|
||||
loadConfigValues()
|
||||
@@ -370,7 +269,6 @@ class AutoBone(server: VRServer) {
|
||||
targetHmdHeight = targetHmdHeight,
|
||||
targetFullHeight = targetFullHeight,
|
||||
frames = frames,
|
||||
intermediateOffsets = EnumMap(offsets),
|
||||
epochCallback = epochCallback,
|
||||
serverConfig = server.configManager
|
||||
)
|
||||
@@ -387,7 +285,7 @@ class AutoBone(server: VRServer) {
|
||||
internalEpoch(trainingStep)
|
||||
}
|
||||
|
||||
val finalHeight = sumSelectConfigs(heightOffsets, offsets)
|
||||
val finalHeight = calcHeight()
|
||||
LogManager
|
||||
.info(
|
||||
"[AutoBone] Target height: ${trainingStep.targetHmdHeight}, New height: $finalHeight"
|
||||
@@ -397,7 +295,7 @@ class AutoBone(server: VRServer) {
|
||||
finalHeight,
|
||||
trainingStep.targetHmdHeight,
|
||||
trainingStep.errorStats,
|
||||
legacyConfigs
|
||||
offsets
|
||||
)
|
||||
}
|
||||
|
||||
@@ -475,20 +373,16 @@ class AutoBone(server: VRServer) {
|
||||
)
|
||||
}
|
||||
|
||||
// Convert current adjusted config to the legacy config format for the epoch
|
||||
// callback, then call it
|
||||
applyConfig(legacyConfigs)
|
||||
trainingStep.epochCallback?.accept(Epoch(epoch + 1, config.numEpochs, errorStats, legacyConfigs))
|
||||
trainingStep.epochCallback?.accept(Epoch(epoch + 1, config.numEpochs, errorStats, offsets))
|
||||
}
|
||||
|
||||
private fun internalIter(trainingStep: AutoBoneStep) {
|
||||
// Pull frequently used variables out of trainingStep to reduce call length
|
||||
val skeleton1 = trainingStep.skeleton1
|
||||
val skeleton2 = trainingStep.skeleton2
|
||||
val intermediateOffsets = trainingStep.intermediateOffsets
|
||||
|
||||
val totalLength = getLengthSum(offsets)
|
||||
val curHeight = sumSelectConfigs(heightOffsets, offsets)
|
||||
val curHeight = calcHeight()
|
||||
trainingStep.currentHmdHeight = curHeight
|
||||
|
||||
val errorDeriv = getErrorDeriv(trainingStep)
|
||||
@@ -528,13 +422,10 @@ class AutoBone(server: VRServer) {
|
||||
.getComputedTracker(TrackerRole.RIGHT_FOOT).position -
|
||||
skeleton1.getComputedTracker(TrackerRole.RIGHT_FOOT).position
|
||||
|
||||
// Load the current offsets into the working offsets holder
|
||||
intermediateOffsets.putAll(offsets)
|
||||
|
||||
for (entry in offsets.entries) {
|
||||
// Skip adjustment if the epoch is before starting (for
|
||||
// logging only)
|
||||
if (trainingStep.curEpoch < 0) {
|
||||
// logging only) or if there are no BoneTypes for this value
|
||||
if (trainingStep.curEpoch < 0 || entry.key.affectedOffsets.isEmpty()) {
|
||||
break
|
||||
}
|
||||
val originalLength = entry.value
|
||||
@@ -571,9 +462,8 @@ class AutoBone(server: VRServer) {
|
||||
}
|
||||
|
||||
// Apply new offset length
|
||||
intermediateOffsets[entry.key] = newLength
|
||||
applyConfig(skeleton1, intermediateOffsets)
|
||||
applyConfig(skeleton2, intermediateOffsets)
|
||||
skeleton1.setOffset(entry.key, newLength)
|
||||
skeleton2.setOffset(entry.key, newLength)
|
||||
|
||||
// Update the skeleton poses for the new offset length
|
||||
skeleton1.update()
|
||||
@@ -587,9 +477,8 @@ class AutoBone(server: VRServer) {
|
||||
|
||||
// Reset the length to minimize bias in other variables,
|
||||
// it's applied later
|
||||
intermediateOffsets[entry.key] = originalLength
|
||||
applyConfig(skeleton1, intermediateOffsets)
|
||||
applyConfig(skeleton2, intermediateOffsets)
|
||||
skeleton1.setOffset(entry.key, originalLength)
|
||||
skeleton2.setOffset(entry.key, originalLength)
|
||||
}
|
||||
|
||||
if (trainingStep.config.scaleEachStep) {
|
||||
@@ -599,12 +488,15 @@ class AutoBone(server: VRServer) {
|
||||
}
|
||||
|
||||
private fun scaleToTargetHeight(trainingStep: AutoBoneStep) {
|
||||
val stepHeight = sumSelectConfigs(heightOffsets, offsets)
|
||||
// Recalculate the height and update it in the AutoBoneStep
|
||||
val stepHeight = calcHeight()
|
||||
trainingStep.currentHmdHeight = stepHeight
|
||||
|
||||
if (stepHeight > 0f) {
|
||||
val stepHeightDiff = trainingStep.targetHmdHeight - stepHeight
|
||||
for (entry in offsets.entries) {
|
||||
// Only height variables
|
||||
if (entry.key == BoneType.NECK ||
|
||||
if (entry.key == SkeletonConfigOffsets.NECK ||
|
||||
!heightOffsets.contains(entry.key)
|
||||
) {
|
||||
continue
|
||||
@@ -671,12 +563,12 @@ class AutoBone(server: VRServer) {
|
||||
val lengthsString: String
|
||||
get() {
|
||||
val configInfo = StringBuilder()
|
||||
offsets.forEach { (key: BoneType, value: Float) ->
|
||||
offsets.forEach { (key: SkeletonConfigOffsets, value: Float) ->
|
||||
if (configInfo.isNotEmpty()) {
|
||||
configInfo.append(", ")
|
||||
}
|
||||
configInfo
|
||||
.append(key.toString())
|
||||
.append(key.configKey)
|
||||
.append(": ")
|
||||
.append(StringUtils.prettyNumber(value * 100f, 2))
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package dev.slimevr.autobone
|
||||
import dev.slimevr.VRServer
|
||||
import dev.slimevr.autobone.AutoBone.AutoBoneResults
|
||||
import dev.slimevr.autobone.AutoBone.Companion.loadDir
|
||||
import dev.slimevr.autobone.AutoBone.Epoch
|
||||
import dev.slimevr.autobone.errors.AutoBoneException
|
||||
import dev.slimevr.poseframeformat.PoseFrames
|
||||
import dev.slimevr.poseframeformat.PoseRecorder
|
||||
@@ -16,7 +15,6 @@ import io.eiren.util.logging.LogManager
|
||||
import org.apache.commons.lang3.tuple.Pair
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import java.util.function.Consumer
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.concurrent.withLock
|
||||
|
||||
@@ -38,44 +36,29 @@ class AutoBoneHandler(private val server: VRServer) {
|
||||
}
|
||||
|
||||
fun removeListener(listener: AutoBoneListener) {
|
||||
listeners.removeIf { l: AutoBoneListener -> listener === l }
|
||||
listeners.removeIf { listener == it }
|
||||
}
|
||||
|
||||
private fun announceProcessStatus(
|
||||
processType: AutoBoneProcessType,
|
||||
message: String?,
|
||||
current: Long,
|
||||
total: Long,
|
||||
completed: Boolean,
|
||||
success: Boolean,
|
||||
) {
|
||||
listeners
|
||||
.forEach(
|
||||
Consumer { listener: AutoBoneListener ->
|
||||
listener
|
||||
.onAutoBoneProcessStatus(
|
||||
processType,
|
||||
message,
|
||||
current,
|
||||
total,
|
||||
completed,
|
||||
success
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun announceProcessStatus(
|
||||
processType: AutoBoneProcessType,
|
||||
message: String,
|
||||
message: String? = null,
|
||||
current: Long = -1L,
|
||||
total: Long = -1L,
|
||||
eta: Float = -1f,
|
||||
completed: Boolean = false,
|
||||
success: Boolean = true,
|
||||
) {
|
||||
announceProcessStatus(processType, message, 0, 0, completed, success)
|
||||
}
|
||||
|
||||
private fun announceProcessStatus(processType: AutoBoneProcessType, current: Long, total: Long) {
|
||||
announceProcessStatus(processType, null, current, total, completed = false, success = true)
|
||||
listeners.forEach {
|
||||
it.onAutoBoneProcessStatus(
|
||||
processType,
|
||||
message,
|
||||
current,
|
||||
total,
|
||||
eta,
|
||||
completed,
|
||||
success
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val lengthsString: String
|
||||
@@ -84,17 +67,16 @@ class AutoBoneHandler(private val server: VRServer) {
|
||||
@Throws(AutoBoneException::class)
|
||||
private fun processFrames(frames: PoseFrames): AutoBoneResults {
|
||||
return autoBone
|
||||
.processFrames(
|
||||
frames
|
||||
) { epoch: Epoch? -> listeners.forEach(Consumer { listener: AutoBoneListener -> listener.onAutoBoneEpoch(epoch!!) }) }
|
||||
.processFrames(frames) { epoch ->
|
||||
listeners.forEach { listener -> listener.onAutoBoneEpoch(epoch) }
|
||||
}
|
||||
}
|
||||
|
||||
fun startProcessByType(processType: AutoBoneProcessType): Boolean {
|
||||
fun startProcessByType(processType: AutoBoneProcessType?): Boolean {
|
||||
when (processType) {
|
||||
AutoBoneProcessType.RECORD -> startRecording()
|
||||
AutoBoneProcessType.SAVE -> saveRecording()
|
||||
AutoBoneProcessType.PROCESS -> processRecording()
|
||||
AutoBoneProcessType.APPLY -> applyValues()
|
||||
else -> {
|
||||
return false
|
||||
}
|
||||
@@ -117,9 +99,12 @@ class AutoBoneHandler(private val server: VRServer) {
|
||||
if (poseRecorder.isReadyToRecord) {
|
||||
announceProcessStatus(AutoBoneProcessType.RECORD, "Recording...")
|
||||
|
||||
// 1000 samples at 20 ms per sample is 20 seconds
|
||||
// ex. 1000 samples at 20 ms per sample is 20 seconds
|
||||
val sampleCount = autoBone.globalConfig.sampleCount
|
||||
val sampleRate = autoBone.globalConfig.sampleRateMs
|
||||
// Calculate total time in seconds
|
||||
val totalTime: Float = (sampleCount * sampleRate) / 1000f
|
||||
|
||||
val framesFuture = poseRecorder
|
||||
.startFrameRecording(
|
||||
sampleCount,
|
||||
@@ -127,9 +112,9 @@ class AutoBoneHandler(private val server: VRServer) {
|
||||
) { progress: RecordingProgress ->
|
||||
announceProcessStatus(
|
||||
AutoBoneProcessType.RECORD,
|
||||
progress.frame.toLong(),
|
||||
progress.totalFrames
|
||||
.toLong()
|
||||
current = progress.frame.toLong(),
|
||||
total = progress.totalFrames.toLong(),
|
||||
eta = totalTime - (progress.frame * totalTime / progress.totalFrames)
|
||||
)
|
||||
}
|
||||
val frames = framesFuture.get()
|
||||
@@ -145,7 +130,7 @@ class AutoBoneHandler(private val server: VRServer) {
|
||||
)
|
||||
autoBone.saveRecording(frames)
|
||||
}
|
||||
listeners.forEach(Consumer { listener: AutoBoneListener -> listener.onAutoBoneRecordingEnd(frames) })
|
||||
listeners.forEach { listener: AutoBoneListener -> listener.onAutoBoneRecordingEnd(frames) }
|
||||
announceProcessStatus(
|
||||
AutoBoneProcessType.RECORD,
|
||||
"Done recording!",
|
||||
@@ -175,6 +160,18 @@ class AutoBoneHandler(private val server: VRServer) {
|
||||
}
|
||||
}
|
||||
|
||||
fun stopRecording() {
|
||||
if (poseRecorder.isRecording) {
|
||||
poseRecorder.stopFrameRecording()
|
||||
}
|
||||
}
|
||||
|
||||
fun cancelRecording() {
|
||||
if (poseRecorder.isRecording) {
|
||||
poseRecorder.cancelFrameRecording()
|
||||
}
|
||||
}
|
||||
|
||||
fun saveRecording() {
|
||||
saveRecordingLock.withLock {
|
||||
// Prevent running multiple times
|
||||
@@ -373,7 +370,7 @@ class AutoBoneHandler(private val server: VRServer) {
|
||||
")"
|
||||
)
|
||||
// #endregion
|
||||
listeners.forEach(Consumer { listener: AutoBoneListener -> listener.onAutoBoneEnd(autoBone.legacyConfigs) })
|
||||
listeners.forEach { listener: AutoBoneListener -> listener.onAutoBoneEnd(autoBone.offsets) }
|
||||
announceProcessStatus(
|
||||
AutoBoneProcessType.PROCESS,
|
||||
"Done processing!",
|
||||
@@ -395,11 +392,5 @@ class AutoBoneHandler(private val server: VRServer) {
|
||||
|
||||
fun applyValues() {
|
||||
autoBone.applyAndSaveConfig()
|
||||
announceProcessStatus(
|
||||
AutoBoneProcessType.APPLY,
|
||||
"Adjusted values applied!",
|
||||
completed = true,
|
||||
success = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package dev.slimevr.autobone
|
||||
import dev.slimevr.autobone.AutoBone.Epoch
|
||||
import dev.slimevr.poseframeformat.PoseFrames
|
||||
import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets
|
||||
import java.util.EnumMap
|
||||
import java.util.*
|
||||
|
||||
interface AutoBoneListener {
|
||||
fun onAutoBoneProcessStatus(
|
||||
@@ -11,6 +11,7 @@ interface AutoBoneListener {
|
||||
message: String?,
|
||||
current: Long,
|
||||
total: Long,
|
||||
eta: Float,
|
||||
completed: Boolean,
|
||||
success: Boolean,
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package dev.slimevr.autobone
|
||||
|
||||
enum class AutoBoneProcessType(val id: Int) {
|
||||
NONE(0), RECORD(1), SAVE(2), PROCESS(3), APPLY(4);
|
||||
NONE(0), RECORD(1), SAVE(2), PROCESS(3);
|
||||
|
||||
companion object {
|
||||
fun getById(id: Int): AutoBoneProcessType? = byId[id]
|
||||
|
||||
@@ -4,9 +4,7 @@ import dev.slimevr.config.AutoBoneConfig
|
||||
import dev.slimevr.config.ConfigManager
|
||||
import dev.slimevr.poseframeformat.PoseFrames
|
||||
import dev.slimevr.poseframeformat.player.TrackerFramesPlayer
|
||||
import dev.slimevr.tracking.processor.BoneType
|
||||
import dev.slimevr.tracking.processor.HumanPoseManager
|
||||
import java.util.*
|
||||
import java.util.function.Consumer
|
||||
|
||||
class AutoBoneStep(
|
||||
@@ -14,8 +12,7 @@ class AutoBoneStep(
|
||||
val targetHmdHeight: Float,
|
||||
val targetFullHeight: Float,
|
||||
val frames: PoseFrames,
|
||||
val intermediateOffsets: EnumMap<BoneType, Float>,
|
||||
val epochCallback: Consumer<AutoBone.Epoch?>?,
|
||||
val epochCallback: Consumer<AutoBone.Epoch>?,
|
||||
serverConfig: ConfigManager,
|
||||
var curEpoch: Int = 0,
|
||||
var curAdjustRate: Float = 0f,
|
||||
@@ -24,6 +21,8 @@ class AutoBoneStep(
|
||||
var currentHmdHeight: Float = 0f,
|
||||
) {
|
||||
|
||||
val eyeHeightToHeightRatio: Float = targetHmdHeight / targetFullHeight
|
||||
|
||||
val maxFrameCount = frames.maxFrameCount
|
||||
|
||||
val framePlayer1 = TrackerFramesPlayer(frames)
|
||||
|
||||
@@ -13,12 +13,11 @@ class BodyProportionError : IAutoBoneError {
|
||||
override fun getStepError(trainingStep: AutoBoneStep): Float {
|
||||
return getBodyProportionError(
|
||||
trainingStep.skeleton1,
|
||||
trainingStep.currentHmdHeight
|
||||
trainingStep.currentHmdHeight / trainingStep.eyeHeightToHeightRatio
|
||||
)
|
||||
}
|
||||
|
||||
fun getBodyProportionError(humanPoseManager: HumanPoseManager, height: Float): Float {
|
||||
val fullHeight = height / eyeHeightToHeightRatio
|
||||
fun getBodyProportionError(humanPoseManager: HumanPoseManager, fullHeight: Float): Float {
|
||||
var sum = 0f
|
||||
for (limiter in proportionLimits) {
|
||||
sum += FastMath.abs(limiter.getProportionError(humanPoseManager, fullHeight))
|
||||
|
||||
@@ -20,8 +20,8 @@ class AutoBoneConfig {
|
||||
var targetFullHeight = -1f
|
||||
var randomizeFrameOrder = true
|
||||
var scaleEachStep = true
|
||||
var sampleCount = 1000
|
||||
var sampleRateMs: Long = 20
|
||||
var sampleCount = 1500
|
||||
var sampleRateMs = 20L
|
||||
var saveRecordings = false
|
||||
var useSkeletonHeight = false
|
||||
var randSeed = 4L
|
||||
|
||||
@@ -225,6 +225,16 @@ public class CurrentVRConfigConverter implements VersionedModelConverter {
|
||||
}
|
||||
}
|
||||
if (version < 9) {
|
||||
// Change default AutoBone recording length from 20 to 30
|
||||
// seconds
|
||||
ObjectNode autoBoneNode = (ObjectNode) modelData.get("autoBone");
|
||||
if (autoBoneNode != null) {
|
||||
JsonNode sampleCountNode = autoBoneNode.get("sampleCount");
|
||||
if (sampleCountNode != null && sampleCountNode.intValue() == 1000) {
|
||||
autoBoneNode.set("sampleCount", new IntNode(1500));
|
||||
}
|
||||
}
|
||||
|
||||
// split chest into 2 offsets
|
||||
ObjectNode skeletonNode = (ObjectNode) modelData.get("skeleton");
|
||||
if (skeletonNode != null) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import dev.slimevr.protocol.GenericConnection;
|
||||
import dev.slimevr.protocol.ProtocolAPI;
|
||||
import dev.slimevr.protocol.ProtocolHandler;
|
||||
import dev.slimevr.tracking.trackers.Tracker;
|
||||
import io.eiren.util.logging.LogManager;
|
||||
import solarxr_protocol.MessageBundle;
|
||||
import solarxr_protocol.data_feed.*;
|
||||
@@ -77,6 +78,7 @@ public class DataFeedHandler extends ProtocolHandler<DataFeedMessageHeader> {
|
||||
this.api.server.deviceManager
|
||||
.getDevices()
|
||||
);
|
||||
// Synthetic tracker is computed tracker apparently
|
||||
int trackersOffset = DataFeedBuilder
|
||||
.createSyntheticTrackersData(
|
||||
fbb,
|
||||
@@ -84,7 +86,7 @@ public class DataFeedHandler extends ProtocolHandler<DataFeedMessageHeader> {
|
||||
this.api.server
|
||||
.getAllTrackers()
|
||||
.stream()
|
||||
.filter(tracker -> tracker.getDevice() == null)
|
||||
.filter(Tracker::isComputed)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
package dev.slimevr.protocol.rpc;
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder;
|
||||
import dev.slimevr.autobone.AutoBone.Epoch;
|
||||
import dev.slimevr.autobone.AutoBoneListener;
|
||||
import dev.slimevr.autobone.AutoBoneProcessType;
|
||||
import dev.slimevr.autobone.errors.BodyProportionError;
|
||||
import dev.slimevr.config.OverlayConfig;
|
||||
import dev.slimevr.poseframeformat.PoseFrames;
|
||||
import dev.slimevr.protocol.GenericConnection;
|
||||
import dev.slimevr.protocol.ProtocolAPI;
|
||||
import dev.slimevr.protocol.ProtocolHandler;
|
||||
import dev.slimevr.protocol.rpc.autobone.RPCAutoBoneHandler;
|
||||
import dev.slimevr.protocol.rpc.reset.RPCResetHandler;
|
||||
import dev.slimevr.protocol.rpc.serial.RPCProvisioningHandler;
|
||||
import dev.slimevr.protocol.rpc.serial.RPCSerialHandler;
|
||||
@@ -26,13 +24,10 @@ import solarxr_protocol.MessageBundle;
|
||||
import solarxr_protocol.datatypes.TransactionId;
|
||||
import solarxr_protocol.rpc.*;
|
||||
|
||||
import java.util.EnumMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
|
||||
public class RPCHandler extends ProtocolHandler<RpcMessageHeader>
|
||||
implements AutoBoneListener {
|
||||
public class RPCHandler extends ProtocolHandler<RpcMessageHeader> {
|
||||
|
||||
private static final String resetSourceName = "WebSocketAPI";
|
||||
|
||||
@@ -50,6 +45,7 @@ public class RPCHandler extends ProtocolHandler<RpcMessageHeader>
|
||||
new RPCSettingsHandler(this, api);
|
||||
new RPCTapSetupHandler(this, api);
|
||||
new RPCStatusHandler(this, api);
|
||||
new RPCAutoBoneHandler(this, api);
|
||||
|
||||
registerPacketListener(RpcMessage.ResetRequest, this::onResetRequest);
|
||||
registerPacketListener(
|
||||
@@ -72,8 +68,6 @@ public class RPCHandler extends ProtocolHandler<RpcMessageHeader>
|
||||
this::onChangeSkeletonConfigRequest
|
||||
);
|
||||
|
||||
registerPacketListener(RpcMessage.AutoBoneProcessRequest, this::onAutoBoneProcessRequest);
|
||||
|
||||
registerPacketListener(
|
||||
RpcMessage.OverlayDisplayModeChangeRequest,
|
||||
this::onOverlayDisplayModeChangeRequest
|
||||
@@ -93,7 +87,7 @@ public class RPCHandler extends ProtocolHandler<RpcMessageHeader>
|
||||
|
||||
registerPacketListener(RpcMessage.SetPauseTrackingRequest, this::onSetPauseTrackingRequest);
|
||||
|
||||
this.api.server.autoBoneHandler.addListener(this);
|
||||
registerPacketListener(RpcMessage.HeightRequest, this::onHeightRequest);
|
||||
}
|
||||
|
||||
private void onServerInfosRequest(
|
||||
@@ -360,122 +354,6 @@ public class RPCHandler extends ProtocolHandler<RpcMessageHeader>
|
||||
return RpcMessage.names.length;
|
||||
}
|
||||
|
||||
public void onAutoBoneProcessRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
AutoBoneProcessRequest req = (AutoBoneProcessRequest) messageHeader
|
||||
.message(new AutoBoneProcessRequest());
|
||||
if (req == null || conn.getContext().useAutoBone())
|
||||
return;
|
||||
|
||||
conn.getContext().setUseAutoBone(true);
|
||||
this.api.server.autoBoneHandler
|
||||
.startProcessByType(AutoBoneProcessType.Companion.getById(req.processType()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneProcessStatus(
|
||||
AutoBoneProcessType processType,
|
||||
String message,
|
||||
long current,
|
||||
long total,
|
||||
boolean completed,
|
||||
boolean success
|
||||
) {
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.filter(conn -> conn.getContext().useAutoBone())
|
||||
.forEach((conn) -> {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
Integer messageOffset = message != null ? fbb.createString(message) : null;
|
||||
|
||||
AutoBoneProcessStatusResponse.startAutoBoneProcessStatusResponse(fbb);
|
||||
AutoBoneProcessStatusResponse.addProcessType(fbb, processType.getId());
|
||||
if (messageOffset != null)
|
||||
AutoBoneProcessStatusResponse.addMessage(fbb, messageOffset);
|
||||
if (total > 0 && current >= 0) {
|
||||
AutoBoneProcessStatusResponse.addCurrent(fbb, current);
|
||||
AutoBoneProcessStatusResponse.addTotal(fbb, total);
|
||||
}
|
||||
AutoBoneProcessStatusResponse.addCompleted(fbb, completed);
|
||||
AutoBoneProcessStatusResponse.addSuccess(fbb, success);
|
||||
int update = AutoBoneProcessStatusResponse
|
||||
.endAutoBoneProcessStatusResponse(fbb);
|
||||
int outbound = this
|
||||
.createRPCMessage(
|
||||
fbb,
|
||||
RpcMessage.AutoBoneProcessStatusResponse,
|
||||
update
|
||||
);
|
||||
fbb.finish(outbound);
|
||||
|
||||
conn.send(fbb.dataBuffer());
|
||||
if (completed) {
|
||||
conn.getContext().setUseAutoBone(false);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneRecordingEnd(PoseFrames recording) {
|
||||
// Do nothing, this is broadcasted by "onAutoBoneProcessStatus" uwu
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneEpoch(Epoch epoch) {
|
||||
this.api
|
||||
.getAPIServers()
|
||||
.forEach(
|
||||
(server) -> server
|
||||
.getAPIConnections()
|
||||
.filter(conn -> conn.getContext().useAutoBone())
|
||||
.forEach((conn) -> {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
int[] skeletonPartOffsets = new int[epoch.getConfigValues().size()];
|
||||
int i = 0;
|
||||
for (
|
||||
Entry<SkeletonConfigOffsets, Float> skeletonConfig : epoch
|
||||
.getConfigValues()
|
||||
.entrySet()
|
||||
) {
|
||||
skeletonPartOffsets[i++] = SkeletonPart
|
||||
.createSkeletonPart(
|
||||
fbb,
|
||||
skeletonConfig.getKey().id,
|
||||
skeletonConfig.getValue()
|
||||
);
|
||||
}
|
||||
|
||||
int skeletonPartsOffset = AutoBoneEpochResponse
|
||||
.createAdjustedSkeletonPartsVector(fbb, skeletonPartOffsets);
|
||||
|
||||
int update = AutoBoneEpochResponse
|
||||
.createAutoBoneEpochResponse(
|
||||
fbb,
|
||||
epoch.getEpoch(),
|
||||
epoch.getTotalEpochs(),
|
||||
epoch.getEpochError().getMean(),
|
||||
skeletonPartsOffset
|
||||
);
|
||||
int outbound = this
|
||||
.createRPCMessage(fbb, RpcMessage.AutoBoneEpochResponse, update);
|
||||
fbb.finish(outbound);
|
||||
|
||||
conn.send(fbb.dataBuffer());
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAutoBoneEnd(EnumMap<SkeletonConfigOffsets, Float> configValues) {
|
||||
// Do nothing, the last epoch from "onAutoBoneEpoch" should be all
|
||||
// that's needed
|
||||
}
|
||||
|
||||
public void onStatusSystemRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
StatusSystemRequest req = (StatusSystemRequest) messageHeader
|
||||
.message(new StatusSystemRequest());
|
||||
@@ -503,4 +381,18 @@ public class RPCHandler extends ProtocolHandler<RpcMessageHeader>
|
||||
|
||||
this.api.server.humanPoseManager.setPauseTracking(req.pauseTracking());
|
||||
}
|
||||
|
||||
public void onHeightRequest(GenericConnection conn, RpcMessageHeader messageHeader) {
|
||||
FlatBufferBuilder fbb = new FlatBufferBuilder(32);
|
||||
|
||||
float hmdHeight = this.api.server.humanPoseManager.getHmdHeight();
|
||||
int response = HeightResponse
|
||||
.createHeightResponse(
|
||||
fbb,
|
||||
hmdHeight,
|
||||
hmdHeight / BodyProportionError.eyeHeightToHeightRatio
|
||||
);
|
||||
fbb.finish(createRPCMessage(fbb, RpcMessage.HeightResponse, response));
|
||||
conn.send(fbb.dataBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,181 @@
|
||||
package dev.slimevr.protocol.rpc.autobone
|
||||
|
||||
import com.google.flatbuffers.FlatBufferBuilder
|
||||
import dev.slimevr.autobone.AutoBone.Epoch
|
||||
import dev.slimevr.autobone.AutoBoneListener
|
||||
import dev.slimevr.autobone.AutoBoneProcessType
|
||||
import dev.slimevr.autobone.AutoBoneProcessType.Companion.getById
|
||||
import dev.slimevr.poseframeformat.PoseFrames
|
||||
import dev.slimevr.protocol.GenericConnection
|
||||
import dev.slimevr.protocol.ProtocolAPI
|
||||
import dev.slimevr.protocol.rpc.RPCBuilder
|
||||
import dev.slimevr.protocol.rpc.RPCHandler
|
||||
import dev.slimevr.tracking.processor.config.SkeletonConfigOffsets
|
||||
import solarxr_protocol.rpc.AutoBoneEpochResponse
|
||||
import solarxr_protocol.rpc.AutoBoneProcessRequest
|
||||
import solarxr_protocol.rpc.AutoBoneProcessStatusResponse
|
||||
import solarxr_protocol.rpc.RpcMessage
|
||||
import solarxr_protocol.rpc.RpcMessageHeader
|
||||
import solarxr_protocol.rpc.SkeletonPart
|
||||
import java.util.*
|
||||
|
||||
class RPCAutoBoneHandler(
|
||||
private val rpcHandler: RPCHandler,
|
||||
val api: ProtocolAPI,
|
||||
) : AutoBoneListener {
|
||||
init {
|
||||
rpcHandler.registerPacketListener(
|
||||
RpcMessage.AutoBoneProcessRequest,
|
||||
::onAutoBoneProcessRequest
|
||||
)
|
||||
rpcHandler.registerPacketListener(
|
||||
RpcMessage.AutoBoneApplyRequest,
|
||||
::onAutoBoneApplyRequest
|
||||
)
|
||||
rpcHandler.registerPacketListener(
|
||||
RpcMessage.AutoBoneStopRecordingRequest,
|
||||
::onAutoBoneStopRecordingRequest
|
||||
)
|
||||
rpcHandler.registerPacketListener(
|
||||
RpcMessage.AutoBoneCancelRecordingRequest,
|
||||
::onAutoBoneCancelRecordingRequest
|
||||
)
|
||||
|
||||
this.api.server.autoBoneHandler.addListener(this)
|
||||
}
|
||||
|
||||
fun onAutoBoneProcessRequest(
|
||||
conn: GenericConnection,
|
||||
messageHeader: RpcMessageHeader,
|
||||
) {
|
||||
val req = messageHeader
|
||||
.message(AutoBoneProcessRequest()) as AutoBoneProcessRequest
|
||||
if (conn.context.useAutoBone()) return
|
||||
conn.context.setUseAutoBone(true)
|
||||
api.server
|
||||
.autoBoneHandler
|
||||
.startProcessByType(getById(req.processType()))
|
||||
}
|
||||
|
||||
override fun onAutoBoneProcessStatus(
|
||||
processType: AutoBoneProcessType,
|
||||
message: String?,
|
||||
current: Long,
|
||||
total: Long,
|
||||
eta: Float,
|
||||
completed: Boolean,
|
||||
success: Boolean,
|
||||
) {
|
||||
forAllListeners { conn ->
|
||||
if (!conn.context.useAutoBone()) {
|
||||
return@forAllListeners
|
||||
}
|
||||
|
||||
val fbb = FlatBufferBuilder(32)
|
||||
|
||||
AutoBoneProcessStatusResponse.startAutoBoneProcessStatusResponse(fbb)
|
||||
AutoBoneProcessStatusResponse.addProcessType(
|
||||
fbb,
|
||||
processType.id
|
||||
)
|
||||
|
||||
AutoBoneProcessStatusResponse.addCurrent(fbb, current)
|
||||
AutoBoneProcessStatusResponse.addTotal(fbb, total)
|
||||
AutoBoneProcessStatusResponse.addEta(fbb, eta)
|
||||
AutoBoneProcessStatusResponse.addCompleted(fbb, completed)
|
||||
AutoBoneProcessStatusResponse.addSuccess(fbb, success)
|
||||
|
||||
val update = AutoBoneProcessStatusResponse
|
||||
.endAutoBoneProcessStatusResponse(fbb)
|
||||
val outbound: Int = rpcHandler.createRPCMessage(
|
||||
fbb,
|
||||
RpcMessage.AutoBoneProcessStatusResponse,
|
||||
update
|
||||
)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
if (completed) {
|
||||
conn.context.setUseAutoBone(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAutoBoneRecordingEnd(recording: PoseFrames) {
|
||||
// Do nothing, this is broadcasted by "onAutoBoneProcessStatus" uwu
|
||||
}
|
||||
|
||||
override fun onAutoBoneEpoch(epoch: Epoch) {
|
||||
forAllListeners { conn ->
|
||||
if (!conn.context.useAutoBone()) {
|
||||
return@forAllListeners
|
||||
}
|
||||
|
||||
val fbb = FlatBufferBuilder(32)
|
||||
|
||||
val skeletonPartsOffset = AutoBoneEpochResponse
|
||||
.createAdjustedSkeletonPartsVector(
|
||||
fbb,
|
||||
epoch.configValues.map { (key, value) ->
|
||||
SkeletonPart.createSkeletonPart(fbb, key.id, value)
|
||||
}.toIntArray()
|
||||
)
|
||||
val update = AutoBoneEpochResponse
|
||||
.createAutoBoneEpochResponse(
|
||||
fbb,
|
||||
epoch.epoch.toLong(),
|
||||
epoch.totalEpochs.toLong(),
|
||||
epoch.epochError.mean,
|
||||
skeletonPartsOffset
|
||||
)
|
||||
val outbound: Int = rpcHandler.createRPCMessage(
|
||||
fbb,
|
||||
RpcMessage.AutoBoneEpochResponse,
|
||||
update
|
||||
)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAutoBoneEnd(configValues: EnumMap<SkeletonConfigOffsets, Float>) {
|
||||
// Do nothing, the last epoch from "onAutoBoneEpoch" should be all
|
||||
// that's needed
|
||||
}
|
||||
|
||||
private fun onAutoBoneApplyRequest(
|
||||
conn: GenericConnection,
|
||||
messageHeader: RpcMessageHeader,
|
||||
) {
|
||||
api.server.autoBoneHandler.applyValues()
|
||||
|
||||
// Send the new body proportions, this is to update the listener's state
|
||||
val fbb = FlatBufferBuilder(300)
|
||||
val outbound = rpcHandler.createRPCMessage(
|
||||
fbb,
|
||||
RpcMessage.SkeletonConfigResponse,
|
||||
RPCBuilder.createSkeletonConfig(fbb, api.server.humanPoseManager)
|
||||
)
|
||||
fbb.finish(outbound)
|
||||
conn.send(fbb.dataBuffer())
|
||||
}
|
||||
|
||||
private fun onAutoBoneStopRecordingRequest(
|
||||
conn: GenericConnection,
|
||||
messageHeader: RpcMessageHeader,
|
||||
) {
|
||||
api.server.autoBoneHandler.stopRecording()
|
||||
}
|
||||
|
||||
private fun onAutoBoneCancelRecordingRequest(
|
||||
conn: GenericConnection,
|
||||
messageHeader: RpcMessageHeader,
|
||||
) {
|
||||
api.server.autoBoneHandler.cancelRecording()
|
||||
}
|
||||
|
||||
private fun forAllListeners(action: (GenericConnection) -> Unit) {
|
||||
api.apiServers.forEach {
|
||||
it.apiConnections.forEach(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,4 +204,126 @@ public class RPCSettingsBuilder {
|
||||
);
|
||||
return ModelSettings.createModelSettings(fbb, togglesOffset, ratiosOffset, legTweaksOffset);
|
||||
}
|
||||
|
||||
public static int createAutoBoneSettings(FlatBufferBuilder fbb, AutoBoneConfig autoBoneConfig) {
|
||||
return AutoBoneSettings
|
||||
.createAutoBoneSettings(
|
||||
fbb,
|
||||
autoBoneConfig.getCursorIncrement(),
|
||||
autoBoneConfig.getMinDataDistance(),
|
||||
autoBoneConfig.getMaxDataDistance(),
|
||||
autoBoneConfig.getNumEpochs(),
|
||||
autoBoneConfig.getPrintEveryNumEpochs(),
|
||||
autoBoneConfig.getInitialAdjustRate(),
|
||||
autoBoneConfig.getAdjustRateDecay(),
|
||||
autoBoneConfig.getSlideErrorFactor(),
|
||||
autoBoneConfig.getOffsetSlideErrorFactor(),
|
||||
autoBoneConfig.getFootHeightOffsetErrorFactor(),
|
||||
autoBoneConfig.getBodyProportionErrorFactor(),
|
||||
autoBoneConfig.getHeightErrorFactor(),
|
||||
autoBoneConfig.getPositionErrorFactor(),
|
||||
autoBoneConfig.getPositionOffsetErrorFactor(),
|
||||
autoBoneConfig.getCalcInitError(),
|
||||
autoBoneConfig.getTargetHmdHeight(),
|
||||
autoBoneConfig.getTargetFullHeight(),
|
||||
autoBoneConfig.getRandomizeFrameOrder(),
|
||||
autoBoneConfig.getScaleEachStep(),
|
||||
autoBoneConfig.getSampleCount(),
|
||||
autoBoneConfig.getSampleRateMs(),
|
||||
autoBoneConfig.getSaveRecordings(),
|
||||
autoBoneConfig.getUseSkeletonHeight(),
|
||||
autoBoneConfig.getRandSeed()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Writes values from AutoBoneSettings to an AutoBoneConfig.
|
||||
*
|
||||
* @param autoBoneSettings The settings to read from.
|
||||
* @param autoBoneConfig The config to write to.
|
||||
* @return The autoBoneConfig parameter.
|
||||
*/
|
||||
public static AutoBoneConfig readAutoBoneSettings(
|
||||
AutoBoneSettings autoBoneSettings,
|
||||
AutoBoneConfig autoBoneConfig
|
||||
) {
|
||||
if (autoBoneSettings.hasCursorIncrement()) {
|
||||
autoBoneConfig.setCursorIncrement(autoBoneSettings.cursorIncrement());
|
||||
}
|
||||
if (autoBoneSettings.hasMinDataDistance()) {
|
||||
autoBoneConfig.setMinDataDistance(autoBoneSettings.minDataDistance());
|
||||
}
|
||||
if (autoBoneSettings.hasMaxDataDistance()) {
|
||||
autoBoneConfig.setMaxDataDistance(autoBoneSettings.maxDataDistance());
|
||||
}
|
||||
if (autoBoneSettings.hasNumEpochs()) {
|
||||
autoBoneConfig.setNumEpochs(autoBoneSettings.numEpochs());
|
||||
}
|
||||
if (autoBoneSettings.hasPrintEveryNumEpochs()) {
|
||||
autoBoneConfig.setPrintEveryNumEpochs(autoBoneSettings.printEveryNumEpochs());
|
||||
}
|
||||
if (autoBoneSettings.hasInitialAdjustRate()) {
|
||||
autoBoneConfig.setInitialAdjustRate(autoBoneSettings.initialAdjustRate());
|
||||
}
|
||||
if (autoBoneSettings.hasAdjustRateDecay()) {
|
||||
autoBoneConfig.setAdjustRateDecay(autoBoneSettings.adjustRateDecay());
|
||||
}
|
||||
if (autoBoneSettings.hasSlideErrorFactor()) {
|
||||
autoBoneConfig.setSlideErrorFactor(autoBoneSettings.slideErrorFactor());
|
||||
}
|
||||
if (autoBoneSettings.hasOffsetSlideErrorFactor()) {
|
||||
autoBoneConfig.setOffsetSlideErrorFactor(autoBoneSettings.offsetSlideErrorFactor());
|
||||
}
|
||||
if (autoBoneSettings.hasFootHeightOffsetErrorFactor()) {
|
||||
autoBoneConfig
|
||||
.setFootHeightOffsetErrorFactor(autoBoneSettings.footHeightOffsetErrorFactor());
|
||||
}
|
||||
if (autoBoneSettings.hasBodyProportionErrorFactor()) {
|
||||
autoBoneConfig
|
||||
.setBodyProportionErrorFactor(autoBoneSettings.bodyProportionErrorFactor());
|
||||
}
|
||||
if (autoBoneSettings.hasHeightErrorFactor()) {
|
||||
autoBoneConfig.setHeightErrorFactor(autoBoneSettings.heightErrorFactor());
|
||||
}
|
||||
if (autoBoneSettings.hasPositionErrorFactor()) {
|
||||
autoBoneConfig.setPositionErrorFactor(autoBoneSettings.positionErrorFactor());
|
||||
}
|
||||
if (autoBoneSettings.hasPositionOffsetErrorFactor()) {
|
||||
autoBoneConfig
|
||||
.setPositionOffsetErrorFactor(autoBoneSettings.positionOffsetErrorFactor());
|
||||
}
|
||||
if (autoBoneSettings.hasCalcInitError()) {
|
||||
autoBoneConfig.setCalcInitError(autoBoneSettings.calcInitError());
|
||||
}
|
||||
if (autoBoneSettings.hasTargetHmdHeight()) {
|
||||
autoBoneConfig.setTargetHmdHeight(autoBoneSettings.targetHmdHeight());
|
||||
}
|
||||
if (autoBoneSettings.hasTargetFullHeight()) {
|
||||
autoBoneConfig.setTargetFullHeight(autoBoneSettings.targetFullHeight());
|
||||
}
|
||||
if (autoBoneSettings.hasRandomizeFrameOrder()) {
|
||||
autoBoneConfig.setRandomizeFrameOrder(autoBoneSettings.randomizeFrameOrder());
|
||||
}
|
||||
if (autoBoneSettings.hasScaleEachStep()) {
|
||||
autoBoneConfig.setScaleEachStep(autoBoneSettings.scaleEachStep());
|
||||
}
|
||||
if (autoBoneSettings.hasSampleCount()) {
|
||||
autoBoneConfig.setSampleCount(autoBoneSettings.sampleCount());
|
||||
}
|
||||
if (autoBoneSettings.hasSampleRateMs()) {
|
||||
autoBoneConfig.setSampleRateMs(autoBoneSettings.sampleRateMs());
|
||||
}
|
||||
if (autoBoneSettings.hasSaveRecordings()) {
|
||||
autoBoneConfig.setSaveRecordings(autoBoneSettings.saveRecordings());
|
||||
}
|
||||
if (autoBoneSettings.hasUseSkeletonHeight()) {
|
||||
autoBoneConfig.setUseSkeletonHeight(autoBoneSettings.useSkeletonHeight());
|
||||
}
|
||||
if (autoBoneSettings.hasRandSeed()) {
|
||||
autoBoneConfig.setRandSeed(autoBoneSettings.randSeed());
|
||||
}
|
||||
|
||||
return autoBoneConfig;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,11 @@ public class RPCSettingsHandler {
|
||||
.createTapDetectionSettings(
|
||||
fbb,
|
||||
this.api.server.configManager.getVrConfig().getTapDetection()
|
||||
),
|
||||
RPCSettingsBuilder
|
||||
.createAutoBoneSettings(
|
||||
fbb,
|
||||
this.api.server.configManager.getVrConfig().getAutoBone()
|
||||
)
|
||||
);
|
||||
int outbound = rpcHandler.createRPCMessage(fbb, RpcMessage.SettingsResponse, settings);
|
||||
@@ -358,6 +363,15 @@ public class RPCSettingsHandler {
|
||||
|
||||
}
|
||||
|
||||
var autoBoneSettings = req.autoBoneSettings();
|
||||
if (autoBoneSettings != null) {
|
||||
AutoBoneConfig autoBoneConfig = this.api.server.configManager
|
||||
.getVrConfig()
|
||||
.getAutoBone();
|
||||
|
||||
RPCSettingsBuilder.readAutoBoneSettings(autoBoneSettings, autoBoneConfig);
|
||||
}
|
||||
|
||||
this.api.server.configManager.saveConfig();
|
||||
}
|
||||
|
||||
|
||||
@@ -23,9 +23,18 @@ const val TIMEOUT_MS = 2000L
|
||||
*/
|
||||
class Tracker @JvmOverloads constructor(
|
||||
val device: Device?,
|
||||
val id: Int, // VRServer.nextLocalTrackerId
|
||||
val name: String, // unique, for config
|
||||
val displayName: String = "Tracker #$id", // default display GUI name
|
||||
/**
|
||||
* VRServer.nextLocalTrackerId
|
||||
*/
|
||||
val id: Int,
|
||||
/**
|
||||
* unique, for config
|
||||
*/
|
||||
val name: String,
|
||||
/**
|
||||
* default display GUI name
|
||||
*/
|
||||
val displayName: String = "Tracker #$id",
|
||||
trackerPosition: TrackerPosition?,
|
||||
/**
|
||||
* It's like the ID, but it should be local to the device if it has one
|
||||
@@ -34,11 +43,23 @@ class Tracker @JvmOverloads constructor(
|
||||
val hasPosition: Boolean = false,
|
||||
val hasRotation: Boolean = false,
|
||||
val hasAcceleration: Boolean = false,
|
||||
val userEditable: Boolean = false, // User can change TrackerPosition, mounting...
|
||||
val isInternal: Boolean = false, // Is used within SlimeVR (shareable trackers)
|
||||
val isComputed: Boolean = false, // Has solved position + rotation (Vive trackers)
|
||||
/**
|
||||
* User can change TrackerPosition, mounting...
|
||||
*/
|
||||
val userEditable: Boolean = false,
|
||||
/**
|
||||
* Is used within SlimeVR (shareable trackers)
|
||||
*/
|
||||
val isInternal: Boolean = false,
|
||||
/**
|
||||
* Has solved position + rotation (Vive trackers)
|
||||
*/
|
||||
val isComputed: Boolean = false,
|
||||
val imuType: IMUType? = null,
|
||||
val usesTimeout: Boolean = false, // Automatically set the status to DISCONNECTED
|
||||
/**
|
||||
* Automatically set the status to DISCONNECTED
|
||||
*/
|
||||
val usesTimeout: Boolean = false,
|
||||
val allowFiltering: Boolean = false,
|
||||
val needsReset: Boolean = false,
|
||||
val needsMounting: Boolean = false,
|
||||
|
||||
Submodule solarxr-protocol updated: e0718504e7...c22d4729ec
Reference in New Issue
Block a user