Skip to content

Commit

Permalink
Add a component to display the list of models from gltf-Sample-Models…
Browse files Browse the repository at this point in the history
… and let you load them (KhronosGroup#420)

* Add a component to display the list of sample models and let you load them at runtime.

* Some improvements to the model list loader and adding a main scene to host it

* Fix warning suppression

* Address pr feedback.  Remove commented line.  Give a nice error if the model list json fails to download.  Also improve orbit camera so that scrolling the other list doesn't cause it to zoom.
  • Loading branch information
AdamMitchell-ms authored Apr 30, 2019
1 parent 71fc402 commit 7cdfa28
Show file tree
Hide file tree
Showing 8 changed files with 360 additions and 4 deletions.
17 changes: 13 additions & 4 deletions Examples/OrbitCameraController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ public class OrbitCameraController : MonoBehaviour
float x = 0.0f;
float y = 0.0f;

float prevMouseX;
float prevMouseY;

Quaternion rotation;

// Use this for initialization
Expand Down Expand Up @@ -59,7 +56,19 @@ void LateUpdate()
rotation = Quaternion.Euler(y, x, 0);
}

distance = Mathf.Clamp(distance * Mathf.Exp(-Input.GetAxis("Mouse ScrollWheel") * zoomSpeed), distanceMin, distanceMax);
var height = Display.main.renderingHeight;
var width = Display.main.renderingWidth;

var mouseOverRenderArea =
Input.mousePosition.x >= 0 &&
Input.mousePosition.x <= width &&
Input.mousePosition.y >= 0 &&
Input.mousePosition.y <= height;

if (Input.GetMouseButton(0) || mouseOverRenderArea)
{
distance = Mathf.Clamp(distance * Mathf.Exp(-Input.GetAxis("Mouse ScrollWheel") * zoomSpeed), distanceMin, distanceMax);
}

Vector3 negDistance = new Vector3(0.0f, 0.0f, -distance);
Vector3 position = rotation * negDistance + target.position;
Expand Down
3 changes: 3 additions & 0 deletions Scripts/GLTFComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class GLTFComponent : MonoBehaviour
public int MaximumLod = 300;
public int Timeout = 8;
public GLTFSceneImporter.ColliderType Collider = GLTFSceneImporter.ColliderType.None;
public GameObject LastLoadedScene { get; private set; } = null;

private AsyncCoroutineHelper asyncCoroutineHelper;

Expand Down Expand Up @@ -137,6 +138,8 @@ public async Task Load()
}
}

LastLoadedScene = sceneImporter.LastLoadedScene;

if (PlayAnimationOnLoad)
{
Animation[] animations = sceneImporter.LastLoadedScene.GetComponents<Animation>();
Expand Down
111 changes: 111 additions & 0 deletions Scripts/Tests/Editor/SampleModelListInspector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using UnityEditor;
using UnityEngine;
using UnityGLTF.Loader;

[CustomEditor(typeof(SampleModelList))]
public class SampleModelListInspector : Editor
{
private List<SampleModel> models = null;
private bool requestedModelList = false;
private Vector2 scroll = Vector2.zero;
private SampleModel currentModel = null;

public override void OnInspectorGUI()
{
EditorGUILayout.PropertyField(serializedObject.FindProperty(SampleModelList.LoaderFieldName));
EditorGUILayout.PropertyField(serializedObject.FindProperty(SampleModelList.PathRootFieldName));
EditorGUILayout.PropertyField(serializedObject.FindProperty(SampleModelList.ManifestRelativePathFieldName));
EditorGUILayout.PropertyField(serializedObject.FindProperty(SampleModelList.ModelRelativePathFieldName));
EditorGUILayout.PropertyField(serializedObject.FindProperty(SampleModelList.LoadThisFrameFieldName));

serializedObject.ApplyModifiedProperties();

if (!Application.isPlaying)
{
models = null;
requestedModelList = false;
scroll = Vector2.zero;
}
else
{
if (!requestedModelList)
{
requestedModelList = true;

DownloadSampleModelList();
}

EditorGUILayout.LabelField("Models:");

if (models != null)
{
scroll = EditorGUILayout.BeginScrollView(scroll);

foreach (var model in models)
{
EditorGUILayout.BeginHorizontal();
GUIStyle style = new GUIStyle(GUI.skin.label);
if (model == currentModel)
{
style.fontStyle = FontStyle.Bold;
}
GUILayout.Label(model.Name, style);

foreach (var variant in model.Variants)
{
var buttonPressed = GUILayout.Button(variant.Type);

if (buttonPressed)
{
currentModel = model;
LoadModel(model.Name, variant.Type, variant.FileName);
}
}

EditorGUILayout.EndHorizontal();
}

EditorGUILayout.EndScrollView();
}
}
}

private void LoadModel(string modelName, string variantType, string variantName)
{
string relativePath = $"{modelName}/{variantType}/{variantName}";

serializedObject.FindProperty(SampleModelList.ModelRelativePathFieldName).stringValue = relativePath;
serializedObject.FindProperty(SampleModelList.LoadThisFrameFieldName).boolValue = true;
serializedObject.ApplyModifiedProperties();
}

private async void DownloadSampleModelList()
{
var pathRoot = serializedObject.FindProperty(SampleModelList.PathRootFieldName).stringValue;
var manifestRelativePath = serializedObject.FindProperty(SampleModelList.ManifestRelativePathFieldName).stringValue;

var loader = new WebRequestLoader(pathRoot);
try
{
await loader.LoadStream(manifestRelativePath);
}
catch (HttpRequestException)
{
Debug.LogError($"Failed to download sample model list manifest from: {pathRoot}{manifestRelativePath}", serializedObject.targetObject);
throw;
}

loader.LoadedStream.Seek(0, SeekOrigin.Begin);

var streamReader = new StreamReader(loader.LoadedStream);

var reader = new JsonTextReader(streamReader);

reader.Read();
models = SampleModelListParser.ParseSampleModels(reader);
}
}
11 changes: 11 additions & 0 deletions Scripts/Tests/Editor/SampleModelListInspector.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

155 changes: 155 additions & 0 deletions Scripts/Tests/Editor/SampleModelListParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;

public class SampleModelVariant
{
public string Type;
public string FileName;
}

public class SampleModel
{
public string Name;
public string ScreenshotPath;
public List<SampleModelVariant> Variants;
}

public static class SampleModelListParser
{
public static List<SampleModel> ParseSampleModels(JsonReader reader)
{
var models = new List<SampleModel>();

ParseStartArray(reader, "models");

while (reader.TokenType != JsonToken.EndArray)
{
models.Add(ParseSampleModel(reader));
}

ParseEndArray(reader, "models");

return models;
}

private static SampleModel ParseSampleModel(JsonReader reader)
{
var result = new SampleModel();

ParseStartObject(reader, "model");

while (reader.TokenType != JsonToken.EndObject)
{
if (reader.TokenType != JsonToken.PropertyName)
{
throw new Exception("Failed to parse model name property");
}
var propertyName = reader.Value.ToString().ToLowerInvariant();
reader.Read();

switch (propertyName)
{
case "name":
result.Name = ParsePropertyValueAsString(reader, propertyName);
break;
case "screenshot":
result.ScreenshotPath = ParsePropertyValueAsString(reader, propertyName);
break;
case "variants":
result.Variants = ParseVariants(reader);
break;
}
}

ParseEndObject(reader, "model");

return result;
}

private static string ParsePropertyValueAsString(JsonReader reader, string propertyName)
{
if (reader.TokenType != JsonToken.String)
{
throw new Exception($"Failed to parse string value for {propertyName}");
}
var result = reader.Value.ToString();

reader.Read();

return result;
}

private static List<SampleModelVariant> ParseVariants(JsonReader reader)
{
var variants = new List<SampleModelVariant>();

ParseStartObject(reader, "variants");

while (reader.TokenType != JsonToken.EndObject)
{
variants.Add(ParseVariant(reader));
}

ParseEndObject(reader, "variants");

return variants;
}

private static SampleModelVariant ParseVariant(JsonReader reader)
{
var result = new SampleModelVariant();

if (reader.TokenType != JsonToken.PropertyName)
{
throw new Exception("Failed to parse model variant name");
}
result.Type = reader.Value.ToString();
reader.Read();

if (reader.TokenType != JsonToken.String)
{
throw new Exception("Failed to parse model variant filename");
}
result.FileName = reader.Value.ToString();
reader.Read();

return result;
}

private static void ParseStartObject(JsonReader reader, string objectName)
{
if (reader.TokenType != JsonToken.StartObject)
{
throw new Exception($"Failed to parse {objectName} start");
}
reader.Read();
}

private static void ParseEndObject(JsonReader reader, string objectName)
{
if (reader.TokenType != JsonToken.EndObject)
{
throw new Exception($"Failed to parse {objectName} end");
}
reader.Read();
}

private static void ParseStartArray(JsonReader reader, string objectName)
{
if (reader.TokenType != JsonToken.StartArray)
{
throw new Exception($"Failed to parse {objectName} start");
}
reader.Read();
}

private static void ParseEndArray(JsonReader reader, string objectName)
{
if (reader.TokenType != JsonToken.EndArray)
{
throw new Exception($"Failed to parse {objectName} end");
}
reader.Read();
}
}
11 changes: 11 additions & 0 deletions Scripts/Tests/Editor/SampleModelListParser.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions Scripts/Tests/SampleModelList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using UnityEngine;
using UnityGLTF;

public class SampleModelList : MonoBehaviour
{
public static string LoaderFieldName => nameof(loader);
public static string PathRootFieldName => nameof(pathRoot);
public static string ManifestRelativePathFieldName => nameof(manifestRelativePath);
public static string ModelRelativePathFieldName => nameof(modelRelativePath);
public static string LoadThisFrameFieldName => nameof(loadThisFrame);

[SerializeField]
private GLTFComponent loader = null;

[SerializeField]
private string pathRoot = "http://localhost:8080/glTF-Sample-Models/2.0/";

// Disable "unused private field" because it is accessed by SampleModelListInspector using serialization.
#pragma warning disable 414
[SerializeField]
private string manifestRelativePath = "model-index.json";
#pragma warning restore 414

[SerializeField]
private string modelRelativePath = null;

[SerializeField]
private bool loadThisFrame = false;

private async void Update()
{
if (loadThisFrame)
{
loadThisFrame = false;

var path = pathRoot + modelRelativePath;
if (loader.LastLoadedScene != null)
{
Destroy(loader.LastLoadedScene);
}
loader.GLTFUri = path;
await loader.Load();
}
}
}
11 changes: 11 additions & 0 deletions Scripts/Tests/SampleModelList.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 7cdfa28

Please sign in to comment.