Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions VR/client/app/create_app.js
Original file line number Diff line number Diff line change
Expand Up @@ -163,15 +163,21 @@ export function createApp({ document, window }) {
dom.characterModeSelect.value = resolvedMode;
applyCharacterMode(resolvedMode);
}

// Controlled arms
const controlled = message.payload.controlled_arm_ids ?? [];
if (state.sessionMode === "vr_client" && controlled.length >= 2) {
state.controlledArmByHand.left = controlled[0];
state.controlledArmByHand.right = controlled[1];
}

// Current user arm IDs
state.currentUserRenderArmIds = message.payload.arm_ids ?? [];
state.currentUserArmIds = (state.currentUserRenderArmIds || []).filter(
(armId) => typeof armId !== "string" || !armId.toLowerCase().includes("head")
);

// Status message
setStatus(
`connected: ${configWindow.serverHost} | role=${state.sessionRole} | user=${state.userId ?? "unknown"} | arms=${(
message.payload.arm_ids ?? []
Expand Down Expand Up @@ -274,6 +280,7 @@ export function createApp({ document, window }) {
applyCharacterMode(mode);
};

// Refresh mode UI when character mode select changes
dom.characterModeSelect.addEventListener("change", refreshModeUI);
refreshModeUI();

Expand Down
119 changes: 119 additions & 0 deletions VR/client/app/session_bindings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
export function bindArmCameraArms({
state,
armCameraController,
userArms,
armStates,
filterVisibleArmIds,
isAvatarHeadArmId,
}) {
const { userId: targetUserId, armId: targetArmId } =
armCameraController.syncSelection({ userArms, armStates });
const selectedUserArmIds = filterVisibleArmIds(userArms?.[targetUserId] ?? []);
const headArmId = Object.values(armStates || {}).find(
(armState) =>
armState?.owner_user_id === targetUserId &&
isAvatarHeadArmId(armState?.arm_id)
)?.arm_id;

state.spectatedUserId = targetUserId;
state.spectatedRenderArmIds = [
...selectedUserArmIds,
...(headArmId ? [headArmId] : []),
];
state.controlledArmByHand.left = targetArmId || selectedUserArmIds[0] || "";
state.controlledArmByHand.right =
targetArmId || selectedUserArmIds[1] || selectedUserArmIds[0] || "";

if (!targetArmId) {
state.spectatedUserId = "";
state.spectatedRenderArmIds = [];
}
}

export function updateArmCamera({
state,
armCameraController,
camera,
armStates,
}) {
if (state.sessionMode !== "arm_camera") return;
const { armId } = armCameraController.getSelection();
if (!armId) return;
armCameraController.applyToCamera(camera, armStates?.[armId] ?? null);
}

export function bindSpectatorArms({
state,
spectatorAvatarManager,
userArms,
armStates,
filterVisibleArmIds,
isAvatarHeadArmId,
clearRenderedArms,
}) {
const visibleUserArms = Object.fromEntries(
Object.entries(userArms || {}).map(([candidateUserId, armIds]) => [
candidateUserId,
filterVisibleArmIds(armIds),
])
);
const followedArms = spectatorAvatarManager.pickFollowedArms({
userArms: visibleUserArms,
armStates,
spectatorUserId: state.userId,
});

state.spectatedUserId = followedArms.userId;
const headArmId = Object.values(armStates || {}).find(
(armState) =>
armState?.owner_user_id === followedArms.userId &&
isAvatarHeadArmId(armState?.arm_id)
)?.arm_id;
state.spectatedRenderArmIds = [
...(followedArms.armIds || []),
...(headArmId ? [headArmId] : []),
];
state.controlledArmByHand.left = followedArms.left;
state.controlledArmByHand.right = followedArms.right;

if (!followedArms.left && !followedArms.right) {
clearRenderedArms();
}
}

export function updateSpectatorHead({
state,
spectatorAvatarManager,
armStates,
isAvatarHeadArmId,
}) {
const allSpectatedArmStates =
state.spectatedUserId && armStates
? Object.values(armStates).filter(
(armState) => armState?.owner_user_id === state.spectatedUserId
)
: [];
const headArmState =
allSpectatedArmStates.find((armState) => isAvatarHeadArmId(armState?.arm_id)) ??
null;
const spectatedArmIds = state.spectatedUserId
? state.currentUserArmIds.length > 0 && state.spectatedUserId === state.userId
? state.currentUserArmIds
: null
: null;

spectatorAvatarManager.updateHead({
sessionRole: state.sessionRole,
armStates,
headArmState,
spectatedArmIds:
state.spectatedUserId && armStates
? allSpectatedArmStates
.map((armState) => armState.arm_id)
.filter((armId) => !isAvatarHeadArmId(armId))
: (spectatedArmIds || [
state.controlledArmByHand.left,
state.controlledArmByHand.right,
]).filter((armId) => armId && !isAvatarHeadArmId(armId)),
});
}
2 changes: 1 addition & 1 deletion VR/client/entities/rod/arm_geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function buildSegmentedPipe(demoArm) {
const r1 = Math.max(0.001, radii[Math.min(i + 1, radii.length - 1)]);
const geom = new THREE.CylinderGeometry(r1, r0, length, 14, 1, true);
const mesh = new THREE.Mesh(geom, material.clone());
mesh.position.copy(p0.clone().add(p1).multiplyScalar(0.5));
mesh.position.copy(p0.clone().add(p1).multiplyScalar(0.5)); // midpoint
mesh.quaternion.setFromUnitVectors(yAxis, segment.normalize());
demoArm.armBodyGroup.add(mesh);
}
Expand Down
Loading