@babylonjs/loaders 5.25.0 → 5.26.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -4,6 +4,7 @@ import { Color3 } from "@babylonjs/core/Maths/math.color.js";
4
4
  import { Tools } from "@babylonjs/core/Misc/tools.js";
5
5
  import { Camera } from "@babylonjs/core/Cameras/camera.js";
6
6
  import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera.js";
7
+ import { AnimationKeyInterpolation } from "@babylonjs/core/Animations/animationKey.js";
7
8
  import { AnimationGroup } from "@babylonjs/core/Animations/animationGroup.js";
8
9
  import { Bone } from "@babylonjs/core/Bones/bone.js";
9
10
  import { Skeleton } from "@babylonjs/core/Bones/skeleton.js";
@@ -20,6 +21,27 @@ import { GLTFFileLoader, GLTFLoaderState, GLTFLoaderCoordinateSystemMode, GLTFLo
20
21
  import { DecodeBase64UrlToBinary, IsBase64DataUrl, LoadFileError } from "@babylonjs/core/Misc/fileTools.js";
21
22
  import { Logger } from "@babylonjs/core/Misc/logger.js";
22
23
  import { BoundingInfo } from "@babylonjs/core/Culling/boundingInfo.js";
24
+ import { nodeAnimationData } from "./glTFLoaderAnimation.js";
25
+ // https://stackoverflow.com/a/48218209
26
+ function mergeDeep(...objects) {
27
+ const isObject = (obj) => obj && typeof obj === "object";
28
+ return objects.reduce((prev, obj) => {
29
+ Object.keys(obj).forEach((key) => {
30
+ const pVal = prev[key];
31
+ const oVal = obj[key];
32
+ if (Array.isArray(pVal) && Array.isArray(oVal)) {
33
+ prev[key] = pVal.concat(...oVal);
34
+ }
35
+ else if (isObject(pVal) && isObject(oVal)) {
36
+ prev[key] = mergeDeep(pVal, oVal);
37
+ }
38
+ else {
39
+ prev[key] = oVal;
40
+ }
41
+ });
42
+ return prev;
43
+ }, {});
44
+ }
23
45
  /**
24
46
  * Helper class for working with arrays when loading the glTF asset
25
47
  */
@@ -49,26 +71,6 @@ export class ArrayItem {
49
71
  }
50
72
  }
51
73
  }
52
- // https://stackoverflow.com/a/48218209
53
- function mergeDeep(...objects) {
54
- const isObject = (obj) => obj && typeof obj === "object";
55
- return objects.reduce((prev, obj) => {
56
- Object.keys(obj).forEach((key) => {
57
- const pVal = prev[key];
58
- const oVal = obj[key];
59
- if (Array.isArray(pVal) && Array.isArray(oVal)) {
60
- prev[key] = pVal.concat(...oVal);
61
- }
62
- else if (isObject(pVal) && isObject(oVal)) {
63
- prev[key] = mergeDeep(pVal, oVal);
64
- }
65
- else {
66
- prev[key] = oVal;
67
- }
68
- });
69
- return prev;
70
- }, {});
71
- }
72
74
  /**
73
75
  * The glTF 2.0 loader
74
76
  */
@@ -1114,6 +1116,7 @@ export class GLTFLoader {
1114
1116
  babylonCamera._parentContainer = this._assetContainer;
1115
1117
  this._babylonScene._blockEntityCollection = false;
1116
1118
  babylonCamera.ignoreParentScaling = true;
1119
+ camera._babylonCamera = babylonCamera;
1117
1120
  babylonCamera.rotation = new Vector3(0, Math.PI, 0);
1118
1121
  switch (camera.type) {
1119
1122
  case "perspective" /* PERSPECTIVE */: {
@@ -1146,8 +1149,6 @@ export class GLTFLoader {
1146
1149
  GLTFLoader.AddPointerMetadata(babylonCamera, context);
1147
1150
  this._parent.onCameraLoadedObservable.notifyObservers(babylonCamera);
1148
1151
  assign(babylonCamera);
1149
- // register the camera to be used later.
1150
- camera._babylonCamera = babylonCamera;
1151
1152
  this.logClose();
1152
1153
  return Promise.all(promises).then(() => {
1153
1154
  return babylonCamera;
@@ -1177,32 +1178,190 @@ export class GLTFLoader {
1177
1178
  * @returns A promise that resolves with the loaded Babylon animation group when the load is complete
1178
1179
  */
1179
1180
  loadAnimationAsync(context, animation) {
1180
- // turn everything into pointer
1181
- for (const channel of animation.channels) {
1182
- if (channel.target.path == "pointer" /* POINTER */) {
1183
- continue;
1184
- }
1185
- // decorate the channel with a KHR_animation_pointer extension.
1186
- channel.target.extensions = channel.target.extensions || {};
1187
- channel.target.extensions.KHR_animation_pointer = {
1188
- pointer: `/nodes/${channel.target.node}/${channel.target.path}`,
1189
- };
1190
- channel.target.path = "pointer" /* POINTER */;
1191
- delete channel.target.node;
1192
- // ensure to declare extension used.
1193
- this._gltf.extensionsUsed = this._gltf.extensionsUsed || [];
1194
- if (this._gltf.extensionsUsed.indexOf(GLTFLoader._KHRAnimationPointerName) === -1) {
1195
- this._gltf.extensionsUsed.push(GLTFLoader._KHRAnimationPointerName);
1196
- }
1181
+ const promise = this._extensionsLoadAnimationAsync(context, animation);
1182
+ if (promise) {
1183
+ return promise;
1197
1184
  }
1198
- // create the animation group to be passed to extension.
1199
1185
  this._babylonScene._blockEntityCollection = !!this._assetContainer;
1200
1186
  const babylonAnimationGroup = new AnimationGroup(animation.name || `animation${animation.index}`, this._babylonScene);
1201
1187
  babylonAnimationGroup._parentContainer = this._assetContainer;
1202
1188
  this._babylonScene._blockEntityCollection = false;
1203
1189
  animation._babylonAnimationGroup = babylonAnimationGroup;
1204
- const promise = this._extensionsLoadAnimationAsync(context, animation);
1205
- return promise ?? Promise.resolve(animation._babylonAnimationGroup);
1190
+ const promises = new Array();
1191
+ ArrayItem.Assign(animation.channels);
1192
+ ArrayItem.Assign(animation.samplers);
1193
+ for (const channel of animation.channels) {
1194
+ promises.push(this._loadAnimationChannelAsync(`${context}/channels/${channel.index}`, context, animation, channel, (babylonTarget, babylonAnimation) => {
1195
+ babylonTarget.animations = babylonTarget.animations || [];
1196
+ babylonTarget.animations.push(babylonAnimation);
1197
+ babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonTarget);
1198
+ }));
1199
+ }
1200
+ return Promise.all(promises).then(() => {
1201
+ babylonAnimationGroup.normalize(0);
1202
+ return babylonAnimationGroup;
1203
+ });
1204
+ }
1205
+ /**
1206
+ * @hidden
1207
+ * Loads a glTF animation channel.
1208
+ * @param context The context when loading the asset
1209
+ * @param animationContext The context of the animation when loading the asset
1210
+ * @param animation The glTF animation property
1211
+ * @param channel The glTF animation channel property
1212
+ * @param onLoad Called for each animation loaded
1213
+ * @returns A void promise that resolves when the load is complete
1214
+ */
1215
+ _loadAnimationChannelAsync(context, animationContext, animation, channel, onLoad) {
1216
+ const promise = this._extensionsLoadAnimationChannelAsync(context, animationContext, animation, channel, onLoad);
1217
+ if (promise) {
1218
+ return promise;
1219
+ }
1220
+ if (channel.target.node == undefined) {
1221
+ return Promise.resolve();
1222
+ }
1223
+ const targetNode = ArrayItem.Get(`${context}/target/node`, this._gltf.nodes, channel.target.node);
1224
+ // Ignore animations that have no animation targets.
1225
+ if ((channel.target.path === "weights" /* WEIGHTS */ && !targetNode._numMorphTargets) ||
1226
+ (channel.target.path !== "weights" /* WEIGHTS */ && !targetNode._babylonTransformNode)) {
1227
+ return Promise.resolve();
1228
+ }
1229
+ let properties;
1230
+ switch (channel.target.path) {
1231
+ case "translation" /* TRANSLATION */: {
1232
+ properties = nodeAnimationData.translation;
1233
+ break;
1234
+ }
1235
+ case "rotation" /* ROTATION */: {
1236
+ properties = nodeAnimationData.rotation;
1237
+ break;
1238
+ }
1239
+ case "scale" /* SCALE */: {
1240
+ properties = nodeAnimationData.scale;
1241
+ break;
1242
+ }
1243
+ case "weights" /* WEIGHTS */: {
1244
+ properties = nodeAnimationData.weights;
1245
+ break;
1246
+ }
1247
+ default: {
1248
+ throw new Error(`${context}/target/path: Invalid value (${channel.target.path})`);
1249
+ }
1250
+ }
1251
+ const targetInfo = {
1252
+ target: targetNode,
1253
+ properties: properties,
1254
+ };
1255
+ return this._loadAnimationChannelFromTargetInfoAsync(context, animationContext, animation, channel, targetInfo, onLoad);
1256
+ }
1257
+ /**
1258
+ * @hidden
1259
+ * Loads a glTF animation channel.
1260
+ * @param context The context when loading the asset
1261
+ * @param animationContext The context of the animation when loading the asset
1262
+ * @param animation The glTF animation property
1263
+ * @param channel The glTF animation channel property
1264
+ * @param targetInfo The glTF target and properties
1265
+ * @param onLoad Called for each animation loaded
1266
+ * @returns A void promise that resolves when the load is complete
1267
+ */
1268
+ _loadAnimationChannelFromTargetInfoAsync(context, animationContext, animation, channel, targetInfo, onLoad) {
1269
+ const fps = this.parent.targetFps;
1270
+ const invfps = 1 / fps;
1271
+ const sampler = ArrayItem.Get(`${context}/sampler`, animation.samplers, channel.sampler);
1272
+ return this._loadAnimationSamplerAsync(`${animationContext}/samplers/${channel.sampler}`, sampler).then((data) => {
1273
+ let numAnimations = 0;
1274
+ // Extract the corresponding values from the read value.
1275
+ // GLTF values may be dispatched to several Babylon properties.
1276
+ // For example, baseColorFactor [`r`, `g`, `b`, `a`] is dispatched to
1277
+ // - albedoColor as Color3(`r`, `g`, `b`)
1278
+ // - alpha as `a`
1279
+ for (const property of targetInfo.properties) {
1280
+ const stride = property.getStride(targetInfo.target);
1281
+ const input = data.input;
1282
+ const output = data.output;
1283
+ const keys = new Array(input.length);
1284
+ let outputOffset = 0;
1285
+ switch (data.interpolation) {
1286
+ case "STEP" /* STEP */: {
1287
+ for (let index = 0; index < input.length; index++) {
1288
+ const value = property.getValue(targetInfo.target, output, outputOffset, 1);
1289
+ outputOffset += stride;
1290
+ keys[index] = {
1291
+ frame: input[index] * fps,
1292
+ value: value,
1293
+ interpolation: AnimationKeyInterpolation.STEP,
1294
+ };
1295
+ }
1296
+ break;
1297
+ }
1298
+ case "CUBICSPLINE" /* CUBICSPLINE */: {
1299
+ for (let index = 0; index < input.length; index++) {
1300
+ const inTangent = property.getValue(targetInfo.target, output, outputOffset, invfps);
1301
+ outputOffset += stride;
1302
+ const value = property.getValue(targetInfo.target, output, outputOffset, 1);
1303
+ outputOffset += stride;
1304
+ const outTangent = property.getValue(targetInfo.target, output, outputOffset, invfps);
1305
+ outputOffset += stride;
1306
+ keys[index] = {
1307
+ frame: input[index] * fps,
1308
+ inTangent: inTangent,
1309
+ value: value,
1310
+ outTangent: outTangent,
1311
+ };
1312
+ }
1313
+ break;
1314
+ }
1315
+ case "LINEAR" /* LINEAR */: {
1316
+ for (let index = 0; index < input.length; index++) {
1317
+ const value = property.getValue(targetInfo.target, output, outputOffset, 1);
1318
+ outputOffset += stride;
1319
+ keys[index] = {
1320
+ frame: input[index] * fps,
1321
+ value: value,
1322
+ };
1323
+ }
1324
+ break;
1325
+ }
1326
+ }
1327
+ if (outputOffset > 0) {
1328
+ const name = `${animation.name || `animation${animation.index}`}_channel${channel.index}_${numAnimations}`;
1329
+ property.buildAnimations(targetInfo.target, name, fps, keys, (babylonAnimatable, babylonAnimation) => {
1330
+ ++numAnimations;
1331
+ onLoad(babylonAnimatable, babylonAnimation);
1332
+ });
1333
+ }
1334
+ }
1335
+ });
1336
+ }
1337
+ _loadAnimationSamplerAsync(context, sampler) {
1338
+ if (sampler._data) {
1339
+ return sampler._data;
1340
+ }
1341
+ const interpolation = sampler.interpolation || "LINEAR" /* LINEAR */;
1342
+ switch (interpolation) {
1343
+ case "STEP" /* STEP */:
1344
+ case "LINEAR" /* LINEAR */:
1345
+ case "CUBICSPLINE" /* CUBICSPLINE */: {
1346
+ break;
1347
+ }
1348
+ default: {
1349
+ throw new Error(`${context}/interpolation: Invalid value (${sampler.interpolation})`);
1350
+ }
1351
+ }
1352
+ const inputAccessor = ArrayItem.Get(`${context}/input`, this._gltf.accessors, sampler.input);
1353
+ const outputAccessor = ArrayItem.Get(`${context}/output`, this._gltf.accessors, sampler.output);
1354
+ sampler._data = Promise.all([
1355
+ this._loadFloatAccessorAsync(`/accessors/${inputAccessor.index}`, inputAccessor),
1356
+ this._loadFloatAccessorAsync(`/accessors/${outputAccessor.index}`, outputAccessor),
1357
+ ]).then(([inputData, outputData]) => {
1358
+ return {
1359
+ input: inputData,
1360
+ interpolation: interpolation,
1361
+ output: outputData,
1362
+ };
1363
+ });
1364
+ return sampler._data;
1206
1365
  }
1207
1366
  /**
1208
1367
  * Loads a glTF buffer.
@@ -1746,7 +1905,6 @@ export class GLTFLoader {
1746
1905
  /**
1747
1906
  * Adds a JSON pointer to the metadata of the Babylon object at `<object>.metadata.gltf.pointers`.
1748
1907
  * @param babylonObject the Babylon object with metadata
1749
- * @param babylonObject.metadata
1750
1908
  * @param pointer the JSON pointer
1751
1909
  */
1752
1910
  static AddPointerMetadata(babylonObject, pointer) {
@@ -2003,6 +2161,9 @@ export class GLTFLoader {
2003
2161
  _extensionsLoadAnimationAsync(context, animation) {
2004
2162
  return this._applyExtensions(animation, "loadAnimation", (extension) => extension.loadAnimationAsync && extension.loadAnimationAsync(context, animation));
2005
2163
  }
2164
+ _extensionsLoadAnimationChannelAsync(context, animationContext, animation, channel, onLoad) {
2165
+ return this._applyExtensions(animation, "loadAnimationChannel", (extension) => extension._loadAnimationChannelAsync && extension._loadAnimationChannelAsync(context, animationContext, animation, channel, onLoad));
2166
+ }
2006
2167
  _extensionsLoadSkinAsync(context, node, skin) {
2007
2168
  return this._applyExtensions(skin, "loadSkin", (extension) => extension._loadSkinAsync && extension._loadSkinAsync(context, node, skin));
2008
2169
  }
@@ -2096,10 +2257,6 @@ export class GLTFLoader {
2096
2257
  this._parent._endPerformanceCounter(counterName);
2097
2258
  }
2098
2259
  }
2099
- /** @internal */
2100
- // note : KHR_animation_pointer is used to load animation in ALL case, turning everything
2101
- // into pointer. This is the reason why this value is located here.
2102
- GLTFLoader._KHRAnimationPointerName = "KHR_animation_pointer";
2103
2260
  GLTFLoader._RegisteredExtensions = {};
2104
2261
  /**
2105
2262
  * The default glTF sampler.