#pragma warning disable 649
#pragma warning disable 108
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using TriLibCore.SFB;
using TriLibCore.Extensions;
using TriLibCore.General;
#if TRILIB_SHOW_MEMORY_USAGE
using TriLibCore.Utils;
#endif
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using Debug = UnityEngine.Debug;
namespace TriLibCore.Samples
{
/// Represents a TriLib sample which allows the user to load models and HDR skyboxes from the local file-system.
public class AssetViewer : AssetViewerBase
{
///
/// Maximum camera distance ratio based on model bounds.
///
private const float MaxCameraDistanceRatio = 3f;
///
/// Camera distance ratio based on model bounds.
///
protected const float CameraDistanceRatio = 2f;
///
/// minimum camera distance.
///
protected const float MinCameraDistance = 0.01f;
///
/// Skybox scale based on model bounds.
///
protected const float SkyboxScale = 100f;
///
/// Skybox game object.
///
[SerializeField]
protected GameObject Skybox;
///
/// Scene CanvasScaler.
///
[SerializeField]
protected CanvasScaler CanvasScaler;
///
/// Camera selection Dropdown.
///
[SerializeField]
private Dropdown _camerasDropdown;
///
/// Camera loading Toggle.
///
[SerializeField]
private Toggle _loadCamerasToggle;
///
/// Lights loading Toggle.
///
[SerializeField]
private Toggle _loadLightsToggle;
///
/// Skybox game object renderer.
///
[SerializeField]
private Renderer _skyboxRenderer;
///
/// Directional light.
///
[SerializeField]
private Light _light;
///
/// Skybox material preset to create the final skybox material.
///
[SerializeField]
private Material _skyboxMaterialPreset;
///
/// Main reflection probe.
///
[SerializeField]
private ReflectionProbe _reflectionProbe;
///
/// Skybox exposure slider.
///
[SerializeField]
private Slider _skyboxExposureSlider;
///
/// Loading time indicator.
///
[SerializeField]
private Text _loadingTimeText;
///
/// Main scene Camera.
///
[SerializeField]
private Camera _mainCamera;
///
/// Current camera distance.
///
protected float CameraDistance = 1f;
///
/// Current camera pivot position.
///
protected Vector3 CameraPivot;
///
/// Input multiplier based on loaded model bounds.
///
protected float InputMultiplier = 1f;
///
/// Skybox instantiated material.
///
private Material _skyboxMaterial;
///
/// Texture loaded for skybox.
///
private Texture2D _skyboxTexture;
///
/// List of loaded animations.
///
private List _animations;
///
/// Created animation component for the loaded model.
///
private Animation _animation;
///
/// Loaded model cameras.
///
private IList _cameras;
///
/// Stop Watch used to track the model loading time.
///
private Stopwatch _stopwatch;
///
/// Represents the memory used by the Unity Player when the scene is loaded.
///
private long _initialMemory;
///
/// Current directional light angle.
///
private Vector2 _lightAngle = new Vector2(0f, -45f);
/// Gets the playing Animation State.
private AnimationState CurrentAnimationState
{
get
{
if (_animation != null)
{
return _animation[PlaybackAnimation.options[PlaybackAnimation.value].text];
}
return null;
}
}
/// Is there any animation playing?
private bool AnimationIsPlaying => _animation != null && _animation.isPlaying;
///
/// Shows the file picker for loading a model from the local file-system.
///
public void LoadModelFromFile()
{
AssetLoaderOptions.ImportCameras = _loadCamerasToggle.isOn;
AssetLoaderOptions.ImportLights = _loadLightsToggle.isOn;
base.LoadModelFromFile();
}
///
/// Shows the URL selector for loading a model from network.
///
public void LoadModelFromURLWithDialogValues()
{
AssetLoaderOptions.ImportCameras = _loadCamerasToggle.isOn;
AssetLoaderOptions.ImportLights = _loadLightsToggle.isOn;
base.LoadModelFromURLWithDialogValues();
}
/// Shows the file picker for loading a skybox from the local file-system.
public void LoadSkyboxFromFile()
{
SetLoading(false);
var title = "Select a skybox image";
var extensions = new ExtensionFilter[]
{
new ExtensionFilter("Radiance HDR Image (hdr)", "hdr")
};
StandaloneFileBrowser.OpenFilePanelAsync(title, null, extensions, true, OnSkyboxStreamSelected);
}
///
/// Removes the skybox texture.
///
public void ClearSkybox()
{
if (_skyboxMaterial == null)
{
_skyboxMaterial = Instantiate(_skyboxMaterialPreset);
}
_skyboxMaterial.mainTexture = null;
_skyboxExposureSlider.value = 1f;
OnSkyboxExposureChanged(1f);
}
public void ResetModelScale()
{
if (RootGameObject != null)
{
RootGameObject.transform.localScale = Vector3.one;
}
}
///
/// Plays the selected animation.
///
public override void PlayAnimation()
{
if (_animation == null)
{
return;
}
_animation.Play(PlaybackAnimation.options[PlaybackAnimation.value].text, PlayMode.StopAll);
}
///
/// Stop playing the selected animation.
///
public override void StopAnimation()
{
if (_animation == null)
{
return;
}
PlaybackSlider.value = 0f;
_animation.Stop();
SampleAnimationAt(0f);
}
/// Switches to the animation selected on the Dropdown.
/// The selected Animation index.
public override void PlaybackAnimationChanged(int index)
{
StopAnimation();
}
/// Switches to the camera selected on the Dropdown.
/// The selected Camera index.
public void CameraChanged(int index)
{
for (var i = 0; i < _cameras.Count; i++)
{
var camera = _cameras[i];
camera.enabled = false;
}
if (index == 0)
{
_mainCamera.enabled = true;
}
else
{
_cameras[index - 1].enabled = true;
}
}
/// Event triggered when the Animation slider value has been changed by the user.
/// The Animation playback normalized position.
public override void PlaybackSliderChanged(float value)
{
if (!AnimationIsPlaying)
{
var animationState = CurrentAnimationState;
if (animationState != null)
{
SampleAnimationAt(value);
}
}
}
/// Samples the Animation at the given normalized time.
/// The Animation normalized time.
private void SampleAnimationAt(float value)
{
if (_animation == null || RootGameObject == null)
{
return;
}
var animationClip = _animation.GetClip(PlaybackAnimation.options[PlaybackAnimation.value].text);
animationClip.SampleAnimation(RootGameObject, animationClip.length * value);
}
///
/// Event triggered when the user selects the skybox on the selection dialog.
///
/// Selected files.
private void OnSkyboxStreamSelected(IList files)
{
if (files != null && files.Count > 0 && files[0].HasData)
{
Utils.Dispatcher.InvokeAsyncUnchecked(LoadSkybox, files[0].OpenStream());
}
else
{
Utils.Dispatcher.InvokeAsync(ClearSkybox);
}
}
/// Loads the skybox from the given Stream.
/// The Stream containing the HDR Image data.
/// Coroutine IEnumerator.
private IEnumerator DoLoadSkybox(Stream stream)
{
//Double frame waiting hack
yield return new WaitForEndOfFrame();
yield return new WaitForEndOfFrame();
if (_skyboxTexture != null)
{
Destroy(_skyboxTexture);
}
ClearSkybox();
_skyboxTexture = HDRLoader.HDRLoader.Load(stream, out var gamma, out var exposure);
_skyboxMaterial.mainTexture = _skyboxTexture;
_skyboxExposureSlider.value = 1f;
OnSkyboxExposureChanged(exposure);
stream.Close();
SetLoading(false);
}
/// Starts the Coroutine to load the skybox from the given Sstream.
/// The Stream containing the HDR Image data.
private void LoadSkybox(Stream stream)
{
SetLoading(true);
StartCoroutine(DoLoadSkybox(stream));
}
/// Event triggered when the skybox exposure Slider has changed.
/// The new exposure value.
public void OnSkyboxExposureChanged(float exposure)
{
_skyboxMaterial.SetFloat("_Exposure", exposure);
_skyboxRenderer.material = _skyboxMaterial;
RenderSettings.skybox = _skyboxMaterial;
DynamicGI.UpdateEnvironment();
_reflectionProbe.RenderProbe();
}
/// Initializes the base-class and clears the skybox Texture.
protected override void Start()
{
base.Start();
if (SystemInfo.deviceType == DeviceType.Handheld)
{
CanvasScaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
}
AssetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();
AssetLoaderOptions.Timeout = 180;
AssetLoaderOptions.ShowLoadingWarnings = true;
#if TRILIB_SHOW_MEMORY_USAGE
_initialMemory = RuntimeProcessUtils.GetProcessMemory();
#endif
ClearSkybox();
}
/// Handles the input.
private void Update()
{
ProcessInput();
UpdateHUD();
}
/// Handles the input and moves the Camera accordingly.
protected virtual void ProcessInput()
{
if (!_mainCamera.enabled)
{
return;
}
ProcessInputInternal(_mainCamera.transform);
}
///
/// Handles the input using the given Camera.
///
/// The Camera to process input movements.
private void ProcessInputInternal(Transform cameraTransform)
{
if (!EventSystem.current.IsPointerOverGameObject())
{
if (GetMouseButton(0))
{
if (GetKey(KeyCode.LeftAlt) || GetKey(KeyCode.RightAlt))
{
_lightAngle.x = Mathf.Repeat(_lightAngle.x + GetAxis("Mouse X"), 360f);
_lightAngle.y = Mathf.Clamp(_lightAngle.y + GetAxis("Mouse Y"), -MaxPitch, MaxPitch);
}
else
{
UpdateCamera();
}
}
if (GetMouseButton(2))
{
CameraPivot -= cameraTransform.up * GetAxis("Mouse Y") * InputMultiplier + cameraTransform.right * GetAxis("Mouse X") * InputMultiplier;
}
CameraDistance = Mathf.Min(CameraDistance - GetMouseScrollDelta().y * InputMultiplier, InputMultiplier * (1f / InputMultiplierRatio) * MaxCameraDistanceRatio);
if (CameraDistance < 0f)
{
CameraPivot += cameraTransform.forward * -CameraDistance;
CameraDistance = 0f;
}
Skybox.transform.position = CameraPivot;
cameraTransform.position = CameraPivot + Quaternion.AngleAxis(CameraAngle.x, Vector3.up) * Quaternion.AngleAxis(CameraAngle.y, Vector3.right) * new Vector3(0f, 0f, Mathf.Max(MinCameraDistance, CameraDistance));
cameraTransform.LookAt(CameraPivot);
_light.transform.position = CameraPivot + Quaternion.AngleAxis(_lightAngle.x, Vector3.up) * Quaternion.AngleAxis(_lightAngle.y, Vector3.right) * Vector3.forward;
_light.transform.LookAt(CameraPivot);
}
}
/// Updates the HUD information.
private void UpdateHUD()
{
var animationState = CurrentAnimationState;
var time = animationState == null ? 0f : PlaybackSlider.value * animationState.length % animationState.length;
var seconds = time % 60f;
var milliseconds = time * 100f % 100f;
PlaybackTime.text = $"{seconds:00}:{milliseconds:00}";
var normalizedTime = animationState == null ? 0f : animationState.normalizedTime % 1f;
if (AnimationIsPlaying)
{
PlaybackSlider.value = float.IsNaN(normalizedTime) ? 0f : normalizedTime;
}
var animationIsPlaying = AnimationIsPlaying;
if (_animation != null)
{
Play.gameObject.SetActive(!animationIsPlaying);
Stop.gameObject.SetActive(animationIsPlaying);
}
else
{
Play.gameObject.SetActive(true);
Stop.gameObject.SetActive(false);
PlaybackSlider.value = 0f;
}
}
/// Event triggered when the user selects a file or cancels the Model selection dialog.
/// If any file has been selected, this value is true, otherwise it is false.
protected override void OnBeginLoadModel(bool hasFiles)
{
base.OnBeginLoadModel(hasFiles);
if (hasFiles)
{
_animations = null;
_loadingTimeText.text = null;
_stopwatch = new Stopwatch();
_stopwatch.Start();
}
}
/// Event triggered when the Model Meshes and hierarchy are loaded.
/// The Asset Loader Context reference. Asset Loader Context contains the Model loading data.
protected override void OnLoad(AssetLoaderContext assetLoaderContext)
{
base.OnLoad(assetLoaderContext);
ResetModelScale();
_camerasDropdown.options.Clear();
PlaybackAnimation.options.Clear();
_cameras = null;
_animation = null;
_mainCamera.enabled = true;
if (assetLoaderContext.RootGameObject != null)
{
if (assetLoaderContext.Options.ImportCameras)
{
_cameras = assetLoaderContext.RootGameObject.GetComponentsInChildren();
if (_cameras.Count > 0)
{
_camerasDropdown.gameObject.SetActive(true);
_camerasDropdown.options.Add(new Dropdown.OptionData("User Camera"));
for (var i = 0; i < _cameras.Count; i++)
{
var camera = _cameras[i];
camera.enabled = false;
_camerasDropdown.options.Add(new Dropdown.OptionData(camera.name));
}
_camerasDropdown.captionText.text = _cameras[0].name;
}
else
{
_cameras = null;
}
}
_animation = assetLoaderContext.RootGameObject.GetComponent();
if (_animation != null)
{
_animations = _animation.GetAllAnimationClips();
if (_animations.Count > 0)
{
PlaybackAnimation.interactable = true;
for (var i = 0; i < _animations.Count; i++)
{
var animationClip = _animations[i];
PlaybackAnimation.options.Add(new Dropdown.OptionData(animationClip.name));
}
PlaybackAnimation.captionText.text = _animations[0].name;
}
else
{
_animation = null;
}
}
_camerasDropdown.value = 0;
PlaybackAnimation.value = 0;
StopAnimation();
RootGameObject = assetLoaderContext.RootGameObject;
}
if (_cameras == null)
{
_camerasDropdown.gameObject.SetActive(false);
}
if (_animation == null)
{
PlaybackAnimation.interactable = false;
PlaybackAnimation.captionText.text = "No Animations";
}
ModelTransformChanged();
}
///
/// Changes the camera placement when the Model has changed.
///
protected virtual void ModelTransformChanged()
{
if (RootGameObject != null && _mainCamera.enabled)
{
var bounds = RootGameObject.CalculateBounds();
_mainCamera.FitToBounds(bounds, CameraDistanceRatio);
// Uncomment this code to scale up small objects
//if (bounds.size.magnitude < 1f)
//{
// var increase = 1f / bounds.size.magnitude;
// RootGameObject.transform.localScale *= increase;
// bounds = RootGameObject.CalculateBounds();
//}
CameraDistance = _mainCamera.transform.position.magnitude;
CameraPivot = bounds.center;
Skybox.transform.localScale = bounds.size.magnitude * SkyboxScale * Vector3.one;
InputMultiplier = bounds.size.magnitude * InputMultiplierRatio;
CameraAngle = Vector2.zero;
}
}
///
/// Event is triggered when any error occurs.
///
/// The Contextualized Error that has occurred.
protected override void OnError(IContextualizedError contextualizedError)
{
base.OnError(contextualizedError);
StopAnimation();
_stopwatch?.Stop();
}
/// Event is triggered when the Model (including Textures and Materials) has been fully loaded.
/// The Asset Loader Context reference. Asset Loader Context contains the Model loading data.
protected override void OnMaterialsLoad(AssetLoaderContext assetLoaderContext)
{
base.OnMaterialsLoad(assetLoaderContext);
_stopwatch.Stop();
#if TRILIB_SHOW_MEMORY_USAGE
var loadedText = $"Loaded in: {_stopwatch.Elapsed.Minutes:00}:{_stopwatch.Elapsed.Seconds:00} Peak Memory Usage: {ProcessUtils.SizeSuffix(RuntimeProcessUtils.GetProcessMemory() - _initialMemory)}";
#else
var loadedText = $"Loaded in: {_stopwatch.Elapsed.Minutes:00}:{_stopwatch.Elapsed.Seconds:00}";
#endif
_loadingTimeText.text = loadedText;
Debug.Log(loadedText);
ModelTransformChanged();
}
}
}