From 589b8d0d3fb8db7300392cad5ee3fce4e52c76c7 Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Fri, 22 May 2026 00:34:57 -0500 Subject: [PATCH 01/12] First part of Size Osc. --- .../SettingsProviderIK.cs | 13 ++ .../Drivers/Common/BasisHeightDriver.cs | 48 ++++++ .../Settings/SMModuleCalibration.cs | 1 + .../Components/Acquisition/OSCAcquisition.cs | 3 +- .../Runtime/OSC/BasisOscAvatarScaling.cs | 157 ++++++++++++++++++ .../Runtime/OSC/BasisOscAvatarScaling.cs.meta | 11 ++ .../Systems/Runtime/OSC/BasisOscService.cs | 6 + .../Tests/Runtime/OscBridgeTests.cs | 88 ++++++++++ 8 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs create mode 100644 Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs.meta diff --git a/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderIK.cs b/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderIK.cs index 9760dace4d..38cddb843c 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderIK.cs +++ b/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderIK.cs @@ -25,6 +25,7 @@ public static class SettingsProviderIK private static PanelToggle _uiEuroPos; private static PanelToggle _uiEuroRot; private static PanelSlider _uiCalibSphereScale; + private static PanelSlider _avatarScaleSlider; private static PanelElementDescriptor _boneEuroEditorGroup; private struct BoneBindings @@ -104,6 +105,7 @@ public static PanelTabPage IKTab(PanelTabGroup tabGroup) ikParent, PanelSlider.SliderSettings.Advanced("Avatar Height Scale", 0.1f, 5f, false, 2, ValueDisplayMode.Meters), BasisSettingsDefaults.SelectedScale); + _avatarScaleSlider = avatarScaleSlider; if (avatarScaleSlider != null) { @@ -612,6 +614,17 @@ public static PanelTabPage IKTab(PanelTabGroup tabGroup) return tabPage; } + public static void SetAvatarScaleSliderValueWithoutNotify(float value) + { + if (_avatarScaleSlider == null) + { + return; + } + + _avatarScaleSlider.SetValueWithoutNotify(value); + } + + // ------------------ // Debug Info // ------------------ diff --git a/Basis/Packages/com.basis.framework/Drivers/Common/BasisHeightDriver.cs b/Basis/Packages/com.basis.framework/Drivers/Common/BasisHeightDriver.cs index 13d7c7e17f..4b948c0f64 100644 --- a/Basis/Packages/com.basis.framework/Drivers/Common/BasisHeightDriver.cs +++ b/Basis/Packages/com.basis.framework/Drivers/Common/BasisHeightDriver.cs @@ -1,3 +1,4 @@ +using Basis.BasisUI; using Basis.Scripts.BasisSdk.Players; using Basis.Scripts.Device_Management; using Basis.Scripts.TransformBinders.BoneControl; @@ -47,11 +48,24 @@ public static class BasisHeightDriver public static float PlayerToDefaultRatioScaled = 1f; public static float AvatarToDefaultRatioScaled = 1f; + public static bool HasRuntimeOscEyeHeightOverride = false; + public static float RuntimeOscEyeHeightMeters = FallbackHeightInMeters; public static float DeviceScale = 1f; public static void ApplyScaleAndHeight() { RevaluateUnscaledHeight(SMModuleCalibration.HeightMode); + if (HasRuntimeOscEyeHeightOverride) + { + if (SMModuleCalibration.ApplyCustomScale) + { + ApplyRuntimeOscEyeHeightOverride(RuntimeOscEyeHeightMeters); + return; + } + + ClearRuntimeOscEyeHeightOverride(); + } + ApplyScale(SMModuleCalibration.ApplyCustomScale, SMModuleCalibration.SelectedScale); ChooseHeightToUse(SMModuleCalibration.HeightMode); ScheduleHeightChangeCallback(HeightModeChange.OnApplyHeightAndScale); @@ -101,6 +115,38 @@ public enum HeightModeChange OnApplyHeightAndScale } + public static bool ApplyRuntimeOscEyeHeightOverride(float eyeHeightMeters) + { + eyeHeightMeters = SanitizePositive(eyeHeightMeters, FallbackHeightInMeters); + + float unscaledAvatarEyeHeight = SanitizePositive(AvatarEyeHeight, FallbackHeightInMeters); + float scaleFactor = eyeHeightMeters / unscaledAvatarEyeHeight; + + HasRuntimeOscEyeHeightOverride = true; + RuntimeOscEyeHeightMeters = eyeHeightMeters; + SMModuleCalibration.SelectedScale = eyeHeightMeters; + BasisSettingsDefaults.SelectedScale.SetValueWithoutNotify(eyeHeightMeters); + SettingsProviderIK.SetAvatarScaleSliderValueWithoutNotify(eyeHeightMeters); + ScaledToMatchValue = scaleFactor; + + ApplyAvatarScale(scaleFactor); + RefreshScaledHeightState(HeightModeChange.OnApplyHeightAndScale); + return true; + } + + public static void ClearRuntimeOscEyeHeightOverride() + { + HasRuntimeOscEyeHeightOverride = false; + RuntimeOscEyeHeightMeters = FallbackHeightInMeters; + } + + public static void RefreshScaledHeightState(HeightModeChange mode) + { + RevaluateUnscaledHeight(SMModuleCalibration.HeightMode); + ChooseHeightToUse(SMModuleCalibration.HeightMode); + ScheduleHeightChangeCallback(mode); + } + /// /// Applies a scale factor to the local avatar and updates cached bone offsets. /// @@ -156,6 +202,8 @@ public static void CapturePlayerHeight() public static void CaptureAvatarHeightDuringTpose() { + ClearRuntimeOscEyeHeightOverride(); + var player = BasisLocalPlayer.Instance; if (player == null) { diff --git a/Basis/Packages/com.basis.framework/Settings/SMModuleCalibration.cs b/Basis/Packages/com.basis.framework/Settings/SMModuleCalibration.cs index 81eb0f00b7..f38e0f85d3 100644 --- a/Basis/Packages/com.basis.framework/Settings/SMModuleCalibration.cs +++ b/Basis/Packages/com.basis.framework/Settings/SMModuleCalibration.cs @@ -268,6 +268,7 @@ public override void ValidSettingsChange(string matchedSettingName, string optio { if (!Mathf.Approximately(old, parsed)) { + BasisHeightDriver.ClearRuntimeOscEyeHeightOverride(); SelectedScale = parsed; _dirty = true; } diff --git a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/Components/Acquisition/OSCAcquisition.cs b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/Components/Acquisition/OSCAcquisition.cs index d48f98e2d5..135d98960e 100644 --- a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/Components/Acquisition/OSCAcquisition.cs +++ b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/Components/Acquisition/OSCAcquisition.cs @@ -34,6 +34,7 @@ internal void OnAvatarReady(bool isWearer) if (_alreadyInitialized) return; + BasisOscService.EnsureInitialized(); _acquisitionServer = OSCAcquisitionServer.SceneInstance; _acquisitionServer.SendWakeUpMessage(FakeWakeUpMessage); BasisOscService.RegisterAddressReceiver(_oscOwnerId, OnOscAddressUpdated); @@ -61,6 +62,6 @@ private void OnOscAddressUpdated(int address, float value) _activityRelay?.NotifySourceSample(); acquisitionService?.Submit(address, value); - } + } } } diff --git a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs new file mode 100644 index 0000000000..40d9eb2b42 --- /dev/null +++ b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs @@ -0,0 +1,157 @@ +using Basis.Scripts.BasisSdk.Players; +using HVR.Basis.Comms.OSC; +using UnityEngine; + +namespace HVR.Basis.Comms +{ + public static class BasisOscAvatarScaling + { + private static bool _initialized; + + public const string EyeHeightAddress = "/avatar/eyeheight"; + public const string EyeHeightMinAddress = "/avatar/eyeheightmin"; + public const string EyeHeightMaxAddress = "/avatar/eyeheightmax"; + public const string EyeHeightScalingAllowedAddress = "/avatar/eyeheightscalingallowed"; + + public const float OscMinimumEyeHeightMeters = 0.01f; + public const float OscMaximumEyeHeightMeters = 10000f; + public const float SupportedMinimumEyeHeightMeters = 0.1f; + public const float SupportedMaximumEyeHeightMeters = 100f; + public const float UserSelectableMinimumEyeHeightMeters = 0.1f; + public const float UserSelectableMaximumEyeHeightMeters = 5f; + + public static bool EyeHeightScalingAllowed => SMModuleCalibration.ApplyCustomScale; + + public static void EnsureInitialized() + { + if (_initialized) + { + return; + } + + _initialized = true; + BasisOscService.MessageReceived -= OnOscMessageReceived; + BasisOscService.MessageReceived += OnOscMessageReceived; + BasisLocalPlayer.OnLocalAvatarChanged -= OnLocalAvatarChanged; + BasisLocalPlayer.OnLocalAvatarChanged += OnLocalAvatarChanged; + OnLocalAvatarChanged(); + } + + public static bool IsAvatarScalingAddress(string path) + { + return path == EyeHeightAddress || + path == EyeHeightMinAddress || + path == EyeHeightMaxAddress || + path == EyeHeightScalingAllowedAddress; + } + + public static bool TryHandleWrite(OscMessage message) + { + if (message == null || !IsAvatarScalingAddress(message.Path)) + { + return false; + } + + if (message.Path != EyeHeightAddress) + { + return true; + } + + OscData[] arguments = message.Arguments; + if (arguments == null || arguments.Length == 0 || arguments[0].Kind != OscDataKind.Float32) + { + return true; + } + + ApplyEyeHeight(arguments[0].FloatValue); + return true; + } + + public static void ClearRuntimeOverride() + { + BasisHeightDriver.ClearRuntimeOscEyeHeightOverride(); + } + + public static void ApplyEyeHeight(float requestedEyeHeightMeters) + { + if (!EyeHeightScalingAllowed) + { + BasisDebug.LogWarning("Ignoring OSC avatar eye height because Custom Scale is disabled.", BasisDebug.LogTag.LocalNetwork); + PublishCurrentState(); + return; + } + + if (float.IsNaN(requestedEyeHeightMeters) || float.IsInfinity(requestedEyeHeightMeters) || requestedEyeHeightMeters <= 0f) + { + BasisDebug.LogWarning($"Ignoring invalid OSC avatar eye height {requestedEyeHeightMeters}.", BasisDebug.LogTag.LocalNetwork); + return; + } + + float eyeHeightMeters = Mathf.Clamp(requestedEyeHeightMeters, OscMinimumEyeHeightMeters, OscMaximumEyeHeightMeters); + if (!Mathf.Approximately(eyeHeightMeters, requestedEyeHeightMeters)) + { + BasisDebug.LogWarning($"Clamped OSC avatar eye height from {requestedEyeHeightMeters}m to {eyeHeightMeters}m.", BasisDebug.LogTag.LocalNetwork); + } + + if (eyeHeightMeters < SupportedMinimumEyeHeightMeters || eyeHeightMeters > SupportedMaximumEyeHeightMeters) + { + BasisDebug.LogWarning( + $"OSC avatar eye height {eyeHeightMeters}m is outside the supported {SupportedMinimumEyeHeightMeters}m..{SupportedMaximumEyeHeightMeters}m range.", + BasisDebug.LogTag.LocalNetwork); + } + + BasisLocalPlayer localPlayer = BasisLocalPlayer.Instance; + if (localPlayer == null || localPlayer.LocalAvatarDriver == null || localPlayer.LocalBoneDriver == null) + { + BasisDebug.LogWarning("Ignoring OSC avatar eye height because the local avatar is not ready.", BasisDebug.LogTag.LocalNetwork); + return; + } + + BasisHeightDriver.ApplyRuntimeOscEyeHeightOverride(eyeHeightMeters); + PublishCurrentState(); + } + + public static void PublishCurrentState() + { + BasisOscService.PublishValue(EyeHeightAddress, OscData.Float32(GetCurrentEyeHeightMeters())); + BasisOscService.PublishValue(EyeHeightMinAddress, OscData.Float32(UserSelectableMinimumEyeHeightMeters)); + BasisOscService.PublishValue(EyeHeightMaxAddress, OscData.Float32(UserSelectableMaximumEyeHeightMeters)); + BasisOscService.PublishValue(EyeHeightScalingAllowedAddress, OscData.Boolean(EyeHeightScalingAllowed)); + } + + private static float GetCurrentEyeHeightMeters() + { + float selected = BasisHeightDriver.SelectedScaledAvatarHeight; + if (!float.IsNaN(selected) && !float.IsInfinity(selected) && selected > 0f) + { + return selected; + } + + float fallback = BasisHeightDriver.AvatarEyeHeight * BasisHeightDriver.AppliedUpScale; + if (!float.IsNaN(fallback) && !float.IsInfinity(fallback) && fallback > 0f) + { + return fallback; + } + + return BasisHeightDriver.FallbackHeightInMeters; + } + + private static void OnLocalAvatarChanged() + { + ClearRuntimeOverride(); + BasisLocalPlayer.OnPlayersHeightChangedNextFrame -= OnPlayerHeightChanged; + BasisLocalPlayer.OnPlayersHeightChangedNextFrame += OnPlayerHeightChanged; + PublishCurrentState(); + } + + private static void OnPlayerHeightChanged(BasisHeightDriver.HeightModeChange mode) + { + PublishCurrentState(); + } + + private static void OnOscMessageReceived(OscMessage message) + { + TryHandleWrite(message); + } + } +} diff --git a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs.meta b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs.meta new file mode 100644 index 0000000000..d6ee4f8517 --- /dev/null +++ b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b41d2e05ce3e47c9b939c6e0c58600b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscService.cs b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscService.cs index e9423683dd..103d4c17a9 100644 --- a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscService.cs +++ b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscService.cs @@ -83,6 +83,7 @@ public DispatcherState( public static void EnsureInitialized() { _ = OSCAcquisitionServer.SceneInstance; + BasisOscAvatarScaling.EnsureInitialized(); } public static void RegisterReceiver(EntityId ownerId, Action handler) @@ -653,6 +654,11 @@ private static bool TryResolveAddressValue(SimpleOSC.OSCMessage message, out int addressId = 0; value = 0f; + if (BasisOscAvatarScaling.IsAvatarScalingAddress(message.path)) + { + return false; + } + object[] arguments = message.arguments; if (arguments == null || arguments.Length == 0 || !(arguments[0] is float floatValue)) { diff --git a/Basis/Packages/dev.hai-vr.basis.comms/Tests/Runtime/OscBridgeTests.cs b/Basis/Packages/dev.hai-vr.basis.comms/Tests/Runtime/OscBridgeTests.cs index 864291d916..cc46b3f3b5 100644 --- a/Basis/Packages/dev.hai-vr.basis.comms/Tests/Runtime/OscBridgeTests.cs +++ b/Basis/Packages/dev.hai-vr.basis.comms/Tests/Runtime/OscBridgeTests.cs @@ -633,6 +633,94 @@ public void BasisOsc_LocalAvatarPublishValue_SubmitsFloatIntoVixxyVariableStore( } } + [Test] + public void BasisOscAvatarScaling_PublishesVrChatScalingEndpoints() + { + DestroySceneInstance(); + bool previousApplyCustomScale = SMModuleCalibration.ApplyCustomScale; + SMModuleCalibration.ApplyCustomScale = true; + + try + { + BasisOscAvatarScaling.PublishCurrentState(); + + object root = GetQueryRoot(); + object eyeHeight = ResolveNode(root, "avatar", "eyeheight"); + object eyeHeightMin = ResolveNode(root, "avatar", "eyeheightmin"); + object eyeHeightMax = ResolveNode(root, "avatar", "eyeheightmax"); + object scalingAllowed = ResolveNode(root, "avatar", "eyeheightscalingallowed"); + + Assert.That(eyeHeight, Is.Not.Null); + Assert.That(eyeHeightMin, Is.Not.Null); + Assert.That(eyeHeightMax, Is.Not.Null); + Assert.That(scalingAllowed, Is.Not.Null); + + Assert.That((string)eyeHeight.GetType().GetField("FULL_PATH").GetValue(eyeHeight), Is.EqualTo("/avatar/eyeheight")); + Assert.That((string)eyeHeight.GetType().GetField("TYPE").GetValue(eyeHeight), Is.EqualTo(",f")); + Assert.That((string)eyeHeightMin.GetType().GetField("TYPE").GetValue(eyeHeightMin), Is.EqualTo(",f")); + Assert.That((string)eyeHeightMax.GetType().GetField("TYPE").GetValue(eyeHeightMax), Is.EqualTo(",f")); + Assert.That((string)scalingAllowed.GetType().GetField("TYPE").GetValue(scalingAllowed), Is.EqualTo(",T")); + + IList minValues = (IList)eyeHeightMin.GetType().GetField("VALUE").GetValue(eyeHeightMin); + IList maxValues = (IList)eyeHeightMax.GetType().GetField("VALUE").GetValue(eyeHeightMax); + IList allowedValues = (IList)scalingAllowed.GetType().GetField("VALUE").GetValue(scalingAllowed); + + Assert.That(minValues[0], Is.EqualTo(BasisOscAvatarScaling.UserSelectableMinimumEyeHeightMeters)); + Assert.That(maxValues[0], Is.EqualTo(BasisOscAvatarScaling.UserSelectableMaximumEyeHeightMeters)); + Assert.That(allowedValues[0], Is.EqualTo(true)); + } + finally + { + SMModuleCalibration.ApplyCustomScale = previousApplyCustomScale; + DestroySceneInstance(); + } + } + + [Test] + public void BasisOscService_AvatarScalingAddress_UsesMessageReceiverOnly() + { + MethodInfo submitRawMessages = typeof(BasisOscService).GetMethod("SubmitRawMessages", BindingFlags.NonPublic | BindingFlags.Static); + Assert.That(submitRawMessages, Is.Not.Null); + + GameObject owner = new GameObject("AvatarScalingMessageReceiverOwner"); + EntityId ownerId = owner.GetEntityId(); + string receivedPath = null; + bool addressReceiverCalled = false; + + try + { + BasisOscService.RegisterReceiver(ownerId, message => + { + receivedPath = message.Path; + }); + BasisOscService.RegisterAddressReceiver(ownerId, (address, value) => addressReceiverCalled = true); + BasisOscService.UpdateSubscriptions(ownerId, true, Array.Empty(), Array.Empty()); + + submitRawMessages.Invoke(null, new object[] + { + new List + { + new SimpleOSC.OSCMessage + { + path = BasisOscAvatarScaling.EyeHeightAddress, + arguments = new object[] { 2f } + } + } + }); + + Assert.That(receivedPath, Is.EqualTo(BasisOscAvatarScaling.EyeHeightAddress)); + Assert.That(addressReceiverCalled, Is.False); + } + finally + { + BasisOscService.UnregisterReceiver(ownerId); + BasisOscService.UnregisterAddressReceiver(ownerId); + BasisOscService.ClearSubscriptions(ownerId); + Object.DestroyImmediate(owner); + DestroySceneInstance(); + } + } + [Test] public void BasisOsc_PublishValue_ReturnsResolvedRelativeAddress() { From 1beb567e418a287a6b487b16608bca6e5aa801cd Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Tue, 26 May 2026 21:49:50 -0500 Subject: [PATCH 02/12] Have minium render scale for the menu. --- .../BasisUI/BasisMenuMover.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/Basis/Packages/com.basis.framework/BasisUI/BasisMenuMover.cs b/Basis/Packages/com.basis.framework/BasisUI/BasisMenuMover.cs index c93dcf16d0..ef3691c7ee 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/BasisMenuMover.cs +++ b/Basis/Packages/com.basis.framework/BasisUI/BasisMenuMover.cs @@ -65,6 +65,7 @@ public struct RootModeOffset private bool _hasLocalMoveEvent; private const float MIN_Z_SCALE = 0.01f; + private const float MIN_TMP_RENDER_SCALE = 0.055f; // --- PlaySpaceStable state (from v1) --- private bool _stableHasAnchor; @@ -254,8 +255,7 @@ private void SetRootOffset(RootModeOffset offset) offsetScale.z = Mathf.Max(MIN_Z_SCALE, offsetScale.z); GroupOffset.localScale = offsetScale; - // Root is avatar-compensated - transform.localScale = Vector3.one * playerHeight; + transform.localScale = Vector3.one * GetRenderSafeMenuScale(playerHeight); } private void SetEyeOffset(float scaleFactor) @@ -269,7 +269,7 @@ private void SetEyeOffset(float scaleFactor) offsetScale.z = Mathf.Max(MIN_Z_SCALE, offsetScale.z); GroupOffset.localScale = offsetScale; - transform.localScale = Vector3.one * playerHeight; + transform.localScale = Vector3.one * GetRenderSafeMenuScale(playerHeight); } /// @@ -290,8 +290,18 @@ private void ApplyScaleOnly() offsetScale.z = Mathf.Max(MIN_Z_SCALE, offsetScale.z); GroupOffset.localScale = offsetScale; - // 2) Root scale (avatar-to-default compensation) - transform.localScale = Vector3.one * BasisHeightDriver.AvatarToDefaultRatioScaledWithAvatarScale; + transform.localScale = Vector3.one * GetRenderSafeMenuScale(BasisHeightDriver.AvatarToDefaultRatioScaledWithAvatarScale); + } + + private static float GetRenderSafeMenuScale(float avatarRelativeScale) + { + // TextMeshPro renders block glyphs at extremely small world scales; keep menu rendering stable without clamping avatar scale. + if (float.IsNaN(avatarRelativeScale) || float.IsInfinity(avatarRelativeScale) || avatarRelativeScale <= 0f) + { + return MIN_TMP_RENDER_SCALE; + } + + return Mathf.Max(avatarRelativeScale, MIN_TMP_RENDER_SCALE); } private void UpdateUILocation() From 42f73e90449d5b54cc9ed2c6173957b789c008aa Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Tue, 26 May 2026 21:51:02 -0500 Subject: [PATCH 03/12] Using Posit16 for scaling, since we need finer control in the lower range than higher. --- .../BasisUnityBitPackerExtensions.cs | 141 +++++++++++++++++- .../MovementSender.cs | 14 +- .../Compression/BasisAvatarBitPacking.cs | 7 +- .../Serializable/LocalAvatarSyncMessage.cs | 2 +- .../Runtime/OSC/BasisOscAvatarScaling.cs | 16 +- 5 files changed, 146 insertions(+), 34 deletions(-) diff --git a/Basis/Packages/com.basis.framework/Networking/Compression/BasisUnityBitPackerExtensions.cs b/Basis/Packages/com.basis.framework/Networking/Compression/BasisUnityBitPackerExtensions.cs index e41f50760d..1715ee63d1 100644 --- a/Basis/Packages/com.basis.framework/Networking/Compression/BasisUnityBitPackerExtensions.cs +++ b/Basis/Packages/com.basis.framework/Networking/Compression/BasisUnityBitPackerExtensions.cs @@ -160,11 +160,8 @@ public static void WritePosition(UnityEngine.Vector3 position, ref byte[] buffer } public static void CompressScale(float scale, ref LocalAvatarSyncMessage message, ref int offset) { - - float clamped = math.clamp(scale, BasisAvatarBitPacking.MinScale, BasisAvatarBitPacking.MaxScale); - float normalized = (clamped - BasisAvatarBitPacking.MinScale) / BasisAvatarBitPacking.ComputedRange; - - ushort compressed = (ushort)(normalized * BasisAvatarBitPacking.UShortRangeDifference); + // Scale needs wide dynamic range and small-avatar precision; Posit16 gives better relative precision than the old linear ushort. + ushort compressed = EncodePositiveScalePosit16(scale); WriteUShort(compressed, ref message.array, ref offset); } /// @@ -176,8 +173,138 @@ public static void CompressScale(float scale, ref LocalAvatarSyncMessage message /// public static float DecompressScale(ushort value) { - float normalized = (float)value / BasisAvatarBitPacking.UShortRangeDifference; - return normalized * (BasisAvatarBitPacking.MaxScale - BasisAvatarBitPacking.MinScale) + BasisAvatarBitPacking.MinScale; + return DecodePositiveScalePosit16(value); + } + + private const int PositScaleBits = 16; + private const int PositScaleExponentBits = 1; + private const int PositScaleUseedShift = 1 << PositScaleExponentBits; + + private static ushort EncodePositiveScalePosit16(float scale) + { + if (!math.isfinite(scale) || scale <= 0f) + { + return 0; + } + + scale = math.min(scale, DecodePositiveScalePosit16(0x7FFF)); + + const int remainingBits = PositScaleBits - 1; + int k = (int)math.floor(math.log2(scale) / PositScaleUseedShift); + float useedPower = math.exp2(k * PositScaleUseedShift); + float normalized = scale / useedPower; + + int exponent = 0; + if (normalized >= 2f) + { + exponent = 1; + normalized *= 0.5f; + } + + float fraction = math.clamp(normalized - 1f, 0f, 1f); + uint payload = 0; + int bit = remainingBits - 1; + + if (k >= 0) + { + int ones = k + 1; + for (int i = 0; i < ones && bit >= 0; i++) + { + payload |= 1u << bit--; + } + + if (bit >= 0) + { + bit--; + } + } + else + { + int zeros = -k; + bit -= zeros; + if (bit >= 0) + { + payload |= 1u << bit--; + } + } + + if (bit >= 0) + { + if (exponent != 0) + { + payload |= 1u << bit; + } + + bit--; + } + + while (bit >= 0) + { + fraction *= 2f; + if (fraction >= 1f) + { + payload |= 1u << bit; + fraction -= 1f; + } + + bit--; + } + + ushort truncated = (ushort)payload; + ushort roundedUp = truncated < 0x7FFF ? (ushort)(truncated + 1) : truncated; + float lower = DecodePositiveScalePosit16(truncated); + float upper = DecodePositiveScalePosit16(roundedUp); + return math.abs(upper - scale) < math.abs(scale - lower) ? roundedUp : truncated; + } + + private static float DecodePositiveScalePosit16(ushort value) + { + if (value == 0) + { + return 0f; + } + + if ((value & 0x8000) != 0) + { + value = (ushort)(~value + 1); + } + + int bit = PositScaleBits - 2; + bool regimeSign = ((value >> bit) & 1) != 0; + int runLength = 0; + while (bit >= 0 && (((value >> bit) & 1) != 0) == regimeSign) + { + runLength++; + bit--; + } + + if (bit >= 0) + { + bit--; + } + + int k = regimeSign ? runLength - 1 : -runLength; + int exponent = 0; + if (bit >= 0) + { + exponent = (value >> bit) & 1; + bit--; + } + + float fraction = 1f; + float fractionBit = 0.5f; + while (bit >= 0) + { + if (((value >> bit) & 1) != 0) + { + fraction += fractionBit; + } + + fractionBit *= 0.5f; + bit--; + } + + return math.exp2((k * PositScaleUseedShift) + exponent) * fraction; } /// diff --git a/Basis/Packages/com.basis.server/BasisNetworkClientConsole/BasisNetworkClientConsole/MovementSender.cs b/Basis/Packages/com.basis.server/BasisNetworkClientConsole/BasisNetworkClientConsole/MovementSender.cs index df2beefe79..f53cfb9331 100644 --- a/Basis/Packages/com.basis.server/BasisNetworkClientConsole/BasisNetworkClientConsole/MovementSender.cs +++ b/Basis/Packages/com.basis.server/BasisNetworkClientConsole/BasisNetworkClientConsole/MovementSender.cs @@ -12,10 +12,6 @@ public static class MovementSender { public static Quaternion Rotation = new Quaternion(0, 0, 0, 1); - private const ushort UShortMin = ushort.MinValue; // 0 - private const ushort UShortMax = ushort.MaxValue; // 65535 - private const ushort UShortRangeDifference = UShortMax - UShortMin; - public static Vector3[] PlayersCurrentPosition; public static PlayerData[] ActivePlayerData; @@ -207,15 +203,7 @@ public unsafe static void WriteQuaternionToBytes(Quaternion q, ref byte[] bytes, private static ushort CompressScaleOnce(float scale) { - const float Min = 0.005f; - const float Max = 150f; - const float Range = Max - Min; - - float clamped = scale; - float normalized = (clamped - Min) / Range; - - ushort compressed = (ushort)(normalized * UShortRangeDifference); - return compressed; + return scale == 1f ? (ushort)0x4000 : (ushort)0; } public static void WriteUShort(ushort value, ref byte[] bytes, ref int offset) diff --git a/Basis/Packages/com.basis.server/BasisNetworkCore/Compression/BasisAvatarBitPacking.cs b/Basis/Packages/com.basis.server/BasisNetworkCore/Compression/BasisAvatarBitPacking.cs index 1baf50fd3c..2646136c2a 100644 --- a/Basis/Packages/com.basis.server/BasisNetworkCore/Compression/BasisAvatarBitPacking.cs +++ b/Basis/Packages/com.basis.server/BasisNetworkCore/Compression/BasisAvatarBitPacking.cs @@ -5,9 +5,8 @@ public static class BasisAvatarBitPacking public const int FloatSize = sizeof(float); public const int UShortSize = sizeof(ushort); public const int Vector3Size = 3 * FloatSize; - public const float MinScale = 0.005f; - public const float MaxScale = 150f; - public const float ComputedRange = MaxScale - MinScale; + // Scale is stored in WriteScale bytes as a positive Posit16-style value (n=16, es=1). + // This keeps the two-byte payload slot while improving relative precision for very small avatars. public const int WritePosition = 12; public const int WriteScale = 2; @@ -53,7 +52,7 @@ public enum BitQuality : byte public static int ConvertToSize(BitQuality q) { - // Position (12) + BoneRotations (variable) + Scale (2) + Rotation (7) + // Position (12) + BoneRotations (variable) + Posit16 Scale (2) + Rotation (7) + hips tail. return BasisBoneRotationCompression.ConvertToSize(q); } // -------------------------- diff --git a/Basis/Packages/com.basis.server/BasisNetworkCore/Serializable/LocalAvatarSyncMessage.cs b/Basis/Packages/com.basis.server/BasisNetworkCore/Serializable/LocalAvatarSyncMessage.cs index 09093c8648..2baf464843 100644 --- a/Basis/Packages/com.basis.server/BasisNetworkCore/Serializable/LocalAvatarSyncMessage.cs +++ b/Basis/Packages/com.basis.server/BasisNetworkCore/Serializable/LocalAvatarSyncMessage.cs @@ -14,7 +14,7 @@ public struct LocalAvatarSyncMessage // Quality and additional-data presence are derived from the channel number. // // Payload layout (current order): - // Position (12) -> Muscles(bitstream, varies by quality) -> Scale (2) -> Rotation (16) + // Position (12) -> bone rotations (bitstream, varies by quality) -> Posit16 scale (2) -> rotation (7) -> hips tail public byte DataQualityLevel; // 0=Low, 1=Medium, 2=High public byte[] array; // payload bytes (length must match ConvertToSize(quality)) diff --git a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs index 40d9eb2b42..5562e7c019 100644 --- a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs +++ b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs @@ -13,7 +13,6 @@ public static class BasisOscAvatarScaling public const string EyeHeightMaxAddress = "/avatar/eyeheightmax"; public const string EyeHeightScalingAllowedAddress = "/avatar/eyeheightscalingallowed"; - public const float OscMinimumEyeHeightMeters = 0.01f; public const float OscMaximumEyeHeightMeters = 10000f; public const float SupportedMinimumEyeHeightMeters = 0.1f; public const float SupportedMaximumEyeHeightMeters = 100f; @@ -86,8 +85,14 @@ public static void ApplyEyeHeight(float requestedEyeHeightMeters) BasisDebug.LogWarning($"Ignoring invalid OSC avatar eye height {requestedEyeHeightMeters}.", BasisDebug.LogTag.LocalNetwork); return; } + BasisLocalPlayer localPlayer = BasisLocalPlayer.Instance; + if (localPlayer == null || localPlayer.LocalAvatarDriver == null || localPlayer.LocalBoneDriver == null) + { + BasisDebug.LogWarning("Ignoring OSC avatar eye height because the local avatar is not ready.", BasisDebug.LogTag.LocalNetwork); + return; + } - float eyeHeightMeters = Mathf.Clamp(requestedEyeHeightMeters, OscMinimumEyeHeightMeters, OscMaximumEyeHeightMeters); + float eyeHeightMeters = Mathf.Min(requestedEyeHeightMeters, OscMaximumEyeHeightMeters); if (!Mathf.Approximately(eyeHeightMeters, requestedEyeHeightMeters)) { BasisDebug.LogWarning($"Clamped OSC avatar eye height from {requestedEyeHeightMeters}m to {eyeHeightMeters}m.", BasisDebug.LogTag.LocalNetwork); @@ -100,13 +105,6 @@ public static void ApplyEyeHeight(float requestedEyeHeightMeters) BasisDebug.LogTag.LocalNetwork); } - BasisLocalPlayer localPlayer = BasisLocalPlayer.Instance; - if (localPlayer == null || localPlayer.LocalAvatarDriver == null || localPlayer.LocalBoneDriver == null) - { - BasisDebug.LogWarning("Ignoring OSC avatar eye height because the local avatar is not ready.", BasisDebug.LogTag.LocalNetwork); - return; - } - BasisHeightDriver.ApplyRuntimeOscEyeHeightOverride(eyeHeightMeters); PublishCurrentState(); } From 11912e1a58074ead4d95e7235b495909c35ce5c5 Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Tue, 26 May 2026 22:16:25 -0500 Subject: [PATCH 04/12] Add additional comments, and limits. --- .../com.basis.framework/BasisUI/BasisMenuMover.cs | 1 + .../Compression/BasisUnityBitPackerExtensions.cs | 12 +++++++----- .../BasisNetworkClientConsole/MovementSender.cs | 8 +++++++- .../Systems/Runtime/OSC/BasisOscAvatarScaling.cs | 1 + 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/Basis/Packages/com.basis.framework/BasisUI/BasisMenuMover.cs b/Basis/Packages/com.basis.framework/BasisUI/BasisMenuMover.cs index ef3691c7ee..a49f7f0b92 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/BasisMenuMover.cs +++ b/Basis/Packages/com.basis.framework/BasisUI/BasisMenuMover.cs @@ -65,6 +65,7 @@ public struct RootModeOffset private bool _hasLocalMoveEvent; private const float MIN_Z_SCALE = 0.01f; + // MIN_TMP_RENDER_SCALE is empirical: TMP rendered block glyphs on the main menu below roughly 0.05328 world scale during OSC tiny-avatar testing. private const float MIN_TMP_RENDER_SCALE = 0.055f; // --- PlaySpaceStable state (from v1) --- diff --git a/Basis/Packages/com.basis.framework/Networking/Compression/BasisUnityBitPackerExtensions.cs b/Basis/Packages/com.basis.framework/Networking/Compression/BasisUnityBitPackerExtensions.cs index 1715ee63d1..a8edca992f 100644 --- a/Basis/Packages/com.basis.framework/Networking/Compression/BasisUnityBitPackerExtensions.cs +++ b/Basis/Packages/com.basis.framework/Networking/Compression/BasisUnityBitPackerExtensions.cs @@ -179,6 +179,7 @@ public static float DecompressScale(ushort value) private const int PositScaleBits = 16; private const int PositScaleExponentBits = 1; private const int PositScaleUseedShift = 1 << PositScaleExponentBits; + private static readonly float MaxRepresentablePositiveScale = DecodePositiveScalePosit16(0x7FFF); private static ushort EncodePositiveScalePosit16(float scale) { @@ -187,7 +188,7 @@ private static ushort EncodePositiveScalePosit16(float scale) return 0; } - scale = math.min(scale, DecodePositiveScalePosit16(0x7FFF)); + scale = math.min(scale, MaxRepresentablePositiveScale); const int remainingBits = PositScaleBits - 1; int k = (int)math.floor(math.log2(scale) / PositScaleUseedShift); @@ -269,10 +270,11 @@ private static float DecodePositiveScalePosit16(ushort value) value = (ushort)(~value + 1); } + uint raw = value; int bit = PositScaleBits - 2; - bool regimeSign = ((value >> bit) & 1) != 0; + bool regimeSign = ((raw >> bit) & 1u) != 0; int runLength = 0; - while (bit >= 0 && (((value >> bit) & 1) != 0) == regimeSign) + while (bit >= 0 && (((raw >> bit) & 1u) != 0) == regimeSign) { runLength++; bit--; @@ -287,7 +289,7 @@ private static float DecodePositiveScalePosit16(ushort value) int exponent = 0; if (bit >= 0) { - exponent = (value >> bit) & 1; + exponent = (int)((raw >> bit) & 1u); bit--; } @@ -295,7 +297,7 @@ private static float DecodePositiveScalePosit16(ushort value) float fractionBit = 0.5f; while (bit >= 0) { - if (((value >> bit) & 1) != 0) + if (((raw >> bit) & 1u) != 0) { fraction += fractionBit; } diff --git a/Basis/Packages/com.basis.server/BasisNetworkClientConsole/BasisNetworkClientConsole/MovementSender.cs b/Basis/Packages/com.basis.server/BasisNetworkClientConsole/BasisNetworkClientConsole/MovementSender.cs index f53cfb9331..a38f6b2204 100644 --- a/Basis/Packages/com.basis.server/BasisNetworkClientConsole/BasisNetworkClientConsole/MovementSender.cs +++ b/Basis/Packages/com.basis.server/BasisNetworkClientConsole/BasisNetworkClientConsole/MovementSender.cs @@ -203,7 +203,13 @@ public unsafe static void WriteQuaternionToBytes(Quaternion q, ref byte[] bytes, private static ushort CompressScaleOnce(float scale) { - return scale == 1f ? (ushort)0x4000 : (ushort)0; + // MovementSender only emits default-scale fake avatars; 0x4000 is Posit16 1.0. Use the shared codec before supporting other scales here. + if (scale != 1f) + { + throw new System.ArgumentOutOfRangeException(nameof(scale), scale, "MovementSender only supports precomputed scale 1.0."); + } + + return 0x4000; } public static void WriteUShort(ushort value, ref byte[] bytes, ref int offset) diff --git a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs index 5562e7c019..40fca578bf 100644 --- a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs +++ b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs @@ -13,6 +13,7 @@ public static class BasisOscAvatarScaling public const string EyeHeightMaxAddress = "/avatar/eyeheightmax"; public const string EyeHeightScalingAllowedAddress = "/avatar/eyeheightscalingallowed"; + // OSC accepts extreme float inputs; OscMaximumEyeHeightMeters is a sanity ceiling, separate from supported/user-selectable limits. public const float OscMaximumEyeHeightMeters = 10000f; public const float SupportedMinimumEyeHeightMeters = 0.1f; public const float SupportedMaximumEyeHeightMeters = 100f; From 0ba01a495bf3b20a886ef054eb6ee4c750764283 Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Tue, 26 May 2026 22:21:32 -0500 Subject: [PATCH 05/12] Add wire test for small values. --- .../Tests/Runtime/OscBridgeTests.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Basis/Packages/dev.hai-vr.basis.comms/Tests/Runtime/OscBridgeTests.cs b/Basis/Packages/dev.hai-vr.basis.comms/Tests/Runtime/OscBridgeTests.cs index cc46b3f3b5..88842a1457 100644 --- a/Basis/Packages/dev.hai-vr.basis.comms/Tests/Runtime/OscBridgeTests.cs +++ b/Basis/Packages/dev.hai-vr.basis.comms/Tests/Runtime/OscBridgeTests.cs @@ -7,6 +7,7 @@ using Basis.Network.Core; using Basis.Shims; using Basis.Scripts.BasisSdk; +using Basis.Scripts.Networking.Compression; using Cilbox; using HVR.Basis.Comms; using HVR.Basis.Comms.OSC; @@ -721,6 +722,29 @@ public void BasisOscService_AvatarScalingAddress_UsesMessageReceiverOnly() } } + [Test] + public void AvatarScalePosit16_RoundTripsTinyAndDefaultScales() + { + byte[] payload = new byte[2]; + SerializableBasis.LocalAvatarSyncMessage message = new SerializableBasis.LocalAvatarSyncMessage(payload); + + AssertScaleRoundTrip(message, payload, 1f, 1e-6f); + AssertScaleRoundTrip(message, payload, 0.005f, 0.00001f); + AssertScaleRoundTrip(message, payload, 0.0005f, 0.000001f); + } + + private static void AssertScaleRoundTrip(SerializableBasis.LocalAvatarSyncMessage message, byte[] payload, float scale, float tolerance) + { + Array.Clear(payload, 0, payload.Length); + int offset = 0; + BasisUnityBitPackerExtensionsUnsafe.CompressScale(scale, ref message, ref offset); + + offset = 0; + Assert.That(BasisUnityBitPackerExtensionsUnsafe.TryReadUShort(ref payload, ref offset, out ushort encoded), Is.True); + float decoded = BasisUnityBitPackerExtensionsUnsafe.DecompressScale(encoded); + Assert.That(decoded, Is.EqualTo(scale).Within(tolerance)); + } + [Test] public void BasisOsc_PublishValue_ReturnsResolvedRelativeAddress() { From c6950ff80dddf043f185723a221a195d0346e909 Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Wed, 27 May 2026 01:13:08 -0500 Subject: [PATCH 06/12] Have nameplates scale to the player's size. --- .../Drivers/Remote/BasisRemoteBoneDriver.cs | 38 +++++++++++++++++-- .../UI/NamePlate/BasisRemoteNamePlate.cs | 2 +- .../NamePlate/BasisRemoteNamePlateDriver.cs | 3 +- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Basis/Packages/com.basis.framework/Drivers/Remote/BasisRemoteBoneDriver.cs b/Basis/Packages/com.basis.framework/Drivers/Remote/BasisRemoteBoneDriver.cs index 261effb978..078662b55e 100644 --- a/Basis/Packages/com.basis.framework/Drivers/Remote/BasisRemoteBoneDriver.cs +++ b/Basis/Packages/com.basis.framework/Drivers/Remote/BasisRemoteBoneDriver.cs @@ -2,6 +2,7 @@ using Basis.Scripts.Common; using Basis.Scripts.Drivers; using Basis.Scripts.Networking.NetworkedAvatar; +using Basis.Scripts.UI.NamePlate; using System; using System.Collections.Generic; using Unity.Burst; @@ -106,6 +107,8 @@ public struct RemoteFrameOutput /// Vertical delta between hips and mouth in scaled TPose space (used for UI placement). /// public float HeightAvatarHipCoord; + /// Per-avatar visual nameplate scale multiplier derived from current avatar height. + public float NamePlateScaleMultiplier; } /// @@ -117,6 +120,11 @@ public struct RemoteFrameOutput [BurstCompile(FloatMode = FloatMode.Fast, FloatPrecision = FloatPrecision.Low)] public struct BasisRemoteBoneJob : IJobParallelFor { + private const float NamePlateMinimumHeightOffset = 0.4f; + private const float NamePlateShrinkStartHeight = 0.5f; + private const float NamePlateMinimumScaleMultiplier = 0.5f; + private const float NamePlateGrowStartHeight = 2f; + /// Authoring-time TPose and offset data (unscaled). [ReadOnly] public NativeArray Authoring; @@ -184,6 +192,8 @@ public void Execute(int i) float3 difference = SafeDivide(nowScale, a.TposeScale); + float rawNamePlateHeight = difference.y * 1.2f; + Out[i] = new RemoteFrameOutput { pos_Head = headP, @@ -202,11 +212,29 @@ public void Execute(int i) rot_Mouth = headR, - // Used for vertical offsetting of the nameplate UI - HeightAvatarHipCoord = difference.y * 1.2f, + // Keep tiny avatars readable without changing developer-branch behavior for avatars at or above 2m. + HeightAvatarHipCoord = rawNamePlateHeight < NamePlateGrowStartHeight ? math.max(rawNamePlateHeight, NamePlateMinimumHeightOffset) : rawNamePlateHeight, + NamePlateScaleMultiplier = ComputeNamePlateScaleMultiplier(rawNamePlateHeight), }; MouthPositions[i] = mouthP; } + + private static float ComputeNamePlateScaleMultiplier(float namePlateHeight) + { + if (namePlateHeight < NamePlateShrinkStartHeight) + { + float t = math.saturate(namePlateHeight / NamePlateShrinkStartHeight); + return math.lerp(NamePlateMinimumScaleMultiplier, 1f, t); + } + + if (namePlateHeight > NamePlateGrowStartHeight) + { + return 1f + (namePlateHeight - NamePlateGrowStartHeight); + } + + return 1f; + } + private readonly float3 SafeDivide(float3 numerator, float3 denominator) { const float eps = 1e-6f; @@ -309,6 +337,8 @@ public struct MappedNameplateApplyJob : IJobParallelForTransform { /// Camera world position used to bill-board the plate (yaw-only). public float3 CameraPosition; + /// User-configured base local scale before per-avatar height compensation. + public float NamePlateBaseScale; /// Input pose data (per-avatar) for nameplate placement. [ReadOnly] public NativeArray NamePlateIn; @@ -319,7 +349,6 @@ public void Execute(int jobIndex, TransformAccess tx) var data = NamePlateIn[jobIndex]; float3 hips = data.pos_Hips; - // y = hips.y + diff * 1.8 float3 nameplatePos = new float3(hips.x, hips.y + data.HeightAvatarHipCoord, hips.z); // Face the camera (yaw only) with zero-distance guard. @@ -329,6 +358,8 @@ public void Execute(int jobIndex, TransformAccess tx) quaternion rot = quaternion.RotateY(yaw); tx.SetPositionAndRotation(nameplatePos, rot); + float localScale = NamePlateBaseScale * data.NamePlateScaleMultiplier; + tx.localScale = new Vector3(localScale, localScale, localScale); } } @@ -1229,6 +1260,7 @@ public static JobHandle Schedule(int maxBatchSize = 64) var nameplateJob = new MappedNameplateApplyJob { CameraPosition = CameraPosition, + NamePlateBaseScale = BasisRemoteNamePlateDriver.BaseNamePlateLocalScale * BasisRemoteNamePlateDriver.NamePlateSize, NamePlateIn = sOut.AsDeferredJobArray(), }.Schedule(sNamePlate, simAndScale); diff --git a/Basis/Packages/com.basis.framework/UI/NamePlate/BasisRemoteNamePlate.cs b/Basis/Packages/com.basis.framework/UI/NamePlate/BasisRemoteNamePlate.cs index eb691e5333..11c8913ad2 100644 --- a/Basis/Packages/com.basis.framework/UI/NamePlate/BasisRemoteNamePlate.cs +++ b/Basis/Packages/com.basis.framework/UI/NamePlate/BasisRemoteNamePlate.cs @@ -106,7 +106,7 @@ public void Initialize(BasisRemotePlayer RemotePlayer) BasisRemotePlayer.NamePlateTransformProvider = GetSelfTransform; Self = this.transform; - Self.localScale = new Vector3(0.02f, 0.02f, 0.02f) * BasisRemoteNamePlateDriver.NamePlateSize; + Self.localScale = Vector3.one * (BasisRemoteNamePlateDriver.BaseNamePlateLocalScale * BasisRemoteNamePlateDriver.NamePlateSize); BasisRemoteNamePlateDriver.QueueTextBake(BasisRemotePlayer, this); LoadingText.enableVertexGradient = false; mpb = new MaterialPropertyBlock(); diff --git a/Basis/Packages/com.basis.framework/UI/NamePlate/BasisRemoteNamePlateDriver.cs b/Basis/Packages/com.basis.framework/UI/NamePlate/BasisRemoteNamePlateDriver.cs index 810c1af65c..a1447f8134 100644 --- a/Basis/Packages/com.basis.framework/UI/NamePlate/BasisRemoteNamePlateDriver.cs +++ b/Basis/Packages/com.basis.framework/UI/NamePlate/BasisRemoteNamePlateDriver.cs @@ -73,6 +73,7 @@ public static class BasisRemoteNamePlateDriver public static bool NamePlateEnabled = true; public static bool NamePlateMenuOnly = false; public static bool NamePlateHoverMenuOnly = false; + public const float BaseNamePlateLocalScale = 0.02f; public static float NamePlateSize = 1f; public static float NamePlateTransparency = 0.45f; public static float ChatSize = 1f; @@ -428,7 +429,7 @@ public static void ApplyNamePlateSettingsFromUI() UpdateCachedColors(newTransparency); - Vector3 scale = new Vector3(0.02f, 0.02f, 0.02f) * newSize; + Vector3 scale = Vector3.one * (BaseNamePlateLocalScale * newSize); var arr = plates; int n = count; for (int i = 0; i < n; i++) From d7d3eabe0c238cfb611b200bf55e49ed38c510e7 Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Wed, 27 May 2026 01:48:41 -0500 Subject: [PATCH 07/12] Add Eye height matching into the selected player menu. --- .../BasisUI/Localization/Languages/ar.json | 3 ++ .../BasisUI/Localization/Languages/bn.json | 3 ++ .../BasisUI/Localization/Languages/de.json | 3 ++ .../BasisUI/Localization/Languages/en.json | 3 ++ .../BasisUI/Localization/Languages/es-MX.json | 3 ++ .../BasisUI/Localization/Languages/es.json | 3 ++ .../BasisUI/Localization/Languages/fr.json | 3 ++ .../BasisUI/Localization/Languages/hi.json | 3 ++ .../BasisUI/Localization/Languages/it.json | 3 ++ .../BasisUI/Localization/Languages/ja.json | 3 ++ .../BasisUI/Localization/Languages/nl.json | 3 ++ .../BasisUI/Localization/Languages/pt.json | 3 ++ .../BasisUI/Localization/Languages/ru.json | 3 ++ .../BasisUI/Localization/Languages/ur.json | 3 ++ .../BasisUI/Localization/Languages/zh-CN.json | 3 ++ .../Localization/Languages/zh-Hans.json | 3 ++ .../Localization/Languages/zh-Hant.json | 3 ++ .../BasisUI/Localization/Languages/zh.json | 3 ++ .../IndividualPlayerProvider.cs | 49 +++++++++++++++++++ 19 files changed, 103 insertions(+) diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ar.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ar.json index 097a272e7b..a1221e0ba7 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ar.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ar.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "الأفاتار" }, { "key": "menu.individualPlayer.avatar.description", "value": "مفاتيح الإظهار والتفاعل." }, { "key": "menu.individualPlayer.avatarLoadError", "value": "خطأ في تحميل الأفاتار" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "مطابقة ارتفاع العين" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "غيّر مقياس أفاتارك ليطابق مقياس جسم هذا اللاعب الحالي ({0:0.###} م كارتفاع عين مستهدف)." }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "ارتفاع عين أفاتار هذا اللاعب الحالي غير متاح بعد." }, { "key": "menu.individualPlayer.hideAvatar", "value": "إخفاء الأفاتار" }, { "key": "menu.individualPlayer.showAvatar", "value": "إظهار الأفاتار" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "تفعيل/تعطيل عرض أفاتار هذا اللاعب على جهازك." }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/bn.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/bn.json index dc00ef1b63..d888dbf33d 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/bn.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/bn.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "অবতার" }, { "key": "menu.individualPlayer.avatar.description", "value": "দৃশ্যমানতা এবং ইন্টারঅ্যাকশন টগল।" }, { "key": "menu.individualPlayer.avatarLoadError", "value": "অবতার লোড ত্রুটি" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "চোখের উচ্চতা মেলান" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "এই খেলোয়াড়ের বর্তমান শরীরের স্কেলের সাথে আপনার অবতার স্কেল করুন ({0:0.###} মি লক্ষ্য চোখের উচ্চতা)।" }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "এই খেলোয়াড়ের বর্তমান অবতার চোখের উচ্চতা এখনো উপলভ্য নয়।" }, { "key": "menu.individualPlayer.hideAvatar", "value": "অবতার লুকান" }, { "key": "menu.individualPlayer.showAvatar", "value": "অবতার দেখান" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "আপনার ক্লায়েন্টে এই খেলোয়াড়ের অবতারের রেন্ডারিং টগল করে।" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/de.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/de.json index 2fadba1565..fffdee9389 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/de.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/de.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "Avatar" }, { "key": "menu.individualPlayer.avatar.description", "value": "Sichtbarkeits- und Interaktions-Schalter." }, { "key": "menu.individualPlayer.avatarLoadError", "value": "Avatar-Ladefehler" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "Augenhöhe anpassen" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "Skaliert deinen Avatar auf die aktuelle Körpergröße dieses Spielers ({0:0.###} m Ziel-Augenhöhe)." }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "Die aktuelle Avatar-Augenhöhe dieses Spielers ist noch nicht verfügbar." }, { "key": "menu.individualPlayer.hideAvatar", "value": "Avatar ausblenden" }, { "key": "menu.individualPlayer.showAvatar", "value": "Avatar anzeigen" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "Schaltet das Rendern des Avatars dieses Spielers auf deinem Client um." }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/en.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/en.json index f2567c137d..51d111b459 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/en.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/en.json @@ -186,6 +186,9 @@ { "key": "menu.individualPlayer.avatar", "value": "Avatar" }, { "key": "menu.individualPlayer.avatar.description", "value": "Visibility and interaction toggles." }, { "key": "menu.individualPlayer.avatarLoadError", "value": "Avatar Load Error" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "Match Eye Height" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "Scale your avatar to match this player's current body scale ({0:0.###} m target eye height)." }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "This player's current avatar eye height is not available yet." }, { "key": "menu.individualPlayer.hideAvatar", "value": "Hide Avatar" }, { "key": "menu.individualPlayer.showAvatar", "value": "Show Avatar" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "Toggles rendering of this player's avatar on your client." }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es-MX.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es-MX.json index 2397a1182f..4a674bc396 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es-MX.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es-MX.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "Avatar" }, { "key": "menu.individualPlayer.avatar.description", "value": "Visibilidad y opciones de interacción." }, { "key": "menu.individualPlayer.avatarLoadError", "value": "Error al cargar el avatar" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "Igualar altura de ojos" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "Escala tu avatar para igualar la escala corporal actual de este jugador ({0:0.###} m de altura de ojos objetivo)." }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "La altura de ojos actual del avatar de este jugador aún no está disponible." }, { "key": "menu.individualPlayer.hideAvatar", "value": "Ocultar avatar" }, { "key": "menu.individualPlayer.showAvatar", "value": "Mostrar avatar" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "Activa o desactiva el renderizado del avatar de este jugador en tu cliente." }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es.json index 4836d8437e..96a5b99206 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "Avatar" }, { "key": "menu.individualPlayer.avatar.description", "value": "Visibilidad y opciones de interacción." }, { "key": "menu.individualPlayer.avatarLoadError", "value": "Error al cargar el avatar" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "Igualar altura de ojos" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "Escala tu avatar para igualar la escala corporal actual de este jugador ({0:0.###} m de altura de ojos objetivo)." }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "La altura de ojos actual del avatar de este jugador aún no está disponible." }, { "key": "menu.individualPlayer.hideAvatar", "value": "Ocultar avatar" }, { "key": "menu.individualPlayer.showAvatar", "value": "Mostrar avatar" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "Activa o desactiva el renderizado del avatar de este jugador en tu cliente." }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/fr.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/fr.json index 7acf646cfa..6b3041e683 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/fr.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/fr.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "Avatar" }, { "key": "menu.individualPlayer.avatar.description", "value": "Options de visibilité et d'interaction." }, { "key": "menu.individualPlayer.avatarLoadError", "value": "Erreur de chargement d'avatar" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "Adapter la hauteur des yeux" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "Redimensionne votre avatar pour correspondre à l'échelle corporelle actuelle de ce joueur ({0:0.###} m de hauteur des yeux cible)." }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "La hauteur actuelle des yeux de l'avatar de ce joueur n'est pas encore disponible." }, { "key": "menu.individualPlayer.hideAvatar", "value": "Masquer l'avatar" }, { "key": "menu.individualPlayer.showAvatar", "value": "Afficher l'avatar" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "Bascule le rendu de l'avatar de ce joueur sur votre client." }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/hi.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/hi.json index e6a23d6777..4d93f8c7f5 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/hi.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/hi.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "अवतार" }, { "key": "menu.individualPlayer.avatar.description", "value": "दृश्यता और इंटरैक्शन टॉगल।" }, { "key": "menu.individualPlayer.avatarLoadError", "value": "अवतार लोड त्रुटि" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "आँख की ऊँचाई मिलाएँ" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "अपने अवतार को इस खिलाड़ी के मौजूदा शरीर स्केल से मिलाएँ ({0:0.###} मी लक्ष्य आँख ऊँचाई)।" }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "इस खिलाड़ी के अवतार की मौजूदा आँख ऊँचाई अभी उपलब्ध नहीं है।" }, { "key": "menu.individualPlayer.hideAvatar", "value": "अवतार छिपाएँ" }, { "key": "menu.individualPlayer.showAvatar", "value": "अवतार दिखाएँ" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "आपके क्लाइंट पर इस खिलाड़ी के अवतार के रेंडरिंग को टॉगल करता है।" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/it.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/it.json index 35301e1b2a..d96ee0329c 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/it.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/it.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "Avatar" }, { "key": "menu.individualPlayer.avatar.description", "value": "Opzioni di visibilità e interazione." }, { "key": "menu.individualPlayer.avatarLoadError", "value": "Errore caricamento avatar" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "Abbina altezza occhi" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "Ridimensiona il tuo avatar per corrispondere alla scala corporea attuale di questo giocatore ({0:0.###} m altezza occhi target)." }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "L'altezza occhi attuale dell'avatar di questo giocatore non è ancora disponibile." }, { "key": "menu.individualPlayer.hideAvatar", "value": "Nascondi avatar" }, { "key": "menu.individualPlayer.showAvatar", "value": "Mostra avatar" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "Attiva o disattiva il rendering dell'avatar di questo giocatore sul tuo client." }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ja.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ja.json index dbe148e9f1..08ed732cc1 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ja.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ja.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "アバター" }, { "key": "menu.individualPlayer.avatar.description", "value": "表示とインタラクションの切替。" }, { "key": "menu.individualPlayer.avatarLoadError", "value": "アバター読み込みエラー" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "目の高さを合わせる" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "このプレイヤーの現在の体のスケールに合わせて自分のアバターを拡大縮小します(目標の目の高さ {0:0.###} m)。" }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "このプレイヤーの現在のアバターの目の高さはまだ利用できません。" }, { "key": "menu.individualPlayer.hideAvatar", "value": "アバターを非表示" }, { "key": "menu.individualPlayer.showAvatar", "value": "アバターを表示" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "自分のクライアントでこのプレイヤーのアバター描画を切替。" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/nl.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/nl.json index 1f01d4a214..a494da8cf4 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/nl.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/nl.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "Avatar" }, { "key": "menu.individualPlayer.avatar.description", "value": "Schakelaars voor zichtbaarheid en interactie." }, { "key": "menu.individualPlayer.avatarLoadError", "value": "Avatar Laadfout" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "Ooghoogte Afstemmen" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "Schaal je avatar zodat deze overeenkomt met de huidige lichaamsschaal van deze speler ({0:0.###} m doel-ooghoogte)." }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "De huidige avatar-ooghoogte van deze speler is nog niet beschikbaar." }, { "key": "menu.individualPlayer.hideAvatar", "value": "Avatar Verbergen" }, { "key": "menu.individualPlayer.showAvatar", "value": "Avatar Tonen" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "Schakelt de weergave van de avatar van deze speler in jouw client." }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/pt.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/pt.json index 8462c1d844..9802a5a32d 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/pt.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/pt.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "Avatar" }, { "key": "menu.individualPlayer.avatar.description", "value": "Alternar visibilidade e interação." }, { "key": "menu.individualPlayer.avatarLoadError", "value": "Erro ao Carregar Avatar" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "Igualar Altura dos Olhos" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "Redimensiona seu avatar para corresponder à escala corporal atual deste jogador ({0:0.###} m de altura dos olhos alvo)." }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "A altura atual dos olhos do avatar deste jogador ainda não está disponível." }, { "key": "menu.individualPlayer.hideAvatar", "value": "Ocultar Avatar" }, { "key": "menu.individualPlayer.showAvatar", "value": "Mostrar Avatar" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "Alterna a renderização do avatar deste jogador no seu cliente." }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ru.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ru.json index 2bd7e2e5a8..745270412b 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ru.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ru.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "Аватар" }, { "key": "menu.individualPlayer.avatar.description", "value": "Переключатели видимости и взаимодействия." }, { "key": "menu.individualPlayer.avatarLoadError", "value": "Ошибка загрузки аватара" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "Сравнять высоту глаз" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "Масштабирует ваш аватар под текущий масштаб тела этого игрока ({0:0.###} м целевой высоты глаз)." }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "Текущая высота глаз аватара этого игрока пока недоступна." }, { "key": "menu.individualPlayer.hideAvatar", "value": "Скрыть аватар" }, { "key": "menu.individualPlayer.showAvatar", "value": "Показать аватар" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "Включает/выключает отображение аватара этого игрока на вашем клиенте." }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ur.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ur.json index 872ffa5b62..6847ef661c 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ur.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ur.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "اواتار" }, { "key": "menu.individualPlayer.avatar.description", "value": "ظاہریت اور تعامل کے ٹوگلز۔" }, { "key": "menu.individualPlayer.avatarLoadError", "value": "اواتار لوڈ خرابی" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "آنکھوں کی اونچائی ملائیں" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "اپنے اواتار کو اس کھلاڑی کے موجودہ جسمانی اسکیل سے ملانے کے لیے اسکیل کریں ({0:0.###} م ہدف آنکھوں کی اونچائی)." }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "اس کھلاڑی کے اواتار کی موجودہ آنکھوں کی اونچائی ابھی دستیاب نہیں ہے۔" }, { "key": "menu.individualPlayer.hideAvatar", "value": "اواتار چھپائیں" }, { "key": "menu.individualPlayer.showAvatar", "value": "اواتار دکھائیں" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "آپ کے کلائنٹ پر اس کھلاڑی کے اواتار کی رینڈرنگ کو ٹوگل کرتا ہے۔" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-CN.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-CN.json index 0c16b85492..0224e45080 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-CN.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-CN.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "虚拟形象" }, { "key": "menu.individualPlayer.avatar.description", "value": "可见性和交互切换。" }, { "key": "menu.individualPlayer.avatarLoadError", "value": "虚拟形象加载错误" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "匹配眼高" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "将你的虚拟形象缩放到匹配该玩家当前身体比例(目标眼高 {0:0.###} 米)。" }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "该玩家当前虚拟形象眼高尚不可用。" }, { "key": "menu.individualPlayer.hideAvatar", "value": "隐藏虚拟形象" }, { "key": "menu.individualPlayer.showAvatar", "value": "显示虚拟形象" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "切换在你的客户端上是否渲染该玩家的虚拟形象。" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hans.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hans.json index b8f97d5019..7e7d26efa0 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hans.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hans.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "虚拟形象" }, { "key": "menu.individualPlayer.avatar.description", "value": "可见性和交互切换。" }, { "key": "menu.individualPlayer.avatarLoadError", "value": "虚拟形象加载错误" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "匹配眼高" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "将你的虚拟形象缩放到匹配该玩家当前身体比例(目标眼高 {0:0.###} 米)。" }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "该玩家当前虚拟形象眼高尚不可用。" }, { "key": "menu.individualPlayer.hideAvatar", "value": "隐藏虚拟形象" }, { "key": "menu.individualPlayer.showAvatar", "value": "显示虚拟形象" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "切换在你的客户端上是否渲染该玩家的虚拟形象。" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json index 1200b31e49..8567306220 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "虚拟形象" }, { "key": "menu.individualPlayer.avatar.description", "value": "可见性和互动开关。" }, { "key": "menu.individualPlayer.avatarLoadError", "value": "虚拟形象加载错误" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "匹配眼高" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "将你的虚拟形象缩放到匹配该玩家当前身体比例(目标眼高 {0:0.###} 米)。" }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "该玩家当前虚拟形象眼高尚不可用。" }, { "key": "menu.individualPlayer.hideAvatar", "value": "隐藏虚拟形象" }, { "key": "menu.individualPlayer.showAvatar", "value": "显示虚拟形象" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "在你的客户端上切换该玩家虚拟形象的渲染。" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json index b5ace579f3..76feba3c08 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json @@ -158,6 +158,9 @@ { "key": "menu.individualPlayer.avatar", "value": "虚拟形象" }, { "key": "menu.individualPlayer.avatar.description", "value": "可见性和互动开关。" }, { "key": "menu.individualPlayer.avatarLoadError", "value": "虚拟形象加载错误" }, + { "key": "menu.individualPlayer.matchEyeHeight", "value": "匹配眼高" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "将你的虚拟形象缩放到匹配该玩家当前身体比例(目标眼高 {0:0.###} 米)。" }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "该玩家当前虚拟形象眼高尚不可用。" }, { "key": "menu.individualPlayer.hideAvatar", "value": "隐藏虚拟形象" }, { "key": "menu.individualPlayer.showAvatar", "value": "显示虚拟形象" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "在你的客户端上切换该玩家虚拟形象的渲染。" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/IndividualPlayerProvider.cs b/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/IndividualPlayerProvider.cs index 1cde3b1a7b..0b39844e45 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/IndividualPlayerProvider.cs +++ b/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/IndividualPlayerProvider.cs @@ -48,6 +48,26 @@ public static void AddToMenu() private static readonly Dictionary s_volumeBeforeMute = new Dictionary(); private const float UnmuteFallbackVolume = 1.0f; + private static bool TryGetMatchedEyeHeightOverrideMeters(BasisRemotePlayer target, out float eyeHeightMeters) + { + eyeHeightMeters = 0f; + if (target?.NetworkReceiver == null || BasisLocalPlayer.Instance?.LocalAvatarDriver == null) + { + return false; + } + + target.NetworkReceiver.GetLatestNetworkPose(out _, out _, out var networkScale); + float localCalibrationScale = BasisLocalPlayer.Instance.LocalAvatarDriver.ScaleAvatarModification.DuringCalibrationScale.y; + if (float.IsNaN(localCalibrationScale) || float.IsInfinity(localCalibrationScale) || localCalibrationScale <= 0f) + { + localCalibrationScale = 1f; + } + + float matchedApplyScale = networkScale.y / localCalibrationScale; + eyeHeightMeters = BasisHeightDriver.AvatarEyeHeight * matchedApplyScale; + return !float.IsNaN(eyeHeightMeters) && !float.IsInfinity(eyeHeightMeters) && eyeHeightMeters > 0f; + } + // ===== Shared player action helpers (used by this panel and UserListProvider rows) ===== /// @@ -791,6 +811,35 @@ void RefreshDirectConnLabel() avatarErrorField.SetDescription(remotePlayer.AvatarLoadErrorMessage); } + PanelButton matchEyeHeightBtn = PanelButton.CreateNew(avatarGroup.ContentParent); + matchEyeHeightBtn.Descriptor.SetTitle(BasisLocalization.Get("menu.individualPlayer.matchEyeHeight")); + if (TryGetMatchedEyeHeightOverrideMeters(remotePlayer, out float initialRemoteEyeHeight)) + { + matchEyeHeightBtn.Descriptor.SetDescription(BasisLocalization.Get("menu.individualPlayer.matchEyeHeight.description", initialRemoteEyeHeight)); + } + else + { + matchEyeHeightBtn.Descriptor.SetDescription(BasisLocalization.Get("menu.individualPlayer.matchEyeHeight.unavailable")); + } + + matchEyeHeightBtn.OnClicked += () => + { + if (!SMModuleCalibration.ApplyCustomScale) + { + BasisSettingsDefaults.CustomScale.SetValue(true); + SMModuleCalibration.ApplyCustomScale = true; + } + + if (!TryGetMatchedEyeHeightOverrideMeters(remotePlayer, out float remoteEyeHeight)) + { + BasisDebug.LogWarning("Cannot match eye height because the selected remote avatar eye height is unavailable.", BasisDebug.LogTag.Avatar); + return; + } + + BasisHeightDriver.ApplyRuntimeOscEyeHeightOverride(remoteEyeHeight); + matchEyeHeightBtn.Descriptor.SetDescription(BasisLocalization.Get("menu.individualPlayer.matchEyeHeight.description", remoteEyeHeight)); + }; + // Performance filter result — tells the local user why a specific remote // avatar was hard-blocked and/or what the trim pass removed from it, // and offers a per-player bypass toggle so they can see this one avatar From 7d1e7fb4307fcdec253bbdb41437bc4bc6366e06 Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Wed, 27 May 2026 02:17:42 -0500 Subject: [PATCH 08/12] Have it be based on standardize --- .../BasisUI/Localization/Languages/zh-Hant.json | 4 ++-- .../BasisUI/Localization/Languages/zh.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json index 8567306220..ea8e924183 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json @@ -159,8 +159,8 @@ { "key": "menu.individualPlayer.avatar.description", "value": "可见性和互动开关。" }, { "key": "menu.individualPlayer.avatarLoadError", "value": "虚拟形象加载错误" }, { "key": "menu.individualPlayer.matchEyeHeight", "value": "匹配眼高" }, - { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "将你的虚拟形象缩放到匹配该玩家当前身体比例(目标眼高 {0:0.###} 米)。" }, - { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "该玩家当前虚拟形象眼高尚不可用。" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "將你的虛擬形象縮放到匹配該玩家目前身體比例(目標眼高 {0:0.###} 公尺)。" }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "該玩家目前虛擬形象眼高尚不可用。" }, { "key": "menu.individualPlayer.hideAvatar", "value": "隐藏虚拟形象" }, { "key": "menu.individualPlayer.showAvatar", "value": "显示虚拟形象" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "在你的客户端上切换该玩家虚拟形象的渲染。" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json index 76feba3c08..d9e875e412 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json @@ -159,8 +159,8 @@ { "key": "menu.individualPlayer.avatar.description", "value": "可见性和互动开关。" }, { "key": "menu.individualPlayer.avatarLoadError", "value": "虚拟形象加载错误" }, { "key": "menu.individualPlayer.matchEyeHeight", "value": "匹配眼高" }, - { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "将你的虚拟形象缩放到匹配该玩家当前身体比例(目标眼高 {0:0.###} 米)。" }, - { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "该玩家当前虚拟形象眼高尚不可用。" }, + { "key": "menu.individualPlayer.matchEyeHeight.description", "value": "將你的虛擬形象縮放到匹配該玩家目前身體比例(目標眼高 {0:0.###} 公尺)。" }, + { "key": "menu.individualPlayer.matchEyeHeight.unavailable", "value": "該玩家目前虛擬形象眼高尚不可用。" }, { "key": "menu.individualPlayer.hideAvatar", "value": "隐藏虚拟形象" }, { "key": "menu.individualPlayer.showAvatar", "value": "显示虚拟形象" }, { "key": "menu.individualPlayer.toggleAvatar.description", "value": "在你的客户端上切换该玩家虚拟形象的渲染。" }, From 2a15dbb060436b1adeaa8844fb8dc563978a7f76 Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Wed, 27 May 2026 02:37:25 -0500 Subject: [PATCH 09/12] Have extra tall avatars still be able to interact with the main menu. --- .../UI/BasisPointRaycaster.cs | 28 +++++++++++++++++-- .../com.basis.framework/UI/BasisUIRaycast.cs | 4 +-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/Basis/Packages/com.basis.framework/UI/BasisPointRaycaster.cs b/Basis/Packages/com.basis.framework/UI/BasisPointRaycaster.cs index 7cdf7e6deb..82cc7448c3 100644 --- a/Basis/Packages/com.basis.framework/UI/BasisPointRaycaster.cs +++ b/Basis/Packages/com.basis.framework/UI/BasisPointRaycaster.cs @@ -1,4 +1,5 @@ using Basis.Scripts.BasisSdk.Interactions; +using Basis.Scripts.BasisSdk.Players; using Basis.Scripts.Device_Management.Devices; using Basis.Scripts.Drivers; using System.Collections.Generic; @@ -10,7 +11,9 @@ namespace Basis.Scripts.UI { public class BasisPointRaycaster : BaseRaycaster { + private const float MaxDistanceScaleThresholdMeters = 100f; public float MaxDistance = 120; + public float EffectiveMaxDistance = 120; public bool UseWorldPosition = true; /// @@ -122,11 +125,30 @@ public void Initialize(BasisInput basisInput) BasisInput = basisInput; PhysicHits = new RaycastHit[BasisPlayerInteract.k_MaxPhysicHitCount]; PhysicBackcastHits = new RaycastHit[4]; // We don't need as many backcast hits. + RefreshEffectiveMaxDistance(); + BasisLocalPlayer.OnPlayersHeightChangedNextFrame -= OnPlayersHeightChangedNextFrame; + BasisLocalPlayer.OnPlayersHeightChangedNextFrame += OnPlayersHeightChangedNextFrame; // Create the ray with the adjusted starting position and direction UpdateRay(); } + protected override void OnDestroy() + { + base.OnDestroy(); + BasisLocalPlayer.OnPlayersHeightChangedNextFrame -= OnPlayersHeightChangedNextFrame; + } + + private void OnPlayersHeightChangedNextFrame(BasisHeightDriver.HeightModeChange _) + { + RefreshEffectiveMaxDistance(); + } + + private void RefreshEffectiveMaxDistance() + { + EffectiveMaxDistance = MaxDistance + Mathf.Max(0f, BasisHeightDriver.SelectedScaledAvatarHeight - MaxDistanceScaleThresholdMeters); + } + public void UpdateRay() { ray = GetUpdatedRay(); @@ -153,7 +175,7 @@ public void UpdateRaycast() PhysicHitCount = Physics.RaycastNonAlloc( ray, PhysicHits, - MaxDistance, + EffectiveMaxDistance, BasisPlayerInteract.Mask, BasisPlayerInteract.TriggerInteraction); @@ -243,7 +265,7 @@ private void UpdateClosestHitPreferOverlayUI() private void DoBackcastFixup() { - float backcastDistance = ClosestRayCastHit.distance > 0 ? ClosestRayCastHit.distance : MaxDistance; + float backcastDistance = ClosestRayCastHit.distance > 0 ? ClosestRayCastHit.distance : EffectiveMaxDistance; Ray backcastRay = new Ray(ray.origin + ray.direction * backcastDistance, -ray.direction); int backcastHitCount = Physics.RaycastNonAlloc( @@ -469,7 +491,7 @@ private void OnDrawGizmosSelected() return; Gizmos.color = Color.cyan; - Gizmos.DrawLine(ray.origin, ray.origin + ray.direction * MaxDistance); + Gizmos.DrawLine(ray.origin, ray.origin + ray.direction * EffectiveMaxDistance); if (_mode == ControlMode.Placement && CurrentPlacement.HasHit) { diff --git a/Basis/Packages/com.basis.framework/UI/BasisUIRaycast.cs b/Basis/Packages/com.basis.framework/UI/BasisUIRaycast.cs index 7f62091998..355e693f6d 100644 --- a/Basis/Packages/com.basis.framework/UI/BasisUIRaycast.cs +++ b/Basis/Packages/com.basis.framework/UI/BasisUIRaycast.cs @@ -544,7 +544,7 @@ public bool ProcessSortedHitsResults(Canvas canvas, bool hitSomething, List 0; } - validHit &= hitData.distance < BasisPointRaycaster.MaxDistance; + validHit &= hitData.distance < BasisPointRaycaster.EffectiveMaxDistance; if (validHit) { @@ -612,7 +612,7 @@ public void SortedRaycastGraphics(Canvas canvas, Camera eventCamera, ref List Date: Wed, 27 May 2026 15:18:31 -0500 Subject: [PATCH 10/12] increase max range to 1000 meters. --- .../BasisUI/Menus/Main Menu Providers/SettingsProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProvider.cs b/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProvider.cs index 089c5ca06b..f171911108 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProvider.cs +++ b/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProvider.cs @@ -1182,7 +1182,7 @@ public static PanelTabPage GraphicsTab(PanelTabGroup tabGroup) // before per-pixel quality knobs. PanelSlider sliderAvatarRange = PanelSlider.CreateEntryAndBind( qualityGroup, - PanelSlider.SliderSettings.Distance(BasisLocalization.Get("settings.general.avatarRange"), 100), + PanelSlider.SliderSettings.Distance(BasisLocalization.Get("settings.general.avatarRange"), 1000), BasisSettingsDefaults.AvatarRange); PanelToggle toggleLimitAvatars = PanelToggle.CreateNewEntry(qualityGroup); From bb64bfa2016b8768dbbfce402a76bb70ed551a2d Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Wed, 27 May 2026 21:26:48 -0500 Subject: [PATCH 11/12] Add toogle for locomotion speed by scaling. --- .../BasisUI/BasisSettingsDefaults.cs | 7 +++++++ .../BasisUI/Localization/Languages/ar.json | 2 ++ .../BasisUI/Localization/Languages/bn.json | 2 ++ .../BasisUI/Localization/Languages/de.json | 2 ++ .../BasisUI/Localization/Languages/en.json | 2 ++ .../BasisUI/Localization/Languages/es-MX.json | 2 ++ .../BasisUI/Localization/Languages/es.json | 2 ++ .../BasisUI/Localization/Languages/fr.json | 2 ++ .../BasisUI/Localization/Languages/hi.json | 2 ++ .../BasisUI/Localization/Languages/it.json | 2 ++ .../BasisUI/Localization/Languages/ja.json | 2 ++ .../BasisUI/Localization/Languages/nl.json | 2 ++ .../BasisUI/Localization/Languages/pt.json | 2 ++ .../BasisUI/Localization/Languages/ru.json | 2 ++ .../BasisUI/Localization/Languages/ur.json | 2 ++ .../BasisUI/Localization/Languages/zh-CN.json | 2 ++ .../BasisUI/Localization/Languages/zh-Hans.json | 2 ++ .../BasisUI/Localization/Languages/zh-Hant.json | 2 ++ .../BasisUI/Localization/Languages/zh.json | 2 ++ .../SettingsProviderParts/SettingsProviderIK.cs | 5 +++++ .../Drivers/Local/BasisLocalAnimatorDriver.cs | 14 +++++++++++++- .../Drivers/Remote/BasisRemoteBoneDriver.cs | 5 ++++- .../Systems/Runtime/OSC/BasisOscAvatarScaling.cs | 1 + 23 files changed, 66 insertions(+), 2 deletions(-) diff --git a/Basis/Packages/com.basis.framework/BasisUI/BasisSettingsDefaults.cs b/Basis/Packages/com.basis.framework/BasisUI/BasisSettingsDefaults.cs index 370af71689..7e9b0d3990 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/BasisSettingsDefaults.cs +++ b/Basis/Packages/com.basis.framework/BasisUI/BasisSettingsDefaults.cs @@ -47,6 +47,12 @@ public static class BasisSettingsDefaults /// public static BasisSettingsBinding DisableAnimationsInFBT = new("disableanimationsinfbt", new BasisPlatformDefault(false)); + /// + /// When enabled, avatar scale affects locomotion blend velocity so small avatars + /// animate faster and large avatars animate slower without changing movement speed. + /// + public static BasisSettingsBinding ScaleAffectsLocomotionSpeed = new("scaleaffectslocomotionspeed", new BasisPlatformDefault(false)); + /// /// Master switch for full-body tracking. When disabled, hip/chest/foot/knee /// trackers are ignored and the avatar falls back to head + hands + procedural @@ -1242,6 +1248,7 @@ public static void LoadAll() EnableEyeTracking.LoadBindingValue(); FootIKEnabled.LoadBindingValue(); DisableAnimationsInFBT.LoadBindingValue(); + ScaleAffectsLocomotionSpeed.LoadBindingValue(); LocalHeadBlendShapes.LoadBindingValue(); // Rendering / Graphics diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ar.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ar.json index a1221e0ba7..cae948acd4 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ar.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ar.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "تشغيل عظام عيون أفاتارك من بيانات تتبع العين. النظر الطبيعي للعين يستمر عند التعطيل." }, { "key": "settings.bodyTracking.footIk.description", "value": "يفعّل وضع القدم الإجرائي عند الوقوف ساكناً بدون متتبعات قدم." }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "يكبح حركات القفز والهبوط وانخفاض الورك عند الهبوط أثناء معايرة متتبعات الجسم الكامل." }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "يؤثر المقياس في سرعة التنقل" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "يضبط سرعة رسوم التنقل المتحركة وفقًا لمقياس الأفاتار دون تغيير سرعة الحركة الفعلية." }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "يفعّل تصادم الكبسولات الافتراضية بين المرفقين والصدر لمنع اختراق الذراع للجسم." }, { "key": "settings.bodyTracking.protectElbow.title", "value": "حماية المرفق" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/bn.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/bn.json index d888dbf33d..5e9a35af9d 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/bn.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/bn.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "চোখ ট্র্যাকিং ডেটা থেকে আপনার অবতারের চোখের হাড় চালান। অক্ষম থাকলে স্বাভাবিক চোখের লুক চলতে থাকে।" }, { "key": "settings.bodyTracking.footIk.description", "value": "পা ট্র্যাকার ছাড়া স্থির দাঁড়িয়ে থাকার সময় পদ্ধতিগত পা স্থাপনা সক্ষম করে।" }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "সম্পূর্ণ-শরীর ট্র্যাকার ক্যালিব্রেট থাকাকালীন জাম্প এবং ল্যান্ডিং অ্যানিমেশন এবং ল্যান্ডিং হিপ ডিপ দমন করে।" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "স্কেল লোকোমোশনের গতিকে প্রভাবিত করে" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "শারীরিক চলাচলের গতি না বদলে, অবতারের স্কেল অনুযায়ী লোকোমোশন অ্যানিমেশনের গতি সমন্বয় করে।" }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "শরীরের ভিতর দিয়ে বাহু ক্লিপিং প্রতিরোধ করতে কনুই এবং বুকের মধ্যে ভার্চুয়াল ক্যাপসুল সংঘর্ষ সক্ষম করে।" }, { "key": "settings.bodyTracking.protectElbow.title", "value": "কনুই রক্ষা করুন" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/de.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/de.json index fffdee9389..d4a89c7b85 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/de.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/de.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "Steuert die Augenknochen deines Avatars über Eye-Tracking-Daten. Der natürliche Augenblick läuft weiter, wenn deaktiviert." }, { "key": "settings.bodyTracking.footIk.description", "value": "Aktiviert prozedurales Fußplatzieren beim ruhigen Stehen ohne Fuß-Tracker." }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "Unterdrückt Sprung- und Lande-Animationen sowie das Hüftabsenken bei der Landung, solange Full-Body-Tracker kalibriert sind." }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "Skalierung beeinflusst Fortbewegungsgeschwindigkeit" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "Passt die Geschwindigkeit der Geh-/Laufanimation an die Avatar-Skalierung an, ohne die tatsächliche Bewegungsgeschwindigkeit zu verändern." }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "Aktiviert die virtuelle Kapsel-Kollision zwischen Ellenbogen und Brust, um zu verhindern, dass Arme durch den Körper schneiden." }, { "key": "settings.bodyTracking.protectElbow.title", "value": "Ellenbogen schützen" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/en.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/en.json index 51d111b459..7cd0c3463f 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/en.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/en.json @@ -689,6 +689,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "Drive your avatar's eye bones from eye tracking data. The natural eye look keeps running when disabled." }, { "key": "settings.bodyTracking.footIk.description", "value": "Enables procedural foot placement when standing still without foot trackers." }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "Suppresses jump and landing animations and the landing hip dip while full-body trackers are calibrated." }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "Scale Affects Locomotion Speed" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "Adjusts locomotion animation velocity by avatar scale without changing physical movement speed." }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "Enables virtual capsule collision between elbows and chest to prevent arm clipping through the body." }, { "key": "settings.bodyTracking.protectElbow.title", "value": "Protect Elbow" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es-MX.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es-MX.json index 4a674bc396..4cfcc10f30 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es-MX.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es-MX.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "Controla los huesos de los ojos de tu avatar a partir de datos de rastreo ocular. La mirada natural sigue funcionando cuando está desactivado." }, { "key": "settings.bodyTracking.footIk.description", "value": "Activa la colocación procedural de los pies al estar quieto sin trackers de pies." }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "Suprime las animaciones de salto y aterrizaje y la caída de cadera al aterrizar mientras los trackers de cuerpo completo estén calibrados." }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "La escala afecta la velocidad de locomoción" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "Ajusta la velocidad de la animación de locomoción según la escala del avatar sin cambiar la velocidad de movimiento físico." }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "Activa la colisión de cápsulas virtuales entre los codos y el pecho para evitar que los brazos atraviesen el cuerpo." }, { "key": "settings.bodyTracking.protectElbow.title", "value": "Proteger codo" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es.json index 96a5b99206..fdaa910c1f 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/es.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "Acciona los huesos de los ojos de tu avatar a partir de los datos de seguimiento ocular. La mirada natural sigue funcionando cuando está desactivado." }, { "key": "settings.bodyTracking.footIk.description", "value": "Activa la colocación procedural de los pies cuando estás quieto sin trackers de pies." }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "Suprime las animaciones de salto y aterrizaje y la flexión de cadera al aterrizar mientras los trackers de cuerpo completo estén calibrados." }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "La escala afecta la velocidad de locomoción" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "Ajusta la velocidad de la animación de locomoción según la escala del avatar sin cambiar la velocidad física de movimiento." }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "Activa la colisión por cápsula virtual entre los codos y el pecho para evitar que los brazos atraviesen el cuerpo." }, { "key": "settings.bodyTracking.protectElbow.title", "value": "Proteger codo" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/fr.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/fr.json index 6b3041e683..084e55cd26 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/fr.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/fr.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "Pilote les os des yeux de votre avatar à partir des données de suivi oculaire. Le regard naturel des yeux continue de fonctionner lorsqu'il est désactivé." }, { "key": "settings.bodyTracking.footIk.description", "value": "Active le placement procédural des pieds en position immobile sans trackers de pieds." }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "Supprime les animations de saut et d'atterrissage ainsi que la flexion des hanches à l'atterrissage tant que les trackers corps complet sont calibrés." }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "L'échelle affecte la vitesse de locomotion" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "Ajuste la vitesse de l'animation de locomotion selon l'échelle de l'avatar sans modifier la vitesse de déplacement physique." }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "Active la collision par capsule virtuelle entre les coudes et le torse pour empêcher les bras de traverser le corps." }, { "key": "settings.bodyTracking.protectElbow.title", "value": "Protéger le coude" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/hi.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/hi.json index 4d93f8c7f5..e1217a7300 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/hi.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/hi.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "आई ट्रैकिंग डेटा से अपने अवतार की आँख की हड्डियों को चलाएँ। अक्षम होने पर प्राकृतिक आँख देखना चलता रहता है।" }, { "key": "settings.bodyTracking.footIk.description", "value": "बिना पैर ट्रैकर्स के स्थिर खड़े होने पर प्रोसीजरल पैर प्लेसमेंट सक्षम करता है।" }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "जब फुल-बॉडी ट्रैकर्स कैलिब्रेट किए गए हों तो जंप और लैंडिंग एनिमेशन और लैंडिंग कूल्हा डिप को दबाता है।" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "स्केल लोकोमोशन की गति को प्रभावित करता है" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "भौतिक गति की रफ़्तार बदले बिना अवतार के स्केल के अनुसार लोकोमोशन एनीमेशन की गति समायोजित करता है।" }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "हाथों को शरीर से क्लिप होने से रोकने के लिए कोहनियों और छाती के बीच वर्चुअल कैप्सूल कोलिज़न सक्षम करता है।" }, { "key": "settings.bodyTracking.protectElbow.title", "value": "कोहनी की सुरक्षा करें" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/it.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/it.json index d96ee0329c..34bd6fb27d 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/it.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/it.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "Pilota le ossa degli occhi del tuo avatar con i dati di tracciamento oculare. Lo sguardo naturale degli occhi continua a funzionare quando disabilitato." }, { "key": "settings.bodyTracking.footIk.description", "value": "Abilita il posizionamento procedurale dei piedi quando si è fermi senza tracker per i piedi." }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "Sopprime le animazioni di salto e atterraggio e l'abbassamento delle anche all'atterraggio mentre i tracker corporei completi sono calibrati." }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "La scala influisce sulla velocità di locomozione" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "Regola la velocità dell'animazione di locomozione in base alla scala dell'avatar senza modificare la velocità di movimento fisica." }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "Abilita la collisione tra capsule virtuali tra gomiti e torace per evitare che le braccia attraversino il corpo." }, { "key": "settings.bodyTracking.protectElbow.title", "value": "Proteggi gomito" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ja.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ja.json index 08ed732cc1..27ec6c7052 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ja.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ja.json @@ -586,6 +586,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "視線トラッキングデータからアバターの目ボーンを駆動します。無効化中も自然な視線の動きは継続します。" }, { "key": "settings.bodyTracking.footIk.description", "value": "足トラッカーなしで静止しているとき、足のプロシージャル配置を有効にします。" }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "フルボディトラッカーがキャリブレーション済みの間、ジャンプと着地のアニメーション、着地時の腰沈み込みを抑制します。" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "スケールが移動アニメーション速度に影響する" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "アバターのスケールに応じて歩行・移動アニメーションの速度を調整しますが、物理的な移動速度は変更しません。" }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "肘と胸の間に仮想カプセルコリジョンを有効化し、腕が体を貫通するのを防ぎます。" }, { "key": "settings.bodyTracking.protectElbow.title", "value": "肘を保護" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/nl.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/nl.json index a494da8cf4..d10e79310e 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/nl.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/nl.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "Stuur de oogbeenderen van je avatar aan vanuit eye tracking-gegevens. De natuurlijke oogblik blijft draaien wanneer uitgeschakeld." }, { "key": "settings.bodyTracking.footIk.description", "value": "Schakelt procedurele voetplaatsing in wanneer je stilstaat zonder voettrackers." }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "Onderdrukt sprong- en landingsanimaties en de heupdip bij landing terwijl full-body trackers gekalibreerd zijn." }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "Schaal beïnvloedt locomotiesnelheid" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "Past de snelheid van de locomotie-animatie aan op basis van de avatarschaal zonder de fysieke bewegingssnelheid te veranderen." }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "Schakelt virtuele capsule-botsing tussen ellebogen en borst in om te voorkomen dat armen door het lichaam clippen." }, { "key": "settings.bodyTracking.protectElbow.title", "value": "Elleboog Beschermen" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/pt.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/pt.json index 9802a5a32d..b01290d070 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/pt.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/pt.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "Comanda os ossos dos olhos do seu avatar a partir de dados de rastreamento ocular. O olhar natural continua funcionando quando desativado." }, { "key": "settings.bodyTracking.footIk.description", "value": "Habilita o posicionamento procedural dos pés ao ficar parado sem trackers de pé." }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "Suprime as animações de pulo e aterrissagem e o agachamento do quadril ao aterrissar enquanto trackers de corpo completo estiverem calibrados." }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "A escala afeta a velocidade de locomoção" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "Ajusta a velocidade da animação de locomoção de acordo com a escala do avatar sem alterar a velocidade física de movimento." }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "Habilita a colisão de cápsula virtual entre cotovelos e peito para evitar que os braços atravessem o corpo." }, { "key": "settings.bodyTracking.protectElbow.title", "value": "Proteger Cotovelo" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ru.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ru.json index 745270412b..6de5a9b0bd 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ru.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ru.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "Управляет костями глаз вашего аватара по данным отслеживания глаз. Естественный взгляд продолжает работать при отключении." }, { "key": "settings.bodyTracking.footIk.description", "value": "Включает процедурное размещение ступней в неподвижном состоянии без трекеров ступней." }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "Подавляет анимации прыжка и приземления, а также провисание бёдер при приземлении, пока откалиброваны трекеры полного отслеживания тела." }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "Масштаб влияет на скорость передвижения" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "Подстраивает скорость анимации передвижения под масштаб аватара, не изменяя фактическую скорость движения." }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "Включает виртуальные столкновения капсул между локтями и грудью, чтобы предотвратить прохождение рук сквозь тело." }, { "key": "settings.bodyTracking.protectElbow.title", "value": "Защитить локоть" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ur.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ur.json index 6847ef661c..5f27088ce4 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ur.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/ur.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "آنکھ کی ٹریکنگ ڈیٹا سے اپنے اواتار کی آنکھ کی ہڈیاں چلائیں۔ غیر فعال ہونے پر قدرتی آنکھ کا منظر چلتا رہتا ہے۔" }, { "key": "settings.bodyTracking.footIk.description", "value": "بغیر پاؤں ٹریکرز کے ساکن کھڑے ہونے پر طریقہ کار سے پاؤں کی پلیسمنٹ کو فعال کرتا ہے۔" }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "فل باڈی ٹریکرز کیلیبریٹ ہونے کے دوران چھلانگ اور لینڈنگ اینیمیشنز اور لینڈنگ ہپ ڈپ کو دباتا ہے۔" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "پیمانہ نقل و حرکت کی رفتار کو متاثر کرتا ہے" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "اوتار کے پیمانے کے مطابق حرکت کی اینیمیشن کی رفتار کو ایڈجسٹ کرتا ہے، جبکہ جسمانی حرکت کی رفتار تبدیل نہیں ہوتی۔" }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "بازو کو جسم سے گزرنے سے روکنے کے لیے کہنیوں اور سینے کے درمیان ورچوئل کیپسول تصادم کو فعال کرتا ہے۔" }, { "key": "settings.bodyTracking.protectElbow.title", "value": "کہنی کی حفاظت" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-CN.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-CN.json index 0224e45080..d0a27488e5 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-CN.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-CN.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "通过眼部追踪数据驱动化身的眼部骨骼。禁用时自然眼神望向继续运行。" }, { "key": "settings.bodyTracking.footIk.description", "value": "在没有脚部追踪器静止站立时启用程序化脚部放置。" }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "在全身追踪器已校准时抑制跳跃和落地动画以及落地时的髋部下沉。" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "缩放会影响移动动画速度" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "会根据头像缩放调整移动动画速度,但不会改变实际移动速度。" }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "启用肘部与胸部之间的虚拟胶囊体碰撞,防止手臂穿过身体。" }, { "key": "settings.bodyTracking.protectElbow.title", "value": "保护肘部" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hans.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hans.json index 7e7d26efa0..610f9e37b2 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hans.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hans.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "通过眼部追踪数据驱动化身的眼部骨骼。禁用时自然眼神望向继续运行。" }, { "key": "settings.bodyTracking.footIk.description", "value": "在没有脚部追踪器静止站立时启用程序化脚部放置。" }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "在全身追踪器已校准时抑制跳跃和落地动画以及落地时的髋部下沉。" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "缩放会影响移动动画速度" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "会根据头像缩放调整移动动画速度,但不会改变实际移动速度。" }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "启用肘部与胸部之间的虚拟胶囊体碰撞,防止手臂穿过身体。" }, { "key": "settings.bodyTracking.protectElbow.title", "value": "保护肘部" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json index ea8e924183..0878e15ac0 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh-Hant.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "透過眼部追蹤資料驅動虛擬人的眼部骨骼。停用時自然眼神望向繼續執行。" }, { "key": "settings.bodyTracking.footIk.description", "value": "在沒有腳部追蹤器靜止站立時啟用程序化腳部放置。" }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "在全身追蹤器已校準時抑制跳躍和落地動畫以及落地時的髖部下沉。" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "縮放會影響移動動畫速度" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "會依照頭像縮放調整移動動畫速度,但不會改變實際移動速度。" }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "啟用肘部與胸部之間的虛擬膠囊體碰撞,防止手臂穿過身體。" }, { "key": "settings.bodyTracking.protectElbow.title", "value": "保護肘部" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json index d9e875e412..b9f126a6b1 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json +++ b/Basis/Packages/com.basis.framework/BasisUI/Localization/Languages/zh.json @@ -585,6 +585,8 @@ { "key": "settings.bodyTracking.eyeTracking.description", "value": "通过眼部追踪数据驱动化身的眼部骨骼。禁用时自然眼神望向继续运行。" }, { "key": "settings.bodyTracking.footIk.description", "value": "在没有脚部追踪器静止站立时启用程序化脚部放置。" }, { "key": "settings.bodyTracking.disableAnimFbt.description", "value": "在全身追踪器已校准时抑制跳跃和落地动画以及落地时的髋部下沉。" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion", "value": "缩放会影响移动动画速度" }, + { "key": "settings.bodyTracking.scaleAffectsLocomotion.description", "value": "会根据头像缩放调整移动动画速度,但不会改变实际移动速度。" }, { "key": "settings.bodyTracking.collisionsEnabled.description", "value": "启用肘部与胸部之间的虚拟胶囊体碰撞,防止手臂穿过身体。" }, { "key": "settings.bodyTracking.protectElbow.title", "value": "保护肘部" }, diff --git a/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderIK.cs b/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderIK.cs index 8bf474e95d..9b4d73fb7b 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderIK.cs +++ b/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderIK.cs @@ -189,6 +189,11 @@ public static PanelTabPage IKTab(PanelTabGroup tabGroup) disableAnimInFBTToggle.Descriptor.SetTitle(BasisLocalization.Get("settings.bodyTracking.disableAnimFbt")); disableAnimInFBTToggle.AssignBinding(BasisSettingsDefaults.DisableAnimationsInFBT); disableAnimInFBTToggle.Descriptor.SetDescription(BasisLocalization.Get("settings.bodyTracking.disableAnimFbt.description")); + + var scaleAffectsLocomotionToggle = PanelToggle.CreateNewEntry(trackingParent); + scaleAffectsLocomotionToggle.Descriptor.SetTitle(BasisLocalization.Get("settings.bodyTracking.scaleAffectsLocomotion")); + scaleAffectsLocomotionToggle.AssignBinding(BasisSettingsDefaults.ScaleAffectsLocomotionSpeed); + scaleAffectsLocomotionToggle.Descriptor.SetDescription(BasisLocalization.Get("settings.bodyTracking.scaleAffectsLocomotion.description")); }); // ============== Body Collision ============== diff --git a/Basis/Packages/com.basis.framework/Drivers/Local/BasisLocalAnimatorDriver.cs b/Basis/Packages/com.basis.framework/Drivers/Local/BasisLocalAnimatorDriver.cs index f70b209573..3bd9557ca4 100644 --- a/Basis/Packages/com.basis.framework/Drivers/Local/BasisLocalAnimatorDriver.cs +++ b/Basis/Packages/com.basis.framework/Drivers/Local/BasisLocalAnimatorDriver.cs @@ -76,6 +76,10 @@ public class BasisLocalAnimatorDriver /// Default: 30. public float AngularDampingFactor = 30; + private const float MinimumLocomotionScale = 0.05f; + private const float MaximumLocomotionScale = 20f; + private const float LocomotionScaleSpeedExponent = 0.5f; + /// /// Last raw (pre-damped) velocity sample used for smoothing. /// @@ -250,7 +254,9 @@ public void SimulateAnimator(float DeltaTime) basisAnimatorVariableApply.BasisAnimatorVariables.Velocity = dampenedVelocity; bool isMoving = dampenedVelocity.sqrMagnitude > StationaryVelocityThreshold; basisAnimatorVariableApply.BasisAnimatorVariables.isMoving = isMoving; - basisAnimatorVariableApply.BasisAnimatorVariables.AnimationsCurrentSpeed = 1; + basisAnimatorVariableApply.BasisAnimatorVariables.AnimationsCurrentSpeed = Basis.BasisUI.BasisSettingsDefaults.ScaleAffectsLocomotionSpeed.RawValue && isMoving + ? GetScaleAdjustedLocomotionSpeed() + : 1f; if (HasHipsInput && isMoving == false) { @@ -291,6 +297,12 @@ public void SimulateAnimator(float DeltaTime) previousHipsRotation = BasisLocalBoneDriver.HipsControl.OutgoingWorldData.rotation; } + private static float GetScaleAdjustedLocomotionSpeed() + { + float avatarScale = Mathf.Clamp(BasisHeightDriver.ScaledToMatchValue, MinimumLocomotionScale, MaximumLocomotionScale); + return Mathf.Exp(-Mathf.Log(avatarScale) * LocomotionScaleSpeedExponent); + } + /// /// Event handler that sets the animator's jump state immediately after a jump begins. /// diff --git a/Basis/Packages/com.basis.framework/Drivers/Remote/BasisRemoteBoneDriver.cs b/Basis/Packages/com.basis.framework/Drivers/Remote/BasisRemoteBoneDriver.cs index 078662b55e..73384d5e06 100644 --- a/Basis/Packages/com.basis.framework/Drivers/Remote/BasisRemoteBoneDriver.cs +++ b/Basis/Packages/com.basis.framework/Drivers/Remote/BasisRemoteBoneDriver.cs @@ -124,6 +124,8 @@ public struct BasisRemoteBoneJob : IJobParallelFor private const float NamePlateShrinkStartHeight = 0.5f; private const float NamePlateMinimumScaleMultiplier = 0.5f; private const float NamePlateGrowStartHeight = 2f; + // Cap tall-avatar growth so the nameplate stays readable without becoming unreasonably large. + private const float MaxNamePlateScaleMultiplier = 3f; /// Authoring-time TPose and offset data (unscaled). [ReadOnly] public NativeArray Authoring; @@ -229,7 +231,8 @@ private static float ComputeNamePlateScaleMultiplier(float namePlateHeight) if (namePlateHeight > NamePlateGrowStartHeight) { - return 1f + (namePlateHeight - NamePlateGrowStartHeight); + // Preserve linear growth above the tall-avatar threshold, but stop before giant avatars overwhelm the UI. + return math.min(1f + (namePlateHeight - NamePlateGrowStartHeight), MaxNamePlateScaleMultiplier); } return 1f; diff --git a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs index 40fca578bf..9ac9c34dc6 100644 --- a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs +++ b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscAvatarScaling.cs @@ -84,6 +84,7 @@ public static void ApplyEyeHeight(float requestedEyeHeightMeters) if (float.IsNaN(requestedEyeHeightMeters) || float.IsInfinity(requestedEyeHeightMeters) || requestedEyeHeightMeters <= 0f) { BasisDebug.LogWarning($"Ignoring invalid OSC avatar eye height {requestedEyeHeightMeters}.", BasisDebug.LogTag.LocalNetwork); + PublishCurrentState(); return; } BasisLocalPlayer localPlayer = BasisLocalPlayer.Instance; From 54efc0638fc9234762effa57be128980f870a804 Mon Sep 17 00:00:00 2001 From: Toys0125 Date: Wed, 27 May 2026 21:43:57 -0500 Subject: [PATCH 12/12] Add comment on IsAvatarScalingAddress. --- .../Scripts/Systems/Runtime/OSC/BasisOscService.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscService.cs b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscService.cs index 364a6ab434..97275540a2 100644 --- a/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscService.cs +++ b/Basis/Packages/dev.hai-vr.basis.comms/Scripts/Systems/Runtime/OSC/BasisOscService.cs @@ -654,6 +654,8 @@ private static bool TryResolveAddressValue(SimpleOSC.OSCMessage message, out int addressId = 0; value = 0f; + // Avatar scaling uses a dedicated OSC message handler and must not be remapped into + // the generic float address-id pipeline used for avatar parameters/acquisition. if (BasisOscAvatarScaling.IsAvatarScalingAddress(message.path)) { return false;