-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathGLTFLoader.cs
More file actions
242 lines (187 loc) · 9.57 KB
/
Copy pathGLTFLoader.cs
File metadata and controls
242 lines (187 loc) · 9.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
using System;
using System.Linq;
using System.Threading.Tasks;
using Data;
using DCL.GLTFast.Wrappers;
using GLTFast;
using UnityEngine;
using UnityEngine.Networking;
using Object = UnityEngine.Object;
namespace Loading
{
public static class GLTFLoader
{
public static async Task<LoadedModel> LoadModel(BodyShape bodyShape, EntityDefinition entityDefinition,
Transform parent)
{
var representation = entityDefinition[bodyShape];
var importer = new GltfImport(
downloadProvider: new BinaryDownloadProvider(representation.Files),
materialGenerator: new ToonMaterialGenerator()
);
var importSettings = new ImportSettings
{
NodeNameMethod = NameImportMethod.OriginalUnique,
AnisotropicFilterLevel = 0,
GenerateMipMaps = false,
AnimationMethod = AnimationMethod.None,
};
var file = representation.Files[representation.MainFile];
Debug.Log($"Loading GLB: {representation.MainFile} - {file}");
var success = await importer.Load(file, importSettings);
if (success)
{
var root = new GameObject(entityDefinition.Category);
root.SetActive(false);
root.transform.SetParent(parent, false);
var instantiator = new GameObjectInstantiator(importer, root.transform);
await importer.InstantiateSceneAsync(instantiator);
Sanitize(root.transform);
Debug.Log($"GLB loaded: {representation.MainFile}");
return new LoadedModel(entityDefinition, root, importer);
}
throw new Exception($"Failed to load GLB: {representation.MainFile}");
}
public static async Task<LoadedFacialFeature> LoadFacialFeature(BodyShape bodyShape,
EntityDefinition entityDefinition)
{
var rep = entityDefinition[bodyShape];
var mainTexture = await LoadTexture(rep.Files[rep.MainFile]);
if (!mainTexture) throw new Exception($"Failed to load texture {rep.Files[rep.MainFile]}");
var maskTexture = rep.Files.Count == 2
? await LoadTexture(rep.Files[rep.Files.Keys.First(x => x != rep.MainFile)])
: null;
return new LoadedFacialFeature(entityDefinition, mainTexture, maskTexture);
async Awaitable<Texture2D> LoadTexture(string file)
{
using var webRequest = UnityWebRequestTexture.GetTexture(file, true);
await webRequest.SendWebRequest();
if (webRequest.result != UnityWebRequest.Result.Success)
{
Debug.LogError($"Failed to load texture {file}: {webRequest.error}");
return null;
}
return DownloadHandlerTexture.GetContent(webRequest);
}
}
public static async Awaitable<LoadedEmote> LoadEmote(BodyShape bodyShape, EntityDefinition entityDefinition, Transform propParent)
{
var rep = entityDefinition[bodyShape];
var importer = new GltfImport(
materialGenerator: new DecentralandMaterialGenerator("DCL/Scene", true),
downloadProvider: new BinaryDownloadProvider(rep.Files)
);
var importSettings = new ImportSettings
{
NodeNameMethod = NameImportMethod.OriginalUnique,
AnisotropicFilterLevel = 0,
AnimationMethod = AnimationMethod.Legacy,
GenerateMipMaps = false,
};
var file = rep.Files[rep.MainFile];
Debug.Log($"Loading Emote: {rep.MainFile} - {file}");
var success = await importer.Load(file, importSettings);
AudioClip audioClip = null;
// TODO: Clean this up cmon
var audioFile = rep.Files.FirstOrDefault(kvp =>
kvp.Key.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase) ||
kvp.Key.EndsWith(".ogg", StringComparison.OrdinalIgnoreCase) ||
kvp.Key.EndsWith(".wav", StringComparison.OrdinalIgnoreCase));
if (audioFile.Key != null)
{
Debug.Log($"Loading audio clip: {audioFile.Key} - {audioFile.Value}");
var audioType = AudioType.UNKNOWN;
if (audioFile.Key.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase))
audioType = AudioType.MPEG;
if (audioFile.Key.EndsWith(".wav", StringComparison.OrdinalIgnoreCase))
audioType = AudioType.WAV;
if (audioFile.Key.EndsWith(".ogg", StringComparison.OrdinalIgnoreCase))
audioType = AudioType.OGGVORBIS;
using var www = UnityWebRequestMultimedia.GetAudioClip(audioFile.Value, audioType);
await www.SendWebRequest();
if (www.result == UnityWebRequest.Result.ConnectionError)
{
Debug.Log(www.error);
}
else
{
audioClip = DownloadHandlerAudioClip.GetContent(www);
}
}
if (success)
{
var clips = importer.GetAnimationClips();
var looping = (entityDefinition.Flags & EntityFlags.Looping) != 0;
Debug.Log($"Loaded emote: {rep.MainFile} with clips: {clips.Length}");
var (avatarClip, propClip) = ExtractClips(clips);
GameObject prop = null;
Animation propAnim = null;
avatarClip.wrapMode = looping ? WrapMode.Loop : WrapMode.Clamp;
if (propClip != null)
{
// Avatar animation controls prop clip so we don't auto loop it
propClip.wrapMode = WrapMode.Clamp;
// We have a prop we need to deal with
Debug.Log("Lading emote prop");
prop = new GameObject("emote");
prop.SetActive(false);
prop.transform.SetParent(propParent, false);
propAnim = prop.AddComponent<Animation>();
propClip.name = entityDefinition.URN;
propAnim.AddClip(propClip, entityDefinition.URN);
propAnim.clip = propClip;
await importer.InstantiateMainSceneAsync(prop.transform);
Sanitize(prop.transform);
}
return new LoadedEmote(entityDefinition, avatarClip, audioClip, prop, propAnim, importer);
}
throw new NotSupportedException($"Failed to load emote: {rep.MainFile}");
}
private static (AnimationClip avatarCliP, AnimationClip propClip) ExtractClips(AnimationClip[] clips)
{
// Note, some GLB's just don't have an animation that ends with _Avatar, because of course they bloody don't.
// Even though conventions say they should: https://docs.decentraland.org/creator/emotes/props-and-sounds/#naming-conventions
// Like this one: urn:decentraland:matic:collections-v2:0xb5e24ada4096b86ce3cf7af5119f19ed6089a80b:0
// Conventions are often not followed, so we try to figure out which clip is which
// Conventions (if it's just one clip, it's the avatar clip)
var avatarClip = clips.Length == 1 ? clips[0] : clips.FirstOrDefault(c => c.name.EndsWith("_Avatar", StringComparison.OrdinalIgnoreCase));
var propClip = clips.Length == 1 ?
null :
clips.FirstOrDefault(c => c.name.EndsWith("_Prop", StringComparison.OrdinalIgnoreCase));
// If the prop clip was found but the avatar clip was not, we can infer that the avatar clip is
// the one that isn't the prop clip
if (avatarClip == null && propClip != null)
{
avatarClip = clips.FirstOrDefault(c => c != propClip);
}
// If the avatar clip was found, but the prop clip wasn't, we can infer that the prop clip is
// the one that isn't the avatar clip
if (avatarClip != null && propClip == null && clips.Length == 2)
{
propClip = clips.FirstOrDefault(c => c != avatarClip);
}
if (avatarClip == null) throw new InvalidOperationException("Failed to determine animation clip assignment from names: " + clips.Select(c => c.name).Aggregate((a, b) => a + ", " + b));
return (avatarClip, propClip);
}
/// <summary>
/// Examples of urns that need this:
///
/// urn:decentraland:matic:collections-v2:0xee8ae4c668edd43b34b98934d6d2ff82e41e6488:0
/// urn:decentraland:matic:collections-v2:0xee8ae4c668edd43b34b98934d6d2ff82e41e6488:5
/// </summary>
private static void Sanitize(Transform root)
{
// If the wearable has 2 root objects they get placed under a Scene object, and we don't want that
if (root.childCount == 1 && root.GetChild(0).name == "Scene")
{
var sceneChild = root.GetChild(0);
while (sceneChild.childCount > 0)
{
var child = sceneChild.GetChild(0);
child.SetParent(root, true);
}
Object.Destroy(sceneChild.gameObject);
}
}
}
}