diff --git a/Basis/Packages/com.basis.framework/BasisUI/BasisSettingsDefaults.cs b/Basis/Packages/com.basis.framework/BasisUI/BasisSettingsDefaults.cs index cd314fab5..221befad4 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/BasisSettingsDefaults.cs +++ b/Basis/Packages/com.basis.framework/BasisUI/BasisSettingsDefaults.cs @@ -150,6 +150,8 @@ public static class BasisSettingsDefaults public static BasisSettingsBinding CacheMaxSizeGB = new("cachemaxsizegb", new BasisPlatformDefault(128)); + public static BasisSettingsBinding SharedContentPreviews = new("sharedcontentpreviews", new BasisPlatformDefault(false)); + public static BasisSettingsBinding AvatarMeshLOD = new("avatarmeshlod", new BasisPlatformDefault { windows = 0.05f, @@ -631,6 +633,7 @@ public static void LoadAll() // LOD / Download limits AvatarDownloadSize.LoadBindingValue(); CacheMaxSizeGB.LoadBindingValue(); + SharedContentPreviews.LoadBindingValue(); AvatarMeshLOD.LoadBindingValue(); GlobalMeshLOD.LoadBindingValue(); diff --git a/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderStorage.cs b/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderStorage.cs index a2bef9465..0d752afa4 100644 --- a/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderStorage.cs +++ b/Basis/Packages/com.basis.framework/BasisUI/Menus/Main Menu Providers/SettingsProviderParts/SettingsProviderStorage.cs @@ -24,6 +24,11 @@ public static PanelTabPage StorageTab(PanelTabGroup tabGroup) PanelSlider.SliderSettings.Advanced("Avatar Download Size", 5, 1024, false, 0, ValueDisplayMode.MemorySize), BasisSettingsDefaults.AvatarDownloadSize); + PanelToggle sharedPreviewToggle = PanelToggle.CreateNewEntry(downloadGroup.ContentParent); + sharedPreviewToggle.Descriptor.SetTitle("Load Shared Content Previews"); + sharedPreviewToggle.Descriptor.SetDescription("Downloads shared content in the background and renders a fitted LOD3-style preview inside the share sphere."); + sharedPreviewToggle.AssignBinding(BasisSettingsDefaults.SharedContentPreviews); + // Cache size limit slider (lightweight, no file I/O) PanelElementDescriptor limitGroup = PanelElementDescriptor.CreateNew(PanelElementDescriptor.ElementStyles.Group, container); @@ -137,5 +142,6 @@ private static void ResetStorageDefaults() { BasisSettingsDefaults.AvatarDownloadSize.ResetToDefault(); BasisSettingsDefaults.CacheMaxSizeGB.ResetToDefault(); + BasisSettingsDefaults.SharedContentPreviews.ResetToDefault(); } } diff --git a/Basis/Packages/com.basis.framework/Drivers/Common/BasisAvatarDriver.cs b/Basis/Packages/com.basis.framework/Drivers/Common/BasisAvatarDriver.cs index 2cb67f6c7..d62d4dcb4 100644 --- a/Basis/Packages/com.basis.framework/Drivers/Common/BasisAvatarDriver.cs +++ b/Basis/Packages/com.basis.framework/Drivers/Common/BasisAvatarDriver.cs @@ -548,98 +548,115 @@ public static bool TryGetTextureWithScaleAndOffset(Material mat, string property result = new BasisTexTransform(tex, scale, offset); return true; } - public static void MaterialCorrection(SkinnedMeshRenderer renderer, Shader fallbackUrpShader) + private static bool TryCreateFallbackMaterial(Material mat, Shader fallbackUrpShader, out Material fixedMat) { - if (renderer == null) + fixedMat = null; + + if (mat == null) { - return; + return false; } - var materials = renderer.sharedMaterials; - if (materials == null || materials.Length == 0) + var shader = mat.shader; + if (shader == null) { - return; + return false; } if (fallbackUrpShader == null) { Debug.LogWarning("MaterialCorrection: fallbackUrpShader is null, cannot swap shaders."); - return; + return false; } - bool anyChanged = false; - - for (int mi = 0; mi < materials.Length; mi++) + bool shaderBroken = !shader.isSupported || (!string.IsNullOrEmpty(shader.name) && shader.name.Contains("InternalErrorShader")); + if (!shaderBroken) { - var mat = materials[mi]; - if (mat == null) - { - continue; - } + return false; + } - var shader = mat.shader; - if (shader == null) - { - continue; - } + bool hasAlbedo = TryGetFirstTextureWithScaleAndOffset(mat, AlbedoProps, out BasisTexTransform albedo, out _); + bool hasNormal = TryGetFirstTextureWithScaleAndOffset(mat, NormalProps, out BasisTexTransform normal, out _); + bool hasMetal = TryGetFirstTextureWithScaleAndOffset(mat, MetallicProps, out BasisTexTransform metal, out _); + bool hasOcc = TryGetFirstTextureWithScaleAndOffset(mat, OcclusionProps, out BasisTexTransform occ, out _); + bool hasColor = TryGetFirstColor(mat, out Color baseColor, out _); + fixedMat = new Material(fallbackUrpShader) + { + name = mat.name + " (Fixed)" + }; + if (hasAlbedo) + { + fixedMat.SetTexture("_BaseMap", albedo.texture); + fixedMat.SetTextureScale("_BaseMap", albedo.scale); + fixedMat.SetTextureOffset("_BaseMap", albedo.offset); + } + if (hasColor) + { + fixedMat.SetColor("_BaseColor", baseColor); + } + if (hasNormal) + { + fixedMat.EnableKeyword("_NORMALMAP"); + fixedMat.SetTexture("_BumpMap", normal.texture); + fixedMat.SetTextureScale("_BumpMap", normal.scale); + fixedMat.SetTextureOffset("_BumpMap", normal.offset); + fixedMat.SetFloat("_BumpScale", 0.2f); + } + if (hasMetal) + { + fixedMat.EnableKeyword("_METALLICSPECGLOSSMAP"); + fixedMat.SetTexture("_MetallicGlossMap", metal.texture); + fixedMat.SetTextureScale("_MetallicGlossMap", metal.scale); + fixedMat.SetTextureOffset("_MetallicGlossMap", metal.offset); + } + fixedMat.SetFloat("_Metallic", 0.2f); + fixedMat.SetFloat("_Smoothness", 0.2f); + if (hasOcc) + { + fixedMat.SetTexture("_OcclusionMap", occ.texture); + fixedMat.SetTextureScale("_OcclusionMap", occ.scale); + fixedMat.SetTextureOffset("_OcclusionMap", occ.offset); + fixedMat.SetFloat("_OcclusionStrength", 0.2f); + } + return true; + } + public static void MaterialCorrection(Renderer renderer, Shader fallbackUrpShader, List createdMaterials = null) + { + if (renderer == null) + { + return; + } - bool shaderBroken = !shader.isSupported || (!string.IsNullOrEmpty(shader.name) && shader.name.Contains("InternalErrorShader")); + var materials = renderer.sharedMaterials; + if (materials == null || materials.Length == 0) + { + return; + } - if (!shaderBroken) - { - continue; - } - bool hasAlbedo = TryGetFirstTextureWithScaleAndOffset(mat, AlbedoProps, out BasisTexTransform albedo, out string albedoProp); - bool hasNormal = TryGetFirstTextureWithScaleAndOffset(mat, NormalProps, out BasisTexTransform normal, out string normalProp); - bool hasMetal = TryGetFirstTextureWithScaleAndOffset(mat, MetallicProps, out BasisTexTransform metal, out string metalProp); - bool hasOcc = TryGetFirstTextureWithScaleAndOffset(mat, OcclusionProps, out BasisTexTransform occ, out string occProp); - bool hasColor = TryGetFirstColor(mat, out Color baseColor, out string colorProp); - var fixedMat = new Material(fallbackUrpShader) - { - name = mat.name + " (Fixed)" - }; - if (hasAlbedo) - { - fixedMat.SetTexture("_BaseMap", albedo.texture); - fixedMat.SetTextureScale("_BaseMap", albedo.scale); - fixedMat.SetTextureOffset("_BaseMap", albedo.offset); - } - if (hasColor) - { - fixedMat.SetColor("_BaseColor", baseColor); - } - if (hasNormal) - { - fixedMat.EnableKeyword("_NORMALMAP"); - fixedMat.SetTexture("_BumpMap", normal.texture); - fixedMat.SetTextureScale("_BumpMap", normal.scale); - fixedMat.SetTextureOffset("_BumpMap", normal.offset); - fixedMat.SetFloat("_BumpScale", 0.2f); - } - if (hasMetal) - { - fixedMat.EnableKeyword("_METALLICSPECGLOSSMAP"); - fixedMat.SetTexture("_MetallicGlossMap", metal.texture); - fixedMat.SetTextureScale("_MetallicGlossMap", metal.scale); - fixedMat.SetTextureOffset("_MetallicGlossMap", metal.offset); - } - fixedMat.SetFloat("_Metallic", 0.2f); - fixedMat.SetFloat("_Smoothness", 0.2f); - if (hasOcc) + bool anyChanged = false; + for (int mi = 0; mi < materials.Length; mi++) + { + if (TryCreateFallbackMaterial(materials[mi], fallbackUrpShader, out Material fixedMat)) { - fixedMat.SetTexture("_OcclusionMap", occ.texture); - fixedMat.SetTextureScale("_OcclusionMap", occ.scale); - fixedMat.SetTextureOffset("_OcclusionMap", occ.offset); - - fixedMat.SetFloat("_OcclusionStrength", 0.2f); + materials[mi] = fixedMat; + createdMaterials?.Add(fixedMat); + anyChanged = true; } - materials[mi] = fixedMat; - anyChanged = true; } + if (anyChanged) { renderer.sharedMaterials = materials; } } + public static void MaterialCorrection(SkinnedMeshRenderer renderer, Shader fallbackUrpShader) + { + if (renderer == null) + { + return; + } + + MaterialCorrection((Renderer)renderer, fallbackUrpShader, null); + } } } diff --git a/Basis/Packages/com.basis.framework/Networking/ContentShare/BasisContentSphere.cs b/Basis/Packages/com.basis.framework/Networking/ContentShare/BasisContentSphere.cs index b800d34c7..dd4d2e664 100644 --- a/Basis/Packages/com.basis.framework/Networking/ContentShare/BasisContentSphere.cs +++ b/Basis/Packages/com.basis.framework/Networking/ContentShare/BasisContentSphere.cs @@ -1,9 +1,12 @@ using Basis.BasisUI; +using Basis.Scripts.Drivers; using Basis.Scripts.BasisSdk.Interactions; using Basis.Scripts.Device_Management.Devices; using Basis.Scripts.TransformBinders.BoneControl; using Basis.Scripts.UI.UI_Panels; using System; +using System.Collections.Generic; +using System.IO; using System.Threading; using System.Threading.Tasks; using TMPro; @@ -30,6 +33,13 @@ public class BasisContentSphere : BasisInteractableObject private float _bobPhase; private Vector3 _restPosition; private CancellationTokenSource _metaLoadCts; + private CancellationTokenSource _previewLoadCts; + private BasisTrackedBundleWrapper _previewBundleWrapper; + private Transform _previewRoot; + private GameObject _previewInstance; + private readonly List _previewMaterials = new(); + private const float PreviewFitPadding = 0.9f; + private const int PreviewLodIndex = 3; public TextMeshPro Label; public Renderer Renderer; public int MaterialIndex; @@ -45,10 +55,18 @@ public void Initialize(string sphereNetID, string contentURL, string unlockPassw ContentType = contentType; CreatorPlayerID = creatorPlayerID; InteractRange = 2f; + SetInitialLabel(); - _metaLoadCts = new CancellationTokenSource(); - _ = LoadMetadataImageAsync(_metaLoadCts.Token); - Label.text = GetContentTypeName(); + if (BasisSettingsDefaults.SharedContentPreviews.RawValue) + { + _previewLoadCts = new CancellationTokenSource(); + _ = LoadLivePreviewAsync(_previewLoadCts.Token); + } + else + { + _metaLoadCts = new CancellationTokenSource(); + _ = LoadMetadataImageAsync(_metaLoadCts.Token); + } } private void Start() @@ -71,51 +89,12 @@ private async Task LoadMetadataImageAsync(CancellationToken cancellationToken) { try { - BasisTrackedBundleWrapper wrapper = new BasisTrackedBundleWrapper - { - LoadableBundle = ToLoadableBundle() - }; - + BasisTrackedBundleWrapper wrapper = new BasisTrackedBundleWrapper { LoadableBundle = ToLoadableBundle() }; BasisProgressReport report = new BasisProgressReport(); await BasisBeeManagement.HandleMetaOnlyLoad(wrapper, report, cancellationToken); if (cancellationToken.IsCancellationRequested || this == null) return; - - Color typeColor = GetTypeColor(); - if (wrapper.LoadableBundle.BasisBundleConnector.ImageBase64 != null) - { - texture = BasisTextureCompression.FromPngBytes(wrapper.LoadableBundle.BasisBundleConnector.ImageBase64); - - // Blend 50% type color with 50% texture - Color[] pixels = texture.GetPixels(); - for (int i = 0; i < pixels.Length; i++) - { - pixels[i] = Color.Lerp(typeColor, pixels[i], 0.5f); - } - texture.SetPixels(pixels); - texture.Apply(); - - // Update label with bundle name if available - string bundleName = wrapper.LoadableBundle.BasisBundleConnector.BasisBundleDescription?.AssetBundleName; - if (!string.IsNullOrEmpty(bundleName) && Label != null) - { - Label.text = $"{GetContentTypeName()}\n{bundleName}"; - } - } - else - { - texture = new Texture2D(1, 1); - texture.SetPixel(0, 0, typeColor); - texture.Apply(); - } - if (Renderer != null) - { - MaterialPropertyBlock block = new MaterialPropertyBlock(); - Renderer.GetPropertyBlock(block, MaterialIndex); - block.SetTexture("_MainTex", texture); - block.SetTexture("_EmissionMap", texture); - Renderer.SetPropertyBlock(block, MaterialIndex); - } + ApplyMetadataFromConnector(wrapper.LoadableBundle.BasisBundleConnector); } catch (OperationCanceledException) { } catch (Exception e) @@ -128,6 +107,10 @@ public override void OnDestroy() GameObject.Destroy(texture); _metaLoadCts?.Cancel(); _metaLoadCts?.Dispose(); + _previewLoadCts?.Cancel(); + _previewLoadCts?.Dispose(); + ReleasePreviewBundle(); + DestroyPreview(); base.OnDestroy(); } @@ -230,6 +213,613 @@ public string GetContentTypeName() } } + private void SetInitialLabel() + { + if (Label == null) + { + return; + } + + string typeName = GetContentTypeName(); + string bundleName = TryGetBundleNameFromUrl(); + Label.text = string.IsNullOrEmpty(bundleName) ? typeName : $"{typeName}\n{bundleName}"; + } + + private string TryGetBundleNameFromUrl() + { + if (string.IsNullOrEmpty(ContentURL)) + { + return string.Empty; + } + + try + { + string path = ContentURL; + if (Uri.TryCreate(ContentURL, UriKind.Absolute, out Uri uri)) + { + path = uri.LocalPath; + } + + string fileName = Path.GetFileNameWithoutExtension(path); + return string.IsNullOrWhiteSpace(fileName) ? string.Empty : fileName; + } + catch + { + return string.Empty; + } + } + + private async Task LoadLivePreviewAsync(CancellationToken cancellationToken) + { + GameObject stagedInstance = null; + List stagedMaterials = new(); + BasisTrackedBundleWrapper wrapper = null; + try + { + wrapper = await RetainPreviewBundleAsync(cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + ApplyMetadataFromConnector(wrapper.LoadableBundle.BasisBundleConnector); + + GameObject previewPrefab = await LoadPreviewPrefabAsync(wrapper, cancellationToken); + if (previewPrefab == null) + { + BasisDebug.LogWarning($"Shared content preview missing loadable GameObject for sphere {SphereNetID} ({ContentURL})."); + return; + } + + if (this == null || gameObject == null) + { + return; + } + + EnsurePreviewRoot(); + stagedInstance = GameObject.Instantiate(previewPrefab, _previewRoot, false); + stagedInstance.name = $"{previewPrefab.name}_SharePreview"; + stagedInstance.transform.localPosition = Vector3.zero; + stagedInstance.transform.localRotation = Quaternion.identity; + stagedInstance.transform.localScale = Vector3.one; + + SanitizePreviewInstance(stagedInstance); + ApplyPreviewOrientation(stagedInstance); + ForcePreviewLods(stagedInstance); + ApplyPreviewFallbackMaterials(stagedInstance, stagedMaterials); + + if (!TryFitPreviewToSphere(stagedInstance, wrapper.LoadableBundle.BasisBundleConnector.Bounds)) + { + CleanupStagedPreview(stagedInstance, stagedMaterials); + BasisDebug.LogWarning($"Shared content preview had no render bounds for sphere {SphereNetID} ({ContentURL})."); + return; + } + + cancellationToken.ThrowIfCancellationRequested(); + DestroyPreview(); + ReleasePreviewBundle(); + _previewBundleWrapper = wrapper; + _previewMaterials.AddRange(stagedMaterials); + _previewInstance = stagedInstance; + wrapper = null; + stagedInstance = null; + HideMetadataPreviewLayer(); + } + catch (OperationCanceledException) + { + CleanupStagedPreview(stagedInstance, stagedMaterials); + ReleasePreviewBundle(wrapper); + } + catch (Exception ex) + { + CleanupStagedPreview(stagedInstance, stagedMaterials); + ReleasePreviewBundle(wrapper); + BasisDebug.LogWarning($"Shared content preview failed for sphere {SphereNetID} ({ContentURL}): {ex.Message}"); + } + } + + private async Task RetainPreviewBundleAsync(CancellationToken cancellationToken) + { + string remoteUrl = ContentURL; + BasisProgressReport report = new BasisProgressReport(); + + while (true) + { + if (BasisLoadHandler.LoadedBundles.TryGetValue(remoteUrl, out BasisTrackedBundleWrapper existingWrapper)) + { + existingWrapper.Increment(); + try + { + await existingWrapper.WaitForBundleLoadAsync(); + cancellationToken.ThrowIfCancellationRequested(); + if (existingWrapper.AssetBundle == null) + { + throw new Exception("Bundle load did not produce an AssetBundle."); + } + + return existingWrapper; + } + catch + { + existingWrapper.DeIncrement(); + throw; + } + } + + BasisTrackedBundleWrapper newWrapper = new BasisTrackedBundleWrapper + { + LoadableBundle = ToLoadableBundle() + }; + + if (!BasisLoadHandler.LoadedBundles.TryAdd(remoteUrl, newWrapper)) + { + continue; + } + + newWrapper.Increment(); + try + { + await BasisBeeManagement.HandleBundleAndMetaLoading(newWrapper, report, cancellationToken); + cancellationToken.ThrowIfCancellationRequested(); + if (newWrapper.AssetBundle == null) + { + throw new Exception("Bundle load did not produce an AssetBundle."); + } + + return newWrapper; + } + catch + { + newWrapper.DeIncrement(); + BasisLoadHandler.LoadedBundles.TryRemove(remoteUrl, out _); + if (newWrapper.AssetBundle != null) + { + newWrapper.AssetBundle.Unload(true); + } + throw; + } + } + } + + private async Task LoadPreviewPrefabAsync(BasisTrackedBundleWrapper wrapper, CancellationToken cancellationToken) + { + if (wrapper?.AssetBundle == null) + { + return null; + } + + BasisBundleGenerated generated = null; + BasisBundleConnector connector = wrapper.LoadableBundle?.BasisBundleConnector; + if (connector != null) + { + connector.GetPlatform(out generated); + } + + string preferredAssetName = generated?.AssetToLoadName; + if (!string.IsNullOrEmpty(preferredAssetName)) + { + GameObject preferred = await LoadAssetAsGameObjectAsync(wrapper.AssetBundle, preferredAssetName, cancellationToken); + if (preferred != null) + { + return preferred; + } + } + + string[] assetNames = wrapper.AssetBundle.GetAllAssetNames(); + for (int index = 0; index < assetNames.Length; index++) + { + cancellationToken.ThrowIfCancellationRequested(); + GameObject fallback = await LoadAssetAsGameObjectAsync(wrapper.AssetBundle, assetNames[index], cancellationToken); + if (fallback != null) + { + return fallback; + } + } + + return null; + } + + private static async Task LoadAssetAsGameObjectAsync(AssetBundle assetBundle, string assetName, CancellationToken cancellationToken) + { + if (assetBundle == null || string.IsNullOrEmpty(assetName)) + { + return null; + } + + string[] candidateNames = + { + assetName, + assetName.Replace(".bundle", ".prefab") + }; + + for (int index = 0; index < candidateNames.Length; index++) + { + string candidate = candidateNames[index]; + if (string.IsNullOrEmpty(candidate)) + { + continue; + } + + AssetBundleRequest request = assetBundle.LoadAssetAsync(candidate); + while (!request.isDone) + { + cancellationToken.ThrowIfCancellationRequested(); + await Task.Yield(); + } + + if (request.asset is GameObject gameObject) + { + return gameObject; + } + } + + return null; + } + + private void ApplyMetadataFromConnector(BasisBundleConnector connector) + { + if (this == null || connector == null) + { + return; + } + + Texture2D nextTexture = BuildMetadataTexture(connector); + if (texture != null) + { + GameObject.Destroy(texture); + } + texture = nextTexture; + + string bundleName = connector.BasisBundleDescription?.AssetBundleName; + if (!string.IsNullOrEmpty(bundleName) && Label != null) + { + Label.text = $"{GetContentTypeName()}\n{bundleName}"; + } + + if (Renderer != null) + { + MaterialPropertyBlock block = new MaterialPropertyBlock(); + Renderer.GetPropertyBlock(block, MaterialIndex); + block.SetTexture("_MainTex", texture); + block.SetTexture("_EmissionMap", texture); + Renderer.SetPropertyBlock(block, MaterialIndex); + } + } + + private Texture2D BuildMetadataTexture(BasisBundleConnector connector) + { + Color typeColor = GetTypeColor(); + if (connector.ImageBase64 != null) + { + Texture2D previewTexture = BasisTextureCompression.FromPngBytes(connector.ImageBase64); + Color[] pixels = previewTexture.GetPixels(); + for (int i = 0; i < pixels.Length; i++) + { + pixels[i] = Color.Lerp(typeColor, pixels[i], 0.5f); + } + previewTexture.SetPixels(pixels); + previewTexture.Apply(); + return previewTexture; + } + + Texture2D fallbackTexture = new Texture2D(1, 1); + fallbackTexture.SetPixel(0, 0, typeColor); + fallbackTexture.Apply(); + return fallbackTexture; + } + + private void EnsurePreviewRoot() + { + if (_previewRoot != null) + { + return; + } + + GameObject previewRoot = new GameObject("Shared Content Preview"); + previewRoot.transform.SetParent(transform, false); + previewRoot.transform.localPosition = Vector3.zero; + previewRoot.transform.localRotation = Quaternion.identity; + previewRoot.transform.localScale = Vector3.one; + _previewRoot = previewRoot.transform; + } + + private void SanitizePreviewInstance(GameObject instance) + { + if (instance == null) + { + return; + } + + foreach (Collider collider in instance.GetComponentsInChildren(true)) + { + collider.enabled = false; + } + + foreach (Rigidbody rigidbody in instance.GetComponentsInChildren(true)) + { + rigidbody.isKinematic = true; + rigidbody.detectCollisions = false; + } + + foreach (AudioSource audioSource in instance.GetComponentsInChildren(true)) + { + audioSource.enabled = false; + } + + foreach (Animator animator in instance.GetComponentsInChildren(true)) + { + animator.enabled = false; + } + + foreach (ParticleSystem particleSystem in instance.GetComponentsInChildren(true)) + { + particleSystem.Stop(true, ParticleSystemStopBehavior.StopEmittingAndClear); + particleSystem.gameObject.SetActive(false); + } + + foreach (BasisInteractableObject interactable in instance.GetComponentsInChildren(true)) + { + interactable.enabled = false; + } + + foreach (MonoBehaviour monoBehaviour in instance.GetComponentsInChildren(true)) + { + if (monoBehaviour == null) + { + continue; + } + + if (monoBehaviour is BasisInteractableObject) + { + continue; + } + + monoBehaviour.enabled = false; + } + } + + private void ApplyPreviewFallbackMaterials(GameObject instance, List createdMaterials) + { + if (instance == null || BundledContentHolder.Instance == null) + { + return; + } + + Shader fallbackShader = BundledContentHolder.Instance.UrpShader; + if (fallbackShader == null) + { + return; + } + + foreach (Renderer childRenderer in instance.GetComponentsInChildren(true)) + { + if (childRenderer == null) + { + continue; + } + + BasisAvatarDriver.MaterialCorrection(childRenderer, fallbackShader, createdMaterials); + } + } + + private void ForcePreviewLods(GameObject instance) + { + if (instance == null) + { + return; + } + + foreach (Renderer childRenderer in instance.GetComponentsInChildren(true)) + { + if (childRenderer == null) + { + continue; + } + + childRenderer.forceMeshLod = PreviewLodIndex; + } + + foreach (LODGroup lodGroup in instance.GetComponentsInChildren(true)) + { + if (lodGroup == null) + { + continue; + } + + int lodCount = lodGroup.GetLODs().Length; + if (lodCount <= 0) + { + continue; + } + + lodGroup.RecalculateBounds(); + lodGroup.ForceLOD(Mathf.Min(PreviewLodIndex, lodCount - 1)); + } + } + + private void ApplyPreviewOrientation(GameObject instance) + { + if (instance == null) + { + return; + } + + if (ContentType == ContentShareType.Avatar) + { + instance.transform.localRotation = Quaternion.Euler(90f, 0f, 0f); + } + } + + private void HideMetadataPreviewLayer() + { + if (Renderer == null) + { + return; + } + Renderer.enabled = false; + } + + private bool TryFitPreviewToSphere(GameObject instance, BasisBounds fallbackBounds) + { + if (instance == null || _previewRoot == null) + { + return false; + } + + if (!TryGetCombinedRendererBounds(instance, out Bounds previewBounds)) + { + if (fallbackBounds.size == Vector3.zero) + { + return false; + } + + previewBounds = new Bounds(instance.transform.position + fallbackBounds.center, fallbackBounds.size); + } + + float targetDiameter = 1f; + Vector3 targetCenter = transform.position; + if (TryGetShellBounds(out Bounds shellBounds)) + { + targetDiameter = Mathf.Min(shellBounds.size.x, shellBounds.size.y, shellBounds.size.z) * PreviewFitPadding; + targetCenter = shellBounds.center; + } + + float previewMaxSize = Mathf.Max(previewBounds.size.x, previewBounds.size.y, previewBounds.size.z); + if (previewMaxSize <= Mathf.Epsilon) + { + return false; + } + + float scaleFactor = targetDiameter / previewMaxSize; + instance.transform.localScale = instance.transform.localScale * scaleFactor; + + if (!TryGetCombinedRendererBounds(instance, out previewBounds)) + { + previewBounds = new Bounds(instance.transform.position + fallbackBounds.center, fallbackBounds.size * scaleFactor); + } + + Vector3 centerOffset = targetCenter - previewBounds.center; + instance.transform.position += centerOffset; + return true; + } + + private bool TryGetShellBounds(out Bounds bounds) + { + Renderer[] shellRenderers = GetComponentsInChildren(true); + bounds = default; + bool hasBounds = false; + + for (int index = 0; index < shellRenderers.Length; index++) + { + Renderer shellRenderer = shellRenderers[index]; + if (shellRenderer == null || !shellRenderer.enabled) + { + continue; + } + + if (_previewRoot != null && shellRenderer.transform.IsChildOf(_previewRoot)) + { + continue; + } + + if (Label != null && shellRenderer.transform.IsChildOf(Label.transform)) + { + continue; + } + + if (!hasBounds) + { + bounds = shellRenderer.bounds; + hasBounds = true; + } + else + { + bounds.Encapsulate(shellRenderer.bounds); + } + } + + return hasBounds; + } + + private static bool TryGetCombinedRendererBounds(GameObject instance, out Bounds bounds) + { + Renderer[] renderers = instance.GetComponentsInChildren(true); + bounds = default; + bool hasBounds = false; + for (int index = 0; index < renderers.Length; index++) + { + Renderer childRenderer = renderers[index]; + if (childRenderer == null || !childRenderer.enabled) + { + continue; + } + + if (!hasBounds) + { + bounds = childRenderer.bounds; + hasBounds = true; + } + else + { + bounds.Encapsulate(childRenderer.bounds); + } + } + + return hasBounds; + } + + private void DestroyPreview() + { + if (_previewInstance != null) + { + GameObject.Destroy(_previewInstance); + _previewInstance = null; + } + + for (int index = 0; index < _previewMaterials.Count; index++) + { + if (_previewMaterials[index] != null) + { + GameObject.Destroy(_previewMaterials[index]); + } + } + _previewMaterials.Clear(); + } + + private void ReleasePreviewBundle() + { + if (_previewBundleWrapper == null) + { + return; + } + + ReleasePreviewBundle(_previewBundleWrapper); + _previewBundleWrapper = null; + } + + private static void ReleasePreviewBundle(BasisTrackedBundleWrapper wrapper) + { + if (wrapper?.LoadableBundle?.BasisRemoteBundleEncrypted?.RemoteBeeFileLocation == null) + { + return; + } + + _ = BasisLoadHandler.RequestDeIncrementOfBundle(wrapper.LoadableBundle); + } + + private static void CleanupStagedPreview(GameObject stagedInstance, List stagedMaterials) + { + if (stagedInstance != null) + { + GameObject.Destroy(stagedInstance); + } + + for (int index = 0; index < stagedMaterials.Count; index++) + { + if (stagedMaterials[index] != null) + { + GameObject.Destroy(stagedMaterials[index]); + } + } + stagedMaterials.Clear(); + } + #region BasisInteractableObject Implementation public override bool CanHover(BasisInput input) diff --git a/Basis/Packages/com.basis.sdk/Meshes/AvatarOrb.fbx b/Basis/Packages/com.basis.sdk/Meshes/AvatarOrb.fbx index 14fa0afb2..84fa34841 100644 Binary files a/Basis/Packages/com.basis.sdk/Meshes/AvatarOrb.fbx and b/Basis/Packages/com.basis.sdk/Meshes/AvatarOrb.fbx differ diff --git a/Basis/Packages/com.basis.sdk/Prefabs/AvatarOrb.prefab b/Basis/Packages/com.basis.sdk/Prefabs/AvatarOrb.prefab index f30ebefa2..d284ab0d3 100644 --- a/Basis/Packages/com.basis.sdk/Prefabs/AvatarOrb.prefab +++ b/Basis/Packages/com.basis.sdk/Prefabs/AvatarOrb.prefab @@ -191,15 +191,15 @@ PrefabInstance: m_Modifications: - target: {fileID: -8679921383154817045, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} propertyPath: m_LocalPosition.x - value: -0.272 + value: -0.49324 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} propertyPath: m_LocalPosition.y - value: 0 + value: -1.55 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} propertyPath: m_LocalPosition.z - value: -29.593 + value: 4.62343 objectReference: {fileID: 0} - target: {fileID: -8679921383154817045, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} propertyPath: m_LocalRotation.w @@ -232,11 +232,15 @@ PrefabInstance: - target: {fileID: -7511558181221131132, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} propertyPath: 'm_Materials.Array.data[0]' value: - objectReference: {fileID: 2100000, guid: 89d91b9e307208f41b092716806fbe14, type: 2} + objectReference: {fileID: 2100000, guid: 98c6e28597211dc4ca46b1232906f316, type: 2} - target: {fileID: -7511558181221131132, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} propertyPath: 'm_Materials.Array.data[1]' value: objectReference: {fileID: 2100000, guid: 98c6e28597211dc4ca46b1232906f316, type: 2} + - target: {fileID: -3089726251873171878, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} + propertyPath: 'm_Materials.Array.data[0]' + value: + objectReference: {fileID: 2100000, guid: 89d91b9e307208f41b092716806fbe14, type: 2} - target: {fileID: -3089726251873171878, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} propertyPath: 'm_Materials.Array.data[1]' value: @@ -262,6 +266,11 @@ PrefabInstance: insertIndex: -1 addedObject: {fileID: 2587787579263692451} m_SourcePrefab: {fileID: 100100000, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} +--- !u!23 &1885848445302283906 stripped +MeshRenderer: + m_CorrespondingSourceObject: {fileID: -3089726251873171878, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} + m_PrefabInstance: {fileID: 5707458702514165976} + m_PrefabAsset: {fileID: 0} --- !u!1 &4896994591437006217 stripped GameObject: m_CorrespondingSourceObject: {fileID: 919132149155446097, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} @@ -301,8 +310,10 @@ MonoBehaviour: m_PersistentCalls: m_Calls: [] InteractRange: 1 + AllowDirectGrab: 1 + GrabRadius: 0.15 Label: {fileID: 8452803495268253739} - Renderer: {fileID: 6410138300982690908} + Renderer: {fileID: 1885848445302283906} MaterialIndex: 0 texture: {fileID: 0} --- !u!54 &3357315586662725574 @@ -358,8 +369,3 @@ Transform: m_CorrespondingSourceObject: {fileID: -8679921383154817045, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} m_PrefabInstance: {fileID: 5707458702514165976} m_PrefabAsset: {fileID: 0} ---- !u!23 &6410138300982690908 stripped -MeshRenderer: - m_CorrespondingSourceObject: {fileID: -7511558181221131132, guid: f0d1d5c0f7212e445a8a6b048a795747, type: 3} - m_PrefabInstance: {fileID: 5707458702514165976} - m_PrefabAsset: {fileID: 0}