From b2cf2ec3e380f3054c29650671e9150d250cf533 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Tue, 28 Jul 2026 20:36:15 -0500 Subject: [PATCH 01/32] Re-add bone references. --- .../java/dev/slimevr/resets/bodypart-sets.kt | 11 +++++++ .../src/main/java/dev/slimevr/vmc/mapping.kt | 29 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt b/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt index f5aafebb44..910ff95694 100644 --- a/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt +++ b/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt @@ -75,3 +75,14 @@ val RIGHT_FINGER_PARTS = setOf( BodyPart.RIGHT_LITTLE_INTERMEDIATE, BodyPart.RIGHT_LITTLE_DISTAL, ) + +val LEFT_TOE_PARTS = setOf( + BodyPart.LEFT_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, +) +val RIGHT_TOE_PARTS = setOf( + BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, +) diff --git a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt index 3118747a63..f6c3641739 100644 --- a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt +++ b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt @@ -58,6 +58,12 @@ val BODY_PART_TO_UNITY_BONE: Map = mapOf( BodyPart.RIGHT_LITTLE_PROXIMAL to "RightLittleProximal", BodyPart.RIGHT_LITTLE_INTERMEDIATE to "RightLittleIntermediate", BodyPart.RIGHT_LITTLE_DISTAL to "RightLittleDistal", + BodyPart.LEFT_ABDUCTOR_HALLUCIS to "LeftAbductorHallucis", + BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS to "LeftFlexorDigitorumBrevis", + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI to "LeftAbductorDigitiMinimi", + BodyPart.RIGHT_ABDUCTOR_HALLUCIS to "RightAbductorHallucis", + BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS to "RightFlexorDigitorumBrevis", + BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI to "RightAbductorDigitiMinimi" ) // HIP-rooted hierarchy. VMC/Unity expects this; our skeleton is HEAD-rooted. @@ -111,6 +117,16 @@ val VMC_HIERARCHY_MAP: Map> = mapOf( BodyPart.RIGHT_RING_INTERMEDIATE to arrayOf(BodyPart.RIGHT_RING_DISTAL), BodyPart.RIGHT_LITTLE_PROXIMAL to arrayOf(BodyPart.RIGHT_LITTLE_INTERMEDIATE), BodyPart.RIGHT_LITTLE_INTERMEDIATE to arrayOf(BodyPart.RIGHT_LITTLE_DISTAL), + BodyPart.LEFT_FOOT to arrayOf( + BodyPart.LEFT_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + ), + BodyPart.RIGHT_FOOT to arrayOf( + BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + ) ) private suspend fun SequenceScope>.visitVMC(parent: BodyPart?, bone: BodyPart) { @@ -146,6 +162,9 @@ val VMC_MIRROR_BONE_PAIRS: List> = listOf( BodyPart.LEFT_LITTLE_PROXIMAL to BodyPart.RIGHT_LITTLE_PROXIMAL, BodyPart.LEFT_LITTLE_INTERMEDIATE to BodyPart.RIGHT_LITTLE_INTERMEDIATE, BodyPart.LEFT_LITTLE_DISTAL to BodyPart.RIGHT_LITTLE_DISTAL, + BodyPart.LEFT_ABDUCTOR_HALLUCIS to BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS to BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI to BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI ) val VMC_MIRROR_BONES: BodyPartMap = BodyPartMap( @@ -177,6 +196,12 @@ val VMC_REST_ROTATIONS: BodyPartMap = run { BodyPart.RIGHT_RING_PROXIMAL, BodyPart.RIGHT_RING_INTERMEDIATE, BodyPart.RIGHT_RING_DISTAL, BodyPart.RIGHT_LITTLE_PROXIMAL, BodyPart.RIGHT_LITTLE_INTERMEDIATE, BodyPart.RIGHT_LITTLE_DISTAL, ) + val leftToes = listOf( + BodyPart.LEFT_ABDUCTOR_HALLUCIS, BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + ) + val rightToes = listOf( + BodyPart.RIGHT_ABDUCTOR_HALLUCIS, BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + ) BodyPartMap( mapOf( BodyPart.LEFT_FOOT to foot, @@ -189,7 +214,9 @@ val VMC_REST_ROTATIONS: BodyPartMap = run { BodyPart.RIGHT_HAND to rightArm, ) + leftFingers.associateWith { leftArm } + - rightFingers.associateWith { rightArm }, + rightFingers.associateWith { rightArm } + + leftToes.associateWith { foot } + + rightToes.associateWith { foot }, ) } From cb0e6bcb211b2e156963cda00b9483b6c6ea00ad Mon Sep 17 00:00:00 2001 From: Sebastina Date: Tue, 28 Jul 2026 21:54:18 -0500 Subject: [PATCH 02/32] Restore toes back to the UI. --- gui/app/src/components/Toolbar.tsx | 9 +++- .../src/components/commons/BodyPartIcon.tsx | 18 ++++++++ gui/app/src/components/home/ResetButton.tsx | 1 + gui/app/src/hooks/body-parts.ts | 15 +++++++ gui/app/src/hooks/reset.ts | 3 +- gui/app/src/utils/skeletonHelper.ts | 41 +++++++++++++++++-- .../slimevr/skeleton/bodypart-structure.kt | 12 ++++++ .../dev/slimevr/skeleton/bone-geometry.kt | 10 ++++- 8 files changed, 103 insertions(+), 6 deletions(-) diff --git a/gui/app/src/components/Toolbar.tsx b/gui/app/src/components/Toolbar.tsx index 9a5c8813d0..f43d490522 100644 --- a/gui/app/src/components/Toolbar.tsx +++ b/gui/app/src/components/Toolbar.tsx @@ -147,7 +147,7 @@ export function Toolbar() { const { visibleGroups, groupVisibility } = useMemo(() => { const groupVisibility = Object.keys(BODY_PARTS_GROUPS) - .filter((k) => ['fingers'].includes(k)) + .filter((k) => ['fingers', 'toes'].includes(k)) .reduce( (curr, key) => { const group = key as MountingResetGroup; @@ -200,6 +200,13 @@ export function Toolbar() { group={'feet'} customName="toolbar-mounting_calibration-feet" /> + {groupVisibility['toes'] && ( + + )} {groupVisibility['fingers'] && ( ), [BodyPart.RIGHT_LITTLE_DISTAL]: ({ width }) => , + [BodyPart.LEFT_TOES_ABDUCTOR_HALLUCIS]: renderFootLeft, + [BodyPart.LEFT_TOES_DIGITORUM_BREVIS]: renderFootLeft, + [BodyPart.LEFT_TOES_ABDUCTOR_DIGITI_MINIMI]: renderFootLeft, + [BodyPart.RIGHT_TOES_ABDUCTOR_HALLUCIS]: renderFootRight, + [BodyPart.RIGHT_TOES_DIGITORUM_BREVIS]: renderFootRight, + [BodyPart.RIGHT_TOES_ABDUCTOR_DIGITI_MINIMI]: renderFootRight, }; +function renderFootLeft({ width, currentLocales }) { + if (currentLocales.includes('en-x-owo')) { + return ; + } + return ; +} +function renderFootRight({ width, currentLocales }) { + if (currentLocales.includes('en-x-owo')) { + return ; + } + return ; +} export function BodyPartIcon({ bodyPart = BodyPart.NONE, width = 24, diff --git a/gui/app/src/components/home/ResetButton.tsx b/gui/app/src/components/home/ResetButton.tsx index ebe4018d64..659632a29c 100644 --- a/gui/app/src/components/home/ResetButton.tsx +++ b/gui/app/src/components/home/ResetButton.tsx @@ -23,6 +23,7 @@ export function ResetButtonIcon(options: UseResetOptions) { if (options.type === ResetType.MOUNTING) { if (options.group === 'default') return ; if (options.group === 'feet') return ; + if (options.group === 'toes') return ; if (options.group === 'fingers') return ; } } diff --git a/gui/app/src/hooks/body-parts.ts b/gui/app/src/hooks/body-parts.ts index a2ac2766f0..0d72b6be30 100644 --- a/gui/app/src/hooks/body-parts.ts +++ b/gui/app/src/hooks/body-parts.ts @@ -54,6 +54,21 @@ export const ALL_BODY_PARTS = [ BodyPart.RIGHT_LITTLE_PROXIMAL, BodyPart.RIGHT_LITTLE_INTERMEDIATE, BodyPart.RIGHT_LITTLE_DISTAL, + BodyPart.LEFT_TOES_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_TOES_DIGITORUM_BREVIS, + BodyPart.LEFT_TOES_ABDUCTOR_DIGITI_MINIMI, + BodyPart.RIGHT_TOES_ABDUCTOR_HALLUCIS, + BodyPart.RIGHT_TOES_DIGITORUM_BREVIS, + BodyPart.RIGHT_TOES_ABDUCTOR_DIGITI_MINIMI +]; + +export const TOE_BODY_PARTS = [ + BodyPart.LEFT_TOES_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_TOES_DIGITORUM_BREVIS, + BodyPart.LEFT_TOES_ABDUCTOR_DIGITI_MINIMI, + BodyPart.RIGHT_TOES_ABDUCTOR_HALLUCIS, + BodyPart.RIGHT_TOES_DIGITORUM_BREVIS, + BodyPart.RIGHT_TOES_ABDUCTOR_DIGITI_MINIMI ]; export const FEET_BODY_PARTS = [BodyPart.LEFT_FOOT, BodyPart.RIGHT_FOOT]; export const FINGER_BODY_PARTS = [ diff --git a/gui/app/src/hooks/reset.ts b/gui/app/src/hooks/reset.ts index 14c1add812..33c58d6b8a 100644 --- a/gui/app/src/hooks/reset.ts +++ b/gui/app/src/hooks/reset.ts @@ -10,7 +10,7 @@ import { import { useWebsocketAPI } from './websocket-api'; import { useAtomValue } from 'jotai'; import { assignedTrackersAtom, serverGuardsAtom } from '@/store/app-store'; -import { FEET_BODY_PARTS, FINGER_BODY_PARTS } from './body-parts'; +import { FEET_BODY_PARTS, FINGER_BODY_PARTS, TOE_BODY_PARTS } from './body-parts'; import { useLocaleConfig } from '@/i18n/config'; import * as Sentry from '@sentry/react'; @@ -24,6 +24,7 @@ export type UseResetOptions = export const BODY_PARTS_GROUPS: Record = { default: [], feet: FEET_BODY_PARTS, + toes: TOE_BODY_PARTS, fingers: FINGER_BODY_PARTS, }; diff --git a/gui/app/src/utils/skeletonHelper.ts b/gui/app/src/utils/skeletonHelper.ts index 8d8c379d9c..aa042dde39 100644 --- a/gui/app/src/utils/skeletonHelper.ts +++ b/gui/app/src/utils/skeletonHelper.ts @@ -239,6 +239,13 @@ export class BoneKind extends Bone { case BodyPart.RIGHT_LITTLE_INTERMEDIATE: case BodyPart.RIGHT_LITTLE_DISTAL: return new Color('pink'); + case BodyPart.LEFT_ABDUCTOR_HALLUCIS: + case BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS: + case BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI: + case BodyPart.RIGHT_ABDUCTOR_HALLUCIS: + case BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS: + case BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI: + return new Color('pink'); } } @@ -274,10 +281,17 @@ export class BoneKind extends Bone { case BodyPart.RIGHT_LOWER_LEG: return [BodyPart.RIGHT_FOOT]; case BodyPart.LEFT_FOOT: - return []; + return [ + BodyPart.LEFT_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + ]; case BodyPart.RIGHT_FOOT: - return []; - + return [ + BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + ]; case BodyPart.LEFT_SHOULDER: return [BodyPart.LEFT_UPPER_ARM]; case BodyPart.RIGHT_SHOULDER: @@ -367,6 +381,14 @@ export class BoneKind extends Bone { return [BodyPart.RIGHT_LITTLE_DISTAL]; case BodyPart.RIGHT_LITTLE_DISTAL: return []; + return []; + case BodyPart.LEFT_ABDUCTOR_HALLUCIS: + case BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS: + case BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI: + case BodyPart.RIGHT_ABDUCTOR_HALLUCIS: + case BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS: + case BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI: + return []; } } @@ -479,6 +501,19 @@ export class BoneKind extends Bone { return BodyPart.RIGHT_LITTLE_PROXIMAL; case BodyPart.RIGHT_LITTLE_DISTAL: return BodyPart.RIGHT_LITTLE_INTERMEDIATE; + + case BodyPart.LEFT_ABDUCTOR_HALLUCIS: + return BodyPart.LEFT_FOOT; + case BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS: + return BodyPart.LEFT_FOOT; + case BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI: + return BodyPart.LEFT_FOOT; + case BodyPart.RIGHT_ABDUCTOR_HALLUCIS: + return BodyPart.RIGHT_FOOT; + case BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS: + return BodyPart.RIGHT_FOOT; + case BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI: + return BodyPart.RIGHT_FOOT; } } } diff --git a/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt b/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt index 3d5e38e8d9..3fec40eae7 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt @@ -63,6 +63,18 @@ val BODY_PART_HIERARCHY_MAP: BodyPartMap> = BodyPartMap( BodyPart.RIGHT_HIP to arrayOf(BodyPart.RIGHT_UPPER_LEG), BodyPart.RIGHT_UPPER_LEG to arrayOf(BodyPart.RIGHT_LOWER_LEG), BodyPart.RIGHT_LOWER_LEG to arrayOf(BodyPart.RIGHT_FOOT), + + BodyPart.LEFT_FOOT to arrayOf( + BodyPart.LEFT_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + ), + + BodyPart.RIGHT_FOOT to arrayOf( + BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + ), ), ) diff --git a/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt b/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt index 52970934d4..3f3564cf26 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt @@ -9,5 +9,13 @@ private val FINGER_OFFSETS = ( iterateBodyPartHierarchy(BodyPart.RIGHT_HAND, true) ).map { it.second }.associateWith { Vector3(0f, -0.025f, 0f) } +private val TOE_OFFSETS = ( + iterateBodyPartHierarchy(BodyPart.LEFT_FOOT, true) + + iterateBodyPartHierarchy(BodyPart.RIGHT_FOOT, true) + ).map { it.second }.associateWith { Vector3(0f, 0f, -0.04f) } + val DEFAULT_BONE_OFFSETS: BodyPartMap = DEFAULT_PROPORTIONS.toBoneOffsets() - .mutate { offsets -> for ((bodyPart, offset) in FINGER_OFFSETS) offsets[bodyPart] = offset } + .mutate { offsets -> + for ((bodyPart, offset) in FINGER_OFFSETS) offsets[bodyPart] = offset + for ((bodyPart, offset) in TOE_OFFSETS) offsets[bodyPart] = offset + } From db58dde1f8b9e557b92863de772c99eb1b62c57c Mon Sep 17 00:00:00 2001 From: Sebastina Date: Tue, 28 Jul 2026 23:08:46 -0500 Subject: [PATCH 03/32] Toe assigments and toe rotation sharing restored. --- gui/app/src/components/Toolbar.tsx | 2 +- .../components/commons/BodyInteractions.tsx | 17 +- .../src/components/commons/BodyPartIcon.tsx | 30 +- .../components/commons/PersonFrontIcon.tsx | 21 ++ .../components/onboarding/BodyAssignment.tsx | 20 ++ .../pages/mounting/MountingSelectionMenu.tsx | 58 +++- .../trackers-assign/ToeAssignmentModal.tsx | 315 ++++++++++++++++++ .../trackers-assign/TrackerAssignment.tsx | 10 + gui/app/src/hooks/body-parts.ts | 24 +- gui/app/src/hooks/reset.ts | 2 +- .../main/java/dev/slimevr/skeleton/module.kt | 2 + .../skeleton/processors/toes-impute.kt | 41 +++ 12 files changed, 515 insertions(+), 27 deletions(-) create mode 100644 gui/app/src/components/onboarding/pages/trackers-assign/ToeAssignmentModal.tsx create mode 100644 server/core/src/main/java/dev/slimevr/skeleton/processors/toes-impute.kt diff --git a/gui/app/src/components/Toolbar.tsx b/gui/app/src/components/Toolbar.tsx index f43d490522..dcae37cd2b 100644 --- a/gui/app/src/components/Toolbar.tsx +++ b/gui/app/src/components/Toolbar.tsx @@ -202,7 +202,7 @@ export function Toolbar() { /> {groupVisibility['toes'] && ( diff --git a/gui/app/src/components/commons/BodyInteractions.tsx b/gui/app/src/components/commons/BodyInteractions.tsx index 71d1008511..6c06fd8c0e 100644 --- a/gui/app/src/components/commons/BodyInteractions.tsx +++ b/gui/app/src/components/commons/BodyInteractions.tsx @@ -13,6 +13,7 @@ export function BodyInteractions({ variant = 'tracker-select', mirror, onSelectRole, + onToesSelected, }: { leftControls?: ReactNode; rightControls?: ReactNode; @@ -21,6 +22,7 @@ export function BodyInteractions({ variant?: 'dots' | 'tracker-select'; assignedRoles: BodyPart[]; onSelectRole: (role: BodyPart) => void; + onToesSelected?: (side: 'left' | 'right') => void; highlightedRoles: BodyPart[]; mirror: boolean; }) { @@ -114,7 +116,10 @@ export function BodyInteractions({ ...slotPosition, id: slot.id, hidden: - variant === 'tracker-select' && !controlsPosIds.includes(slot.id), + variant === 'tracker-select' && + !controlsPosIds.includes(slot.id) && + slot.id !== 'left-toes' && + slot.id !== 'right-toes', buttonOffset: { left: canvasBox.left - personBox.left, top: canvasBox.top - personBox.top, @@ -211,8 +216,14 @@ export function BodyInteractions({ ({ top, left, height, width, id, hidden, buttonOffset }) => (
onSelectRole((BodyPart as any)[id])} + className={classNames('absolute z-10', 'cursor-pointer')} + onClick={() => { + if (id === 'left-toes' || id === 'right-toes') { + onToesSelected?.(id === 'left-toes' ? 'left' : 'right'); + return; + } + onSelectRole((BodyPart as any)[id]); + }} style={{ top: top + height / 2 - dotsSize / 2 + buttonOffset.top, left: left + width / 2 - dotsSize / 2 + buttonOffset.left, diff --git a/gui/app/src/components/commons/BodyPartIcon.tsx b/gui/app/src/components/commons/BodyPartIcon.tsx index 48cbff5d05..82c580ccba 100644 --- a/gui/app/src/components/commons/BodyPartIcon.tsx +++ b/gui/app/src/components/commons/BodyPartIcon.tsx @@ -125,21 +125,33 @@ export const mapPart: Record< ), [BodyPart.RIGHT_LITTLE_DISTAL]: ({ width }) => , - [BodyPart.LEFT_TOES_ABDUCTOR_HALLUCIS]: renderFootLeft, - [BodyPart.LEFT_TOES_DIGITORUM_BREVIS]: renderFootLeft, - [BodyPart.LEFT_TOES_ABDUCTOR_DIGITI_MINIMI]: renderFootLeft, - [BodyPart.RIGHT_TOES_ABDUCTOR_HALLUCIS]: renderFootRight, - [BodyPart.RIGHT_TOES_DIGITORUM_BREVIS]: renderFootRight, - [BodyPart.RIGHT_TOES_ABDUCTOR_DIGITI_MINIMI]: renderFootRight, + [BodyPart.LEFT_ABDUCTOR_HALLUCIS]: renderFootLeft, + [BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]: renderFootLeft, + [BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]: renderFootLeft, + [BodyPart.RIGHT_ABDUCTOR_HALLUCIS]: renderFootRight, + [BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]: renderFootRight, + [BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]: renderFootRight, }; -function renderFootLeft({ width, currentLocales }) { +function renderFootLeft({ + width, + currentLocales, +}: { + width?: number; + currentLocales: string[]; +}) { if (currentLocales.includes('en-x-owo')) { return ; } return ; } -function renderFootRight({ width, currentLocales }) { +function renderFootRight({ + width, + currentLocales, +}: { + width?: number; + currentLocales: string[]; +}) { if (currentLocales.includes('en-x-owo')) { return ; } @@ -156,7 +168,7 @@ export function BodyPartIcon({ return ( - {mapPart[bodyPart]({ width, currentLocales })} + {mapPart[bodyPart]?.({ width, currentLocales })} ); } diff --git a/gui/app/src/components/commons/PersonFrontIcon.tsx b/gui/app/src/components/commons/PersonFrontIcon.tsx index c8061ee674..6d95a36663 100644 --- a/gui/app/src/components/commons/PersonFrontIcon.tsx +++ b/gui/app/src/components/commons/PersonFrontIcon.tsx @@ -9,6 +9,9 @@ export const SIDES = [ upperLeg: BodyPart.LEFT_UPPER_LEG, lowerLeg: BodyPart.LEFT_LOWER_LEG, foot: BodyPart.LEFT_FOOT, + toesAbductorHallucis: BodyPart.LEFT_ABDUCTOR_HALLUCIS, + toesDigitorumBrevis: BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + toesAbductorDigitiMinimi: BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, }, { shoulder: BodyPart.RIGHT_SHOULDER, @@ -18,6 +21,9 @@ export const SIDES = [ upperLeg: BodyPart.RIGHT_UPPER_LEG, lowerLeg: BodyPart.RIGHT_LOWER_LEG, foot: BodyPart.RIGHT_FOOT, + toesAbductorHallucis: BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + toesDigitorumBrevis: BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + toesAbductorDigitiMinimi: BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, }, ]; @@ -121,6 +127,14 @@ export function PersonFrontIcon({ mirror = true }: { mirror?: boolean }) { id={BodyPart[SIDES[right].foot]} /> + + + ); } diff --git a/gui/app/src/components/onboarding/BodyAssignment.tsx b/gui/app/src/components/onboarding/BodyAssignment.tsx index b264d815f7..11aa009f45 100644 --- a/gui/app/src/components/onboarding/BodyAssignment.tsx +++ b/gui/app/src/components/onboarding/BodyAssignment.tsx @@ -26,6 +26,16 @@ export const LOWER_BODY = new Set([ BodyPart.RIGHT_FOOT, ...LEGS_PARTS, ]); + +export const TOES_PARTS = new Set([ + BodyPart.LEFT_ABDUCTOR_HALLUCIS, + BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, +]); + export const SPINE_PARTS = [ BodyPart.UPPER_CHEST, BodyPart.CHEST, @@ -35,6 +45,12 @@ export const SPINE_PARTS = [ export const ASSIGNMENT_RULES: Partial< Record > = { + [BodyPart.LEFT_ABDUCTOR_HALLUCIS]: [BodyPart.LEFT_FOOT], + [BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]: [BodyPart.LEFT_FOOT], + [BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]: [BodyPart.LEFT_FOOT], + [BodyPart.RIGHT_ABDUCTOR_HALLUCIS]: [BodyPart.RIGHT_FOOT], + [BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]: [BodyPart.RIGHT_FOOT], + [BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]: [BodyPart.RIGHT_FOOT], [BodyPart.LEFT_FOOT]: [ BodyPart.LEFT_LOWER_LEG, BodyPart.LEFT_UPPER_LEG, @@ -93,6 +109,7 @@ export const ASSIGNMENT_MODES: Record = { ...SPINE_PARTS, ...ARMS_PARTS, ...LEGS_PARTS, + ...TOES_PARTS, ], }; @@ -100,6 +117,7 @@ export function BodyAssignment({ assignMode, mirror, onRoleSelected, + onToesSelected, rolesWithErrors = {}, highlightedRoles = [], onlyAssigned = false, @@ -111,6 +129,7 @@ export function BodyAssignment({ rolesWithErrors?: Partial>; highlightedRoles?: BodyPart[]; onRoleSelected: (role: BodyPart) => void; + onToesSelected?: (side: 'left' | 'right') => void; width?: number; dotSize?: number; }) { @@ -159,6 +178,7 @@ export function BodyAssignment({ assignedRoles={assignedRoles} highlightedRoles={highlightedRoles} onSelectRole={onRoleSelected} + onToesSelected={onToesSelected} leftControls={
diff --git a/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx b/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx index eaf0aba3dc..5e5446f83d 100644 --- a/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx +++ b/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx @@ -148,6 +148,60 @@ export const mapPart: Record< ), [BodyPart.RIGHT_LITTLE_DISTAL]: ({ width }) => , + [BodyPart.LEFT_ABDUCTOR_HALLUCIS]: ({ width, currentLocales }) => + currentLocales.includes('en-x-owo') ? ( + + ) : ( + + ), + [BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]: ({ width, currentLocales }) => + currentLocales.includes('en-x-owo') ? ( + + ) : ( + + ), + [BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]: ({ width, currentLocales }) => + currentLocales.includes('en-x-owo') ? ( + + ) : ( + + ), + [BodyPart.RIGHT_ABDUCTOR_HALLUCIS]: ({ width, currentLocales }) => + currentLocales.includes('en-x-owo') ? ( + + ) : ( + + ), + [BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]: ({ width, currentLocales }) => + currentLocales.includes('en-x-owo') ? ( + + ) : ( + + ), + [BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]: ({ width, currentLocales }) => + currentLocales.includes('en-x-owo') ? ( + + ) : ( + + ), }; export function MountingBodyPartIcon({ @@ -158,7 +212,9 @@ export function MountingBodyPartIcon({ width?: number; }) { const { currentLocales } = useLocaleConfig(); - return mapPart[bodyPart]({ width, currentLocales }); + return ( + mapPart[bodyPart]?.({ width, currentLocales }) || + ); } function PieSliceOfFeet({ diff --git a/gui/app/src/components/onboarding/pages/trackers-assign/ToeAssignmentModal.tsx b/gui/app/src/components/onboarding/pages/trackers-assign/ToeAssignmentModal.tsx new file mode 100644 index 0000000000..e7f8f93e93 --- /dev/null +++ b/gui/app/src/components/onboarding/pages/trackers-assign/ToeAssignmentModal.tsx @@ -0,0 +1,315 @@ +import classNames from 'classnames'; +import ReactModal from 'react-modal'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + AssignTrackerRequestT, + BodyPart, + QuatT, + RpcMessage, +} from 'solarxr-protocol'; +import { Button } from '@/components/commons/Button'; +import { Typography } from '@/components/commons/Typography'; +import { TrackerSelectionMenu } from './TrackerSelectionMenu'; +import { useLocalization } from '@fluent/react'; +import { useAtomValue } from 'jotai'; +import { assignedTrackersAtom, FlatDeviceTracker } from '@/store/app-store'; +import { useWebsocketAPI } from '@/hooks/websocket-api'; +import { FootIcon } from '@/components/commons/icon/FootIcon'; + +export function ToeAssignmentModal({ + isOpen, + side, + onClose, +}: { + isOpen: boolean; + side: 'left' | 'right'; + onClose: () => void; +}) { + const { l10n } = useLocalization(); + const { sendRPCPacket } = useWebsocketAPI(); + const assignedTrackers = useAtomValue(assignedTrackersAtom); + const [selectedRole, setSelectedRole] = useState(BodyPart.NONE); + + // Defined inside component to ensure BodyPart enum is fully resolved + // (Vite's CJS-to-ESM dep cache may not include newly added enum values) + const TOE_LABELS: Record = useMemo( + () => ({ + [BodyPart.LEFT_ABDUCTOR_HALLUCIS]: 'Left Big Toe', + [BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]: 'Left Middle Toes', + [BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]: 'Left Pinky Toe', + [BodyPart.RIGHT_ABDUCTOR_HALLUCIS]: 'Right Big Toe', + [BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]: 'Right Middle Toes', + [BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]: 'Right Pinky Toe', + }), + [] + ); + + const TOE_PARTS = useMemo( + () => ({ + left: [ + { + part: BodyPart.LEFT_ABDUCTOR_HALLUCIS, + cx: 110, + cy: 30, + }, + { + part: BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + cx: 73, + cy: 40, + }, + { + part: BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + cx: 50, + cy: 62, + }, + ], + right: [ + { + part: BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + cx: 77, + cy: 30, + }, + { + part: BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + cx: 114, + cy: 40, + }, + { + part: BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + cx: 137, + cy: 62, + }, + ], + }), + [] + ); + + const toeParts = TOE_PARTS[side]; + const DOT_SIZE = 18; + + const canvasRef = useRef(null); + const containerRef = useRef(null); + const cardsRef = useRef(null); + const footRef = useRef(null); + + const drawLines = useCallback(() => { + const canvas = canvasRef.current; + const container = containerRef.current; + const cards = cardsRef.current; + const foot = footRef.current; + if (!canvas || !container || !cards || !foot) return; + + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + canvas.width = container.clientWidth; + canvas.height = container.clientHeight; + const containerRect = container.getBoundingClientRect(); + + ctx.strokeStyle = '#608AAB'; + ctx.lineWidth = 2; + + toeParts.forEach(({ part, cx, cy }) => { + const card = cards.querySelector(`[data-part="${part}"]`) as HTMLElement; + if (!card) return; + + const cardRect = card.getBoundingClientRect(); + const footRect = foot.getBoundingClientRect(); + + // Card connection point: right edge, vertical center + const cardX = cardRect.right - containerRect.left; + const cardY = cardRect.top + cardRect.height / 2 - containerRect.top; + + // Dot position relative to container + const dotX = footRect.left - containerRect.left + cx; + const dotY = footRect.top - containerRect.top + cy; + + ctx.beginPath(); + ctx.moveTo(cardX, cardY); + ctx.lineTo(cardX + 20, cardY); + ctx.lineTo(dotX, dotY); + ctx.stroke(); + }); + }, [toeParts]); + + useEffect(() => { + // Delay to let modal finish rendering/animating + const timer = setTimeout(drawLines, 100); + window.addEventListener('resize', drawLines); + return () => { + clearTimeout(timer); + window.removeEventListener('resize', drawLines); + }; + }, [drawLines, isOpen]); + + const trackerPartGrouped = useMemo( + () => + assignedTrackers.reduce<{ [key: number]: FlatDeviceTracker[] }>( + (curr, td) => { + const key = td.tracker.info?.bodyPart || BodyPart.NONE; + return { + ...curr, + [key]: [...(curr[key] || []), td], + }; + }, + {} + ), + [assignedTrackers] + ); + + const assignedRoles = useMemo( + () => + assignedTrackers.map( + ({ tracker }) => tracker.info?.bodyPart || BodyPart.NONE + ), + [assignedTrackers] + ); + + const onTrackerSelected = (tracker: FlatDeviceTracker | null) => { + const assign = ( + role: BodyPart, + rotation: QuatT | null, + trackerId: number + ) => { + const assignreq = new AssignTrackerRequestT(); + assignreq.bodyPosition = role; + assignreq.mountingOrientation = rotation; + assignreq.trackerId = trackerId; + sendRPCPacket(RpcMessage.AssignTrackerRequest, assignreq); + }; + + // Unassign existing trackers from this role + (trackerPartGrouped[selectedRole] || []).forEach((td) => + assign( + BodyPart.NONE, + td.tracker.info?.mountingOrientation || null, + td.tracker.trackerId + ) + ); + + if (!tracker) { + setSelectedRole(BodyPart.NONE); + return; + } + assign( + selectedRole, + tracker.tracker.info?.mountingOrientation || null, + tracker.tracker.trackerId + ); + setSelectedRole(BodyPart.NONE); + }; + + const sideLabel = side === 'left' ? 'Left' : 'Right'; + + return ( + <> + setSelectedRole(BodyPart.NONE)} + onTrackerSelected={onTrackerSelected} + /> + +
+ + {sideLabel} Foot ΓÇö Toe Assignment + + + Click a toe position to assign a tracker + + +
+ + {/* Toe cards */} +
+ {toeParts.map(({ part }) => ( +
setSelectedRole(part)} + > + + {TOE_LABELS[part] || BodyPart[part]} + + + {trackerPartGrouped[part]?.[0] + ? `${trackerPartGrouped[part][0].tracker.info?.customName || trackerPartGrouped[part][0].tracker.info?.displayName}` + : l10n.getString('tracker-part_card-unassigned')} + +
+ ))} +
+ + {/* Foot graphic with dots */} +
+ + {toeParts.map(({ part, cx, cy }) => ( +
setSelectedRole(part)} + style={{ + top: cy - DOT_SIZE / 2, + left: cx - DOT_SIZE / 2, + }} + > +
+
+ ))} +
+
+ +
+ +
+
+ + + ); +} diff --git a/gui/app/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx b/gui/app/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx index 37e7a4ab64..191c7ec97f 100644 --- a/gui/app/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx +++ b/gui/app/src/components/onboarding/pages/trackers-assign/TrackerAssignment.tsx @@ -28,6 +28,7 @@ import { defaultConfig, useConfig } from '@/hooks/config'; import { playTapSetupSound } from '@/sounds/sounds'; import { useBreakpoint } from '@/hooks/breakpoint'; import { TrackerAssignOptions } from './TrackerAssignOptions'; +import { ToeAssignmentModal } from './ToeAssignmentModal'; import { useAtomValue } from 'jotai'; import { assignedTrackersAtom, @@ -61,6 +62,7 @@ export function TrackersAssignPage() { }>({ defaultValues }); const { mirrorView } = watch(); const [selectedRole, setSelectRole] = useState(BodyPart.NONE); + const [toeSide, setToeSide] = useState<'left' | 'right' | null>(null); const assignedTrackers = useAtomValue(assignedTrackersAtom); const trackers = useAtomValue(flatTrackersAtom); @@ -319,11 +321,19 @@ export function TrackersAssignPage() { assignMode={config?.assignMode ?? null} mirror={mirrorView} onRoleSelected={tryOpenChokerWarning} + onToesSelected={setToeSide} />
+ {toeSide && ( + setToeSide(null)} + /> + )} ); } diff --git a/gui/app/src/hooks/body-parts.ts b/gui/app/src/hooks/body-parts.ts index 0d72b6be30..6dfff1a793 100644 --- a/gui/app/src/hooks/body-parts.ts +++ b/gui/app/src/hooks/body-parts.ts @@ -54,21 +54,21 @@ export const ALL_BODY_PARTS = [ BodyPart.RIGHT_LITTLE_PROXIMAL, BodyPart.RIGHT_LITTLE_INTERMEDIATE, BodyPart.RIGHT_LITTLE_DISTAL, - BodyPart.LEFT_TOES_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_TOES_DIGITORUM_BREVIS, - BodyPart.LEFT_TOES_ABDUCTOR_DIGITI_MINIMI, - BodyPart.RIGHT_TOES_ABDUCTOR_HALLUCIS, - BodyPart.RIGHT_TOES_DIGITORUM_BREVIS, - BodyPart.RIGHT_TOES_ABDUCTOR_DIGITI_MINIMI + BodyPart.LEFT_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, ]; export const TOE_BODY_PARTS = [ - BodyPart.LEFT_TOES_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_TOES_DIGITORUM_BREVIS, - BodyPart.LEFT_TOES_ABDUCTOR_DIGITI_MINIMI, - BodyPart.RIGHT_TOES_ABDUCTOR_HALLUCIS, - BodyPart.RIGHT_TOES_DIGITORUM_BREVIS, - BodyPart.RIGHT_TOES_ABDUCTOR_DIGITI_MINIMI + BodyPart.LEFT_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, ]; export const FEET_BODY_PARTS = [BodyPart.LEFT_FOOT, BodyPart.RIGHT_FOOT]; export const FINGER_BODY_PARTS = [ diff --git a/gui/app/src/hooks/reset.ts b/gui/app/src/hooks/reset.ts index 33c58d6b8a..98e9beab42 100644 --- a/gui/app/src/hooks/reset.ts +++ b/gui/app/src/hooks/reset.ts @@ -16,7 +16,7 @@ import * as Sentry from '@sentry/react'; export type ResetBtnStatus = 'idle' | 'counting' | 'finished'; -export type MountingResetGroup = 'default' | 'feet' | 'fingers'; +export type MountingResetGroup = 'default' | 'feet' | 'fingers' | 'toes'; export type UseResetOptions = | { type: ResetType.FULL | ResetType.YAW } | { type: ResetType.MOUNTING; group: MountingResetGroup }; diff --git a/server/core/src/main/java/dev/slimevr/skeleton/module.kt b/server/core/src/main/java/dev/slimevr/skeleton/module.kt index b628200b5f..ada774401c 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/module.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/module.kt @@ -11,6 +11,7 @@ import dev.slimevr.skeleton.processors.BoneSmoothingProcessor import dev.slimevr.skeleton.processors.BoneYawRollAlignProcessor import dev.slimevr.skeleton.processors.FingerImputeProcessor import dev.slimevr.skeleton.processors.SpineImputeProcessor +import dev.slimevr.skeleton.processors.ToesImputeProcessor import io.github.axisangles.ktmath.Quaternion import io.github.axisangles.ktmath.Vector3 import kotlinx.coroutines.CoroutineScope @@ -143,6 +144,7 @@ class Skeleton( BoneYawRollAlignProcessor(ctx.config.settings), BoneDirectLinkProcessor(), FingerImputeProcessor(), + ToesImputeProcessor(), BonePredictionProcessor(ctx.config.settings), BoneSmoothingProcessor(ctx.config.settings, skeletonRefreshRate), ), diff --git a/server/core/src/main/java/dev/slimevr/skeleton/processors/toes-impute.kt b/server/core/src/main/java/dev/slimevr/skeleton/processors/toes-impute.kt new file mode 100644 index 0000000000..9f526ebe76 --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/skeleton/processors/toes-impute.kt @@ -0,0 +1,41 @@ +package dev.slimevr.skeleton.processors + +import dev.slimevr.skeleton.BodyPartMap +import dev.slimevr.skeleton.SkeletonProcessor +import dev.slimevr.skeleton.SkeletonState +import solarxr_protocol.datatypes.BodyPart +import kotlin.collections.get +import kotlin.collections.iterator + +/** + * Handles rotations of inactive finger bones. + */ +class ToesImputeProcessor : SkeletonProcessor { + /** + * First element is the linked BodyPart. + * + * Second element is the BodyPart the first element is linked to. + */ + private val toesToSource = arrayOf( + BodyPart.LEFT_ABDUCTOR_HALLUCIS to BodyPart.LEFT_FOOT, + BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS to BodyPart.LEFT_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI to BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.RIGHT_ABDUCTOR_HALLUCIS to BodyPart.RIGHT_FOOT, + BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS to BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI to BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + ) + + override fun process(state: SkeletonState): SkeletonState { + val updatedBoneInputs = BodyPartMap(state.boneInputs) + + for ((bodyPart, source) in toesToSource) { + val bone = updatedBoneInputs.getValue(bodyPart) + if (bone.isActive) continue + + val sourceBone = updatedBoneInputs[source] + updatedBoneInputs[bodyPart] = bone.copy(rawRotation = sourceBone?.rawRotation ?: bone.rawRotation) + } + + return state.copy(boneInputs = updatedBoneInputs) + } +} From 602e173e7565aa23d842c183ff137f7582239115 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 00:17:19 -0500 Subject: [PATCH 04/32] Add toe tracking plugin OSC output for toes. --- .../dev/slimevr/skeleton/bone-geometry.kt | 4 +- .../main/java/dev/slimevr/skeleton/module.kt | 2 +- .../skeleton/processors/toes-impute.kt | 2 +- .../src/main/java/dev/slimevr/vmc/mapping.kt | 16 ++- .../java/dev/slimevr/vrcosc/output-encoder.kt | 4 +- .../dev/slimevr/vrcosc/toes-output-encoder.kt | 130 ++++++++++++++++++ solarxr-protocol | 2 +- 7 files changed, 148 insertions(+), 12 deletions(-) create mode 100644 server/core/src/main/java/dev/slimevr/vrcosc/toes-output-encoder.kt diff --git a/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt b/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt index 3f3564cf26..f4124988cb 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt @@ -15,7 +15,7 @@ private val TOE_OFFSETS = ( ).map { it.second }.associateWith { Vector3(0f, 0f, -0.04f) } val DEFAULT_BONE_OFFSETS: BodyPartMap = DEFAULT_PROPORTIONS.toBoneOffsets() - .mutate { offsets -> - for ((bodyPart, offset) in FINGER_OFFSETS) offsets[bodyPart] = offset + .mutate { offsets -> + for ((bodyPart, offset) in FINGER_OFFSETS) offsets[bodyPart] = offset for ((bodyPart, offset) in TOE_OFFSETS) offsets[bodyPart] = offset } diff --git a/server/core/src/main/java/dev/slimevr/skeleton/module.kt b/server/core/src/main/java/dev/slimevr/skeleton/module.kt index ada774401c..40ebd3dd7a 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/module.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/module.kt @@ -5,9 +5,9 @@ import dev.slimevr.context.Behaviour import dev.slimevr.context.Context import dev.slimevr.skeleton.processors.BoneActiveLinkProcessor import dev.slimevr.skeleton.processors.BoneDirectLinkProcessor -import dev.slimevr.skeleton.processors.BoneYawFallbackProcessor import dev.slimevr.skeleton.processors.BonePredictionProcessor import dev.slimevr.skeleton.processors.BoneSmoothingProcessor +import dev.slimevr.skeleton.processors.BoneYawFallbackProcessor import dev.slimevr.skeleton.processors.BoneYawRollAlignProcessor import dev.slimevr.skeleton.processors.FingerImputeProcessor import dev.slimevr.skeleton.processors.SpineImputeProcessor diff --git a/server/core/src/main/java/dev/slimevr/skeleton/processors/toes-impute.kt b/server/core/src/main/java/dev/slimevr/skeleton/processors/toes-impute.kt index 9f526ebe76..3ff07a1e2f 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/processors/toes-impute.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/processors/toes-impute.kt @@ -8,7 +8,7 @@ import kotlin.collections.get import kotlin.collections.iterator /** - * Handles rotations of inactive finger bones. + * Handles rotations of inactive toe bones. */ class ToesImputeProcessor : SkeletonProcessor { /** diff --git a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt index f6c3641739..c0c294c6e3 100644 --- a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt +++ b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt @@ -63,7 +63,7 @@ val BODY_PART_TO_UNITY_BONE: Map = mapOf( BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI to "LeftAbductorDigitiMinimi", BodyPart.RIGHT_ABDUCTOR_HALLUCIS to "RightAbductorHallucis", BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS to "RightFlexorDigitorumBrevis", - BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI to "RightAbductorDigitiMinimi" + BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI to "RightAbductorDigitiMinimi", ) // HIP-rooted hierarchy. VMC/Unity expects this; our skeleton is HEAD-rooted. @@ -126,7 +126,7 @@ val VMC_HIERARCHY_MAP: Map> = mapOf( BodyPart.RIGHT_ABDUCTOR_HALLUCIS, BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, - ) + ), ) private suspend fun SequenceScope>.visitVMC(parent: BodyPart?, bone: BodyPart) { @@ -164,7 +164,7 @@ val VMC_MIRROR_BONE_PAIRS: List> = listOf( BodyPart.LEFT_LITTLE_DISTAL to BodyPart.RIGHT_LITTLE_DISTAL, BodyPart.LEFT_ABDUCTOR_HALLUCIS to BodyPart.RIGHT_ABDUCTOR_HALLUCIS, BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS to BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI to BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI to BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, ) val VMC_MIRROR_BONES: BodyPartMap = BodyPartMap( @@ -197,10 +197,14 @@ val VMC_REST_ROTATIONS: BodyPartMap = run { BodyPart.RIGHT_LITTLE_PROXIMAL, BodyPart.RIGHT_LITTLE_INTERMEDIATE, BodyPart.RIGHT_LITTLE_DISTAL, ) val leftToes = listOf( - BodyPart.LEFT_ABDUCTOR_HALLUCIS, BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.LEFT_ABDUCTOR_HALLUCIS, + BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, ) val rightToes = listOf( - BodyPart.RIGHT_ABDUCTOR_HALLUCIS, BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, ) BodyPartMap( mapOf( @@ -215,7 +219,7 @@ val VMC_REST_ROTATIONS: BodyPartMap = run { ) + leftFingers.associateWith { leftArm } + rightFingers.associateWith { rightArm } + - leftToes.associateWith { foot } + + leftToes.associateWith { foot } + rightToes.associateWith { foot }, ) } diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/output-encoder.kt index 3d5bd49d7e..9d25b8f84e 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/output-encoder.kt @@ -28,7 +28,7 @@ internal fun buildOutgoingBundle( bones: Map, outputTrackerToggle: OutputTrackerToggleManager, ): OscBundle? { - val messages = buildList { + val messages = buildList { for ((bodyPart, trackerId) in trackerIdsByBodyPart) { if (bodyPart !in outputTrackerToggle.context.state.value.trackers) continue @@ -52,6 +52,8 @@ internal fun buildOutgoingBundle( ), ) } + + addAll(buildToeMessages(bones)) } return messages.takeIf { it.isNotEmpty() }?.let { OscBundle(1L, it) } diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toes-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toes-output-encoder.kt new file mode 100644 index 0000000000..e87ad22537 --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toes-output-encoder.kt @@ -0,0 +1,130 @@ +package dev.slimevr.vrcosc + +import dev.slimevr.osc.OscArg +import dev.slimevr.osc.OscContent +import dev.slimevr.osc.OscMessage +import dev.slimevr.skeleton.BoneState +import io.github.axisangles.ktmath.Quaternion +import io.github.axisangles.ktmath.Vector3 +import solarxr_protocol.datatypes.BodyPart +import kotlin.math.* + +private enum class FootSide { Left, Right } + +internal fun buildToeMessages(bones: Map): List { + val messages = mutableListOf() + + // LEFT FOOT + TOES + val leftFoot = bones[BodyPart.LEFT_FOOT] + if (leftFoot != null) { + val leftToes = listOf( + bones[BodyPart.LEFT_ABDUCTOR_HALLUCIS], + bones[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS], + bones[BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI], + ) + processToesForFoot(leftFoot, leftToes, FootSide.Left, messages) + } + + // RIGHT FOOT + TOES + val rightFoot = bones[BodyPart.RIGHT_FOOT] + if (rightFoot != null) { + val rightToes = listOf( + bones[BodyPart.RIGHT_ABDUCTOR_HALLUCIS], + bones[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS], + bones[BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI], + ) + processToesForFoot(rightFoot, rightToes, FootSide.Right, messages) + } + + return messages +} + +private fun processToesForFoot( + foot: BoneState, + toeBones: List, + side: FootSide, + messages: MutableList, +) { + var lastAssigned: BoneState? = null + + for ((segmentIndex, toe) in toeBones.withIndex()) { + if (toe != null) { + lastAssigned = toe + } + + if (lastAssigned == null) continue + + when (segmentIndex) { + 0 -> processToe(foot, lastAssigned, side, 0, oppositeSide(side), messages) + + 1 -> { + processToe(foot, lastAssigned, side, 1, oppositeSide(side), messages) + processToe(foot, lastAssigned, side, 2, oppositeSide(side), messages) + } + + 2 -> { + processToe(foot, lastAssigned, side, 3, side, messages) + processToe(foot, lastAssigned, side, 4, side, messages) + } + } + } +} + +private fun oppositeSide(side: FootSide): FootSide = when (side) { + FootSide.Left -> FootSide.Right + FootSide.Right -> FootSide.Left +} + +private fun processToe( + foot: BoneState, + toe: BoneState, + side: FootSide, + toeNumber: Int, + splayDirection: FootSide, + messages: MutableList, +) { + val footRot = foot.rotation + val toeRot = toe.rotation + val currentRelative = footRot.inv() * toeRot + + val euler = quaternionToEulerDegrees(currentRelative) + + val pitch = euler.z + val yaw = euler.y + val tipToe = pitch < -14f + val bending = pitch > 15f && !tipToe + val splayed = when (splayDirection) { + FootSide.Left -> yaw < -7f + FootSide.Right -> yaw > 7f + } + + messages.add(OscContent.Message(OscMessage("/avatar/parameters/TipToes${side.name}", listOf(if (tipToe) OscArg.True else OscArg.False)))) + messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.name}${toeNumber + 1}Bool", listOf(if (bending) OscArg.True else OscArg.False)))) + messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.name}${toeNumber + 1}", listOf(if (splayed) OscArg.True else OscArg.False)))) + + val floatValue = (pitch / 90f).coerceIn(-1f, 1f) + messages.add(OscContent.Message(OscMessage("/avatar/parameters/Toe${side.name}${toeNumber + 1}Float", listOf(OscArg.Float(floatValue))))) +} + +private fun quaternionToEulerDegrees(q: Quaternion): Vector3 { + val sinrCosp = 2f * (q.w * q.x + q.y * q.z) + val cosrCosp = 1f - 2f * (q.x * q.x + q.y * q.y) + val roll = atan2(sinrCosp, cosrCosp) + + val sinp = 2f * (q.w * q.y - q.z * q.x) + val pitch = if (abs(sinp) >= 1f) { + (PI.toFloat() / 2f) * sign(sinp) + } else { + asin(sinp) + } + + val sinyCosp = 2f * (q.w * q.z + q.x * q.y) + val cosyCosp = 1f - 2f * (q.y * q.y + q.z * q.z) + val yaw = atan2(sinyCosp, cosyCosp) + + return Vector3( + Math.toDegrees(pitch.toDouble()).toFloat(), + Math.toDegrees(yaw.toDouble()).toFloat(), + Math.toDegrees(roll.toDouble()).toFloat(), + ) +} diff --git a/solarxr-protocol b/solarxr-protocol index b8c68382fe..cc9eeeaee7 160000 --- a/solarxr-protocol +++ b/solarxr-protocol @@ -1 +1 @@ -Subproject commit b8c68382fe0bb1916479b9d3411fa3f332b9b8c2 +Subproject commit cc9eeeaee780acfba06350afb41bcaf53f784175 From 6ccb2732e7e46ab0169b551ae0f828eb50289454 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 15:18:25 -0500 Subject: [PATCH 05/32] Add tests for ToesImputeProcessor --- .../tracker/ToesImputeProcessorTest.kt | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt diff --git a/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt b/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt new file mode 100644 index 0000000000..7d3f31f123 --- /dev/null +++ b/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt @@ -0,0 +1,154 @@ +package dev.slimevr.tracker +import dev.slimevr.skeleton.BoneInput +import dev.slimevr.skeleton.BoneState +import dev.slimevr.skeleton.DEFAULT_SKELETON_STATE +import dev.slimevr.skeleton.SkeletonState +import dev.slimevr.skeleton.bodyPartMap +import dev.slimevr.skeleton.mutate +import dev.slimevr.skeleton.processors.ToesImputeProcessor +import io.github.axisangles.ktmath.Quaternion +import io.github.axisangles.ktmath.Vector3 +import org.junit.jupiter.api.Test +import solarxr_protocol.datatypes.BodyPart +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ToesImputeProcessorTest { + @Test + fun `test impute missing all toe trackers`() { + val processor = ToesImputeProcessor() + val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> + map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + } + + val state = SkeletonState( + boneInputs = inputs, + skeletonHeight = 1.7f, + paused = false + ) + + val newInputs = processor.process(state) + + val leftAbductorDigitorumBrevisIsSameAsLeftFoot = + newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_HALLUCIS]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FOOT]?.rawRotation + + val leftFlexorDigitorumBrevisDigitiMinimiIsSameAsLeftAbductorHallucis = + newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_HALLUCIS]?.rawRotation + + val leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis = + newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + + val rightAbductorDigitorumBrevisIsSameAsRightFoot = + newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_HALLUCIS]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FOOT]?.rawRotation + + val rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis = + newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_HALLUCIS]?.rawRotation + + val rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis = + newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + + val testSucceeded = + leftAbductorDigitorumBrevisIsSameAsLeftFoot + && leftFlexorDigitorumBrevisDigitiMinimiIsSameAsLeftAbductorHallucis + && leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis + && rightAbductorDigitorumBrevisIsSameAsRightFoot + && rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis + && rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis + + assertTrue (testSucceeded ) + } + @Test + fun `test impute missing toe trackers from flexor digitorum brevis and abductor digiti minimi`() { + val processor = ToesImputeProcessor() + val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> + map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + map[BodyPart.LEFT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.LEFT_ABDUCTOR_HALLUCIS).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + map[BodyPart.RIGHT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.RIGHT_ABDUCTOR_HALLUCIS).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + } + + val state = SkeletonState( + boneInputs = inputs, + skeletonHeight = 1.7f, + paused = false + ) + + val newInputs = processor.process(state) + + val leftFlexorDigitorumBrevisDigitiMinimiIsSameAsLeftAbductorHallucis = + newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_HALLUCIS]?.rawRotation + + val leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis = + newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + + val rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis = + newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_HALLUCIS]?.rawRotation + + val rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis = + newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + + val testSucceeded = leftFlexorDigitorumBrevisDigitiMinimiIsSameAsLeftAbductorHallucis + && leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis + && rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis + && rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis + + assertTrue (testSucceeded ) + } + + @Test + fun `test impute missing toe tracker from abductor digiti minimi`() { + val processor = ToesImputeProcessor() + val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> + map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + map[BodyPart.LEFT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.LEFT_ABDUCTOR_HALLUCIS).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + map[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS] = map.getValue(BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + map[BodyPart.RIGHT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.RIGHT_ABDUCTOR_HALLUCIS).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + map[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS] = map.getValue(BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS).copy( + rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + isActive = true) + } + + val state = SkeletonState( + boneInputs = inputs, + skeletonHeight = 1.7f, + paused = false + ) + + val newInputs = processor.process(state) + + val leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis = + newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + + val rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis = + newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + + val testSucceeded = leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis + && rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis + + assertTrue (testSucceeded ) + } +} From 1bcd01fd57952e65261e01f12241994439d51eba Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 15:27:30 -0500 Subject: [PATCH 06/32] Adjust variable wording to be more precise in ToeImputeProcessor test. --- .../tracker/ToesImputeProcessorTest.kt | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt b/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt index 7d3f31f123..ed9602f829 100644 --- a/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt +++ b/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt @@ -34,31 +34,31 @@ class ToesImputeProcessorTest { val newInputs = processor.process(state) - val leftAbductorDigitorumBrevisIsSameAsLeftFoot = + val leftAbductorDigitorumBrevisIsSameRotationAsLeftFoot = newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_HALLUCIS]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FOOT]?.rawRotation - val leftFlexorDigitorumBrevisDigitiMinimiIsSameAsLeftAbductorHallucis = + val leftFlexorDigitorumBrevisDigitiMinimiIsSameRotationAsLeftAbductorHallucis = newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_HALLUCIS]?.rawRotation - val leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis = + val leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis = newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation - val rightAbductorDigitorumBrevisIsSameAsRightFoot = + val rightAbductorDigitorumBrevisIsSameRotationAsRightFoot = newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_HALLUCIS]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FOOT]?.rawRotation val rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis = newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_HALLUCIS]?.rawRotation - val rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis = + val rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis = newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation val testSucceeded = - leftAbductorDigitorumBrevisIsSameAsLeftFoot - && leftFlexorDigitorumBrevisDigitiMinimiIsSameAsLeftAbductorHallucis - && leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis - && rightAbductorDigitorumBrevisIsSameAsRightFoot + leftAbductorDigitorumBrevisIsSameRotationAsLeftFoot + && leftFlexorDigitorumBrevisDigitiMinimiIsSameRotationAsLeftAbductorHallucis + && leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis + && rightAbductorDigitorumBrevisIsSameRotationAsRightFoot && rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis - && rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis + && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis assertTrue (testSucceeded ) } @@ -88,22 +88,22 @@ class ToesImputeProcessorTest { val newInputs = processor.process(state) - val leftFlexorDigitorumBrevisDigitiMinimiIsSameAsLeftAbductorHallucis = + val leftFlexorDigitorumBrevisDigitiMinimiIsSameRotationAsLeftAbductorHallucis = newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_HALLUCIS]?.rawRotation - val leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis = + val leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis = newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation val rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis = newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_HALLUCIS]?.rawRotation - val rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis = + val rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis = newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation - val testSucceeded = leftFlexorDigitorumBrevisDigitiMinimiIsSameAsLeftAbductorHallucis - && leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis + val testSucceeded = leftFlexorDigitorumBrevisDigitiMinimiIsSameRotationAsLeftAbductorHallucis + && leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis && rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis - && rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis + && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis assertTrue (testSucceeded ) } @@ -140,14 +140,14 @@ class ToesImputeProcessorTest { val newInputs = processor.process(state) - val leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis = + val leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis = newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation - val rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis = + val rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis = newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation - val testSucceeded = leftAbductorDigitiMinimiIsSameAsLeftFlexorDigitorumBrevis - && rightAbductorDigitiMinimiIsSameAsRightFlexorDigitorumBrevis + val testSucceeded = leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis + && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis assertTrue (testSucceeded ) } From f02b03ce7ecf9cc9cbee4ce86972c568928bca4d Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 15:29:30 -0500 Subject: [PATCH 07/32] Minor adjustments. --- .../java/dev/slimevr/tracker/ToesImputeProcessorTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt b/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt index ed9602f829..691e0be42d 100644 --- a/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt +++ b/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt @@ -60,7 +60,7 @@ class ToesImputeProcessorTest { && rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis - assertTrue (testSucceeded ) + assertTrue (testSucceeded) } @Test fun `test impute missing toe trackers from flexor digitorum brevis and abductor digiti minimi`() { @@ -105,7 +105,7 @@ class ToesImputeProcessorTest { && rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis - assertTrue (testSucceeded ) + assertTrue (testSucceeded) } @Test @@ -149,6 +149,6 @@ class ToesImputeProcessorTest { val testSucceeded = leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis - assertTrue (testSucceeded ) + assertTrue (testSucceeded) } } From 05eb0309650416f3b66452e7844b6c20eff41523 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 15:47:23 -0500 Subject: [PATCH 08/32] Refactor naming for toe tracking mapping and associated test. --- .../main/java/dev/slimevr/skeleton/module.kt | 2 +- .../{toes-impute.kt => toe-direct-link.kt} | 4 +- .../ToeDirectLinkProcessorTest.kt} | 43 ++++++++----------- 3 files changed, 20 insertions(+), 29 deletions(-) rename server/core/src/main/java/dev/slimevr/skeleton/processors/{toes-impute.kt => toe-direct-link.kt} (92%) rename server/core/src/test/java/dev/slimevr/{tracker/ToesImputeProcessorTest.kt => skeleton/ToeDirectLinkProcessorTest.kt} (83%) diff --git a/server/core/src/main/java/dev/slimevr/skeleton/module.kt b/server/core/src/main/java/dev/slimevr/skeleton/module.kt index 40ebd3dd7a..e563121bbe 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/module.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/module.kt @@ -144,7 +144,7 @@ class Skeleton( BoneYawRollAlignProcessor(ctx.config.settings), BoneDirectLinkProcessor(), FingerImputeProcessor(), - ToesImputeProcessor(), + ToeDirectLinkProcessor(), BonePredictionProcessor(ctx.config.settings), BoneSmoothingProcessor(ctx.config.settings, skeletonRefreshRate), ), diff --git a/server/core/src/main/java/dev/slimevr/skeleton/processors/toes-impute.kt b/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt similarity index 92% rename from server/core/src/main/java/dev/slimevr/skeleton/processors/toes-impute.kt rename to server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt index 3ff07a1e2f..27209a9d07 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/processors/toes-impute.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt @@ -4,13 +4,11 @@ import dev.slimevr.skeleton.BodyPartMap import dev.slimevr.skeleton.SkeletonProcessor import dev.slimevr.skeleton.SkeletonState import solarxr_protocol.datatypes.BodyPart -import kotlin.collections.get -import kotlin.collections.iterator /** * Handles rotations of inactive toe bones. */ -class ToesImputeProcessor : SkeletonProcessor { +class ToeDirectLinkProcessor : SkeletonProcessor { /** * First element is the linked BodyPart. * diff --git a/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt similarity index 83% rename from server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt rename to server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt index 691e0be42d..a7672e8f9d 100644 --- a/server/core/src/test/java/dev/slimevr/tracker/ToesImputeProcessorTest.kt +++ b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt @@ -1,28 +1,21 @@ -package dev.slimevr.tracker -import dev.slimevr.skeleton.BoneInput -import dev.slimevr.skeleton.BoneState -import dev.slimevr.skeleton.DEFAULT_SKELETON_STATE -import dev.slimevr.skeleton.SkeletonState -import dev.slimevr.skeleton.bodyPartMap -import dev.slimevr.skeleton.mutate +package dev.slimevr.skeleton + import dev.slimevr.skeleton.processors.ToesImputeProcessor import io.github.axisangles.ktmath.Quaternion -import io.github.axisangles.ktmath.Vector3 import org.junit.jupiter.api.Test import solarxr_protocol.datatypes.BodyPart -import kotlin.test.assertEquals import kotlin.test.assertTrue -class ToesImputeProcessorTest { +class ToeDirectLinkProcessorTest { @Test fun `test impute missing all toe trackers`() { val processor = ToesImputeProcessor() val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) } @@ -60,23 +53,23 @@ class ToesImputeProcessorTest { && rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis - assertTrue (testSucceeded) + assertTrue(testSucceeded) } @Test fun `test impute missing toe trackers from flexor digitorum brevis and abductor digiti minimi`() { val processor = ToesImputeProcessor() val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.LEFT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.LEFT_ABDUCTOR_HALLUCIS).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.RIGHT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.RIGHT_ABDUCTOR_HALLUCIS).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) } @@ -105,7 +98,7 @@ class ToesImputeProcessorTest { && rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis - assertTrue (testSucceeded) + assertTrue(testSucceeded) } @Test @@ -113,22 +106,22 @@ class ToesImputeProcessorTest { val processor = ToesImputeProcessor() val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.LEFT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.LEFT_ABDUCTOR_HALLUCIS).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS] = map.getValue(BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.RIGHT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.RIGHT_ABDUCTOR_HALLUCIS).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS] = map.getValue(BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS).copy( - rawRotation = Quaternion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) } @@ -149,6 +142,6 @@ class ToesImputeProcessorTest { val testSucceeded = leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis - assertTrue (testSucceeded) + assertTrue(testSucceeded) } } From aec5ae64f088a290c8b6d647428816a9687c8e65 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 17:57:00 -0500 Subject: [PATCH 09/32] Refactor toe joints, and add two additional toe joints. --- .../src/components/commons/BodyPartIcon.tsx | 12 +-- .../components/commons/PersonFrontIcon.tsx | 12 +-- .../components/onboarding/BodyAssignment.tsx | 24 ++--- .../pages/mounting/MountingSelectionMenu.tsx | 12 +-- .../trackers-assign/ToeAssignmentModal.tsx | 24 ++--- gui/app/src/hooks/body-parts.ts | 24 ++--- gui/app/src/utils/skeletonHelper.ts | 48 +++++----- .../java/dev/slimevr/resets/bodypart-sets.kt | 12 +-- .../slimevr/skeleton/bodypart-structure.kt | 16 ++-- .../main/java/dev/slimevr/skeleton/module.kt | 2 +- .../skeleton/processors/toe-direct-link.kt | 17 ++-- .../src/main/java/dev/slimevr/vmc/mapping.kt | 46 +++++----- ...ncoder.kt => toe-plugin-output-encoder.kt} | 15 ++- .../skeleton/ToeDirectLinkProcessorTest.kt | 92 +++++++++---------- 14 files changed, 184 insertions(+), 172 deletions(-) rename server/core/src/main/java/dev/slimevr/vrcosc/{toes-output-encoder.kt => toe-plugin-output-encoder.kt} (92%) diff --git a/gui/app/src/components/commons/BodyPartIcon.tsx b/gui/app/src/components/commons/BodyPartIcon.tsx index 82c580ccba..f0c67296ff 100644 --- a/gui/app/src/components/commons/BodyPartIcon.tsx +++ b/gui/app/src/components/commons/BodyPartIcon.tsx @@ -125,12 +125,12 @@ export const mapPart: Record< ), [BodyPart.RIGHT_LITTLE_DISTAL]: ({ width }) => , - [BodyPart.LEFT_ABDUCTOR_HALLUCIS]: renderFootLeft, - [BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]: renderFootLeft, - [BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]: renderFootLeft, - [BodyPart.RIGHT_ABDUCTOR_HALLUCIS]: renderFootRight, - [BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]: renderFootRight, - [BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]: renderFootRight, + [BodyPart.LEFT_BIG_TOE]: renderFootLeft, + [BodyPart.LEFT_INDEX_TOE]: renderFootLeft, + [BodyPart.LEFT_PINKY_TOE]: renderFootLeft, + [BodyPart.RIGHT_BIG_TOE]: renderFootRight, + [BodyPart.RIGHT_INDEX_TOE]: renderFootRight, + [BodyPart.RIGHT_PINKY_TOE]: renderFootRight, }; function renderFootLeft({ width, diff --git a/gui/app/src/components/commons/PersonFrontIcon.tsx b/gui/app/src/components/commons/PersonFrontIcon.tsx index 6d95a36663..e9d7c20d1f 100644 --- a/gui/app/src/components/commons/PersonFrontIcon.tsx +++ b/gui/app/src/components/commons/PersonFrontIcon.tsx @@ -9,9 +9,9 @@ export const SIDES = [ upperLeg: BodyPart.LEFT_UPPER_LEG, lowerLeg: BodyPart.LEFT_LOWER_LEG, foot: BodyPart.LEFT_FOOT, - toesAbductorHallucis: BodyPart.LEFT_ABDUCTOR_HALLUCIS, - toesDigitorumBrevis: BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, - toesAbductorDigitiMinimi: BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + bigToe: BodyPart.LEFT_BIG_TOE, + indexToe: BodyPart.LEFT_INDEX_TOE, + pinkyToe: BodyPart.LEFT_PINKY_TOE, }, { shoulder: BodyPart.RIGHT_SHOULDER, @@ -21,9 +21,9 @@ export const SIDES = [ upperLeg: BodyPart.RIGHT_UPPER_LEG, lowerLeg: BodyPart.RIGHT_LOWER_LEG, foot: BodyPart.RIGHT_FOOT, - toesAbductorHallucis: BodyPart.RIGHT_ABDUCTOR_HALLUCIS, - toesDigitorumBrevis: BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, - toesAbductorDigitiMinimi: BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + bigToe: BodyPart.RIGHT_BIG_TOE, + indexToe: BodyPart.RIGHT_INDEX_TOE, + pinkyToe: BodyPart.RIGHT_PINKY_TOE, }, ]; diff --git a/gui/app/src/components/onboarding/BodyAssignment.tsx b/gui/app/src/components/onboarding/BodyAssignment.tsx index 11aa009f45..2650cfcb2f 100644 --- a/gui/app/src/components/onboarding/BodyAssignment.tsx +++ b/gui/app/src/components/onboarding/BodyAssignment.tsx @@ -28,12 +28,12 @@ export const LOWER_BODY = new Set([ ]); export const TOES_PARTS = new Set([ - BodyPart.LEFT_ABDUCTOR_HALLUCIS, - BodyPart.RIGHT_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, - BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.LEFT_BIG_TOE, + BodyPart.RIGHT_BIG_TOE, + BodyPart.LEFT_INDEX_TOE, + BodyPart.RIGHT_INDEX_TOE, + BodyPart.LEFT_PINKY_TOE, + BodyPart.RIGHT_PINKY_TOE, ]); export const SPINE_PARTS = [ @@ -45,12 +45,12 @@ export const SPINE_PARTS = [ export const ASSIGNMENT_RULES: Partial< Record > = { - [BodyPart.LEFT_ABDUCTOR_HALLUCIS]: [BodyPart.LEFT_FOOT], - [BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]: [BodyPart.LEFT_FOOT], - [BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]: [BodyPart.LEFT_FOOT], - [BodyPart.RIGHT_ABDUCTOR_HALLUCIS]: [BodyPart.RIGHT_FOOT], - [BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]: [BodyPart.RIGHT_FOOT], - [BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]: [BodyPart.RIGHT_FOOT], + [BodyPart.LEFT_BIG_TOE]: [BodyPart.LEFT_FOOT], + [BodyPart.LEFT_INDEX_TOE]: [BodyPart.LEFT_FOOT], + [BodyPart.LEFT_PINKY_TOE]: [BodyPart.LEFT_FOOT], + [BodyPart.RIGHT_BIG_TOE]: [BodyPart.RIGHT_FOOT], + [BodyPart.RIGHT_INDEX_TOE]: [BodyPart.RIGHT_FOOT], + [BodyPart.RIGHT_PINKY_TOE]: [BodyPart.RIGHT_FOOT], [BodyPart.LEFT_FOOT]: [ BodyPart.LEFT_LOWER_LEG, BodyPart.LEFT_UPPER_LEG, diff --git a/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx b/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx index 5e5446f83d..a0438a0ffb 100644 --- a/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx +++ b/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx @@ -148,7 +148,7 @@ export const mapPart: Record< ), [BodyPart.RIGHT_LITTLE_DISTAL]: ({ width }) => , - [BodyPart.LEFT_ABDUCTOR_HALLUCIS]: ({ width, currentLocales }) => + [BodyPart.LEFT_BIG_TOE]: ({ width, currentLocales }) => currentLocales.includes('en-x-owo') ? ( ), - [BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]: ({ width, currentLocales }) => + [BodyPart.LEFT_INDEX_TOE]: ({ width, currentLocales }) => currentLocales.includes('en-x-owo') ? ( ), - [BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]: ({ width, currentLocales }) => + [BodyPart.LEFT_PINKY_TOE]: ({ width, currentLocales }) => currentLocales.includes('en-x-owo') ? ( ), - [BodyPart.RIGHT_ABDUCTOR_HALLUCIS]: ({ width, currentLocales }) => + [BodyPart.RIGHT_BIG_TOE]: ({ width, currentLocales }) => currentLocales.includes('en-x-owo') ? ( ), - [BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]: ({ width, currentLocales }) => + [BodyPart.RIGHT_INDEX_TOE]: ({ width, currentLocales }) => currentLocales.includes('en-x-owo') ? ( ), - [BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]: ({ width, currentLocales }) => + [BodyPart.RIGHT_PINKY_TOE]: ({ width, currentLocales }) => currentLocales.includes('en-x-owo') ? ( = useMemo( () => ({ - [BodyPart.LEFT_ABDUCTOR_HALLUCIS]: 'Left Big Toe', - [BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]: 'Left Middle Toes', - [BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]: 'Left Pinky Toe', - [BodyPart.RIGHT_ABDUCTOR_HALLUCIS]: 'Right Big Toe', - [BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]: 'Right Middle Toes', - [BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]: 'Right Pinky Toe', + [BodyPart.LEFT_BIG_TOE]: 'Left Big Toe', + [BodyPart.LEFT_INDEX_TOE]: 'Left Middle Toes', + [BodyPart.LEFT_PINKY_TOE]: 'Left Pinky Toe', + [BodyPart.RIGHT_BIG_TOE]: 'Right Big Toe', + [BodyPart.RIGHT_INDEX_TOE]: 'Right Middle Toes', + [BodyPart.RIGHT_PINKY_TOE]: 'Right Pinky Toe', }), [] ); @@ -48,34 +48,34 @@ export function ToeAssignmentModal({ () => ({ left: [ { - part: BodyPart.LEFT_ABDUCTOR_HALLUCIS, + part: BodyPart.LEFT_BIG_TOE, cx: 110, cy: 30, }, { - part: BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, + part: BodyPart.LEFT_INDEX_TOE, cx: 73, cy: 40, }, { - part: BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + part: BodyPart.LEFT_PINKY_TOE, cx: 50, cy: 62, }, ], right: [ { - part: BodyPart.RIGHT_ABDUCTOR_HALLUCIS, + part: BodyPart.RIGHT_BIG_TOE, cx: 77, cy: 30, }, { - part: BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + part: BodyPart.RIGHT_INDEX_TOE, cx: 114, cy: 40, }, { - part: BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + part: BodyPart.RIGHT_PINKY_TOE, cx: 137, cy: 62, }, diff --git a/gui/app/src/hooks/body-parts.ts b/gui/app/src/hooks/body-parts.ts index 6dfff1a793..79e40b6d0e 100644 --- a/gui/app/src/hooks/body-parts.ts +++ b/gui/app/src/hooks/body-parts.ts @@ -54,21 +54,21 @@ export const ALL_BODY_PARTS = [ BodyPart.RIGHT_LITTLE_PROXIMAL, BodyPart.RIGHT_LITTLE_INTERMEDIATE, BodyPart.RIGHT_LITTLE_DISTAL, - BodyPart.LEFT_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, - BodyPart.RIGHT_ABDUCTOR_HALLUCIS, - BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.LEFT_BIG_TOE, + BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_PINKY_TOE, + BodyPart.RIGHT_BIG_TOE, + BodyPart.RIGHT_INDEX_TOE, + BodyPart.RIGHT_PINKY_TOE, ]; export const TOE_BODY_PARTS = [ - BodyPart.LEFT_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, - BodyPart.RIGHT_ABDUCTOR_HALLUCIS, - BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.LEFT_BIG_TOE, + BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_PINKY_TOE, + BodyPart.RIGHT_BIG_TOE, + BodyPart.RIGHT_INDEX_TOE, + BodyPart.RIGHT_PINKY_TOE, ]; export const FEET_BODY_PARTS = [BodyPart.LEFT_FOOT, BodyPart.RIGHT_FOOT]; export const FINGER_BODY_PARTS = [ diff --git a/gui/app/src/utils/skeletonHelper.ts b/gui/app/src/utils/skeletonHelper.ts index aa042dde39..60881b36ff 100644 --- a/gui/app/src/utils/skeletonHelper.ts +++ b/gui/app/src/utils/skeletonHelper.ts @@ -239,12 +239,12 @@ export class BoneKind extends Bone { case BodyPart.RIGHT_LITTLE_INTERMEDIATE: case BodyPart.RIGHT_LITTLE_DISTAL: return new Color('pink'); - case BodyPart.LEFT_ABDUCTOR_HALLUCIS: - case BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS: - case BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI: - case BodyPart.RIGHT_ABDUCTOR_HALLUCIS: - case BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS: - case BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI: + case BodyPart.LEFT_BIG_TOE: + case BodyPart.LEFT_INDEX_TOE: + case BodyPart.LEFT_PINKY_TOE: + case BodyPart.RIGHT_BIG_TOE: + case BodyPart.RIGHT_INDEX_TOE: + case BodyPart.RIGHT_PINKY_TOE: return new Color('pink'); } } @@ -282,15 +282,15 @@ export class BoneKind extends Bone { return [BodyPart.RIGHT_FOOT]; case BodyPart.LEFT_FOOT: return [ - BodyPart.LEFT_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.LEFT_BIG_TOE, + BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_PINKY_TOE, ]; case BodyPart.RIGHT_FOOT: return [ - BodyPart.RIGHT_ABDUCTOR_HALLUCIS, - BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.RIGHT_BIG_TOE, + BodyPart.RIGHT_INDEX_TOE, + BodyPart.RIGHT_PINKY_TOE, ]; case BodyPart.LEFT_SHOULDER: return [BodyPart.LEFT_UPPER_ARM]; @@ -382,12 +382,12 @@ export class BoneKind extends Bone { case BodyPart.RIGHT_LITTLE_DISTAL: return []; return []; - case BodyPart.LEFT_ABDUCTOR_HALLUCIS: - case BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS: - case BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI: - case BodyPart.RIGHT_ABDUCTOR_HALLUCIS: - case BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS: - case BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI: + case BodyPart.LEFT_BIG_TOE: + case BodyPart.LEFT_INDEX_TOE: + case BodyPart.LEFT_PINKY_TOE: + case BodyPart.RIGHT_BIG_TOE: + case BodyPart.RIGHT_INDEX_TOE: + case BodyPart.RIGHT_PINKY_TOE: return []; } } @@ -502,17 +502,17 @@ export class BoneKind extends Bone { case BodyPart.RIGHT_LITTLE_DISTAL: return BodyPart.RIGHT_LITTLE_INTERMEDIATE; - case BodyPart.LEFT_ABDUCTOR_HALLUCIS: + case BodyPart.LEFT_BIG_TOE: return BodyPart.LEFT_FOOT; - case BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS: + case BodyPart.LEFT_INDEX_TOE: return BodyPart.LEFT_FOOT; - case BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI: + case BodyPart.LEFT_PINKY_TOE: return BodyPart.LEFT_FOOT; - case BodyPart.RIGHT_ABDUCTOR_HALLUCIS: + case BodyPart.RIGHT_BIG_TOE: return BodyPart.RIGHT_FOOT; - case BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS: + case BodyPart.RIGHT_INDEX_TOE: return BodyPart.RIGHT_FOOT; - case BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI: + case BodyPart.RIGHT_PINKY_TOE: return BodyPart.RIGHT_FOOT; } } diff --git a/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt b/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt index 910ff95694..256782555f 100644 --- a/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt +++ b/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt @@ -77,12 +77,12 @@ val RIGHT_FINGER_PARTS = setOf( ) val LEFT_TOE_PARTS = setOf( - BodyPart.LEFT_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.LEFT_BIG_TOE, + BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_PINKY_TOE, ) val RIGHT_TOE_PARTS = setOf( - BodyPart.RIGHT_ABDUCTOR_HALLUCIS, - BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.RIGHT_BIG_TOE, + BodyPart.RIGHT_INDEX_TOE, + BodyPart.RIGHT_PINKY_TOE, ) diff --git a/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt b/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt index 3fec40eae7..4bb5655121 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt @@ -65,15 +65,19 @@ val BODY_PART_HIERARCHY_MAP: BodyPartMap> = BodyPartMap( BodyPart.RIGHT_LOWER_LEG to arrayOf(BodyPart.RIGHT_FOOT), BodyPart.LEFT_FOOT to arrayOf( - BodyPart.LEFT_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.LEFT_BIG_TOE, + BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_MIDDLE_TOE, + BodyPart.LEFT_RING_TOE, + BodyPart.LEFT_PINKY_TOE, ), BodyPart.RIGHT_FOOT to arrayOf( - BodyPart.RIGHT_ABDUCTOR_HALLUCIS, - BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.RIGHT_BIG_TOE, + BodyPart.RIGHT_INDEX_TOE, + BodyPart.RIGHT_MIDDLE_TOE, + BodyPart.RIGHT_RING_TOE, + BodyPart.RIGHT_PINKY_TOE, ), ), ) diff --git a/server/core/src/main/java/dev/slimevr/skeleton/module.kt b/server/core/src/main/java/dev/slimevr/skeleton/module.kt index e563121bbe..b8f18b0ee9 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/module.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/module.kt @@ -11,7 +11,7 @@ import dev.slimevr.skeleton.processors.BoneYawFallbackProcessor import dev.slimevr.skeleton.processors.BoneYawRollAlignProcessor import dev.slimevr.skeleton.processors.FingerImputeProcessor import dev.slimevr.skeleton.processors.SpineImputeProcessor -import dev.slimevr.skeleton.processors.ToesImputeProcessor +import dev.slimevr.skeleton.processors.ToeDirectLinkProcessor import io.github.axisangles.ktmath.Quaternion import io.github.axisangles.ktmath.Vector3 import kotlinx.coroutines.CoroutineScope diff --git a/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt b/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt index 27209a9d07..571f9b5955 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt @@ -15,12 +15,17 @@ class ToeDirectLinkProcessor : SkeletonProcessor { * Second element is the BodyPart the first element is linked to. */ private val toesToSource = arrayOf( - BodyPart.LEFT_ABDUCTOR_HALLUCIS to BodyPart.LEFT_FOOT, - BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS to BodyPart.LEFT_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI to BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.RIGHT_ABDUCTOR_HALLUCIS to BodyPart.RIGHT_FOOT, - BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS to BodyPart.RIGHT_ABDUCTOR_HALLUCIS, - BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI to BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, + BodyPart.LEFT_BIG_TOE to BodyPart.LEFT_FOOT, + BodyPart.LEFT_INDEX_TOE to BodyPart.LEFT_BIG_TOE, + BodyPart.LEFT_MIDDLE_TOE to BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_RING_TOE to BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_PINKY_TOE to BodyPart.LEFT_INDEX_TOE, + + BodyPart.RIGHT_BIG_TOE to BodyPart.RIGHT_FOOT, + BodyPart.RIGHT_INDEX_TOE to BodyPart.RIGHT_BIG_TOE, + BodyPart.RIGHT_MIDDLE_TOE to BodyPart.LEFT_INDEX_TOE, + BodyPart.RIGHT_RING_TOE to BodyPart.LEFT_INDEX_TOE, + BodyPart.RIGHT_PINKY_TOE to BodyPart.RIGHT_INDEX_TOE, ) override fun process(state: SkeletonState): SkeletonState { diff --git a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt index c0c294c6e3..a4111d7c80 100644 --- a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt +++ b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt @@ -58,12 +58,16 @@ val BODY_PART_TO_UNITY_BONE: Map = mapOf( BodyPart.RIGHT_LITTLE_PROXIMAL to "RightLittleProximal", BodyPart.RIGHT_LITTLE_INTERMEDIATE to "RightLittleIntermediate", BodyPart.RIGHT_LITTLE_DISTAL to "RightLittleDistal", - BodyPart.LEFT_ABDUCTOR_HALLUCIS to "LeftAbductorHallucis", - BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS to "LeftFlexorDigitorumBrevis", - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI to "LeftAbductorDigitiMinimi", - BodyPart.RIGHT_ABDUCTOR_HALLUCIS to "RightAbductorHallucis", - BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS to "RightFlexorDigitorumBrevis", - BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI to "RightAbductorDigitiMinimi", + BodyPart.LEFT_BIG_TOE to "LeftBigToe", + BodyPart.LEFT_INDEX_TOE to "LeftIndexToe", + BodyPart.LEFT_MIDDLE_TOE to "LeftMiddleToe", + BodyPart.LEFT_RING_TOE to "LeftRingToe", + BodyPart.LEFT_PINKY_TOE to "LeftPinkyToe", + BodyPart.RIGHT_BIG_TOE to "RightBigToe", + BodyPart.RIGHT_INDEX_TOE to "RightIndexToe", + BodyPart.RIGHT_MIDDLE_TOE to "RightMiddleToe", + BodyPart.RIGHT_RING_TOE to "RightRingToe", + BodyPart.RIGHT_PINKY_TOE to "RightPinkyToe", ) // HIP-rooted hierarchy. VMC/Unity expects this; our skeleton is HEAD-rooted. @@ -118,14 +122,14 @@ val VMC_HIERARCHY_MAP: Map> = mapOf( BodyPart.RIGHT_LITTLE_PROXIMAL to arrayOf(BodyPart.RIGHT_LITTLE_INTERMEDIATE), BodyPart.RIGHT_LITTLE_INTERMEDIATE to arrayOf(BodyPart.RIGHT_LITTLE_DISTAL), BodyPart.LEFT_FOOT to arrayOf( - BodyPart.LEFT_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.LEFT_BIG_TOE, + BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_PINKY_TOE, ), BodyPart.RIGHT_FOOT to arrayOf( - BodyPart.RIGHT_ABDUCTOR_HALLUCIS, - BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.RIGHT_BIG_TOE, + BodyPart.RIGHT_INDEX_TOE, + BodyPart.RIGHT_PINKY_TOE, ), ) @@ -162,9 +166,9 @@ val VMC_MIRROR_BONE_PAIRS: List> = listOf( BodyPart.LEFT_LITTLE_PROXIMAL to BodyPart.RIGHT_LITTLE_PROXIMAL, BodyPart.LEFT_LITTLE_INTERMEDIATE to BodyPart.RIGHT_LITTLE_INTERMEDIATE, BodyPart.LEFT_LITTLE_DISTAL to BodyPart.RIGHT_LITTLE_DISTAL, - BodyPart.LEFT_ABDUCTOR_HALLUCIS to BodyPart.RIGHT_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS to BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI to BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.LEFT_BIG_TOE to BodyPart.RIGHT_BIG_TOE, + BodyPart.LEFT_INDEX_TOE to BodyPart.RIGHT_INDEX_TOE, + BodyPart.LEFT_PINKY_TOE to BodyPart.RIGHT_PINKY_TOE, ) val VMC_MIRROR_BONES: BodyPartMap = BodyPartMap( @@ -197,14 +201,14 @@ val VMC_REST_ROTATIONS: BodyPartMap = run { BodyPart.RIGHT_LITTLE_PROXIMAL, BodyPart.RIGHT_LITTLE_INTERMEDIATE, BodyPart.RIGHT_LITTLE_DISTAL, ) val leftToes = listOf( - BodyPart.LEFT_ABDUCTOR_HALLUCIS, - BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.LEFT_BIG_TOE, + BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_PINKY_TOE, ) val rightToes = listOf( - BodyPart.RIGHT_ABDUCTOR_HALLUCIS, - BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS, - BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI, + BodyPart.RIGHT_BIG_TOE, + BodyPart.RIGHT_INDEX_TOE, + BodyPart.RIGHT_PINKY_TOE, ) BodyPartMap( mapOf( diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toes-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt similarity index 92% rename from server/core/src/main/java/dev/slimevr/vrcosc/toes-output-encoder.kt rename to server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index e87ad22537..6270a2a143 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toes-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -10,7 +10,6 @@ import solarxr_protocol.datatypes.BodyPart import kotlin.math.* private enum class FootSide { Left, Right } - internal fun buildToeMessages(bones: Map): List { val messages = mutableListOf() @@ -18,9 +17,9 @@ internal fun buildToeMessages(bones: Map): List val leftFoot = bones[BodyPart.LEFT_FOOT] if (leftFoot != null) { val leftToes = listOf( - bones[BodyPart.LEFT_ABDUCTOR_HALLUCIS], - bones[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS], - bones[BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI], + bones[BodyPart.LEFT_BIG_TOE], + bones[BodyPart.LEFT_INDEX_TOE], + bones[BodyPart.LEFT_PINKY_TOE], ) processToesForFoot(leftFoot, leftToes, FootSide.Left, messages) } @@ -29,9 +28,9 @@ internal fun buildToeMessages(bones: Map): List val rightFoot = bones[BodyPart.RIGHT_FOOT] if (rightFoot != null) { val rightToes = listOf( - bones[BodyPart.RIGHT_ABDUCTOR_HALLUCIS], - bones[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS], - bones[BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI], + bones[BodyPart.RIGHT_BIG_TOE], + bones[BodyPart.RIGHT_INDEX_TOE], + bones[BodyPart.RIGHT_PINKY_TOE], ) processToesForFoot(rightFoot, rightToes, FootSide.Right, messages) } @@ -101,7 +100,7 @@ private fun processToe( messages.add(OscContent.Message(OscMessage("/avatar/parameters/TipToes${side.name}", listOf(if (tipToe) OscArg.True else OscArg.False)))) messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.name}${toeNumber + 1}Bool", listOf(if (bending) OscArg.True else OscArg.False)))) messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.name}${toeNumber + 1}", listOf(if (splayed) OscArg.True else OscArg.False)))) - + val floatValue = (pitch / 90f).coerceIn(-1f, 1f) messages.add(OscContent.Message(OscMessage("/avatar/parameters/Toe${side.name}${toeNumber + 1}Float", listOf(OscArg.Float(floatValue))))) } diff --git a/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt index a7672e8f9d..3958768764 100644 --- a/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt +++ b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt @@ -1,6 +1,6 @@ package dev.slimevr.skeleton -import dev.slimevr.skeleton.processors.ToesImputeProcessor +import dev.slimevr.skeleton.processors.ToeDirectLinkProcessor import io.github.axisangles.ktmath.Quaternion import org.junit.jupiter.api.Test import solarxr_protocol.datatypes.BodyPart @@ -9,7 +9,7 @@ import kotlin.test.assertTrue class ToeDirectLinkProcessorTest { @Test fun `test impute missing all toe trackers`() { - val processor = ToesImputeProcessor() + val processor = ToeDirectLinkProcessor() val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), @@ -27,48 +27,48 @@ class ToeDirectLinkProcessorTest { val newInputs = processor.process(state) - val leftAbductorDigitorumBrevisIsSameRotationAsLeftFoot = - newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_HALLUCIS]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FOOT]?.rawRotation + val leftBigToeIsSameRotationAsLeftFoot = + newInputs.boneInputs[BodyPart.LEFT_BIG_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FOOT]?.rawRotation - val leftFlexorDigitorumBrevisDigitiMinimiIsSameRotationAsLeftAbductorHallucis = - newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_HALLUCIS]?.rawRotation + val leftIndexToeIsSameRotationAsLeftBigToe = + newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_BIG_TOE]?.rawRotation - val leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis = - newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + val leftPinkyToesIsSameRotationAsLeftIndexToe = + newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation val rightAbductorDigitorumBrevisIsSameRotationAsRightFoot = - newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_HALLUCIS]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FOOT]?.rawRotation + newInputs.boneInputs[BodyPart.RIGHT_BIG_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FOOT]?.rawRotation - val rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis = - newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_HALLUCIS]?.rawRotation + val rightIndexToeIsDigitiMinimiSameAsRightBigToe = + newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_BIG_TOE]?.rawRotation - val rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis = - newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + val rightPinkyToesIsSameRotationAsRightIndexToe = + newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation val testSucceeded = - leftAbductorDigitorumBrevisIsSameRotationAsLeftFoot - && leftFlexorDigitorumBrevisDigitiMinimiIsSameRotationAsLeftAbductorHallucis - && leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis + leftBigToeIsSameRotationAsLeftFoot + && leftIndexToeIsSameRotationAsLeftBigToe + && leftPinkyToesIsSameRotationAsLeftIndexToe && rightAbductorDigitorumBrevisIsSameRotationAsRightFoot - && rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis - && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis + && rightIndexToeIsDigitiMinimiSameAsRightBigToe + && rightPinkyToesIsSameRotationAsRightIndexToe assertTrue(testSucceeded) } @Test - fun `test impute missing toe trackers from flexor digitorum brevis and abductor digiti minimi`() { - val processor = ToesImputeProcessor() + fun `test impute missing toe trackers from index toe and pinky toe`() { + val processor = ToeDirectLinkProcessor() val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) - map[BodyPart.LEFT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.LEFT_ABDUCTOR_HALLUCIS).copy( + map[BodyPart.LEFT_BIG_TOE] = map.getValue(BodyPart.LEFT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) - map[BodyPart.RIGHT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.RIGHT_ABDUCTOR_HALLUCIS).copy( + map[BodyPart.RIGHT_BIG_TOE] = map.getValue(BodyPart.RIGHT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) } @@ -81,46 +81,46 @@ class ToeDirectLinkProcessorTest { val newInputs = processor.process(state) - val leftFlexorDigitorumBrevisDigitiMinimiIsSameRotationAsLeftAbductorHallucis = - newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_HALLUCIS]?.rawRotation + val leftIndexToeIsSameRotationAsLeftBigToe = + newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_BIG_TOE]?.rawRotation - val leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis = - newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + val leftPinkyToesIsSameRotationAsLeftIndexToe = + newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation - val rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis = - newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_HALLUCIS]?.rawRotation + val rightIndexToeIsDigitiMinimiSameAsRightBigToe = + newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_BIG_TOE]?.rawRotation - val rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis = - newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + val rightPinkyToesIsSameRotationAsRightIndexToe = + newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation - val testSucceeded = leftFlexorDigitorumBrevisDigitiMinimiIsSameRotationAsLeftAbductorHallucis - && leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis - && rightFlexorDigitorumBrevisIsDigitiMinimiSameAsRightAbductorHallucis - && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis + val testSucceeded = leftIndexToeIsSameRotationAsLeftBigToe + && leftPinkyToesIsSameRotationAsLeftIndexToe + && rightIndexToeIsDigitiMinimiSameAsRightBigToe + && rightPinkyToesIsSameRotationAsRightIndexToe assertTrue(testSucceeded) } @Test - fun `test impute missing toe tracker from abductor digiti minimi`() { - val processor = ToesImputeProcessor() + fun `test impute missing toe tracker from pinky toe`() { + val processor = ToeDirectLinkProcessor() val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) - map[BodyPart.LEFT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.LEFT_ABDUCTOR_HALLUCIS).copy( + map[BodyPart.LEFT_BIG_TOE] = map.getValue(BodyPart.LEFT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) - map[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS] = map.getValue(BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS).copy( + map[BodyPart.LEFT_INDEX_TOE] = map.getValue(BodyPart.LEFT_INDEX_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) - map[BodyPart.RIGHT_ABDUCTOR_HALLUCIS] = map.getValue(BodyPart.RIGHT_ABDUCTOR_HALLUCIS).copy( + map[BodyPart.RIGHT_BIG_TOE] = map.getValue(BodyPart.RIGHT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) - map[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS] = map.getValue(BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS).copy( + map[BodyPart.RIGHT_INDEX_TOE] = map.getValue(BodyPart.RIGHT_INDEX_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) } @@ -133,14 +133,14 @@ class ToeDirectLinkProcessorTest { val newInputs = processor.process(state) - val leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis = - newInputs.boneInputs[BodyPart.LEFT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + val leftPinkyToesIsSameRotationAsLeftIndexToe = + newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation - val rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis = - newInputs.boneInputs[BodyPart.RIGHT_ABDUCTOR_DIGITI_MINIMI]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FLEXOR_DIGITORUM_BREVIS]?.rawRotation + val rightPinkyToesIsSameRotationAsRightIndexToe = + newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation - val testSucceeded = leftAbductorDigitiMinimiIsSameRotationAsLeftFlexorDigitorumBrevis - && rightAbductorDigitiMinimiIsSameRotationAsRightFlexorDigitorumBrevis + val testSucceeded = leftPinkyToesIsSameRotationAsLeftIndexToe + && rightPinkyToesIsSameRotationAsRightIndexToe assertTrue(testSucceeded) } From 674586ed306262e80cb5cedee7ed8aff6ca5bd5e Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 18:07:24 -0500 Subject: [PATCH 10/32] Add extra toe bones to skeleton helper. --- gui/app/src/utils/skeletonHelper.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/gui/app/src/utils/skeletonHelper.ts b/gui/app/src/utils/skeletonHelper.ts index 60881b36ff..b345ac2792 100644 --- a/gui/app/src/utils/skeletonHelper.ts +++ b/gui/app/src/utils/skeletonHelper.ts @@ -12,7 +12,7 @@ const _matrixWorldInv = new Matrix4(); export class BasedSkeletonHelper extends LineSegments2 { isSkeletonHelper: boolean; - root: Object3D; + root: Object3D bones: Bone[]; readonly type = 'SkeletonHelper'; @@ -241,9 +241,13 @@ export class BoneKind extends Bone { return new Color('pink'); case BodyPart.LEFT_BIG_TOE: case BodyPart.LEFT_INDEX_TOE: + case BodyPart.LEFT_MIDDLE_TOE: + case BodyPart.LEFT_RING_TOE: case BodyPart.LEFT_PINKY_TOE: case BodyPart.RIGHT_BIG_TOE: case BodyPart.RIGHT_INDEX_TOE: + case BodyPart.RIGHT_MIDDLE_TOE: + case BodyPart.RIGHT_RING_TOE: case BodyPart.RIGHT_PINKY_TOE: return new Color('pink'); } @@ -284,12 +288,16 @@ export class BoneKind extends Bone { return [ BodyPart.LEFT_BIG_TOE, BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_MIDDLE_TOE, + BodyPart.LEFT_RING_TOE, BodyPart.LEFT_PINKY_TOE, ]; case BodyPart.RIGHT_FOOT: return [ BodyPart.RIGHT_BIG_TOE, BodyPart.RIGHT_INDEX_TOE, + BodyPart.RIGHT_MIDDLE_TOE, + BodyPart.RIGHT_RING_TOE, BodyPart.RIGHT_PINKY_TOE, ]; case BodyPart.LEFT_SHOULDER: @@ -384,9 +392,13 @@ export class BoneKind extends Bone { return []; case BodyPart.LEFT_BIG_TOE: case BodyPart.LEFT_INDEX_TOE: + case BodyPart.LEFT_MIDDLE_TOE: + case BodyPart.LEFT_RING_TOE: case BodyPart.LEFT_PINKY_TOE: case BodyPart.RIGHT_BIG_TOE: case BodyPart.RIGHT_INDEX_TOE: + case BodyPart.RIGHT_MIDDLE_TOE: + case BodyPart.RIGHT_RING_TOE: case BodyPart.RIGHT_PINKY_TOE: return []; } @@ -506,12 +518,20 @@ export class BoneKind extends Bone { return BodyPart.LEFT_FOOT; case BodyPart.LEFT_INDEX_TOE: return BodyPart.LEFT_FOOT; + case BodyPart.LEFT_MIDDLE_TOE: + return BodyPart.LEFT_FOOT; + case BodyPart.LEFT_RING_TOE: + return BodyPart.LEFT_FOOT; case BodyPart.LEFT_PINKY_TOE: return BodyPart.LEFT_FOOT; case BodyPart.RIGHT_BIG_TOE: return BodyPart.RIGHT_FOOT; case BodyPart.RIGHT_INDEX_TOE: return BodyPart.RIGHT_FOOT; + case BodyPart.RIGHT_MIDDLE_TOE: + return BodyPart.RIGHT_FOOT; + case BodyPart.RIGHT_RING_TOE: + return BodyPart.RIGHT_FOOT; case BodyPart.RIGHT_PINKY_TOE: return BodyPart.RIGHT_FOOT; } From 4e00b2d60713092c5a2d7cd0fe56894ee3f9863d Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 19:49:59 -0500 Subject: [PATCH 11/32] Fix translations for toes. --- gui/app/public/i18n/en/translation.ftl | 59 +++++++++++++++++++ .../trackers-assign/ToeAssignmentModal.tsx | 4 +- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/gui/app/public/i18n/en/translation.ftl b/gui/app/public/i18n/en/translation.ftl index 25ee5c2308..7c96819a4f 100644 --- a/gui/app/public/i18n/en/translation.ftl +++ b/gui/app/public/i18n/en/translation.ftl @@ -438,6 +438,17 @@ tracker_selection_menu-LEFT_UPPER_LEG = { -tracker_selection-part } left thigh? tracker_selection_menu-LEFT_LOWER_LEG = { -tracker_selection-part } left ankle? tracker_selection_menu-LEFT_FOOT = { -tracker_selection-part } left foot? tracker_selection_menu-LEFT_CONTROLLER = { -tracker_selection-part } left controller? +tracker_selection_menu-LEFT_BIG_TOE = { -tracker_selection-part } left big toe? +tracker_selection_menu-LEFT_INDEX_TOE = { -tracker_selection-part } left index toe? +tracker_selection_menu-LEFT_MIDDLE_TOE = { -tracker_selection-part } left middle toe? +tracker_selection_menu-LEFT_RING_TOE = { -tracker_selection-part } left ring toe? +tracker_selection_menu-LEFT_PINKY_TOE = { -tracker_selection-part } left pinky toe? +tracker_selection_menu-RIGHT_BIG_TOE = { -tracker_selection-part } right big toe? +tracker_selection_menu-RIGHT_INDEX_TOE = { -tracker_selection-part } right index toe? +tracker_selection_menu-RIGHT_MIDDLE_TOE = { -tracker_selection-part } right middle toe? +tracker_selection_menu-RIGHT_RING_TOE = { -tracker_selection-part } right ring toe? +tracker_selection_menu-RIGHT_PINKY_TOE = { -tracker_selection-part } right pinky toe? + tracker_selection_menu-unassigned = Unassigned Trackers tracker_selection_menu-assigned = Assigned Trackers @@ -1194,6 +1205,42 @@ onboarding-assign_trackers-warning-WAIST = Waist is assigned but you need { $una *[unknown] Unknown unassigned body part } to also be assigned! +onboarding-assign_trackers-warning-LEFT_BIG_TOE = Left Big Toe is assigned but you need { $unassigned -> + [0] the left foot + *[unknown] Unknown unassigned body part +} to also be assigned! +onboarding-assign_trackers-warning-LEFT_INDEX_TOE = Left Index Toe is assigned but you need { $unassigned -> + [0] the left foot + *[unknown] Unknown unassigned body part +} to also be assigned! +onboarding-assign_trackers-warning-LEFT_MIDDLE_TOE = Left Middle Toe is assigned but you need { $unassigned -> + [0] the left foot + *[unknown] Unknown unassigned body part} to also be assigned! +onboarding-assign_trackers-warning-LEFT_RING_TOE = Left Ring Toe is assigned but you need { $unassigned -> + [0] the left foot + *[unknown] Unknown unassigned body part} to also be assigned! +onboarding-assign_trackers-warning-LEFT_PINKY_TOE = Left Pinky Toe is assigned but you need { $unassigned -> + [0] the left foot + *[unknown] Unknown unassigned body part} to also be assigned! +onboarding-assign_trackers-warning-RIGHT_BIG_TOE = Left Big Toe is assigned but you need { $unassigned -> + [0] the right foot + *[unknown] Unknown unassigned body part +} to also be assigned! +onboarding-assign_trackers-warning-RIGHT_INDEX_TOE = Right Index Toe is assigned but you need { $unassigned -> + [0] the right foot + *[unknown] Unknown unassigned body part +} to also be assigned! +onboarding-assign_trackers-warning-RIGHT_MIDDLE_TOE = Right Middle Toe is assigned but you need { $unassigned -> + [0] the right foot + *[unknown] Unknown unassigned body part} to also be assigned! +onboarding-assign_trackers-warning-RIGHT_RING_TOE = Right Ring Toe is assigned but you need { $unassigned -> + [0] the right foot + *[unknown] Unknown unassigned body part} to also be assigned! +onboarding-assign_trackers-warning-RIGHT_PINKY_TOE = Right Pinky Toe is assigned but you need { $unassigned -> + [0] the right foot + *[unknown] Unknown unassigned body part} to also be assigned! + + ## Tracker mounting method choose onboarding-choose_mounting = What mounting calibration method to use? # Multiline text @@ -1703,3 +1750,15 @@ toolbar-mounting_calibration-fingers = Fingers toolbar-drift_reset = Drift Reset toolbar-assigned_trackers = {$count} trackers assigned toolbar-unassigned_trackers = {$count} trackers unassigned +body_part-LEFT_BIG_TOE = Left Big Toe +body_part-LEFT_INDEX_TOE = Left Index Toe +body_part-LEFT_MIDDLE_TOE = Left Middle Toe +body_part-LEFT_RING_TOE = Left Ring Toe +body_part-LEFT_PINKY_TOE = Left Pinky Toe + +body_part-RIGHT_TOES = Right Toes +body_part-RIGHT_BIG_TOE = Right Big Toe +body_part-RIGHT_INDEX_TOE = Right Index Toe +body_part-RIGHT_MIDDLE_TOE = Right Middle Toe +body_part-RIGHT_RING_TOE = Right Ring Toe +body_part-RIGHT_PINKY_TOE = Right Pinky Toe diff --git a/gui/app/src/components/onboarding/pages/trackers-assign/ToeAssignmentModal.tsx b/gui/app/src/components/onboarding/pages/trackers-assign/ToeAssignmentModal.tsx index 3cf969c1b2..057a6e587d 100644 --- a/gui/app/src/components/onboarding/pages/trackers-assign/ToeAssignmentModal.tsx +++ b/gui/app/src/components/onboarding/pages/trackers-assign/ToeAssignmentModal.tsx @@ -35,10 +35,10 @@ export function ToeAssignmentModal({ const TOE_LABELS: Record = useMemo( () => ({ [BodyPart.LEFT_BIG_TOE]: 'Left Big Toe', - [BodyPart.LEFT_INDEX_TOE]: 'Left Middle Toes', + [BodyPart.LEFT_INDEX_TOE]: 'Left Index Toe', [BodyPart.LEFT_PINKY_TOE]: 'Left Pinky Toe', [BodyPart.RIGHT_BIG_TOE]: 'Right Big Toe', - [BodyPart.RIGHT_INDEX_TOE]: 'Right Middle Toes', + [BodyPart.RIGHT_INDEX_TOE]: 'Right Index Toe', [BodyPart.RIGHT_PINKY_TOE]: 'Right Pinky Toe', }), [] From 6b638b1f336f21081120d298633814cc595a5574 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 20:51:21 -0500 Subject: [PATCH 12/32] Fix incorrect toe mappings. --- .../java/dev/slimevr/skeleton/processors/toe-direct-link.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt b/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt index 571f9b5955..7cf359ef55 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt @@ -23,8 +23,8 @@ class ToeDirectLinkProcessor : SkeletonProcessor { BodyPart.RIGHT_BIG_TOE to BodyPart.RIGHT_FOOT, BodyPart.RIGHT_INDEX_TOE to BodyPart.RIGHT_BIG_TOE, - BodyPart.RIGHT_MIDDLE_TOE to BodyPart.LEFT_INDEX_TOE, - BodyPart.RIGHT_RING_TOE to BodyPart.LEFT_INDEX_TOE, + BodyPart.RIGHT_MIDDLE_TOE to BodyPart.RIGHT_INDEX_TOE, + BodyPart.RIGHT_RING_TOE to BodyPart.RIGHT_INDEX_TOE, BodyPart.RIGHT_PINKY_TOE to BodyPart.RIGHT_INDEX_TOE, ) From ba7c0023cc29c5d52bd43cc370268aefd329f7c4 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 21:53:30 -0500 Subject: [PATCH 13/32] Update test to cover all new toe entries. --- .../skeleton/ToeDirectLinkProcessorTest.kt | 227 ++++++++++++++---- 1 file changed, 179 insertions(+), 48 deletions(-) diff --git a/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt index 3958768764..a3f90ac856 100644 --- a/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt +++ b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt @@ -8,14 +8,14 @@ import kotlin.test.assertTrue class ToeDirectLinkProcessorTest { @Test - fun `test impute missing all toe trackers`() { + fun `test missing all toe trackers`() { val processor = ToeDirectLinkProcessor() val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( - rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(20f, 50f, 25f), isActive = true) } @@ -29,47 +29,53 @@ class ToeDirectLinkProcessorTest { val leftBigToeIsSameRotationAsLeftFoot = newInputs.boneInputs[BodyPart.LEFT_BIG_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_FOOT]?.rawRotation - val leftIndexToeIsSameRotationAsLeftBigToe = newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_BIG_TOE]?.rawRotation + val leftMiddleToeIsSameRotationAsLeftIndexToe = + newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation + val leftRingToeIsSameRotationAsLeftMiddleToe = + newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation + val leftPinkyToeIsSameRotationAsLeftRingToe = + newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation - val leftPinkyToesIsSameRotationAsLeftIndexToe = - newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation - - val rightAbductorDigitorumBrevisIsSameRotationAsRightFoot = + val rightBigToeIsSameRotationAsRightFoot = newInputs.boneInputs[BodyPart.RIGHT_BIG_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FOOT]?.rawRotation - - val rightIndexToeIsDigitiMinimiSameAsRightBigToe = + val rightIndexToeIsSameRotationAsRightBigToe = newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_BIG_TOE]?.rawRotation + val rightMiddleToeIsSameRotationAsRightIndexToe = + newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation + val rightRingToeIsSameRotationAsRightMiddleToe = + newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation + val rightPinkyToeIsSameRotationAsRightRingToe = + newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation - val rightPinkyToesIsSameRotationAsRightIndexToe = - newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation - - val testSucceeded = - leftBigToeIsSameRotationAsLeftFoot + assertTrue(leftBigToeIsSameRotationAsLeftFoot && leftIndexToeIsSameRotationAsLeftBigToe - && leftPinkyToesIsSameRotationAsLeftIndexToe - && rightAbductorDigitorumBrevisIsSameRotationAsRightFoot - && rightIndexToeIsDigitiMinimiSameAsRightBigToe - && rightPinkyToesIsSameRotationAsRightIndexToe - - assertTrue(testSucceeded) + && leftMiddleToeIsSameRotationAsLeftIndexToe + && leftRingToeIsSameRotationAsLeftMiddleToe + && leftPinkyToeIsSameRotationAsLeftRingToe) + assertTrue(rightBigToeIsSameRotationAsRightFoot + && rightIndexToeIsSameRotationAsRightBigToe + && rightMiddleToeIsSameRotationAsRightIndexToe + && rightRingToeIsSameRotationAsRightMiddleToe + && rightPinkyToeIsSameRotationAsRightRingToe) } + @Test - fun `test impute missing toe trackers from index toe and pinky toe`() { + fun `test missing toe trackers from index toe downwards`() { val processor = ToeDirectLinkProcessor() val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.LEFT_BIG_TOE] = map.getValue(BodyPart.LEFT_BIG_TOE).copy( - rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(11f, 41f, 16f), isActive = true) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( - rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(20f, 50f, 25f), isActive = true) map[BodyPart.RIGHT_BIG_TOE] = map.getValue(BodyPart.RIGHT_BIG_TOE).copy( - rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(21f, 51f, 26f), isActive = true) } @@ -83,45 +89,115 @@ class ToeDirectLinkProcessorTest { val leftIndexToeIsSameRotationAsLeftBigToe = newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_BIG_TOE]?.rawRotation + val leftMiddleToeIsSameRotationAsLeftIndexToe = + newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation + val leftRingToeIsSameRotationAsLeftMiddleToe = + newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation + val leftPinkyToeIsSameRotationAsLeftRingToe = + newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation - val leftPinkyToesIsSameRotationAsLeftIndexToe = - newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation - - val rightIndexToeIsDigitiMinimiSameAsRightBigToe = + val rightIndexToeIsSameRotationAsRightBigToe = newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_BIG_TOE]?.rawRotation + val rightMiddleToeIsSameRotationAsRightIndexToe = + newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation + val rightRingToeIsSameRotationAsRightMiddleToe = + newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation + val rightPinkyToeIsSameRotationAsRightRingToe = + newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation - val rightPinkyToesIsSameRotationAsRightIndexToe = - newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation + assertTrue(leftIndexToeIsSameRotationAsLeftBigToe + && leftMiddleToeIsSameRotationAsLeftIndexToe + && leftRingToeIsSameRotationAsLeftMiddleToe + && leftPinkyToeIsSameRotationAsLeftRingToe) + assertTrue(rightIndexToeIsSameRotationAsRightBigToe + && rightMiddleToeIsSameRotationAsRightIndexToe + && rightRingToeIsSameRotationAsRightMiddleToe + && rightPinkyToeIsSameRotationAsRightRingToe) + } - val testSucceeded = leftIndexToeIsSameRotationAsLeftBigToe - && leftPinkyToesIsSameRotationAsLeftIndexToe - && rightIndexToeIsDigitiMinimiSameAsRightBigToe - && rightPinkyToesIsSameRotationAsRightIndexToe + @Test + fun `test missing toe trackers from middle toe downwards`() { + val processor = ToeDirectLinkProcessor() + val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> + map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), + isActive = true) + map[BodyPart.LEFT_BIG_TOE] = map.getValue(BodyPart.LEFT_BIG_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(11f, 41f, 16f), + isActive = true) + map[BodyPart.LEFT_INDEX_TOE] = map.getValue(BodyPart.LEFT_INDEX_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(12f, 42f, 17f), + isActive = true) + + map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( + rawRotation = Quaternion.Companion.fromRotationVector(20f, 50f, 25f), + isActive = true) + map[BodyPart.RIGHT_BIG_TOE] = map.getValue(BodyPart.RIGHT_BIG_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(21f, 51f, 26f), + isActive = true) + map[BodyPart.RIGHT_INDEX_TOE] = map.getValue(BodyPart.RIGHT_INDEX_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(22f, 52f, 27f), + isActive = true) + } + + val state = SkeletonState( + boneInputs = inputs, + skeletonHeight = 1.7f, + paused = false + ) - assertTrue(testSucceeded) + val newInputs = processor.process(state) + + val leftMiddleToeIsSameRotationAsLeftIndexToe = + newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation + val leftRingToeIsSameRotationAsLeftMiddleToe = + newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation + val leftPinkyToeIsSameRotationAsLeftRingToe = + newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation + + val rightMiddleToeIsSameRotationAsRightIndexToe = + newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation + val rightRingToeIsSameRotationAsRightMiddleToe = + newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation + val rightPinkyToeIsSameRotationAsRightRingToe = + newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation + + assertTrue(leftMiddleToeIsSameRotationAsLeftIndexToe + && leftRingToeIsSameRotationAsLeftMiddleToe + && leftPinkyToeIsSameRotationAsLeftRingToe) + assertTrue(rightMiddleToeIsSameRotationAsRightIndexToe + && rightRingToeIsSameRotationAsRightMiddleToe + && rightPinkyToeIsSameRotationAsRightRingToe) } @Test - fun `test impute missing toe tracker from pinky toe`() { + fun `test missing toe trackers from ring toe downwards`() { val processor = ToeDirectLinkProcessor() val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), isActive = true) map[BodyPart.LEFT_BIG_TOE] = map.getValue(BodyPart.LEFT_BIG_TOE).copy( - rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(11f, 41f, 16f), isActive = true) map[BodyPart.LEFT_INDEX_TOE] = map.getValue(BodyPart.LEFT_INDEX_TOE).copy( - rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(12f, 42f, 17f), + isActive = true) + map[BodyPart.LEFT_MIDDLE_TOE] = map.getValue(BodyPart.LEFT_MIDDLE_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(13f, 43f, 18f), isActive = true) + map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( - rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(20f, 50f, 25f), isActive = true) map[BodyPart.RIGHT_BIG_TOE] = map.getValue(BodyPart.RIGHT_BIG_TOE).copy( - rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(21f, 51f, 26f), isActive = true) map[BodyPart.RIGHT_INDEX_TOE] = map.getValue(BodyPart.RIGHT_INDEX_TOE).copy( - rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), + rawRotation = Quaternion.Companion.fromRotationVector(22f, 52f, 27f), + isActive = true) + map[BodyPart.RIGHT_MIDDLE_TOE] = map.getValue(BodyPart.RIGHT_MIDDLE_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(23f, 53f, 28f), isActive = true) } @@ -133,15 +209,70 @@ class ToeDirectLinkProcessorTest { val newInputs = processor.process(state) - val leftPinkyToesIsSameRotationAsLeftIndexToe = - newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation + val leftRingToeIsSameRotationAsLeftMiddleToe = + newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation + val leftPinkyToeIsSameRotationAsLeftRingToe = + newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation + + val rightRingToeIsSameRotationAsRightMiddleToe = + newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation + val rightPinkyToeIsSameRotationAsRightRingToe = + newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation + + assertTrue(leftRingToeIsSameRotationAsLeftMiddleToe && leftPinkyToeIsSameRotationAsLeftRingToe) + assertTrue(rightRingToeIsSameRotationAsRightMiddleToe && rightPinkyToeIsSameRotationAsRightRingToe) + } + + @Test + fun `test impute missing pinky toe tracker`() { + val processor = ToeDirectLinkProcessor() + val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> + map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( + rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), + isActive = true) + map[BodyPart.LEFT_BIG_TOE] = map.getValue(BodyPart.LEFT_BIG_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(11f, 41f, 16f), + isActive = true) + map[BodyPart.LEFT_INDEX_TOE] = map.getValue(BodyPart.LEFT_INDEX_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(12f, 42f, 17f), + isActive = true) + map[BodyPart.LEFT_MIDDLE_TOE] = map.getValue(BodyPart.LEFT_MIDDLE_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(15f, 45f, 19f), + isActive = true) + map[BodyPart.LEFT_RING_TOE] = map.getValue(BodyPart.LEFT_RING_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(13f, 43f, 18f), + isActive = true) + + map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( + rawRotation = Quaternion.Companion.fromRotationVector(20f, 50f, 25f), + isActive = true) + map[BodyPart.RIGHT_BIG_TOE] = map.getValue(BodyPart.RIGHT_BIG_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(21f, 51f, 26f), + isActive = true) + map[BodyPart.RIGHT_INDEX_TOE] = map.getValue(BodyPart.RIGHT_INDEX_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(22f, 52f, 27f), + isActive = true) + map[BodyPart.RIGHT_MIDDLE_TOE] = map.getValue(BodyPart.RIGHT_MIDDLE_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(23f, 53f, 28f), + isActive = true) + map[BodyPart.RIGHT_RING_TOE] = map.getValue(BodyPart.RIGHT_RING_TOE).copy( + rawRotation = Quaternion.Companion.fromRotationVector(24f, 54f, 29f), + isActive = true) + } + + val state = SkeletonState( + boneInputs = inputs, + skeletonHeight = 1.7f, + paused = false + ) - val rightPinkyToesIsSameRotationAsRightIndexToe = - newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation + val newInputs = processor.process(state) - val testSucceeded = leftPinkyToesIsSameRotationAsLeftIndexToe - && rightPinkyToesIsSameRotationAsRightIndexToe + val leftPinkyToeIsSameRotationAsLeftRingToe = + newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation + val rightPinkyToeIsSameRotationAsRightRingToe = + newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation - assertTrue(testSucceeded) + assertTrue(leftPinkyToeIsSameRotationAsLeftRingToe && rightPinkyToeIsSameRotationAsRightRingToe) } } From 8c8d9c9d806e46aa974f5614818f142d92bb2455 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 21:56:37 -0500 Subject: [PATCH 14/32] Adjust toe entries --- .../java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt index a3f90ac856..576f7cda21 100644 --- a/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt +++ b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt @@ -224,7 +224,7 @@ class ToeDirectLinkProcessorTest { } @Test - fun `test impute missing pinky toe tracker`() { + fun `test missing pinky toe tracker`() { val processor = ToeDirectLinkProcessor() val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( From f374e70f535527ea60780c4b420f283fbe2b85e2 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Wed, 29 Jul 2026 21:57:01 -0500 Subject: [PATCH 15/32] Fix toe assignments again. --- .../dev/slimevr/skeleton/processors/toe-direct-link.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt b/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt index 7cf359ef55..b8fed01368 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt @@ -18,14 +18,14 @@ class ToeDirectLinkProcessor : SkeletonProcessor { BodyPart.LEFT_BIG_TOE to BodyPart.LEFT_FOOT, BodyPart.LEFT_INDEX_TOE to BodyPart.LEFT_BIG_TOE, BodyPart.LEFT_MIDDLE_TOE to BodyPart.LEFT_INDEX_TOE, - BodyPart.LEFT_RING_TOE to BodyPart.LEFT_INDEX_TOE, - BodyPart.LEFT_PINKY_TOE to BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_RING_TOE to BodyPart.LEFT_MIDDLE_TOE, + BodyPart.LEFT_PINKY_TOE to BodyPart.LEFT_RING_TOE, BodyPart.RIGHT_BIG_TOE to BodyPart.RIGHT_FOOT, BodyPart.RIGHT_INDEX_TOE to BodyPart.RIGHT_BIG_TOE, BodyPart.RIGHT_MIDDLE_TOE to BodyPart.RIGHT_INDEX_TOE, - BodyPart.RIGHT_RING_TOE to BodyPart.RIGHT_INDEX_TOE, - BodyPart.RIGHT_PINKY_TOE to BodyPart.RIGHT_INDEX_TOE, + BodyPart.RIGHT_RING_TOE to BodyPart.RIGHT_MIDDLE_TOE, + BodyPart.RIGHT_PINKY_TOE to BodyPart.RIGHT_RING_TOE, ) override fun process(state: SkeletonState): SkeletonState { From 5a314e83a06ce81cc0d6be2b71283be080f4f521 Mon Sep 17 00:00:00 2001 From: Sebby Date: Thu, 30 Jul 2026 17:23:01 -0500 Subject: [PATCH 16/32] Update server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt Co-authored-by: Erimel --- server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt b/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt index f4124988cb..56d64d3ce5 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt @@ -12,7 +12,7 @@ private val FINGER_OFFSETS = ( private val TOE_OFFSETS = ( iterateBodyPartHierarchy(BodyPart.LEFT_FOOT, true) + iterateBodyPartHierarchy(BodyPart.RIGHT_FOOT, true) - ).map { it.second }.associateWith { Vector3(0f, 0f, -0.04f) } + ).map { it.second }.associateWith { Vector3(0f, 0f, -0.025f) } val DEFAULT_BONE_OFFSETS: BodyPartMap = DEFAULT_PROPORTIONS.toBoneOffsets() .mutate { offsets -> From 4f308ee76dfdc3228bbbc1bee21d99dd7ef6d109 Mon Sep 17 00:00:00 2001 From: Sebby Date: Thu, 30 Jul 2026 17:23:14 -0500 Subject: [PATCH 17/32] Update server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt Co-authored-by: Erimel --- .../core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt b/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt index 56d64d3ce5..13b16e2593 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/bone-geometry.kt @@ -15,7 +15,4 @@ private val TOE_OFFSETS = ( ).map { it.second }.associateWith { Vector3(0f, 0f, -0.025f) } val DEFAULT_BONE_OFFSETS: BodyPartMap = DEFAULT_PROPORTIONS.toBoneOffsets() - .mutate { offsets -> - for ((bodyPart, offset) in FINGER_OFFSETS) offsets[bodyPart] = offset - for ((bodyPart, offset) in TOE_OFFSETS) offsets[bodyPart] = offset - } + .mutate { offsets -> for ((bodyPart, offset) in FINGER_OFFSETS + TOE_OFFSETS) offsets[bodyPart] = offset } From 211222f04cae49451326252e67eefc7d89c53b9b Mon Sep 17 00:00:00 2001 From: Sebby Date: Thu, 30 Jul 2026 17:23:25 -0500 Subject: [PATCH 18/32] Update server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt Co-authored-by: Erimel --- .../main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index 6270a2a143..dd8edc801f 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -86,7 +86,7 @@ private fun processToe( val toeRot = toe.rotation val currentRelative = footRot.inv() * toeRot - val euler = quaternionToEulerDegrees(currentRelative) + val euler = currentRelative.toEulerAngles(EulerOrder.YZX) val pitch = euler.z val yaw = euler.y From 64620bb8ee41e1dc8fdf2268cde20aa39b3b140e Mon Sep 17 00:00:00 2001 From: Sebastina Date: Thu, 30 Jul 2026 17:31:28 -0500 Subject: [PATCH 19/32] Rename pinky toe to little toe. --- gui/app/public/i18n/en/translation.ftl | 4 +- .../src/components/commons/BodyPartIcon.tsx | 4 +- .../components/commons/PersonFrontIcon.tsx | 4 +- .../components/onboarding/BodyAssignment.tsx | 8 +-- .../pages/mounting/MountingSelectionMenu.tsx | 4 +- .../trackers-assign/ToeAssignmentModal.tsx | 8 +-- gui/app/src/hooks/body-parts.ts | 8 +-- gui/app/src/utils/skeletonHelper.ts | 16 ++--- .../java/dev/slimevr/resets/bodypart-sets.kt | 4 +- .../slimevr/skeleton/bodypart-structure.kt | 4 +- .../skeleton/processors/toe-direct-link.kt | 4 +- .../src/main/java/dev/slimevr/vmc/mapping.kt | 14 ++--- .../vrcosc/toe-plugin-output-encoder.kt | 4 +- .../skeleton/ToeDirectLinkProcessorTest.kt | 60 +++++++++---------- 14 files changed, 73 insertions(+), 73 deletions(-) diff --git a/gui/app/public/i18n/en/translation.ftl b/gui/app/public/i18n/en/translation.ftl index 7c96819a4f..b286abe0e5 100644 --- a/gui/app/public/i18n/en/translation.ftl +++ b/gui/app/public/i18n/en/translation.ftl @@ -442,12 +442,12 @@ tracker_selection_menu-LEFT_BIG_TOE = { -tracker_selection-part } left big toe? tracker_selection_menu-LEFT_INDEX_TOE = { -tracker_selection-part } left index toe? tracker_selection_menu-LEFT_MIDDLE_TOE = { -tracker_selection-part } left middle toe? tracker_selection_menu-LEFT_RING_TOE = { -tracker_selection-part } left ring toe? -tracker_selection_menu-LEFT_PINKY_TOE = { -tracker_selection-part } left pinky toe? +tracker_selection_menu-LEFT_LITTLE_TOE = { -tracker_selection-part } left little toe? tracker_selection_menu-RIGHT_BIG_TOE = { -tracker_selection-part } right big toe? tracker_selection_menu-RIGHT_INDEX_TOE = { -tracker_selection-part } right index toe? tracker_selection_menu-RIGHT_MIDDLE_TOE = { -tracker_selection-part } right middle toe? tracker_selection_menu-RIGHT_RING_TOE = { -tracker_selection-part } right ring toe? -tracker_selection_menu-RIGHT_PINKY_TOE = { -tracker_selection-part } right pinky toe? +tracker_selection_menu-RIGHT_LITTLE_TOE = { -tracker_selection-part } right little toe? tracker_selection_menu-unassigned = Unassigned Trackers diff --git a/gui/app/src/components/commons/BodyPartIcon.tsx b/gui/app/src/components/commons/BodyPartIcon.tsx index f0c67296ff..0178f167c2 100644 --- a/gui/app/src/components/commons/BodyPartIcon.tsx +++ b/gui/app/src/components/commons/BodyPartIcon.tsx @@ -127,10 +127,10 @@ export const mapPart: Record< [BodyPart.RIGHT_LITTLE_DISTAL]: ({ width }) => , [BodyPart.LEFT_BIG_TOE]: renderFootLeft, [BodyPart.LEFT_INDEX_TOE]: renderFootLeft, - [BodyPart.LEFT_PINKY_TOE]: renderFootLeft, + [BodyPart.LEFT_LITTLE_TOE]: renderFootLeft, [BodyPart.RIGHT_BIG_TOE]: renderFootRight, [BodyPart.RIGHT_INDEX_TOE]: renderFootRight, - [BodyPart.RIGHT_PINKY_TOE]: renderFootRight, + [BodyPart.RIGHT_LITTLE_TOE]: renderFootRight, }; function renderFootLeft({ width, diff --git a/gui/app/src/components/commons/PersonFrontIcon.tsx b/gui/app/src/components/commons/PersonFrontIcon.tsx index e9d7c20d1f..5d4891cc08 100644 --- a/gui/app/src/components/commons/PersonFrontIcon.tsx +++ b/gui/app/src/components/commons/PersonFrontIcon.tsx @@ -11,7 +11,7 @@ export const SIDES = [ foot: BodyPart.LEFT_FOOT, bigToe: BodyPart.LEFT_BIG_TOE, indexToe: BodyPart.LEFT_INDEX_TOE, - pinkyToe: BodyPart.LEFT_PINKY_TOE, + littleToe: BodyPart.LEFT_LITTLE_TOE, }, { shoulder: BodyPart.RIGHT_SHOULDER, @@ -23,7 +23,7 @@ export const SIDES = [ foot: BodyPart.RIGHT_FOOT, bigToe: BodyPart.RIGHT_BIG_TOE, indexToe: BodyPart.RIGHT_INDEX_TOE, - pinkyToe: BodyPart.RIGHT_PINKY_TOE, + littleToe: BodyPart.RIGHT_LITTLE_TOE, }, ]; diff --git a/gui/app/src/components/onboarding/BodyAssignment.tsx b/gui/app/src/components/onboarding/BodyAssignment.tsx index 2650cfcb2f..880d327432 100644 --- a/gui/app/src/components/onboarding/BodyAssignment.tsx +++ b/gui/app/src/components/onboarding/BodyAssignment.tsx @@ -32,8 +32,8 @@ export const TOES_PARTS = new Set([ BodyPart.RIGHT_BIG_TOE, BodyPart.LEFT_INDEX_TOE, BodyPart.RIGHT_INDEX_TOE, - BodyPart.LEFT_PINKY_TOE, - BodyPart.RIGHT_PINKY_TOE, + BodyPart.LEFT_LITTLE_TOE, + BodyPart.RIGHT_LITTLE_TOE, ]); export const SPINE_PARTS = [ @@ -47,10 +47,10 @@ export const ASSIGNMENT_RULES: Partial< > = { [BodyPart.LEFT_BIG_TOE]: [BodyPart.LEFT_FOOT], [BodyPart.LEFT_INDEX_TOE]: [BodyPart.LEFT_FOOT], - [BodyPart.LEFT_PINKY_TOE]: [BodyPart.LEFT_FOOT], + [BodyPart.LEFT_LITTLE_TOE]: [BodyPart.LEFT_FOOT], [BodyPart.RIGHT_BIG_TOE]: [BodyPart.RIGHT_FOOT], [BodyPart.RIGHT_INDEX_TOE]: [BodyPart.RIGHT_FOOT], - [BodyPart.RIGHT_PINKY_TOE]: [BodyPart.RIGHT_FOOT], + [BodyPart.RIGHT_LITTLE_TOE]: [BodyPart.RIGHT_FOOT], [BodyPart.LEFT_FOOT]: [ BodyPart.LEFT_LOWER_LEG, BodyPart.LEFT_UPPER_LEG, diff --git a/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx b/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx index a0438a0ffb..a0c2e03efa 100644 --- a/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx +++ b/gui/app/src/components/onboarding/pages/mounting/MountingSelectionMenu.tsx @@ -166,7 +166,7 @@ export const mapPart: Record< ) : ( ), - [BodyPart.LEFT_PINKY_TOE]: ({ width, currentLocales }) => + [BodyPart.LEFT_LITTLE_TOE]: ({ width, currentLocales }) => currentLocales.includes('en-x-owo') ? ( ), - [BodyPart.RIGHT_PINKY_TOE]: ({ width, currentLocales }) => + [BodyPart.RIGHT_LITTLE_TOE]: ({ width, currentLocales }) => currentLocales.includes('en-x-owo') ? ( ({ [BodyPart.LEFT_BIG_TOE]: 'Left Big Toe', [BodyPart.LEFT_INDEX_TOE]: 'Left Index Toe', - [BodyPart.LEFT_PINKY_TOE]: 'Left Pinky Toe', + [BodyPart.LEFT_LITTLE_TOE]: 'Left little toe', [BodyPart.RIGHT_BIG_TOE]: 'Right Big Toe', [BodyPart.RIGHT_INDEX_TOE]: 'Right Index Toe', - [BodyPart.RIGHT_PINKY_TOE]: 'Right Pinky Toe', + [BodyPart.RIGHT_LITTLE_TOE]: 'Right little toe', }), [] ); @@ -58,7 +58,7 @@ export function ToeAssignmentModal({ cy: 40, }, { - part: BodyPart.LEFT_PINKY_TOE, + part: BodyPart.LEFT_LITTLE_TOE, cx: 50, cy: 62, }, @@ -75,7 +75,7 @@ export function ToeAssignmentModal({ cy: 40, }, { - part: BodyPart.RIGHT_PINKY_TOE, + part: BodyPart.RIGHT_LITTLE_TOE, cx: 137, cy: 62, }, diff --git a/gui/app/src/hooks/body-parts.ts b/gui/app/src/hooks/body-parts.ts index 79e40b6d0e..b7035cc570 100644 --- a/gui/app/src/hooks/body-parts.ts +++ b/gui/app/src/hooks/body-parts.ts @@ -56,19 +56,19 @@ export const ALL_BODY_PARTS = [ BodyPart.RIGHT_LITTLE_DISTAL, BodyPart.LEFT_BIG_TOE, BodyPart.LEFT_INDEX_TOE, - BodyPart.LEFT_PINKY_TOE, + BodyPart.LEFT_LITTLE_TOE, BodyPart.RIGHT_BIG_TOE, BodyPart.RIGHT_INDEX_TOE, - BodyPart.RIGHT_PINKY_TOE, + BodyPart.RIGHT_LITTLE_TOE, ]; export const TOE_BODY_PARTS = [ BodyPart.LEFT_BIG_TOE, BodyPart.LEFT_INDEX_TOE, - BodyPart.LEFT_PINKY_TOE, + BodyPart.LEFT_LITTLE_TOE, BodyPart.RIGHT_BIG_TOE, BodyPart.RIGHT_INDEX_TOE, - BodyPart.RIGHT_PINKY_TOE, + BodyPart.RIGHT_LITTLE_TOE, ]; export const FEET_BODY_PARTS = [BodyPart.LEFT_FOOT, BodyPart.RIGHT_FOOT]; export const FINGER_BODY_PARTS = [ diff --git a/gui/app/src/utils/skeletonHelper.ts b/gui/app/src/utils/skeletonHelper.ts index b345ac2792..92334b433d 100644 --- a/gui/app/src/utils/skeletonHelper.ts +++ b/gui/app/src/utils/skeletonHelper.ts @@ -243,12 +243,12 @@ export class BoneKind extends Bone { case BodyPart.LEFT_INDEX_TOE: case BodyPart.LEFT_MIDDLE_TOE: case BodyPart.LEFT_RING_TOE: - case BodyPart.LEFT_PINKY_TOE: + case BodyPart.LEFT_LITTLE_TOE: case BodyPart.RIGHT_BIG_TOE: case BodyPart.RIGHT_INDEX_TOE: case BodyPart.RIGHT_MIDDLE_TOE: case BodyPart.RIGHT_RING_TOE: - case BodyPart.RIGHT_PINKY_TOE: + case BodyPart.RIGHT_LITTLE_TOE: return new Color('pink'); } } @@ -290,7 +290,7 @@ export class BoneKind extends Bone { BodyPart.LEFT_INDEX_TOE, BodyPart.LEFT_MIDDLE_TOE, BodyPart.LEFT_RING_TOE, - BodyPart.LEFT_PINKY_TOE, + BodyPart.LEFT_LITTLE_TOE, ]; case BodyPart.RIGHT_FOOT: return [ @@ -298,7 +298,7 @@ export class BoneKind extends Bone { BodyPart.RIGHT_INDEX_TOE, BodyPart.RIGHT_MIDDLE_TOE, BodyPart.RIGHT_RING_TOE, - BodyPart.RIGHT_PINKY_TOE, + BodyPart.RIGHT_LITTLE_TOE, ]; case BodyPart.LEFT_SHOULDER: return [BodyPart.LEFT_UPPER_ARM]; @@ -394,12 +394,12 @@ export class BoneKind extends Bone { case BodyPart.LEFT_INDEX_TOE: case BodyPart.LEFT_MIDDLE_TOE: case BodyPart.LEFT_RING_TOE: - case BodyPart.LEFT_PINKY_TOE: + case BodyPart.LEFT_LITTLE_TOE: case BodyPart.RIGHT_BIG_TOE: case BodyPart.RIGHT_INDEX_TOE: case BodyPart.RIGHT_MIDDLE_TOE: case BodyPart.RIGHT_RING_TOE: - case BodyPart.RIGHT_PINKY_TOE: + case BodyPart.RIGHT_LITTLE_TOE: return []; } } @@ -522,7 +522,7 @@ export class BoneKind extends Bone { return BodyPart.LEFT_FOOT; case BodyPart.LEFT_RING_TOE: return BodyPart.LEFT_FOOT; - case BodyPart.LEFT_PINKY_TOE: + case BodyPart.LEFT_LITTLE_TOE: return BodyPart.LEFT_FOOT; case BodyPart.RIGHT_BIG_TOE: return BodyPart.RIGHT_FOOT; @@ -532,7 +532,7 @@ export class BoneKind extends Bone { return BodyPart.RIGHT_FOOT; case BodyPart.RIGHT_RING_TOE: return BodyPart.RIGHT_FOOT; - case BodyPart.RIGHT_PINKY_TOE: + case BodyPart.RIGHT_LITTLE_TOE: return BodyPart.RIGHT_FOOT; } } diff --git a/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt b/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt index 256782555f..55dd27dd80 100644 --- a/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt +++ b/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt @@ -79,10 +79,10 @@ val RIGHT_FINGER_PARTS = setOf( val LEFT_TOE_PARTS = setOf( BodyPart.LEFT_BIG_TOE, BodyPart.LEFT_INDEX_TOE, - BodyPart.LEFT_PINKY_TOE, + BodyPart.LEFT_LITTLE_TOE, ) val RIGHT_TOE_PARTS = setOf( BodyPart.RIGHT_BIG_TOE, BodyPart.RIGHT_INDEX_TOE, - BodyPart.RIGHT_PINKY_TOE, + BodyPart.RIGHT_LITTLE_TOE, ) diff --git a/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt b/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt index 4bb5655121..fdf617c029 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/bodypart-structure.kt @@ -69,7 +69,7 @@ val BODY_PART_HIERARCHY_MAP: BodyPartMap> = BodyPartMap( BodyPart.LEFT_INDEX_TOE, BodyPart.LEFT_MIDDLE_TOE, BodyPart.LEFT_RING_TOE, - BodyPart.LEFT_PINKY_TOE, + BodyPart.LEFT_LITTLE_TOE, ), BodyPart.RIGHT_FOOT to arrayOf( @@ -77,7 +77,7 @@ val BODY_PART_HIERARCHY_MAP: BodyPartMap> = BodyPartMap( BodyPart.RIGHT_INDEX_TOE, BodyPart.RIGHT_MIDDLE_TOE, BodyPart.RIGHT_RING_TOE, - BodyPart.RIGHT_PINKY_TOE, + BodyPart.RIGHT_LITTLE_TOE, ), ), ) diff --git a/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt b/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt index b8fed01368..2e79edacf3 100644 --- a/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt +++ b/server/core/src/main/java/dev/slimevr/skeleton/processors/toe-direct-link.kt @@ -19,13 +19,13 @@ class ToeDirectLinkProcessor : SkeletonProcessor { BodyPart.LEFT_INDEX_TOE to BodyPart.LEFT_BIG_TOE, BodyPart.LEFT_MIDDLE_TOE to BodyPart.LEFT_INDEX_TOE, BodyPart.LEFT_RING_TOE to BodyPart.LEFT_MIDDLE_TOE, - BodyPart.LEFT_PINKY_TOE to BodyPart.LEFT_RING_TOE, + BodyPart.LEFT_LITTLE_TOE to BodyPart.LEFT_RING_TOE, BodyPart.RIGHT_BIG_TOE to BodyPart.RIGHT_FOOT, BodyPart.RIGHT_INDEX_TOE to BodyPart.RIGHT_BIG_TOE, BodyPart.RIGHT_MIDDLE_TOE to BodyPart.RIGHT_INDEX_TOE, BodyPart.RIGHT_RING_TOE to BodyPart.RIGHT_MIDDLE_TOE, - BodyPart.RIGHT_PINKY_TOE to BodyPart.RIGHT_RING_TOE, + BodyPart.RIGHT_LITTLE_TOE to BodyPart.RIGHT_RING_TOE, ) override fun process(state: SkeletonState): SkeletonState { diff --git a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt index a4111d7c80..3bd782987f 100644 --- a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt +++ b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt @@ -62,12 +62,12 @@ val BODY_PART_TO_UNITY_BONE: Map = mapOf( BodyPart.LEFT_INDEX_TOE to "LeftIndexToe", BodyPart.LEFT_MIDDLE_TOE to "LeftMiddleToe", BodyPart.LEFT_RING_TOE to "LeftRingToe", - BodyPart.LEFT_PINKY_TOE to "LeftPinkyToe", + BodyPart.LEFT_LITTLE_TOE to "LeftLittleToe", BodyPart.RIGHT_BIG_TOE to "RightBigToe", BodyPart.RIGHT_INDEX_TOE to "RightIndexToe", BodyPart.RIGHT_MIDDLE_TOE to "RightMiddleToe", BodyPart.RIGHT_RING_TOE to "RightRingToe", - BodyPart.RIGHT_PINKY_TOE to "RightPinkyToe", + BodyPart.RIGHT_LITTLE_TOE to "RightLittleToe", ) // HIP-rooted hierarchy. VMC/Unity expects this; our skeleton is HEAD-rooted. @@ -124,12 +124,12 @@ val VMC_HIERARCHY_MAP: Map> = mapOf( BodyPart.LEFT_FOOT to arrayOf( BodyPart.LEFT_BIG_TOE, BodyPart.LEFT_INDEX_TOE, - BodyPart.LEFT_PINKY_TOE, + BodyPart.LEFT_LITTLE_TOE, ), BodyPart.RIGHT_FOOT to arrayOf( BodyPart.RIGHT_BIG_TOE, BodyPart.RIGHT_INDEX_TOE, - BodyPart.RIGHT_PINKY_TOE, + BodyPart.RIGHT_LITTLE_TOE, ), ) @@ -168,7 +168,7 @@ val VMC_MIRROR_BONE_PAIRS: List> = listOf( BodyPart.LEFT_LITTLE_DISTAL to BodyPart.RIGHT_LITTLE_DISTAL, BodyPart.LEFT_BIG_TOE to BodyPart.RIGHT_BIG_TOE, BodyPart.LEFT_INDEX_TOE to BodyPart.RIGHT_INDEX_TOE, - BodyPart.LEFT_PINKY_TOE to BodyPart.RIGHT_PINKY_TOE, + BodyPart.LEFT_LITTLE_TOE to BodyPart.RIGHT_LITTLE_TOE, ) val VMC_MIRROR_BONES: BodyPartMap = BodyPartMap( @@ -203,12 +203,12 @@ val VMC_REST_ROTATIONS: BodyPartMap = run { val leftToes = listOf( BodyPart.LEFT_BIG_TOE, BodyPart.LEFT_INDEX_TOE, - BodyPart.LEFT_PINKY_TOE, + BodyPart.LEFT_LITTLE_TOE, ) val rightToes = listOf( BodyPart.RIGHT_BIG_TOE, BodyPart.RIGHT_INDEX_TOE, - BodyPart.RIGHT_PINKY_TOE, + BodyPart.RIGHT_LITTLE_TOE, ) BodyPartMap( mapOf( diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index 6270a2a143..a9aa301353 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -19,7 +19,7 @@ internal fun buildToeMessages(bones: Map): List val leftToes = listOf( bones[BodyPart.LEFT_BIG_TOE], bones[BodyPart.LEFT_INDEX_TOE], - bones[BodyPart.LEFT_PINKY_TOE], + bones[BodyPart.LEFT_LITTLE_TOE], ) processToesForFoot(leftFoot, leftToes, FootSide.Left, messages) } @@ -30,7 +30,7 @@ internal fun buildToeMessages(bones: Map): List val rightToes = listOf( bones[BodyPart.RIGHT_BIG_TOE], bones[BodyPart.RIGHT_INDEX_TOE], - bones[BodyPart.RIGHT_PINKY_TOE], + bones[BodyPart.RIGHT_LITTLE_TOE], ) processToesForFoot(rightFoot, rightToes, FootSide.Right, messages) } diff --git a/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt index 576f7cda21..3bdbc2623e 100644 --- a/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt +++ b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt @@ -35,8 +35,8 @@ class ToeDirectLinkProcessorTest { newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation val leftRingToeIsSameRotationAsLeftMiddleToe = newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation - val leftPinkyToeIsSameRotationAsLeftRingToe = - newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation + val leftLittleToeIsSameRotationAsLeftRingToe = + newInputs.boneInputs[BodyPart.LEFT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation val rightBigToeIsSameRotationAsRightFoot = newInputs.boneInputs[BodyPart.RIGHT_BIG_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_FOOT]?.rawRotation @@ -46,19 +46,19 @@ class ToeDirectLinkProcessorTest { newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation val rightRingToeIsSameRotationAsRightMiddleToe = newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation - val rightPinkyToeIsSameRotationAsRightRingToe = - newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation + val rightLittleToeIsSameRotationAsRightRingToe = + newInputs.boneInputs[BodyPart.RIGHT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation assertTrue(leftBigToeIsSameRotationAsLeftFoot && leftIndexToeIsSameRotationAsLeftBigToe && leftMiddleToeIsSameRotationAsLeftIndexToe && leftRingToeIsSameRotationAsLeftMiddleToe - && leftPinkyToeIsSameRotationAsLeftRingToe) + && leftLittleToeIsSameRotationAsLeftRingToe) assertTrue(rightBigToeIsSameRotationAsRightFoot && rightIndexToeIsSameRotationAsRightBigToe && rightMiddleToeIsSameRotationAsRightIndexToe && rightRingToeIsSameRotationAsRightMiddleToe - && rightPinkyToeIsSameRotationAsRightRingToe) + && rightLittleToeIsSameRotationAsRightRingToe) } @Test @@ -93,8 +93,8 @@ class ToeDirectLinkProcessorTest { newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation val leftRingToeIsSameRotationAsLeftMiddleToe = newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation - val leftPinkyToeIsSameRotationAsLeftRingToe = - newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation + val leftLittleToeIsSameRotationAsLeftRingToe = + newInputs.boneInputs[BodyPart.LEFT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation val rightIndexToeIsSameRotationAsRightBigToe = newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_BIG_TOE]?.rawRotation @@ -102,17 +102,17 @@ class ToeDirectLinkProcessorTest { newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation val rightRingToeIsSameRotationAsRightMiddleToe = newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation - val rightPinkyToeIsSameRotationAsRightRingToe = - newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation + val rightLittleToeIsSameRotationAsRightRingToe = + newInputs.boneInputs[BodyPart.RIGHT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation assertTrue(leftIndexToeIsSameRotationAsLeftBigToe && leftMiddleToeIsSameRotationAsLeftIndexToe && leftRingToeIsSameRotationAsLeftMiddleToe - && leftPinkyToeIsSameRotationAsLeftRingToe) + && leftLittleToeIsSameRotationAsLeftRingToe) assertTrue(rightIndexToeIsSameRotationAsRightBigToe && rightMiddleToeIsSameRotationAsRightIndexToe && rightRingToeIsSameRotationAsRightMiddleToe - && rightPinkyToeIsSameRotationAsRightRingToe) + && rightLittleToeIsSameRotationAsRightRingToe) } @Test @@ -152,22 +152,22 @@ class ToeDirectLinkProcessorTest { newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_INDEX_TOE]?.rawRotation val leftRingToeIsSameRotationAsLeftMiddleToe = newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation - val leftPinkyToeIsSameRotationAsLeftRingToe = - newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation + val leftLittleToeIsSameRotationAsLeftRingToe = + newInputs.boneInputs[BodyPart.LEFT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation val rightMiddleToeIsSameRotationAsRightIndexToe = newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_INDEX_TOE]?.rawRotation val rightRingToeIsSameRotationAsRightMiddleToe = newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation - val rightPinkyToeIsSameRotationAsRightRingToe = - newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation + val rightLittleToeIsSameRotationAsRightRingToe = + newInputs.boneInputs[BodyPart.RIGHT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation assertTrue(leftMiddleToeIsSameRotationAsLeftIndexToe && leftRingToeIsSameRotationAsLeftMiddleToe - && leftPinkyToeIsSameRotationAsLeftRingToe) + && leftLittleToeIsSameRotationAsLeftRingToe) assertTrue(rightMiddleToeIsSameRotationAsRightIndexToe && rightRingToeIsSameRotationAsRightMiddleToe - && rightPinkyToeIsSameRotationAsRightRingToe) + && rightLittleToeIsSameRotationAsRightRingToe) } @Test @@ -211,20 +211,20 @@ class ToeDirectLinkProcessorTest { val leftRingToeIsSameRotationAsLeftMiddleToe = newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_MIDDLE_TOE]?.rawRotation - val leftPinkyToeIsSameRotationAsLeftRingToe = - newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation + val leftLittleToeIsSameRotationAsLeftRingToe = + newInputs.boneInputs[BodyPart.LEFT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation val rightRingToeIsSameRotationAsRightMiddleToe = newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_MIDDLE_TOE]?.rawRotation - val rightPinkyToeIsSameRotationAsRightRingToe = - newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation + val rightLittleToeIsSameRotationAsRightRingToe = + newInputs.boneInputs[BodyPart.RIGHT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation - assertTrue(leftRingToeIsSameRotationAsLeftMiddleToe && leftPinkyToeIsSameRotationAsLeftRingToe) - assertTrue(rightRingToeIsSameRotationAsRightMiddleToe && rightPinkyToeIsSameRotationAsRightRingToe) + assertTrue(leftRingToeIsSameRotationAsLeftMiddleToe && leftLittleToeIsSameRotationAsLeftRingToe) + assertTrue(rightRingToeIsSameRotationAsRightMiddleToe && rightLittleToeIsSameRotationAsRightRingToe) } @Test - fun `test missing pinky toe tracker`() { + fun `test missing little toe tracker`() { val processor = ToeDirectLinkProcessor() val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( @@ -268,11 +268,11 @@ class ToeDirectLinkProcessorTest { val newInputs = processor.process(state) - val leftPinkyToeIsSameRotationAsLeftRingToe = - newInputs.boneInputs[BodyPart.LEFT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation - val rightPinkyToeIsSameRotationAsRightRingToe = - newInputs.boneInputs[BodyPart.RIGHT_PINKY_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation + val leftLittleToeIsSameRotationAsLeftRingToe = + newInputs.boneInputs[BodyPart.LEFT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.LEFT_RING_TOE]?.rawRotation + val rightLittleToeIsSameRotationAsRightRingToe = + newInputs.boneInputs[BodyPart.RIGHT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation - assertTrue(leftPinkyToeIsSameRotationAsLeftRingToe && rightPinkyToeIsSameRotationAsRightRingToe) + assertTrue(leftLittleToeIsSameRotationAsLeftRingToe && rightLittleToeIsSameRotationAsRightRingToe) } } From 96f212f7ec0a35a16c1fd9b11831ffbdb92eab3e Mon Sep 17 00:00:00 2001 From: Sebastina Date: Thu, 30 Jul 2026 18:13:52 -0500 Subject: [PATCH 20/32] Read in data from additional toes. Define toe calculation values with more description. --- .../java/dev/slimevr/resets/bodypart-sets.kt | 4 + .../main/java/dev/slimevr/resets/module.kt | 3 +- .../src/main/java/dev/slimevr/util/Side.kt | 15 ++++ .../vrcosc/toe-plugin-output-encoder.kt | 89 +++++++++---------- 4 files changed, 62 insertions(+), 49 deletions(-) create mode 100644 server/core/src/main/java/dev/slimevr/util/Side.kt diff --git a/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt b/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt index 55dd27dd80..004c0145af 100644 --- a/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt +++ b/server/core/src/main/java/dev/slimevr/resets/bodypart-sets.kt @@ -79,10 +79,14 @@ val RIGHT_FINGER_PARTS = setOf( val LEFT_TOE_PARTS = setOf( BodyPart.LEFT_BIG_TOE, BodyPart.LEFT_INDEX_TOE, + BodyPart.LEFT_MIDDLE_TOE, + BodyPart.LEFT_RING_TOE, BodyPart.LEFT_LITTLE_TOE, ) val RIGHT_TOE_PARTS = setOf( BodyPart.RIGHT_BIG_TOE, BodyPart.RIGHT_INDEX_TOE, + BodyPart.RIGHT_MIDDLE_TOE, + BodyPart.RIGHT_RING_TOE, BodyPart.RIGHT_LITTLE_TOE, ) diff --git a/server/core/src/main/java/dev/slimevr/resets/module.kt b/server/core/src/main/java/dev/slimevr/resets/module.kt index d6761533f7..0b312b5f9f 100644 --- a/server/core/src/main/java/dev/slimevr/resets/module.kt +++ b/server/core/src/main/java/dev/slimevr/resets/module.kt @@ -45,7 +45,7 @@ typealias ResetsBehaviour = Behaviour class ResetsManager(val context: ResetsContext, val server: VRServer, val settings: Settings) { fun startObserving() = context.observeAll(this) - + private var resetJob: Job = Job() /** @@ -149,6 +149,7 @@ class ResetsManager(val context: ResetsContext, val server: VRServer, val settin in RIGHT_FINGER_PARTS -> -FastMath.HALF_PI + // Going right in LEFT_ARM_PARTS if armsResetMode == ArmsResetMode.T_POSE_DOWN -> FastMath.HALF_PI diff --git a/server/core/src/main/java/dev/slimevr/util/Side.kt b/server/core/src/main/java/dev/slimevr/util/Side.kt new file mode 100644 index 0000000000..99b3bfaccd --- /dev/null +++ b/server/core/src/main/java/dev/slimevr/util/Side.kt @@ -0,0 +1,15 @@ +package dev.slimevr.util + +/** + * Used to represent the side of a bone. + */ +enum class Side { + LEFT, + RIGHT, +} + +val Side.opposite: Side + get() = when (this) { + Side.LEFT -> Side.RIGHT + Side.RIGHT -> Side.LEFT + } diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index fb4983971a..df106d0972 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -4,12 +4,14 @@ import dev.slimevr.osc.OscArg import dev.slimevr.osc.OscContent import dev.slimevr.osc.OscMessage import dev.slimevr.skeleton.BoneState +import io.github.axisangles.ktmath.EulerOrder import io.github.axisangles.ktmath.Quaternion import io.github.axisangles.ktmath.Vector3 import solarxr_protocol.datatypes.BodyPart import kotlin.math.* +import dev.slimevr.util.Side +import dev.slimevr.util.opposite -private enum class FootSide { Left, Right } internal fun buildToeMessages(bones: Map): List { val messages = mutableListOf() @@ -19,9 +21,11 @@ internal fun buildToeMessages(bones: Map): List val leftToes = listOf( bones[BodyPart.LEFT_BIG_TOE], bones[BodyPart.LEFT_INDEX_TOE], + bones[BodyPart.LEFT_MIDDLE_TOE], + bones[BodyPart.LEFT_RING_TOE], bones[BodyPart.LEFT_LITTLE_TOE], ) - processToesForFoot(leftFoot, leftToes, FootSide.Left, messages) + processToesForFoot(leftFoot, leftToes, Side.LEFT, messages) } // RIGHT FOOT + TOES @@ -30,9 +34,11 @@ internal fun buildToeMessages(bones: Map): List val rightToes = listOf( bones[BodyPart.RIGHT_BIG_TOE], bones[BodyPart.RIGHT_INDEX_TOE], + bones[BodyPart.RIGHT_MIDDLE_TOE], + bones[BodyPart.RIGHT_RING_TOE], bones[BodyPart.RIGHT_LITTLE_TOE], ) - processToesForFoot(rightFoot, rightToes, FootSide.Right, messages) + processToesForFoot(rightFoot, rightToes, Side.RIGHT, messages) } return messages @@ -41,7 +47,7 @@ internal fun buildToeMessages(bones: Map): List private fun processToesForFoot( foot: BoneState, toeBones: List, - side: FootSide, + side: Side, messages: MutableList, ) { var lastAssigned: BoneState? = null @@ -54,76 +60,63 @@ private fun processToesForFoot( if (lastAssigned == null) continue when (segmentIndex) { - 0 -> processToe(foot, lastAssigned, side, 0, oppositeSide(side), messages) - + 0 -> { + processToe(foot, lastAssigned, side, 0, side.opposite, messages) + } 1 -> { - processToe(foot, lastAssigned, side, 1, oppositeSide(side), messages) - processToe(foot, lastAssigned, side, 2, oppositeSide(side), messages) + processToe(foot, lastAssigned, side, 1, side.opposite, messages) } - 2 -> { + processToe(foot, lastAssigned, side, 2, side.opposite, messages) + } + 3 -> { processToe(foot, lastAssigned, side, 3, side, messages) + } + 4 -> { processToe(foot, lastAssigned, side, 4, side, messages) } } } } -private fun oppositeSide(side: FootSide): FootSide = when (side) { - FootSide.Left -> FootSide.Right - FootSide.Right -> FootSide.Left -} +private val Side.oscName: String + get() = when (this) { + Side.LEFT -> "Left" + Side.RIGHT -> "Right" + } private fun processToe( foot: BoneState, toe: BoneState, - side: FootSide, + side: Side, toeNumber: Int, - splayDirection: FootSide, + splayDirection: Side, messages: MutableList, ) { + val absoluteSplayThresholdAngle = 7 + val minimumTipToePitch = -14 + val maximumBendingPitch = 15 + val maximumAbsoluteToeRange = 90 + val footRot = foot.rotation val toeRot = toe.rotation val currentRelative = footRot.inv() * toeRot - val euler = currentRelative.toEulerAngles(EulerOrder.YZX) + val euler = currentRelative.toEulerAngles(EulerOrder.XYZ) val pitch = euler.z val yaw = euler.y - val tipToe = pitch < -14f - val bending = pitch > 15f && !tipToe + val tipToe = pitch < minimumTipToePitch + val bending = pitch > maximumBendingPitch && !tipToe val splayed = when (splayDirection) { - FootSide.Left -> yaw < -7f - FootSide.Right -> yaw > 7f - } - - messages.add(OscContent.Message(OscMessage("/avatar/parameters/TipToes${side.name}", listOf(if (tipToe) OscArg.True else OscArg.False)))) - messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.name}${toeNumber + 1}Bool", listOf(if (bending) OscArg.True else OscArg.False)))) - messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.name}${toeNumber + 1}", listOf(if (splayed) OscArg.True else OscArg.False)))) - - val floatValue = (pitch / 90f).coerceIn(-1f, 1f) - messages.add(OscContent.Message(OscMessage("/avatar/parameters/Toe${side.name}${toeNumber + 1}Float", listOf(OscArg.Float(floatValue))))) -} - -private fun quaternionToEulerDegrees(q: Quaternion): Vector3 { - val sinrCosp = 2f * (q.w * q.x + q.y * q.z) - val cosrCosp = 1f - 2f * (q.x * q.x + q.y * q.y) - val roll = atan2(sinrCosp, cosrCosp) - - val sinp = 2f * (q.w * q.y - q.z * q.x) - val pitch = if (abs(sinp) >= 1f) { - (PI.toFloat() / 2f) * sign(sinp) - } else { - asin(sinp) + Side.LEFT -> yaw < -absoluteSplayThresholdAngle + Side.RIGHT -> yaw > absoluteSplayThresholdAngle } - val sinyCosp = 2f * (q.w * q.z + q.x * q.y) - val cosyCosp = 1f - 2f * (q.y * q.y + q.z * q.z) - val yaw = atan2(sinyCosp, cosyCosp) + messages.add(OscContent.Message(OscMessage("/avatar/parameters/TipToes${side.oscName}", listOf(if (tipToe) OscArg.True else OscArg.False)))) + messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.oscName}${toeNumber + 1}Bool", listOf(if (bending) OscArg.True else OscArg.False)))) + messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.oscName}${toeNumber + 1}", listOf(if (splayed) OscArg.True else OscArg.False)))) - return Vector3( - Math.toDegrees(pitch.toDouble()).toFloat(), - Math.toDegrees(yaw.toDouble()).toFloat(), - Math.toDegrees(roll.toDouble()).toFloat(), - ) + val floatValue = (pitch / maximumAbsoluteToeRange).coerceIn(-1f, 1f) + messages.add(OscContent.Message(OscMessage("/avatar/parameters/Toe${side.oscName}${toeNumber + 1}Float", listOf(OscArg.Float(floatValue))))) } From a5f3ac438f0829119893414f181351c5bf71fd33 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Thu, 30 Jul 2026 18:28:57 -0500 Subject: [PATCH 21/32] Apparently constants dont need classes. --- .../vrcosc/toe-plugin-output-encoder.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index df106d0972..898985de99 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -12,6 +12,10 @@ import kotlin.math.* import dev.slimevr.util.Side import dev.slimevr.util.opposite +private const val Absolute_Splay_Threshold_Angle = 7 +private const val Minimum_Tip_Toe_Pitch = -14 +private const val Maximum_Bending_Pitch = 15 +private const val Maximum_Absolute_Toe_Range = 90 internal fun buildToeMessages(bones: Map): List { val messages = mutableListOf() @@ -93,10 +97,6 @@ private fun processToe( splayDirection: Side, messages: MutableList, ) { - val absoluteSplayThresholdAngle = 7 - val minimumTipToePitch = -14 - val maximumBendingPitch = 15 - val maximumAbsoluteToeRange = 90 val footRot = foot.rotation val toeRot = toe.rotation @@ -106,17 +106,17 @@ private fun processToe( val pitch = euler.z val yaw = euler.y - val tipToe = pitch < minimumTipToePitch - val bending = pitch > maximumBendingPitch && !tipToe + val tipToe = pitch < Minimum_Tip_Toe_Pitch + val bending = pitch > Maximum_Bending_Pitch && !tipToe val splayed = when (splayDirection) { - Side.LEFT -> yaw < -absoluteSplayThresholdAngle - Side.RIGHT -> yaw > absoluteSplayThresholdAngle + Side.LEFT -> yaw < -Absolute_Splay_Threshold_Angle + Side.RIGHT -> yaw > Absolute_Splay_Threshold_Angle } messages.add(OscContent.Message(OscMessage("/avatar/parameters/TipToes${side.oscName}", listOf(if (tipToe) OscArg.True else OscArg.False)))) messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.oscName}${toeNumber + 1}Bool", listOf(if (bending) OscArg.True else OscArg.False)))) messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.oscName}${toeNumber + 1}", listOf(if (splayed) OscArg.True else OscArg.False)))) - val floatValue = (pitch / maximumAbsoluteToeRange).coerceIn(-1f, 1f) + val floatValue = (pitch / Maximum_Absolute_Toe_Range).coerceIn(-1f, 1f) messages.add(OscContent.Message(OscMessage("/avatar/parameters/Toe${side.oscName}${toeNumber + 1}Float", listOf(OscArg.Float(floatValue))))) } From 43fa7c6e124b4ca112179fbc0277599ef3c16855 Mon Sep 17 00:00:00 2001 From: Sebby Date: Thu, 30 Jul 2026 19:05:58 -0500 Subject: [PATCH 22/32] Update server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt Co-authored-by: Erimel --- .../vrcosc/toe-plugin-output-encoder.kt | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index 898985de99..a06865bf63 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -63,23 +63,13 @@ private fun processToesForFoot( if (lastAssigned == null) continue - when (segmentIndex) { - 0 -> { - processToe(foot, lastAssigned, side, 0, side.opposite, messages) - } - 1 -> { - processToe(foot, lastAssigned, side, 1, side.opposite, messages) - } - 2 -> { - processToe(foot, lastAssigned, side, 2, side.opposite, messages) - } - 3 -> { - processToe(foot, lastAssigned, side, 3, side, messages) - } - 4 -> { - processToe(foot, lastAssigned, side, 4, side, messages) - } + val splayDirection = when (segmentIndex) { + 0, 1, 2 -> side.opposite + + 3, 4 -> side } + + processToe(foot, lastAssigned, side, segmentIndex, splayDirection, messages) } } From 8966312bd394a762fe56436ea517757c2c1d6d11 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Thu, 30 Jul 2026 19:14:44 -0500 Subject: [PATCH 23/32] Adjust VMC mappings, correct casing of constants. --- .../src/main/java/dev/slimevr/util/Side.kt | 5 ++++ .../src/main/java/dev/slimevr/vmc/mapping.kt | 20 ++------------ .../vrcosc/toe-plugin-output-encoder.kt | 26 +++++++------------ 3 files changed, 17 insertions(+), 34 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/util/Side.kt b/server/core/src/main/java/dev/slimevr/util/Side.kt index 99b3bfaccd..1c4612df37 100644 --- a/server/core/src/main/java/dev/slimevr/util/Side.kt +++ b/server/core/src/main/java/dev/slimevr/util/Side.kt @@ -13,3 +13,8 @@ val Side.opposite: Side Side.LEFT -> Side.RIGHT Side.RIGHT -> Side.LEFT } +val Side.oscName: String + get() = when (this) { + Side.LEFT -> "Left" + Side.RIGHT -> "Right" + } diff --git a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt index 3bd782987f..6fb0ba980e 100644 --- a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt +++ b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt @@ -58,16 +58,8 @@ val BODY_PART_TO_UNITY_BONE: Map = mapOf( BodyPart.RIGHT_LITTLE_PROXIMAL to "RightLittleProximal", BodyPart.RIGHT_LITTLE_INTERMEDIATE to "RightLittleIntermediate", BodyPart.RIGHT_LITTLE_DISTAL to "RightLittleDistal", - BodyPart.LEFT_BIG_TOE to "LeftBigToe", - BodyPart.LEFT_INDEX_TOE to "LeftIndexToe", - BodyPart.LEFT_MIDDLE_TOE to "LeftMiddleToe", - BodyPart.LEFT_RING_TOE to "LeftRingToe", - BodyPart.LEFT_LITTLE_TOE to "LeftLittleToe", - BodyPart.RIGHT_BIG_TOE to "RightBigToe", - BodyPart.RIGHT_INDEX_TOE to "RightIndexToe", - BodyPart.RIGHT_MIDDLE_TOE to "RightMiddleToe", - BodyPart.RIGHT_RING_TOE to "RightRingToe", - BodyPart.RIGHT_LITTLE_TOE to "RightLittleToe", + BodyPart.LEFT_BIG_TOE to "LeftToes", + BodyPart.RIGHT_BIG_TOE to "RightToes", ) // HIP-rooted hierarchy. VMC/Unity expects this; our skeleton is HEAD-rooted. @@ -123,13 +115,9 @@ val VMC_HIERARCHY_MAP: Map> = mapOf( BodyPart.RIGHT_LITTLE_INTERMEDIATE to arrayOf(BodyPart.RIGHT_LITTLE_DISTAL), BodyPart.LEFT_FOOT to arrayOf( BodyPart.LEFT_BIG_TOE, - BodyPart.LEFT_INDEX_TOE, - BodyPart.LEFT_LITTLE_TOE, ), BodyPart.RIGHT_FOOT to arrayOf( BodyPart.RIGHT_BIG_TOE, - BodyPart.RIGHT_INDEX_TOE, - BodyPart.RIGHT_LITTLE_TOE, ), ) @@ -202,13 +190,9 @@ val VMC_REST_ROTATIONS: BodyPartMap = run { ) val leftToes = listOf( BodyPart.LEFT_BIG_TOE, - BodyPart.LEFT_INDEX_TOE, - BodyPart.LEFT_LITTLE_TOE, ) val rightToes = listOf( BodyPart.RIGHT_BIG_TOE, - BodyPart.RIGHT_INDEX_TOE, - BodyPart.RIGHT_LITTLE_TOE, ) BodyPartMap( mapOf( diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index 898985de99..f443183275 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -11,11 +11,11 @@ import solarxr_protocol.datatypes.BodyPart import kotlin.math.* import dev.slimevr.util.Side import dev.slimevr.util.opposite - -private const val Absolute_Splay_Threshold_Angle = 7 -private const val Minimum_Tip_Toe_Pitch = -14 -private const val Maximum_Bending_Pitch = 15 -private const val Maximum_Absolute_Toe_Range = 90 +import dev.slimevr.util.oscName +private const val ABSOLUTE_SPLAY_THRESHOLD_ANGLE = 7 +private const val MINIMUM_TIP_TOE_PITCH = -14 +private const val MINIMUM_BENDING_PITCH = 15 +private const val MAXIMUM_ABSOLUTE_TOE_RANGE = 90 internal fun buildToeMessages(bones: Map): List { val messages = mutableListOf() @@ -83,12 +83,6 @@ private fun processToesForFoot( } } -private val Side.oscName: String - get() = when (this) { - Side.LEFT -> "Left" - Side.RIGHT -> "Right" - } - private fun processToe( foot: BoneState, toe: BoneState, @@ -106,17 +100,17 @@ private fun processToe( val pitch = euler.z val yaw = euler.y - val tipToe = pitch < Minimum_Tip_Toe_Pitch - val bending = pitch > Maximum_Bending_Pitch && !tipToe + val tipToe = pitch < MINIMUM_TIP_TOE_PITCH + val bending = pitch > MINIMUM_BENDING_PITCH && !tipToe val splayed = when (splayDirection) { - Side.LEFT -> yaw < -Absolute_Splay_Threshold_Angle - Side.RIGHT -> yaw > Absolute_Splay_Threshold_Angle + Side.LEFT -> yaw < -ABSOLUTE_SPLAY_THRESHOLD_ANGLE + Side.RIGHT -> yaw > ABSOLUTE_SPLAY_THRESHOLD_ANGLE } messages.add(OscContent.Message(OscMessage("/avatar/parameters/TipToes${side.oscName}", listOf(if (tipToe) OscArg.True else OscArg.False)))) messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.oscName}${toeNumber + 1}Bool", listOf(if (bending) OscArg.True else OscArg.False)))) messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.oscName}${toeNumber + 1}", listOf(if (splayed) OscArg.True else OscArg.False)))) - val floatValue = (pitch / Maximum_Absolute_Toe_Range).coerceIn(-1f, 1f) + val floatValue = (pitch / MAXIMUM_ABSOLUTE_TOE_RANGE).coerceIn(-1f, 1f) messages.add(OscContent.Message(OscMessage("/avatar/parameters/Toe${side.oscName}${toeNumber + 1}Float", listOf(OscArg.Float(floatValue))))) } From 78c06bdbf41365c116bc1bc71ff019e11f1d5381 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Thu, 30 Jul 2026 19:24:25 -0500 Subject: [PATCH 24/32] Simplify splay direction syntax. --- .../java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index 1e1cd2326c..9345f572de 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -63,12 +63,8 @@ private fun processToesForFoot( if (lastAssigned == null) continue - val splayDirection = when (segmentIndex) { - 0, 1, 2 -> side.opposite - - 3, 4 -> side - } - + val splayDirection = if (segmentIndex <= 2) side.opposite else side + processToe(foot, lastAssigned, side, segmentIndex, splayDirection, messages) } } From e41a3dcdfe65d497fe2a8406f59835cf18ed2727 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Thu, 30 Jul 2026 19:29:58 -0500 Subject: [PATCH 25/32] Move osc name extensions back to the toe plugin output encoder. Add spacing between constants. --- server/core/src/main/java/dev/slimevr/util/Side.kt | 5 ----- .../java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt | 9 ++++++++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/util/Side.kt b/server/core/src/main/java/dev/slimevr/util/Side.kt index 1c4612df37..99b3bfaccd 100644 --- a/server/core/src/main/java/dev/slimevr/util/Side.kt +++ b/server/core/src/main/java/dev/slimevr/util/Side.kt @@ -13,8 +13,3 @@ val Side.opposite: Side Side.LEFT -> Side.RIGHT Side.RIGHT -> Side.LEFT } -val Side.oscName: String - get() = when (this) { - Side.LEFT -> "Left" - Side.RIGHT -> "Right" - } diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index 9345f572de..b84118a4cb 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -11,11 +11,12 @@ import solarxr_protocol.datatypes.BodyPart import kotlin.math.* import dev.slimevr.util.Side import dev.slimevr.util.opposite -import dev.slimevr.util.oscName + private const val ABSOLUTE_SPLAY_THRESHOLD_ANGLE = 7 private const val MINIMUM_TIP_TOE_PITCH = -14 private const val MINIMUM_BENDING_PITCH = 15 private const val MAXIMUM_ABSOLUTE_TOE_RANGE = 90 + internal fun buildToeMessages(bones: Map): List { val messages = mutableListOf() @@ -69,6 +70,12 @@ private fun processToesForFoot( } } +private val Side.oscName: String + get() = when (this) { + Side.LEFT -> "Left" + Side.RIGHT -> "Right" + } + private fun processToe( foot: BoneState, toe: BoneState, From fd61681186e34cd80439f527866d83cae94afbca Mon Sep 17 00:00:00 2001 From: Sebastina Date: Thu, 30 Jul 2026 19:34:27 -0500 Subject: [PATCH 26/32] Use add all for messages. --- .../dev/slimevr/vrcosc/toe-plugin-output-encoder.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index b84118a4cb..754bc0252e 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -99,11 +99,11 @@ private fun processToe( Side.LEFT -> yaw < -ABSOLUTE_SPLAY_THRESHOLD_ANGLE Side.RIGHT -> yaw > ABSOLUTE_SPLAY_THRESHOLD_ANGLE } - - messages.add(OscContent.Message(OscMessage("/avatar/parameters/TipToes${side.oscName}", listOf(if (tipToe) OscArg.True else OscArg.False)))) - messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.oscName}${toeNumber + 1}Bool", listOf(if (bending) OscArg.True else OscArg.False)))) - messages.add(OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.oscName}${toeNumber + 1}", listOf(if (splayed) OscArg.True else OscArg.False)))) - val floatValue = (pitch / MAXIMUM_ABSOLUTE_TOE_RANGE).coerceIn(-1f, 1f) - messages.add(OscContent.Message(OscMessage("/avatar/parameters/Toe${side.oscName}${toeNumber + 1}Float", listOf(OscArg.Float(floatValue))))) + messages.addAll(listOf(OscContent.Message( + OscMessage("/avatar/parameters/TipToes${side.oscName}", listOf(if (tipToe) OscArg.True else OscArg.False))), + OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.oscName}${toeNumber + 1}Bool", listOf(if (bending) OscArg.True else OscArg.False))), + OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.oscName}${toeNumber + 1}", listOf(if (splayed) OscArg.True else OscArg.False))), + OscContent.Message(OscMessage("/avatar/parameters/Toe${side.oscName}${toeNumber + 1}Float", listOf(OscArg.Float(floatValue)))))) + } From 895b7c81d5cbd59964e344fc15515508dff32981 Mon Sep 17 00:00:00 2001 From: Sebastina Date: Thu, 30 Jul 2026 19:36:01 -0500 Subject: [PATCH 27/32] We can calculate the OSC toe number once. --- .../java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index 754bc0252e..1c2e8de58a 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -85,6 +85,7 @@ private fun processToe( messages: MutableList, ) { + val oscToeNumber = toeNumber + 1; val footRot = foot.rotation val toeRot = toe.rotation val currentRelative = footRot.inv() * toeRot @@ -102,8 +103,8 @@ private fun processToe( val floatValue = (pitch / MAXIMUM_ABSOLUTE_TOE_RANGE).coerceIn(-1f, 1f) messages.addAll(listOf(OscContent.Message( OscMessage("/avatar/parameters/TipToes${side.oscName}", listOf(if (tipToe) OscArg.True else OscArg.False))), - OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.oscName}${toeNumber + 1}Bool", listOf(if (bending) OscArg.True else OscArg.False))), - OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.oscName}${toeNumber + 1}", listOf(if (splayed) OscArg.True else OscArg.False))), - OscContent.Message(OscMessage("/avatar/parameters/Toe${side.oscName}${toeNumber + 1}Float", listOf(OscArg.Float(floatValue)))))) - + OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.oscName}${oscToeNumber}Bool", listOf(if (bending) OscArg.True else OscArg.False))), + OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.oscName}${oscToeNumber}", listOf(if (splayed) OscArg.True else OscArg.False))), + OscContent.Message(OscMessage("/avatar/parameters/Toe${side.oscName}${oscToeNumber}Float", listOf(OscArg.Float(floatValue)))))) + } From 82e4995105753a607ccc0b735d8705d3e2ad3b02 Mon Sep 17 00:00:00 2001 From: Erimel Date: Thu, 30 Jul 2026 20:56:14 -0400 Subject: [PATCH 28/32] spotless --- .../main/java/dev/slimevr/resets/module.kt | 1 - .../src/main/java/dev/slimevr/util/Side.kt | 3 - .../src/main/java/dev/slimevr/util/side.kt | 3 - .../vrcosc/toe-plugin-output-encoder.kt | 21 +-- .../skeleton/ToeDirectLinkProcessorTest.kt | 160 +++++++++++------- solarxr-protocol | 2 +- 6 files changed, 113 insertions(+), 77 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/resets/module.kt b/server/core/src/main/java/dev/slimevr/resets/module.kt index a027f731c8..2f31de1eff 100644 --- a/server/core/src/main/java/dev/slimevr/resets/module.kt +++ b/server/core/src/main/java/dev/slimevr/resets/module.kt @@ -167,7 +167,6 @@ class ResetsManager(val context: ResetsContext, val server: VRServer, val settin in ResetBodyParts.RIGHT_FINGERS -> -FastMath.HALF_PI - // Going right in ResetBodyParts.LEFT_ARM if armsResetMode == ArmsResetMode.T_POSE_DOWN -> FastMath.HALF_PI diff --git a/server/core/src/main/java/dev/slimevr/util/Side.kt b/server/core/src/main/java/dev/slimevr/util/Side.kt index 99b3bfaccd..b0bea21539 100644 --- a/server/core/src/main/java/dev/slimevr/util/Side.kt +++ b/server/core/src/main/java/dev/slimevr/util/Side.kt @@ -1,8 +1,5 @@ package dev.slimevr.util -/** - * Used to represent the side of a bone. - */ enum class Side { LEFT, RIGHT, diff --git a/server/core/src/main/java/dev/slimevr/util/side.kt b/server/core/src/main/java/dev/slimevr/util/side.kt index 79d030308d..b0bea21539 100644 --- a/server/core/src/main/java/dev/slimevr/util/side.kt +++ b/server/core/src/main/java/dev/slimevr/util/side.kt @@ -1,8 +1,5 @@ package dev.slimevr.util -/** - * Used to represent the side of the skeleton a bone is on. - */ enum class Side { LEFT, RIGHT, diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index 1c2e8de58a..b9186e1305 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -4,13 +4,13 @@ import dev.slimevr.osc.OscArg import dev.slimevr.osc.OscContent import dev.slimevr.osc.OscMessage import dev.slimevr.skeleton.BoneState +import dev.slimevr.util.Side +import dev.slimevr.util.opposite import io.github.axisangles.ktmath.EulerOrder import io.github.axisangles.ktmath.Quaternion import io.github.axisangles.ktmath.Vector3 import solarxr_protocol.datatypes.BodyPart import kotlin.math.* -import dev.slimevr.util.Side -import dev.slimevr.util.opposite private const val ABSOLUTE_SPLAY_THRESHOLD_ANGLE = 7 private const val MINIMUM_TIP_TOE_PITCH = -14 @@ -84,8 +84,7 @@ private fun processToe( splayDirection: Side, messages: MutableList, ) { - - val oscToeNumber = toeNumber + 1; + val oscToeNumber = toeNumber + 1 val footRot = foot.rotation val toeRot = toe.rotation val currentRelative = footRot.inv() * toeRot @@ -101,10 +100,12 @@ private fun processToe( Side.RIGHT -> yaw > ABSOLUTE_SPLAY_THRESHOLD_ANGLE } val floatValue = (pitch / MAXIMUM_ABSOLUTE_TOE_RANGE).coerceIn(-1f, 1f) - messages.addAll(listOf(OscContent.Message( - OscMessage("/avatar/parameters/TipToes${side.oscName}", listOf(if (tipToe) OscArg.True else OscArg.False))), - OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.oscName}${oscToeNumber}Bool", listOf(if (bending) OscArg.True else OscArg.False))), - OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.oscName}${oscToeNumber}", listOf(if (splayed) OscArg.True else OscArg.False))), - OscContent.Message(OscMessage("/avatar/parameters/Toe${side.oscName}${oscToeNumber}Float", listOf(OscArg.Float(floatValue)))))) - + messages.addAll( + listOf( + OscContent.Message(OscMessage("/avatar/parameters/TipToes${side.oscName}", listOf(if (tipToe) OscArg.True else OscArg.False))), + OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.oscName}${oscToeNumber}Bool", listOf(if (bending) OscArg.True else OscArg.False))), + OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.oscName}$oscToeNumber", listOf(if (splayed) OscArg.True else OscArg.False))), + OscContent.Message(OscMessage("/avatar/parameters/Toe${side.oscName}${oscToeNumber}Float", listOf(OscArg.Float(floatValue)))), + ), + ) } diff --git a/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt index 3bdbc2623e..9d78cf5783 100644 --- a/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt +++ b/server/core/src/test/java/dev/slimevr/skeleton/ToeDirectLinkProcessorTest.kt @@ -13,16 +13,18 @@ class ToeDirectLinkProcessorTest { val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(20f, 50f, 25f), - isActive = true) + isActive = true, + ) } val state = SkeletonState( boneInputs = inputs, skeletonHeight = 1.7f, - paused = false + paused = false, ) val newInputs = processor.process(state) @@ -49,16 +51,20 @@ class ToeDirectLinkProcessorTest { val rightLittleToeIsSameRotationAsRightRingToe = newInputs.boneInputs[BodyPart.RIGHT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation - assertTrue(leftBigToeIsSameRotationAsLeftFoot - && leftIndexToeIsSameRotationAsLeftBigToe - && leftMiddleToeIsSameRotationAsLeftIndexToe - && leftRingToeIsSameRotationAsLeftMiddleToe - && leftLittleToeIsSameRotationAsLeftRingToe) - assertTrue(rightBigToeIsSameRotationAsRightFoot - && rightIndexToeIsSameRotationAsRightBigToe - && rightMiddleToeIsSameRotationAsRightIndexToe - && rightRingToeIsSameRotationAsRightMiddleToe - && rightLittleToeIsSameRotationAsRightRingToe) + assertTrue( + leftBigToeIsSameRotationAsLeftFoot && + leftIndexToeIsSameRotationAsLeftBigToe && + leftMiddleToeIsSameRotationAsLeftIndexToe && + leftRingToeIsSameRotationAsLeftMiddleToe && + leftLittleToeIsSameRotationAsLeftRingToe, + ) + assertTrue( + rightBigToeIsSameRotationAsRightFoot && + rightIndexToeIsSameRotationAsRightBigToe && + rightMiddleToeIsSameRotationAsRightIndexToe && + rightRingToeIsSameRotationAsRightMiddleToe && + rightLittleToeIsSameRotationAsRightRingToe, + ) } @Test @@ -67,22 +73,26 @@ class ToeDirectLinkProcessorTest { val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), - isActive = true) + isActive = true, + ) map[BodyPart.LEFT_BIG_TOE] = map.getValue(BodyPart.LEFT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(11f, 41f, 16f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(20f, 50f, 25f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_BIG_TOE] = map.getValue(BodyPart.RIGHT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(21f, 51f, 26f), - isActive = true) + isActive = true, + ) } val state = SkeletonState( boneInputs = inputs, skeletonHeight = 1.7f, - paused = false + paused = false, ) val newInputs = processor.process(state) @@ -105,14 +115,18 @@ class ToeDirectLinkProcessorTest { val rightLittleToeIsSameRotationAsRightRingToe = newInputs.boneInputs[BodyPart.RIGHT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation - assertTrue(leftIndexToeIsSameRotationAsLeftBigToe - && leftMiddleToeIsSameRotationAsLeftIndexToe - && leftRingToeIsSameRotationAsLeftMiddleToe - && leftLittleToeIsSameRotationAsLeftRingToe) - assertTrue(rightIndexToeIsSameRotationAsRightBigToe - && rightMiddleToeIsSameRotationAsRightIndexToe - && rightRingToeIsSameRotationAsRightMiddleToe - && rightLittleToeIsSameRotationAsRightRingToe) + assertTrue( + leftIndexToeIsSameRotationAsLeftBigToe && + leftMiddleToeIsSameRotationAsLeftIndexToe && + leftRingToeIsSameRotationAsLeftMiddleToe && + leftLittleToeIsSameRotationAsLeftRingToe, + ) + assertTrue( + rightIndexToeIsSameRotationAsRightBigToe && + rightMiddleToeIsSameRotationAsRightIndexToe && + rightRingToeIsSameRotationAsRightMiddleToe && + rightLittleToeIsSameRotationAsRightRingToe, + ) } @Test @@ -121,29 +135,35 @@ class ToeDirectLinkProcessorTest { val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), - isActive = true) + isActive = true, + ) map[BodyPart.LEFT_BIG_TOE] = map.getValue(BodyPart.LEFT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(11f, 41f, 16f), - isActive = true) + isActive = true, + ) map[BodyPart.LEFT_INDEX_TOE] = map.getValue(BodyPart.LEFT_INDEX_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(12f, 42f, 17f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(20f, 50f, 25f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_BIG_TOE] = map.getValue(BodyPart.RIGHT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(21f, 51f, 26f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_INDEX_TOE] = map.getValue(BodyPart.RIGHT_INDEX_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(22f, 52f, 27f), - isActive = true) + isActive = true, + ) } val state = SkeletonState( boneInputs = inputs, skeletonHeight = 1.7f, - paused = false + paused = false, ) val newInputs = processor.process(state) @@ -162,12 +182,16 @@ class ToeDirectLinkProcessorTest { val rightLittleToeIsSameRotationAsRightRingToe = newInputs.boneInputs[BodyPart.RIGHT_LITTLE_TOE]?.rawRotation == newInputs.boneInputs[BodyPart.RIGHT_RING_TOE]?.rawRotation - assertTrue(leftMiddleToeIsSameRotationAsLeftIndexToe - && leftRingToeIsSameRotationAsLeftMiddleToe - && leftLittleToeIsSameRotationAsLeftRingToe) - assertTrue(rightMiddleToeIsSameRotationAsRightIndexToe - && rightRingToeIsSameRotationAsRightMiddleToe - && rightLittleToeIsSameRotationAsRightRingToe) + assertTrue( + leftMiddleToeIsSameRotationAsLeftIndexToe && + leftRingToeIsSameRotationAsLeftMiddleToe && + leftLittleToeIsSameRotationAsLeftRingToe, + ) + assertTrue( + rightMiddleToeIsSameRotationAsRightIndexToe && + rightRingToeIsSameRotationAsRightMiddleToe && + rightLittleToeIsSameRotationAsRightRingToe, + ) } @Test @@ -176,35 +200,43 @@ class ToeDirectLinkProcessorTest { val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), - isActive = true) + isActive = true, + ) map[BodyPart.LEFT_BIG_TOE] = map.getValue(BodyPart.LEFT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(11f, 41f, 16f), - isActive = true) + isActive = true, + ) map[BodyPart.LEFT_INDEX_TOE] = map.getValue(BodyPart.LEFT_INDEX_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(12f, 42f, 17f), - isActive = true) + isActive = true, + ) map[BodyPart.LEFT_MIDDLE_TOE] = map.getValue(BodyPart.LEFT_MIDDLE_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(13f, 43f, 18f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(20f, 50f, 25f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_BIG_TOE] = map.getValue(BodyPart.RIGHT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(21f, 51f, 26f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_INDEX_TOE] = map.getValue(BodyPart.RIGHT_INDEX_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(22f, 52f, 27f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_MIDDLE_TOE] = map.getValue(BodyPart.RIGHT_MIDDLE_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(23f, 53f, 28f), - isActive = true) + isActive = true, + ) } val state = SkeletonState( boneInputs = inputs, skeletonHeight = 1.7f, - paused = false + paused = false, ) val newInputs = processor.process(state) @@ -229,41 +261,51 @@ class ToeDirectLinkProcessorTest { val inputs = DEFAULT_SKELETON_STATE.boneInputs.mutate { map -> map[BodyPart.LEFT_FOOT] = map.getValue(BodyPart.LEFT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(10f, 40f, 15f), - isActive = true) + isActive = true, + ) map[BodyPart.LEFT_BIG_TOE] = map.getValue(BodyPart.LEFT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(11f, 41f, 16f), - isActive = true) + isActive = true, + ) map[BodyPart.LEFT_INDEX_TOE] = map.getValue(BodyPart.LEFT_INDEX_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(12f, 42f, 17f), - isActive = true) + isActive = true, + ) map[BodyPart.LEFT_MIDDLE_TOE] = map.getValue(BodyPart.LEFT_MIDDLE_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(15f, 45f, 19f), - isActive = true) + isActive = true, + ) map[BodyPart.LEFT_RING_TOE] = map.getValue(BodyPart.LEFT_RING_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(13f, 43f, 18f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_FOOT] = map.getValue(BodyPart.RIGHT_FOOT).copy( rawRotation = Quaternion.Companion.fromRotationVector(20f, 50f, 25f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_BIG_TOE] = map.getValue(BodyPart.RIGHT_BIG_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(21f, 51f, 26f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_INDEX_TOE] = map.getValue(BodyPart.RIGHT_INDEX_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(22f, 52f, 27f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_MIDDLE_TOE] = map.getValue(BodyPart.RIGHT_MIDDLE_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(23f, 53f, 28f), - isActive = true) + isActive = true, + ) map[BodyPart.RIGHT_RING_TOE] = map.getValue(BodyPart.RIGHT_RING_TOE).copy( rawRotation = Quaternion.Companion.fromRotationVector(24f, 54f, 29f), - isActive = true) + isActive = true, + ) } val state = SkeletonState( boneInputs = inputs, skeletonHeight = 1.7f, - paused = false + paused = false, ) val newInputs = processor.process(state) diff --git a/solarxr-protocol b/solarxr-protocol index cc9eeeaee7..48b858edc3 160000 --- a/solarxr-protocol +++ b/solarxr-protocol @@ -1 +1 @@ -Subproject commit cc9eeeaee780acfba06350afb41bcaf53f784175 +Subproject commit 48b858edc314968ab9f6fbdae869e3897c625de0 From 6dbaa032cf9806115ef9ba47094019ad8dbfdc48 Mon Sep 17 00:00:00 2001 From: Erimel Date: Thu, 30 Jul 2026 20:56:43 -0400 Subject: [PATCH 29/32] fix side.kt --- server/core/src/main/java/dev/slimevr/util/Side.kt | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 server/core/src/main/java/dev/slimevr/util/Side.kt diff --git a/server/core/src/main/java/dev/slimevr/util/Side.kt b/server/core/src/main/java/dev/slimevr/util/Side.kt deleted file mode 100644 index b0bea21539..0000000000 --- a/server/core/src/main/java/dev/slimevr/util/Side.kt +++ /dev/null @@ -1,12 +0,0 @@ -package dev.slimevr.util - -enum class Side { - LEFT, - RIGHT, -} - -val Side.opposite: Side - get() = when (this) { - Side.LEFT -> Side.RIGHT - Side.RIGHT -> Side.LEFT - } From f945f220cd2c96c6da98553992502801dab030eb Mon Sep 17 00:00:00 2001 From: Erimel Date: Thu, 30 Jul 2026 21:01:14 -0400 Subject: [PATCH 30/32] cleanup extra VMC_MIRROR_BONE_PAIRS entries --- server/core/src/main/java/dev/slimevr/vmc/mapping.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt index 6fb0ba980e..a9cd8630bc 100644 --- a/server/core/src/main/java/dev/slimevr/vmc/mapping.kt +++ b/server/core/src/main/java/dev/slimevr/vmc/mapping.kt @@ -155,8 +155,6 @@ val VMC_MIRROR_BONE_PAIRS: List> = listOf( BodyPart.LEFT_LITTLE_INTERMEDIATE to BodyPart.RIGHT_LITTLE_INTERMEDIATE, BodyPart.LEFT_LITTLE_DISTAL to BodyPart.RIGHT_LITTLE_DISTAL, BodyPart.LEFT_BIG_TOE to BodyPart.RIGHT_BIG_TOE, - BodyPart.LEFT_INDEX_TOE to BodyPart.RIGHT_INDEX_TOE, - BodyPart.LEFT_LITTLE_TOE to BodyPart.RIGHT_LITTLE_TOE, ) val VMC_MIRROR_BONES: BodyPartMap = BodyPartMap( From cd9bd764bf088459fffc314048c35a9ee4725d8f Mon Sep 17 00:00:00 2001 From: Sebastina Date: Thu, 30 Jul 2026 20:20:51 -0500 Subject: [PATCH 31/32] Only the big toe needs to splay in the opposite direction. --- .../main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index b9186e1305..92c170faf1 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -64,7 +64,7 @@ private fun processToesForFoot( if (lastAssigned == null) continue - val splayDirection = if (segmentIndex <= 2) side.opposite else side + val splayDirection = if (segmentIndex > 0) side else side.opposite processToe(foot, lastAssigned, side, segmentIndex, splayDirection, messages) } From 4836218f18cd7abcc01306ecfd84120543a003eb Mon Sep 17 00:00:00 2001 From: Sebastina Date: Thu, 30 Jul 2026 20:34:54 -0500 Subject: [PATCH 32/32] Just to future proof. We should also send toe splay as a float for future support by receivers. --- .../java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt index 92c170faf1..1f7aac23d3 100644 --- a/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt +++ b/server/core/src/main/java/dev/slimevr/vrcosc/toe-plugin-output-encoder.kt @@ -99,13 +99,15 @@ private fun processToe( Side.LEFT -> yaw < -ABSOLUTE_SPLAY_THRESHOLD_ANGLE Side.RIGHT -> yaw > ABSOLUTE_SPLAY_THRESHOLD_ANGLE } - val floatValue = (pitch / MAXIMUM_ABSOLUTE_TOE_RANGE).coerceIn(-1f, 1f) + val toeCurlValue = (pitch / MAXIMUM_ABSOLUTE_TOE_RANGE).coerceIn(-1f, 1f) + val toeSplayValue = (yaw / MAXIMUM_ABSOLUTE_TOE_RANGE).coerceIn(-1f, 1f) messages.addAll( listOf( OscContent.Message(OscMessage("/avatar/parameters/TipToes${side.oscName}", listOf(if (tipToe) OscArg.True else OscArg.False))), OscContent.Message(OscMessage("/avatar/parameters/ToeBent${side.oscName}${oscToeNumber}Bool", listOf(if (bending) OscArg.True else OscArg.False))), OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.oscName}$oscToeNumber", listOf(if (splayed) OscArg.True else OscArg.False))), - OscContent.Message(OscMessage("/avatar/parameters/Toe${side.oscName}${oscToeNumber}Float", listOf(OscArg.Float(floatValue)))), + OscContent.Message(OscMessage("/avatar/parameters/Toe${side.oscName}${oscToeNumber}Float", listOf(OscArg.Float(toeCurlValue)))), + OscContent.Message(OscMessage("/avatar/parameters/ToeSplay${side.oscName}${oscToeNumber}Float", listOf(OscArg.Float(toeSplayValue)))), ), ) }