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
5 changes: 3 additions & 2 deletions src/viser/client/src/BatchedLabelManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ export const BatchedLabelManager: React.FC<{
);

// Billboard rotation, position updates, and visibility culling in render loop.
useFrame(({ camera }) => {
useFrame(({ camera, size }) => {
if (!group) return;

// Sync dirty batches (batches multiple add/remove/update calls).
Expand Down Expand Up @@ -324,14 +324,15 @@ export const BatchedLabelManager: React.FC<{
let paddingY = LABEL_BACKGROUND_PADDING_Y;

if (textInfo.fontSizeMode === "screen") {
// Scale based on distance and FOV to maintain consistent visual size.
// Scale based on distance, FOV, and viewport size to maintain consistent pixel size.
// Convert text position from local space to world space using group's matrix.
tempWorldPos.copy(text.position);
tempWorldPos.applyMatrix4(group.matrixWorld);
const scale = calculateScreenSpaceScale(
camera,
tempWorldPos,
tempCameraSpacePos,
size.height,
);

// Set fontSize directly - Troika applies this as a scale, no sync needed.
Expand Down
15 changes: 12 additions & 3 deletions src/viser/client/src/LabelUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,22 @@ export function calculateBaseFontSize(
return mode === "screen" ? 0.3 * screenScale : sceneHeight;
}

/**
* Reference viewport height for screen-space label sizing.
* Labels maintain consistent pixel size by scaling relative to this reference.
*/
const REFERENCE_VIEWPORT_HEIGHT = 800;

/**
* Calculate screen-space scale factor for labels.
* Returns scale factor to apply to base font size.
* Accounts for camera distance, FOV, and viewport size to maintain constant pixel size.
*/
export function calculateScreenSpaceScale(
camera: THREE.Camera,
worldPosition: THREE.Vector3,
tempCameraSpacePos: THREE.Vector3,
viewportHeight: number,
): number {
if ("fov" in camera && typeof camera.fov === "number") {
// PerspectiveCamera: use Z-coordinate in camera space (not Euclidean distance).
Expand All @@ -99,9 +107,10 @@ export function calculateScreenSpaceScale(
((camera as THREE.PerspectiveCamera).fov * Math.PI) / 360,
);
// Reference depth is 10 units (baseFontSize is calibrated for this).
return (depth / 10.0) * fovScale;
// Scale by reference/actual viewport height to maintain constant pixel size.
return (depth / 10.0) * fovScale * (REFERENCE_VIEWPORT_HEIGHT / viewportHeight);
} else {
// OrthographicCamera: use constant scale (no perspective).
return 1.0;
// OrthographicCamera: scale based on viewport height only.
return REFERENCE_VIEWPORT_HEIGHT / viewportHeight;
}
}