Tanoda
AssetViewer.cs
Go to the documentation of this file.
1#pragma warning disable 649
2using System.Collections;
3using System.Collections.Generic;
4using System.Diagnostics;
5using System.IO;
6using TriLibCore.Extensions;
7using TriLibCore.General;
8using TriLibCore.SFB;
9using UnityEngine;
11using UnityEngine.UI;
12using Debug = UnityEngine.Debug;
13
15{
18 {
22 private const float MaxCameraDistanceRatio = 3f;
23
27 protected const float CameraDistanceRatio = 2f;
28
32 protected const float MinCameraDistance = 0.01f;
33
37 protected const float SkyboxScale = 100f;
38
42 [SerializeField] protected GameObject Skybox;
43
47 [SerializeField] private Renderer _skyboxRenderer;
48
52 [SerializeField] private Light _light;
53
57 [SerializeField] private Material _skyboxMaterialPreset;
58
62 [SerializeField] private ReflectionProbe _reflectionProbe;
63
67 [SerializeField] private Slider _skyboxExposureSlider;
68
72 protected float CameraDistance = 1f;
73
77 protected Vector3 CameraPivot;
78
82 private Vector2 _lightAngle = new Vector2(0f, -45f);
83
87 protected float InputMultiplier = 1f;
88
92 private Material _skyboxMaterial;
93
97 private Texture2D _skyboxTexture;
98
102 private List<AnimationClip> _animations;
103
107 private Animation _animation;
108
109 private Stopwatch _stopwatch;
110
112 private AnimationState CurrentAnimationState
113 {
114 get
115 {
116 if (_animation != null) return _animation[PlaybackAnimation.options[PlaybackAnimation.value].text];
117 return null;
118 }
119 }
120
122 private bool AnimationIsPlaying => _animation != null && _animation.isPlaying;
123
125 public void LoadModelFromFile()
126 {
127 _stopwatch = new Stopwatch();
128 _stopwatch.Start();
129 base.LoadModelFromFile();
130 }
131
133 public void LoadSkyboxFromFile()
134 {
135 SetLoading(false);
136 var title = "Select a skybox image";
137 var extensions = new[]
138 {
139 new ExtensionFilter("Radiance HDR Image (hdr)", "hdr")
140 };
141 StandaloneFileBrowser.OpenFilePanelAsync(title, null, extensions, true, OnSkyboxStreamSelected);
142 }
143
147 public void ClearSkybox()
148 {
149 if (_skyboxMaterial == null) _skyboxMaterial = Instantiate(_skyboxMaterialPreset);
150 _skyboxMaterial.mainTexture = null;
151 _skyboxExposureSlider.value = 1f;
153 }
154
155 public void ResetModelScale()
156 {
157 if (RootGameObject != null) RootGameObject.transform.localScale = Vector3.one;
158 }
159
163 public override void PlayAnimation()
164 {
165 if (_animation == null) return;
166 _animation.Play(PlaybackAnimation.options[PlaybackAnimation.value].text);
167 }
168
172 public override void StopAnimation()
173 {
174 if (_animation == null) return;
175 PlaybackSlider.value = 0f;
176 _animation.Stop();
177 SampleAnimationAt(0f);
178 }
179
182 public override void PlaybackAnimationChanged(int index)
183 {
185 }
186
189 public override void PlaybackSliderChanged(float value)
190 {
191 if (!AnimationIsPlaying)
192 {
193 var animationState = CurrentAnimationState;
194 if (animationState != null) SampleAnimationAt(value);
195 }
196 }
197
200 private void SampleAnimationAt(float value)
201 {
202 if (_animation == null || RootGameObject == null) return;
203 var animationClip = _animation.GetClip(PlaybackAnimation.options[PlaybackAnimation.value].text);
204 animationClip.SampleAnimation(RootGameObject, animationClip.length * value);
205 }
206
211 private void OnSkyboxStreamSelected(IList<ItemWithStream> files)
212 {
213 if (files != null && files.Count > 0 && files[0].HasData)
214 {
215#if (UNITY_WSA || UNITY_ANDROID) && !UNITY_EDITOR
216 Dispatcher.InvokeAsync(new ContextualizedAction<Stream>(LoadSkybox, files[0].OpenStream()));
217#else
218 LoadSkybox(files[0].OpenStream());
219#endif
220 }
221 else
222 {
223#if (UNITY_WSA || UNITY_ANDROID) && !UNITY_EDITOR
224 Dispatcher.InvokeAsync(new ContextualizedAction(ClearSkybox));
225#else
226 ClearSkybox();
227#endif
228 }
229 }
230
234 private IEnumerator DoLoadSkybox(Stream stream)
235 {
236 //Double frame waiting hack
237 yield return new WaitForEndOfFrame();
238 yield return new WaitForEndOfFrame();
239 if (_skyboxTexture != null) Destroy(_skyboxTexture);
240 ClearSkybox();
241 _skyboxTexture = HDRLoader.HDRLoader.Load(stream, out var gamma, out var exposure);
242 _skyboxMaterial.mainTexture = _skyboxTexture;
243 _skyboxExposureSlider.value = 1f;
244 OnSkyboxExposureChanged(exposure);
245 stream.Close();
246 SetLoading(false);
247 }
248
251 private void LoadSkybox(Stream stream)
252 {
253 SetLoading(true);
254 StartCoroutine(DoLoadSkybox(stream));
255 }
256
259 public void OnSkyboxExposureChanged(float exposure)
260 {
261 _skyboxMaterial.SetFloat("_Exposure", exposure);
262 _skyboxRenderer.material = _skyboxMaterial;
263 RenderSettings.skybox = _skyboxMaterial;
264 DynamicGI.UpdateEnvironment();
265 _reflectionProbe.RenderProbe();
266 }
267
269 protected override void Start()
270 {
271 base.Start();
272 AssetLoaderOptions = AssetLoader.CreateDefaultLoaderOptions();
273 AssetLoaderOptions.Timeout = 180;
274 ClearSkybox();
275 }
276
278 private void Update()
279 {
280 ProcessInput();
281 UpdateHUD();
282 }
283
285 protected virtual void ProcessInput()
286 {
287 ProcessInputInternal(Camera.main.transform);
288 }
289
294 private void ProcessInputInternal(Transform cameraTransform)
295 {
296 if (!EventSystem.current.IsPointerOverGameObject())
297 {
298 if (Input.GetMouseButton(0))
299 if (Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt))
300 {
301 _lightAngle.x = Mathf.Repeat(_lightAngle.x + Input.GetAxis("Mouse X"), 360f);
302 _lightAngle.y = Mathf.Clamp(_lightAngle.y + Input.GetAxis("Mouse Y"), -MaxPitch, MaxPitch);
303 }
304 else
305 {
306 UpdateCamera();
307 }
308
309 if (Input.GetMouseButton(2))
310 CameraPivot -= cameraTransform.up * Input.GetAxis("Mouse Y") * InputMultiplier +
311 cameraTransform.right * Input.GetAxis("Mouse X") * InputMultiplier;
312 CameraDistance = Mathf.Min(CameraDistance - Input.mouseScrollDelta.y * InputMultiplier,
313 InputMultiplier * (1f / InputMultiplierRatio) * MaxCameraDistanceRatio);
314 if (CameraDistance < 0f)
315 {
316 CameraPivot += cameraTransform.forward * -CameraDistance;
317 CameraDistance = 0f;
318 }
319
320 Skybox.transform.position = CameraPivot;
321 cameraTransform.position = CameraPivot + Quaternion.AngleAxis(CameraAngle.x, Vector3.up) *
322 Quaternion.AngleAxis(CameraAngle.y, Vector3.right) * new Vector3(0f, 0f,
324 cameraTransform.LookAt(CameraPivot);
325 _light.transform.position = CameraPivot + Quaternion.AngleAxis(_lightAngle.x, Vector3.up) *
326 Quaternion.AngleAxis(_lightAngle.y, Vector3.right) * Vector3.forward;
327 _light.transform.LookAt(CameraPivot);
328 }
329 }
330
332 private void UpdateHUD()
333 {
334 var animationState = CurrentAnimationState;
335 var time = animationState == null
336 ? 0f
337 : PlaybackSlider.value * animationState.length % animationState.length;
338 var seconds = time % 60f;
339 var milliseconds = time * 100f % 100f;
340 PlaybackTime.text = $"{seconds:00}:{milliseconds:00}";
341 var normalizedTime = animationState == null ? 0f : animationState.normalizedTime % 1f;
342 if (AnimationIsPlaying) PlaybackSlider.value = float.IsNaN(normalizedTime) ? 0f : normalizedTime;
343 var animationIsPlaying = AnimationIsPlaying;
344 if (_animation != null)
345 {
346 Play.gameObject.SetActive(!animationIsPlaying);
347 Stop.gameObject.SetActive(animationIsPlaying);
348 }
349 else
350 {
351 Play.gameObject.SetActive(true);
352 Stop.gameObject.SetActive(false);
353 }
354 }
355
358 protected override void OnBeginLoadModel(bool hasFiles)
359 {
360 base.OnBeginLoadModel(hasFiles);
361 if (hasFiles) _animations = null;
362 }
363
369 protected override void OnLoad(AssetLoaderContext assetLoaderContext)
370 {
371 base.OnLoad(assetLoaderContext);
373 if (assetLoaderContext.RootGameObject != null)
374 {
375 PlaybackAnimation.options.Clear();
376 if (assetLoaderContext.Options.AnimationType == AnimationType.Legacy)
377 {
378 _animation = assetLoaderContext.RootGameObject.GetComponent<Animation>();
379 if (_animation != null)
380 {
381 _animations = _animation.GetAllAnimationClips();
382 if (_animations.Count > 0)
383 {
384 for (var i = 0; i < _animations.Count; i++)
385 {
386 var animationClip = _animations[i];
387 PlaybackAnimation.options.Add(new Dropdown.OptionData(animationClip.name));
388 }
389
390 PlaybackAnimation.captionText.text = _animations[0].name;
391 }
392 else
393 {
394 _animation = null;
395 }
396 }
397
398 if (_animation == null) PlaybackAnimation.captionText.text = null;
399 }
400
401 PlaybackAnimation.value = 0;
403 RootGameObject = assetLoaderContext.RootGameObject;
404 }
405
407 }
408
412 protected virtual void ModelTransformChanged()
413 {
414 if (RootGameObject != null)
415 {
416 var bounds = RootGameObject.CalculateBounds();
417 Camera.main.FitToBounds(bounds, CameraDistanceRatio);
418 CameraDistance = Camera.main.transform.position.magnitude;
419 CameraPivot = bounds.center;
420 Skybox.transform.localScale = bounds.size.magnitude * SkyboxScale * Vector3.one;
421 InputMultiplier = bounds.size.magnitude * InputMultiplierRatio;
422 CameraAngle = Vector2.zero;
423 }
424 }
425
430 protected override void OnError(IContextualizedError contextualizedError)
431 {
432 base.OnError(contextualizedError);
434 _stopwatch?.Stop();
435 }
436
437 protected override void OnMaterialsLoad(AssetLoaderContext assetLoaderContext)
438 {
439 base.OnMaterialsLoad(assetLoaderContext);
440 _stopwatch.Stop();
441 Debug.Log("Loaded in:" + _stopwatch.Elapsed);
442 }
443 }
444}
UnityEngine.Debug Debug
Definition: TanodaServer.cs:19
TriLibCore.AssetLoaderOptions AssetLoaderOptions
Definition: TriLibLoader.cs:9
Represents a platform-specific file browser.
static void OpenFilePanelAsync(string title, string directory, string extension, bool multiselect, Action< IList< ItemWithStream > > cb)
Native open file dialog async
Represents a base class used in TriLib samples.
Text PlaybackTime
Animation playback time.
Vector2 CameraAngle
Current camera pitch and yaw angles.
Dropdown PlaybackAnimation
Animation selector.
void SetLoading(bool value)
Enables/disables the loading flag.
GameObject RootGameObject
Loaded game object.
Slider PlaybackSlider
Animation playback slider.
const float InputMultiplierRatio
Mouse input multiplier. Higher values will make the mouse movement more sensible.
void UpdateCamera()
Updates the Camera based on mouse Input.
const float MaxPitch
Maximum camera pitch and light pitch (rotation around local X-axis).
Represents a TriLib sample which allows the user to load models and HDR skyboxes from the local file-...
Definition: AssetViewer.cs:18
override void OnBeginLoadModel(bool hasFiles)
Event triggered when the user selects a file or cancels the Model selection dialog.
Definition: AssetViewer.cs:358
GameObject Skybox
Skybox game object.
Definition: AssetViewer.cs:42
const float MinCameraDistance
minimum camera distance.
Definition: AssetViewer.cs:32
override void Start()
Initializes the base-class and clears the skybox Texture.
Definition: AssetViewer.cs:269
virtual void ProcessInput()
Handles the input and moves the Camera accordingly.
Definition: AssetViewer.cs:285
override void PlaybackSliderChanged(float value)
Event triggered when the Animation slider value has been changed by the user.
Definition: AssetViewer.cs:189
void OnSkyboxExposureChanged(float exposure)
Event triggered when the skybox exposure Slider has changed.
Definition: AssetViewer.cs:259
override void StopAnimation()
Stop playing the selected animation.
Definition: AssetViewer.cs:172
float InputMultiplier
Input multiplier based on loaded model bounds.
Definition: AssetViewer.cs:87
override void OnLoad(AssetLoaderContext assetLoaderContext)
Event triggered when the Model Meshes and hierarchy are loaded.
Definition: AssetViewer.cs:369
float CameraDistance
Current camera distance.
Definition: AssetViewer.cs:72
override void PlayAnimation()
Plays the selected animation.
Definition: AssetViewer.cs:163
Vector3 CameraPivot
Current camera pivot position.
Definition: AssetViewer.cs:77
const float SkyboxScale
Skybox scale based on model bounds.
Definition: AssetViewer.cs:37
void LoadModelFromFile()
Shows the file picker for loading a model from the local file-system.
Definition: AssetViewer.cs:125
override void OnError(IContextualizedError contextualizedError)
Event is triggered when any error occurs.
Definition: AssetViewer.cs:430
virtual void ModelTransformChanged()
Changes the camera placement when the Model has changed.
Definition: AssetViewer.cs:412
void ClearSkybox()
Removes the skybox texture.
Definition: AssetViewer.cs:147
void LoadSkyboxFromFile()
Shows the file picker for loading a skybox from the local file-system.
Definition: AssetViewer.cs:133
const float CameraDistanceRatio
Camera distance ratio based on model bounds.
Definition: AssetViewer.cs:27
override void OnMaterialsLoad(AssetLoaderContext assetLoaderContext)
Event is triggered when the Model (including Textures and Materials) has been fully loaded.
Definition: AssetViewer.cs:437
override void PlaybackAnimationChanged(int index)
Switches to the animation selected on the Dropdown.
Definition: AssetViewer.cs:182
Represents a file picker extension filter.