@babylonjs/loaders 9.29.0 → 9.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -129,7 +129,7 @@ export interface IKHRInteractivityExportProvider {
129
129
  /**
130
130
  * Builds the canonical extension after glTF entity indices are finalized.
131
131
  * @param context final serializer remapping context
132
- * @returns ratified KHR_interactivity extension payload
132
+ * @returns KHR_interactivity extension payload
133
133
  */
134
134
  build(context: IKHRInteractivitySerializerContext): IKHRInteractivity;
135
135
  }
@@ -141,6 +141,12 @@ export interface IKHRInteractivityExportOptions {
141
141
  document?: IKHRInteractivityDocument;
142
142
  /** Loader glTF tree used to resolve original entity references. */
143
143
  sourceGLTF?: IGLTF;
144
+ /**
145
+ * Whether export patches the retained source document instead of reserializing the scene.
146
+ * Preserves source entity indices, extension-backed pointers, and imported compatibility flow sockets.
147
+ * Requires sourceGLTF and buildWithSourceIndices; runtime scene changes are not exported.
148
+ */
149
+ preserveSourceDocument?: boolean;
144
150
  /** Root default graph index. Defaults to the canonical document selection or zero. */
145
151
  defaultGraphIndex?: number;
146
152
  /** Target animation frame rate used by imported animation composites. Defaults to 60. */
@@ -171,6 +177,7 @@ export declare class KHRInteractivityExportPlan implements IKHRInteractivityExpo
171
177
  private _additionalExtensionsUsed;
172
178
  private readonly _rootDiagnostics;
173
179
  private _isPreflight;
180
+ private _sourceIndexContext?;
174
181
  /** Whether KHR_interactivity is required in the exported asset. */
175
182
  readonly required: boolean;
176
183
  /** Additional operation/companion extensions referenced by the exported graphs. */
@@ -401,6 +401,9 @@ export class KHRInteractivityExportPlan {
401
401
  this._additionalExtensionsUsed = [];
402
402
  this._rootDiagnostics = [];
403
403
  this._isPreflight = false;
404
+ if (_options.preserveSourceDocument && !_options.sourceGLTF) {
405
+ throw new Error("Source-preserving interactivity export requires the source glTF document.");
406
+ }
404
407
  this.required = _options.required ?? true;
405
408
  this.additionalExtensionsRequired = Array.from(new Set(_options.additionalExtensionsRequired ?? [])).sort();
406
409
  this._refreshAnalysis();
@@ -463,6 +466,9 @@ export class KHRInteractivityExportPlan {
463
466
  * @returns canonical KHR_interactivity payload
464
467
  */
465
468
  build(context) {
469
+ if (this._options.preserveSourceDocument && context !== this._sourceIndexContext) {
470
+ throw new Error("Source-preserving interactivity export must use buildWithSourceIndices().");
471
+ }
466
472
  this._refreshAnalysis();
467
473
  if (!this._analysis.representable) {
468
474
  throw new KHRInteractivityExportError(this._analysis.diagnostics);
@@ -509,9 +515,12 @@ export class KHRInteractivityExportPlan {
509
515
  return provenance?.source ? CloneKHRInteractivityGraph(provenance.source) : undefined;
510
516
  }
511
517
  _createSourceIndexContext() {
518
+ if (this._sourceIndexContext) {
519
+ return this._sourceIndexContext;
520
+ }
512
521
  const sourceGLTF = this._options.sourceGLTF;
513
522
  const indexOrUndefined = (index) => (index !== undefined && index >= 0 ? index : undefined);
514
- return {
523
+ return (this._sourceIndexContext = {
515
524
  getNodeCount: () => sourceGLTF?.nodes?.length ?? 0,
516
525
  getNodeIndex: (node) => indexOrUndefined(sourceGLTF?.nodes?.findIndex((candidate) => candidate._babylonTransformNode === node)),
517
526
  getAnimationIndex: (animation) => indexOrUndefined(sourceGLTF?.animations?.findIndex((candidate) => candidate._babylonAnimationGroup === animation)),
@@ -519,7 +528,7 @@ export class KHRInteractivityExportPlan {
519
528
  getMaterialIndex: (material) => indexOrUndefined(sourceGLTF?.materials?.findIndex((candidate) => Object.values(candidate._data ?? {}).some((entry) => entry.babylonMaterial === material))),
520
529
  getRootIndex: (collection, entity) => this._getSourceRootIndex(collection, entity),
521
530
  setNodeExtension: () => { },
522
- };
531
+ });
523
532
  }
524
533
  _validateBuildWithSourceIndices() {
525
534
  const diagnostics = [];
@@ -653,8 +662,9 @@ export class KHRInteractivityExportPlan {
653
662
  const declaration = source.declarations?.[sourceNode.declaration];
654
663
  const declarationModel = this._options.document?.graphs[graphIndex]?.declarations[sourceNode.declaration];
655
664
  const operation = declaration ? _FullOperationName(declaration.op, declaration.extension) : "";
656
- const mapping = declaration && declarationModel?.support !== "unsupported-extension" ? getMappingForDeclaration(declaration, false) : undefined;
657
665
  const blocks = groups.get(nodeIndex) ?? [];
666
+ const hasCompatibilityRuntime = this._options.preserveSourceDocument && blocks.some((block) => block.getClassName() !== _UnsupportedBlockClassName);
667
+ const mapping = declaration && (declarationModel?.support !== "unsupported-extension" || hasCompatibilityRuntime) ? getMappingForDeclaration(declaration, false) : undefined;
658
668
  const diagnostics = [];
659
669
  let classification = mapping && mapping.blocks.length > 1 ? "inverse-composite" : "exact";
660
670
  if (!declaration) {
@@ -667,7 +677,7 @@ export class KHRInteractivityExportPlan {
667
677
  message: `Declaration index ${sourceNode.declaration} is unavailable.`,
668
678
  });
669
679
  }
670
- else if (declaration.extension === "BABYLON") {
680
+ else if (declaration.extension === "BABYLON" && !this._options.preserveSourceDocument) {
671
681
  classification = "unsupported";
672
682
  _PushDiagnostic(diagnostics, {
673
683
  code: "BLOCK_UNSUPPORTED",
@@ -1021,7 +1031,7 @@ export class KHRInteractivityExportPlan {
1021
1031
  });
1022
1032
  }
1023
1033
  }
1024
- return graph;
1034
+ return this._options.preserveSourceDocument && _JsonEquivalent(graph, analysis.source) ? CloneKHRInteractivityGraph(analysis.source) : graph;
1025
1035
  }
1026
1036
  _rebuildNode(graph, sourceNode, logicalNode, graphAnalysis, context, diagnostics) {
1027
1037
  const node = _CloneJson(sourceNode);
@@ -1192,7 +1202,7 @@ export class KHRInteractivityExportPlan {
1192
1202
  ? _CloneJson(sourceNode.flows[socket])
1193
1203
  : { node: provenance.nodeIndex };
1194
1204
  rebuiltFlow.node = provenance.nodeIndex;
1195
- if (provenance.socket === "in") {
1205
+ if (provenance.socket === "in" && !(this._options.preserveSourceDocument && rebuiltFlow.socket === "in")) {
1196
1206
  delete rebuiltFlow.socket;
1197
1207
  }
1198
1208
  else {
@@ -1201,7 +1211,11 @@ export class KHRInteractivityExportPlan {
1201
1211
  _SetOwnProperty(rebuiltFlows, socket, rebuiltFlow);
1202
1212
  }
1203
1213
  if (Object.keys(rebuiltFlows).length > 0) {
1204
- node.flows = rebuiltFlows;
1214
+ node.flows = this._options.preserveSourceDocument
1215
+ ? Object.fromEntries([...new Set([...Object.keys(sourceNode.flows ?? {}), ...Object.keys(rebuiltFlows)])]
1216
+ .filter((socket) => Object.prototype.hasOwnProperty.call(rebuiltFlows, socket))
1217
+ .map((socket) => [socket, rebuiltFlows[socket]]))
1218
+ : rebuiltFlows;
1205
1219
  }
1206
1220
  else {
1207
1221
  delete node.flows;
@@ -1318,7 +1332,7 @@ export class KHRInteractivityExportPlan {
1318
1332
  return Object.prototype.hasOwnProperty.call(node.flows ?? {}, socket);
1319
1333
  }
1320
1334
  const fixed = _GetOwn(mappings, socket);
1321
- if (fixed && !fixed.compatibilityOnly) {
1335
+ if (fixed && (!fixed.compatibilityOnly || (this._options.preserveSourceDocument && Object.prototype.hasOwnProperty.call(node.flows ?? {}, socket)))) {
1322
1336
  return true;
1323
1337
  }
1324
1338
  if (!Object.keys(mappings ?? {}).some((key) => key.startsWith("[") && key.endsWith("]"))) {
@@ -1465,7 +1479,7 @@ export class KHRInteractivityExportPlan {
1465
1479
  const rebuiltReference = _CloneJson(sourceValue);
1466
1480
  delete rebuiltReference.value;
1467
1481
  rebuiltReference.node = provenance.nodeIndex;
1468
- if (provenance.socket === "value") {
1482
+ if (provenance.socket === "value" && !(this._options.preserveSourceDocument && rebuiltReference.socket === "value")) {
1469
1483
  delete rebuiltReference.socket;
1470
1484
  }
1471
1485
  else {
@@ -1657,8 +1671,7 @@ export class KHRInteractivityExportPlan {
1657
1671
  _JsonEquivalent(_GetConfigurationBlock(logicalNode, property)?.config?.[property.name], provenance.runtimeValue) &&
1658
1672
  !_IsValidConfigurationValue(provenance.sourceValue, property, graph, this._options.sourceGLTF?.nodes?.length);
1659
1673
  if (!preservesFallback && property.indexSource === "assetNodes" && current.length === 1 && typeof current[0] === "number") {
1660
- const sourceNode = this._options.sourceGLTF?.nodes?.[current[0]]?._babylonTransformNode;
1661
- const remapped = sourceNode ? context.getNodeIndex(sourceNode) : undefined;
1674
+ const remapped = this._getRemappedRootIndex("nodes", current[0], context);
1662
1675
  if (remapped === undefined) {
1663
1676
  _PushDiagnostic(diagnostics, {
1664
1677
  code: "REFERENCE_UNRESOLVED",
@@ -1701,6 +1714,9 @@ export class KHRInteractivityExportPlan {
1701
1714
  const rootMatch = typeof rootReference === "string" ? /^\/([^/]+)$/.exec(rootReference) : undefined;
1702
1715
  collection = rootMatch?.[1] ?? "";
1703
1716
  if (!_KnownIndexedRootCollections.has(collection)) {
1717
+ if (this._options.preserveSourceDocument) {
1718
+ continue;
1719
+ }
1704
1720
  _PushDiagnostic(diagnostics, {
1705
1721
  code: "REFERENCE_UNRESOLVED",
1706
1722
  graphIndex,
@@ -1726,6 +1742,9 @@ export class KHRInteractivityExportPlan {
1726
1742
  const socket = node.values?.[indexPlaceholder[1]];
1727
1743
  const path = `/graphs/${graphIndex}/nodes/${logicalNode.sourceIndex}/values/${indexPlaceholder[1]}`;
1728
1744
  if (!socket || "node" in socket) {
1745
+ if (socket && this._options.preserveSourceDocument) {
1746
+ continue;
1747
+ }
1729
1748
  _PushDiagnostic(diagnostics, {
1730
1749
  code: "REFERENCE_UNRESOLVED",
1731
1750
  graphIndex,
@@ -1981,7 +2000,7 @@ export class KHRInteractivityExportPlan {
1981
2000
  for (const [socket, value] of Object.entries(node.values ?? {})) {
1982
2001
  if (!("node" in value) && graph.types?.[value.type]?.signature === "ref" && value.value) {
1983
2002
  const pointerCollection = this._getPointerReferenceCollection(node, mapping, socket);
1984
- if (pointerCollection === null) {
2003
+ if (pointerCollection === null && !(this._options.preserveSourceDocument && typeof value.value[0] === "string")) {
1985
2004
  _PushDiagnostic(diagnostics, {
1986
2005
  code: "REFERENCE_UNRESOLVED",
1987
2006
  graphIndex,
@@ -2074,6 +2093,9 @@ export class KHRInteractivityExportPlan {
2074
2093
  return undefined;
2075
2094
  }
2076
2095
  _getRemappedRootIndex(collection, sourceIndex, context) {
2096
+ if (this._options.preserveSourceDocument) {
2097
+ return this._options.sourceGLTF?.[collection]?.[sourceIndex] ? sourceIndex : undefined;
2098
+ }
2077
2099
  const indices = new Set(this._getSourceRootObjects(collection, sourceIndex)
2078
2100
  .map((target) => this._getExportedObjectIndex(collection, target, context))
2079
2101
  .filter((index) => index !== undefined));
@@ -2154,11 +2176,7 @@ export class KHRInteractivityExportPlan {
2154
2176
  if (!match || !_KnownIndexedRootCollections.has(match[1])) {
2155
2177
  return this._remapReference(reference, context, diagnostics, graphIndex, nodeIndex);
2156
2178
  }
2157
- const targets = this._getSourceRootObjects(targetCollection, parseInt(match[2], 10));
2158
- const targetIndices = new Set(targets
2159
- .map((target) => this._getExportedObjectIndex(targetCollection, target, context))
2160
- .filter((index) => index !== undefined));
2161
- const targetIndex = targetIndices.size === 1 ? targetIndices.values().next().value : undefined;
2179
+ const targetIndex = this._getRemappedRootIndex(targetCollection, parseInt(match[2], 10), context);
2162
2180
  if (targetIndex === undefined) {
2163
2181
  _PushDiagnostic(diagnostics, {
2164
2182
  code: "REFERENCE_UNRESOLVED",
@@ -2175,8 +2193,22 @@ export class KHRInteractivityExportPlan {
2175
2193
  if (!reference || reference.startsWith("/extensions/KHR_interactivity/events/") || reference.startsWith("/extensions/KHR_interactivity/delays/")) {
2176
2194
  return reference;
2177
2195
  }
2178
- const extensionCollection = /^\/extensions\/([^/]+)\/[^/]+\/(0|[1-9]\d*)(?:\/|$)/.exec(reference);
2196
+ const extensionCollection = /^\/extensions\/([^/]+)\/([^/]+)\/(0|[1-9]\d*)(?:\/|$)/.exec(reference);
2179
2197
  if (extensionCollection && extensionCollection[1] !== "KHR_interactivity") {
2198
+ if (this._options.preserveSourceDocument) {
2199
+ const collection = this._options.sourceGLTF?.extensions?.[extensionCollection[1]]?.[extensionCollection[2]];
2200
+ if (!Array.isArray(collection) || collection[Number(extensionCollection[3])] === undefined) {
2201
+ _PushDiagnostic(diagnostics, {
2202
+ code: "REFERENCE_UNRESOLVED",
2203
+ graphIndex,
2204
+ nodeIndex,
2205
+ path: `/graphs/${graphIndex}${nodeIndex === undefined ? "" : `/nodes/${nodeIndex}`}`,
2206
+ message: `Reference "${reference}" does not identify an entry in the retained source document.`,
2207
+ });
2208
+ return reference;
2209
+ }
2210
+ return reference;
2211
+ }
2180
2212
  _PushDiagnostic(diagnostics, {
2181
2213
  code: "REFERENCE_UNRESOLVED",
2182
2214
  graphIndex,
@@ -2210,7 +2242,7 @@ export class KHRInteractivityExportPlan {
2210
2242
  }
2211
2243
  _validateExtensionReferencePreservation(reference, resolvedCollection, diagnostics, graphIndex, nodeIndex, path = `/graphs/${graphIndex}${nodeIndex === undefined ? "" : `/nodes/${nodeIndex}`}`) {
2212
2244
  const nestedExtensions = _GetPointerExtensionNames(reference).filter((extension) => extension !== "KHR_interactivity");
2213
- if (nestedExtensions.length === 0 || _CanPreserveNestedExtensionPointer(reference, resolvedCollection)) {
2245
+ if (this._options.preserveSourceDocument || nestedExtensions.length === 0 || _CanPreserveNestedExtensionPointer(reference, resolvedCollection)) {
2214
2246
  return true;
2215
2247
  }
2216
2248
  _PushDiagnostic(diagnostics, {
@@ -2308,6 +2340,9 @@ export class KHRInteractivityExportPlan {
2308
2340
  return Array.from(extensions).sort();
2309
2341
  }
2310
2342
  _writeCompanionNodeExtensions(context, diagnostics) {
2343
+ if (this._options.preserveSourceDocument) {
2344
+ return;
2345
+ }
2311
2346
  for (let sourceIndex = 0; sourceIndex < (this._options.sourceGLTF?.nodes?.length ?? 0); sourceIndex++) {
2312
2347
  const sourceNode = this._options.sourceGLTF.nodes[sourceIndex];
2313
2348
  for (const extensionName of _CompanionNodeExtensions) {