@babylonjs/viewer
Version:
The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.
1,544 lines • 69 kB
JavaScript
import { u as RegisterClass, b3 as Vector2, aD as Quaternion, M as Matrix, b1 as Color4, C as Constants } from './index-HyNDfLMI.esm.js';
import { S as SpotLight } from './spotLight.pure-C65PiYTZ.esm.js';
/** This file must only contain pure code and pure imports */
/**
* Class that represents an integer value.
*/
class FlowGraphInteger {
constructor(value) {
this.value = this._toInt(value);
}
/**
* Converts a float to an integer.
* @param n the float to convert
* @returns the result of n | 0 - converting it to a int
*/
_toInt(n) {
return n | 0;
}
/**
* Adds two integers together.
* @param other the other integer to add
* @returns a FlowGraphInteger with the result of the addition
*/
add(other) {
return new FlowGraphInteger(this.value + other.value);
}
/**
* Subtracts two integers.
* @param other the other integer to subtract
* @returns a FlowGraphInteger with the result of the subtraction
*/
subtract(other) {
return new FlowGraphInteger(this.value - other.value);
}
/**
* Multiplies two integers.
* @param other the other integer to multiply
* @returns a FlowGraphInteger with the result of the multiplication
*/
multiply(other) {
return new FlowGraphInteger(Math.imul(this.value, other.value));
}
/**
* Divides two integers.
* @param other the other integer to divide
* @returns a FlowGraphInteger with the result of the division
*/
divide(other) {
return new FlowGraphInteger(this.value / other.value);
}
/**
* The class name of this type.
* @returns
*/
getClassName() {
return FlowGraphInteger.ClassName;
}
/**
* Compares two integers for equality.
* @param other the other integer to compare
* @returns true if the integers are equal
*/
equals(other) {
return this.value === other.value;
}
/**
* Parses a FlowGraphInteger from a serialization object.
* @param value te number to parse
* @returns a new FlowGraphInteger
*/
static FromValue(value) {
return new FlowGraphInteger(value);
}
/**
* Returns a string representation of this integer
* @returns the string representation
*/
toString() {
return this.value.toString();
}
}
/** The class name of this type */
FlowGraphInteger.ClassName = "FlowGraphInteger";
let _Registered = false;
/**
* Register side effects for flowGraphInteger.
* Safe to call multiple times; only the first call has an effect.
*/
function RegisterFlowGraphInteger() {
if (_Registered) {
return;
}
_Registered = true;
RegisterClass("FlowGraphInteger", FlowGraphInteger);
}
/**
* Adding an exception here will break traversing through the glTF object tree.
* This is used for properties that might not be in the glTF object model, but are optional and have a default value.
* For example, the path /nodes/\{\}/extensions/KHR_node_visibility/visible is optional - the object can be deferred without the object fully existing.
*/
const OptionalPathExceptionsList = [
{
// get the node as object when reading an extension
regex: new RegExp(`^/nodes/\\d+/extensions/`),
},
{
// KHR_interactivity animation pointers (e.g. /animations/{}/extensions/KHR_interactivity/maxTime)
// are virtual: they are computed from the Babylon animation group and are not stored in the
// glTF JSON, so the animation object has no `extensions` member to traverse into.
regex: new RegExp(`^/animations/\\d+/extensions/`),
},
{
// weights may be undefined on nodes without morph targets
regex: new RegExp(`^/nodes/\\d+/weights`),
},
{
// weights may be undefined on meshes without morph targets
regex: new RegExp(`^/meshes/\\d+/weights`),
},
{
// KHR_texture_transform may not be present on texture info objects
regex: new RegExp(`/extensions/KHR_texture_transform/`),
},
];
/**
* A converter that takes a glTF Object Model JSON Pointer
* and transforms it into an ObjectAccessorContainer, allowing
* objects referenced in the glTF to be associated with their
* respective Babylon.js objects.
*/
class GLTFPathToObjectConverter {
constructor(_gltf, _infoTree) {
this._gltf = _gltf;
this._infoTree = _infoTree;
}
/**
* The pointer string is represented by a [JSON pointer](https://datatracker.ietf.org/doc/html/rfc6901).
* See also https://github.com/KhronosGroup/glTF/blob/main/specification/2.0/ObjectModel.adoc#core-pointers
* <animationPointer> := /<rootNode>/<assetIndex>/<propertyPath>
* <rootNode> := "nodes" | "materials" | "meshes" | "cameras" | "extensions"
* <assetIndex> := <digit> | <name>
* <propertyPath> := <extensionPath> | <standardPath>
* <extensionPath> := "extensions"/<name>/<standardPath>
* <standardPath> := <name> | <name>/<standardPath>
* <name> := W+
* <digit> := D+
*
* Examples:
* - "/nodes/0/rotation"
* - "/nodes.length"
* - "/materials/2/emissiveFactor"
* - "/materials/2/pbrMetallicRoughness/baseColorFactor"
* - "/materials/2/extensions/KHR_materials_emissive_strength/emissiveStrength"
*
* @param path The path to convert
* @returns The object and info associated with the path
*/
convert(path) {
let objectTree = this._gltf;
let infoTree = this._infoTree;
let target = undefined;
if (!path.startsWith("/")) {
throw new Error("Path must start with a /");
}
const parts = path.split("/");
parts.shift();
//if the last part has ".length" in it, separate that as an extra part
if (parts[parts.length - 1].includes(".length")) {
const lastPart = parts[parts.length - 1];
const split = lastPart.split(".");
parts.pop();
parts.push(...split);
}
let ignoreObjectTree = false;
for (const part of parts) {
const isLength = part === "length";
if (isLength) {
// For .length, check if the current level has a 'length' accessor
if (infoTree.length) {
infoTree = infoTree.length;
}
else if (infoTree.__array__) {
// Fallback: length of an array that doesn't have explicit length accessor
throw new Error(`Path ${path} is invalid - no length accessor`);
}
else {
throw new Error(`Path ${path} is invalid`);
}
// Set target to the current object tree (the array itself)
// Only update target if objectTree is defined, otherwise keep the last valid target
if (objectTree !== undefined) {
target = objectTree;
}
continue;
}
if (infoTree.__ignoreObjectTree__) {
ignoreObjectTree = true;
}
if (infoTree.__array__) {
infoTree = infoTree.__array__;
}
else {
infoTree = infoTree[part];
if (!infoTree) {
throw new Error(`Path ${path} is invalid`);
}
}
// __passThroughTarget__: skip objectTree traversal for this property.
// The accessor functions will receive the parent target (e.g., the INode)
// instead of the child property (e.g., node.weights which may be undefined).
if (infoTree.__passThroughTarget__) {
ignoreObjectTree = true;
}
else if (!ignoreObjectTree) {
if (objectTree === undefined) {
// check if the path is in the exception list. If it is, break and return the last object that was found
const exception = OptionalPathExceptionsList.find((e) => e.regex.test(path));
if (!exception) {
throw new Error(`Path ${path} is invalid`);
}
}
else {
objectTree = objectTree?.[part];
}
}
else {
// When ignoring object tree traversal and encountering a numeric array index,
// wrap the accessor functions to pass the index through as the second argument.
// The remaining arguments are forwarded unchanged so context-aware accessors, which
// receive the runtime context as their payload, keep working. Require an all-digits
// segment: parseInt would otherwise accept "1/foo" as 1 and bind to the wrong index.
const numericIndex = /^\d+$/.test(part) ? parseInt(part, 10) : NaN;
if (!isNaN(numericIndex) && typeof infoTree.get === "function") {
const orig = infoTree;
infoTree = { ...orig };
infoTree.get = (target, _index, ...rest) => orig.get(target, numericIndex, ...rest);
if (typeof orig.set === "function") {
infoTree.set = (value, target, _index, ...rest) => orig.set(value, target, numericIndex, ...rest);
}
if (typeof orig.getTarget === "function") {
infoTree.getTarget = (target, _index, ...rest) => orig.getTarget(target, numericIndex, ...rest);
}
}
}
if (infoTree.__target__) {
// Only update target if objectTree is defined, otherwise keep the last valid target
if (objectTree !== undefined) {
target = objectTree;
}
}
}
return {
object: target,
info: infoTree,
};
}
}
/* eslint-disable @typescript-eslint/naming-convention */
/**
* Builds an accessor for an array `length` pointer (e.g. `/nodes.length`, `/meshes/{}/primitives.length`).
*
* The KHR object model types these `length` values as `int`, so the accessor reports the count as a
* {@link FlowGraphInteger} (type {@link FlowGraphTypes.Integer}). Keeping the representation an integer
* at the source lets integer-typed consumers such as `FlowGraphIntToFloat` accept the precise
* `RichTypeFlowGraphInteger` instead of a widened `RichTypeAny`.
* @param getLength returns the raw count for the addressed array; may be `undefined` when the array is not addressable
* @param getTarget returns the Babylon object(s) backing the array
* @returns an object accessor whose value is the count as a {@link FlowGraphInteger}
*/
function _CreateLengthAccessor(getLength, getTarget) {
return {
type: "FlowGraphInteger" /* FlowGraphTypes.Integer */,
get: (target) => {
const length = getLength(target);
return length === undefined ? undefined : FlowGraphInteger.FromValue(length);
},
getTarget,
getPropertyName: [() => "length"],
};
}
const nodesTree = {
length: _CreateLengthAccessor((nodes) => nodes.length, (nodes) => nodes.map((node) => node._babylonTransformNode)),
__array__: {
__target__: true,
translation: {
type: "Vector3",
get: (node) => node._babylonTransformNode?.position,
set: (value, node) => node._babylonTransformNode?.position.copyFrom(value),
getTarget: (node) => node._babylonTransformNode,
getPropertyName: [() => "position"],
},
rotation: {
type: "Quaternion",
get: (node) => node._babylonTransformNode?.rotationQuaternion,
set: (value, node) => node._babylonTransformNode?.rotationQuaternion?.copyFrom(value),
getTarget: (node) => node._babylonTransformNode,
getPropertyName: [() => "rotationQuaternion"],
},
scale: {
type: "Vector3",
get: (node) => node._babylonTransformNode?.scaling,
set: (value, node) => node._babylonTransformNode?.scaling.copyFrom(value),
getTarget: (node) => node._babylonTransformNode,
getPropertyName: [() => "scaling"],
},
weights: {
// Skip glTF objectTree traversal — weights may be undefined on the glTF node
// but accessible via the Babylon MorphTargetManager on the INode's meshes
__passThroughTarget__: true,
length: _CreateLengthAccessor((node) => {
const found = _findNodeMorphTargets(node);
return found ? found.mtm.numTargets : node?.mesh !== undefined ? 0 : undefined;
}, (node) => node?._babylonTransformNode),
__array__: {
__target__: true,
type: "number",
get: (node, index) => {
const found = _findNodeMorphTargets(node);
if (found && index !== undefined && index >= 0 && index < found.mtm.numTargets) {
return _roundFloat32Artifact(found.mtm.getTarget(index).influence);
}
return undefined;
},
set: (value, node, index) => {
const numValue = typeof value === "number" ? value : typeof value?.value === "number" ? value.value : value;
const found = _findNodeMorphTargets(node);
if (!found || index === undefined || index < 0 || index >= found.mtm.numTargets) {
return;
}
// Fan out to every mesh that shares this morph target manager so
// multi-primitive meshes stay in sync.
for (const mesh of found.meshes) {
const target = mesh.morphTargetManager?.getTarget(index);
if (target) {
target.influence = numValue;
}
}
},
getTarget: (node, index) => {
const found = _findNodeMorphTargets(node);
if (found && index !== undefined && index >= 0 && index < found.mtm.numTargets) {
return found.mtm.getTarget(index);
}
return node?._babylonTransformNode;
},
getPropertyName: [() => "influence"],
},
type: "number[]",
get: (node) => {
const found = _findNodeMorphTargets(node);
if (!found) {
return [];
}
const weights = [];
for (let i = 0; i < found.mtm.numTargets; i++) {
weights.push(_roundFloat32Artifact(found.mtm.getTarget(i).influence));
}
return weights;
},
getTarget: (node) => node?._babylonTransformNode,
getPropertyName: [() => "influence"],
},
// readonly!
matrix: {
type: "Matrix",
get: (node) => Matrix.Compose(node._babylonTransformNode?.scaling, node._babylonTransformNode?.rotationQuaternion, node._babylonTransformNode?.position),
getTarget: (node) => node._babylonTransformNode,
isReadOnly: true,
},
globalMatrix: {
type: "Matrix",
get: (node) => {
const matrix = Matrix.Identity();
// RHS/LHS support
let rootNode = node.parent;
while (rootNode && rootNode.parent) {
rootNode = rootNode.parent;
}
const forceUpdate = node._babylonTransformNode?.position._isDirty || node._babylonTransformNode?.rotationQuaternion?._isDirty || node._babylonTransformNode?.scaling._isDirty;
if (rootNode) {
// take the parent root node's world matrix, invert it, and multiply it with the current node's world matrix
// This will provide the global matrix, ignoring the RHS->LHS conversion
const rootMatrix = rootNode._babylonTransformNode?.computeWorldMatrix(true).invert();
if (rootMatrix) {
node._babylonTransformNode?.computeWorldMatrix(forceUpdate)?.multiplyToRef(rootMatrix, matrix);
}
}
else if (node._babylonTransformNode) {
matrix.copyFrom(node._babylonTransformNode.computeWorldMatrix(forceUpdate));
}
return matrix;
},
getTarget: (node) => node._babylonTransformNode,
isReadOnly: true,
},
camera: {
type: "string",
// Per KHR_interactivity Object Model: read-only ref pointing to the
// attached camera, encoded as a JSON Pointer string. Empty string
// when no camera is attached (the spec's null-ref convention).
get: (node) => (node.camera !== undefined ? `/cameras/${node.camera}` : ""),
getTarget: (node) => node,
isReadOnly: true,
},
mesh: {
type: "string",
get: (node) => (node.mesh !== undefined ? `/meshes/${node.mesh}` : ""),
getTarget: (node) => node,
isReadOnly: true,
},
skin: {
type: "string",
get: (node) => (node.skin !== undefined ? `/skins/${node.skin}` : ""),
getTarget: (node) => node,
isReadOnly: true,
},
parent: {
type: "string",
get: (node) => (node.parent && node.parent.index !== undefined ? `/nodes/${node.parent.index}` : ""),
getTarget: (node) => node,
isReadOnly: true,
},
children: {
length: _CreateLengthAccessor((children) => children?.length ?? 0, (children) => children ?? []),
__array__: {
__target__: true,
type: "string",
// The wrapping converter passes the indexed child value (an
// INode index); convert it to a JSON Pointer ref string.
get: (childIndex) => (typeof childIndex === "number" ? `/nodes/${childIndex}` : ""),
getTarget: () => ({ __nodeIndex: true }),
isReadOnly: true,
},
},
extensions: {
EXT_lights_ies: {
multiplier: {
type: "number",
get: (node) => {
return node._babylonTransformNode?.getChildren((child) => child instanceof SpotLight, true)[0]?.intensity;
},
getTarget: (node) => node._babylonTransformNode?.getChildren((child) => child instanceof SpotLight, true)[0],
set: (value, node) => {
if (node._babylonTransformNode) {
const light = node._babylonTransformNode.getChildren((child) => child instanceof SpotLight, true)[0];
if (light) {
light.intensity = value;
}
}
},
},
color: {
type: "Color3",
get: (node) => {
return node._babylonTransformNode?.getChildren((child) => child instanceof SpotLight, true)[0]?.diffuse;
},
getTarget: (node) => node._babylonTransformNode?.getChildren((child) => child instanceof SpotLight, true)[0],
set: (value, node) => {
if (node._babylonTransformNode) {
const light = node._babylonTransformNode.getChildren((child) => child instanceof SpotLight, true)[0];
if (light) {
light.diffuse = value;
}
}
},
},
},
KHR_node_visibility: {
visible: {
type: "boolean",
get: (node) => {
return node._primitiveBabylonMeshes ? node._primitiveBabylonMeshes[0].isVisible : false;
},
getTarget: () => undefined, // TODO: what should this return?
set: (value, node) => {
if (node._primitiveBabylonMeshes) {
node._primitiveBabylonMeshes.forEach((mesh) => (mesh.isVisible = value));
}
},
},
},
},
},
};
const animationsTree = {
length: _CreateLengthAccessor((animations) => animations.length, (animations) => animations.map((animation) => animation._babylonAnimationGroup)),
__array__: {
// Indexed access to the animation, surfaced as a JSON Pointer ref string so blocks like
// ``animation/start`` can consume it directly. Uses the animation's own ``index`` property
// (populated by the loader's ArrayItem.Assign step) so the ref is resolved without needing
// a separate index payload from the path converter.
__target__: true,
type: "string",
get: (animation) => (animation && typeof animation.index === "number" ? `/animations/${animation.index}` : ""),
getTarget: (animation) => animation._babylonAnimationGroup,
isReadOnly: true,
},
};
const meshesTree = {
length: _CreateLengthAccessor((meshes) => meshes.length, (meshes) => meshes.map((mesh) => mesh.primitives[0]._instanceData?.babylonSourceMesh)),
__array__: {
__target__: true,
primitives: {
length: _CreateLengthAccessor((primitives) => primitives?.length ?? 0, (primitives) => primitives ?? []),
__array__: {
__target__: true,
material: {
type: "string",
// Read-only ref to the assigned material, JSON Pointer encoded.
get: (primitive) => (primitive.material !== undefined ? `/materials/${primitive.material}` : ""),
getTarget: (primitive) => primitive,
isReadOnly: true,
},
},
},
weights: {
length: _CreateLengthAccessor((weights) => weights?.length ?? 0, (weights) => weights ?? []),
__array__: {
__target__: true,
type: "number",
get: (weightValue) => weightValue,
getTarget: () => ({ __weightValue: true }),
isReadOnly: true,
},
},
},
};
const camerasTree = {
length: _CreateLengthAccessor((cameras) => cameras.length, (cameras) => cameras.map((camera) => camera._babylonCamera)),
__array__: {
__target__: true,
orthographic: {
xmag: {
componentsCount: 2,
type: "Vector2",
get: (camera) => new Vector2(camera._babylonCamera?.orthoLeft ?? 0, camera._babylonCamera?.orthoRight ?? 0),
set: (value, camera) => {
if (camera._babylonCamera) {
camera._babylonCamera.orthoLeft = value.x;
camera._babylonCamera.orthoRight = value.y;
}
},
getTarget: (camera) => camera,
getPropertyName: [() => "orthoLeft", () => "orthoRight"],
},
ymag: {
componentsCount: 2,
type: "Vector2",
get: (camera) => new Vector2(camera._babylonCamera?.orthoBottom ?? 0, camera._babylonCamera?.orthoTop ?? 0),
set: (value, camera) => {
if (camera._babylonCamera) {
camera._babylonCamera.orthoBottom = value.x;
camera._babylonCamera.orthoTop = value.y;
}
},
getTarget: (camera) => camera,
getPropertyName: [() => "orthoBottom", () => "orthoTop"],
},
zfar: {
type: "number",
get: (camera) => camera._babylonCamera?.maxZ,
set: (value, camera) => {
if (camera._babylonCamera) {
camera._babylonCamera.maxZ = value;
}
},
getTarget: (camera) => camera,
getPropertyName: [() => "maxZ"],
},
znear: {
type: "number",
get: (camera) => camera._babylonCamera?.minZ,
set: (value, camera) => {
if (camera._babylonCamera) {
camera._babylonCamera.minZ = value;
}
},
getTarget: (camera) => camera,
getPropertyName: [() => "minZ"],
},
},
perspective: {
aspectRatio: {
type: "number",
get: (camera) => camera._babylonCamera?.getEngine().getAspectRatio(camera._babylonCamera),
getTarget: (camera) => camera,
getPropertyName: [() => "aspectRatio"],
isReadOnly: true, // might not be the case for glTF?
},
yfov: {
type: "number",
get: (camera) => camera._babylonCamera?.fov,
set: (value, camera) => {
if (camera._babylonCamera) {
camera._babylonCamera.fov = value;
}
},
getTarget: (camera) => camera,
getPropertyName: [() => "fov"],
},
zfar: {
type: "number",
get: (camera) => camera._babylonCamera?.maxZ,
set: (value, camera) => {
if (camera._babylonCamera) {
camera._babylonCamera.maxZ = value;
}
},
getTarget: (camera) => camera,
getPropertyName: [() => "maxZ"],
},
znear: {
type: "number",
get: (camera) => camera._babylonCamera?.minZ,
set: (value, camera) => {
if (camera._babylonCamera) {
camera._babylonCamera.minZ = value;
}
},
getTarget: (camera) => camera,
getPropertyName: [() => "minZ"],
},
},
},
};
const materialsTree = {
length: _CreateLengthAccessor((materials) => materials.length, (materials) => materials.map((material) => material._data?.[Constants.MATERIAL_TriangleFillMode]?.babylonMaterial)),
__array__: {
__target__: true,
doubleSided: {
type: "boolean",
get: (material, index, payload) => !GetMaterial(material, index, payload)?.backFaceCulling,
set: (value, material, index, payload) => {
const mat = GetMaterial(material, index, payload);
if (mat) {
mat.backFaceCulling = !value;
}
},
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
getPropertyName: [() => "backFaceCulling"],
},
alphaCutoff: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload)?.alphaCutOff,
set: (value, material, index, payload) => {
const mat = GetMaterial(material, index, payload);
if (mat) {
mat.alphaCutOff = value;
}
},
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
getPropertyName: [() => "alphaCutOff"],
},
emissiveFactor: {
type: "Color3",
get: (material, index, payload) => GetMaterial(material, index, payload).emissiveColor,
set: (value, material, index, payload) => GetMaterial(material, index, payload).emissiveColor.copyFrom(value),
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
getPropertyName: [() => "emissiveColor"],
},
emissiveTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("emissiveTexture"),
},
},
normalTexture: {
scale: {
type: "number",
get: (material, index, payload) => GetTexture(material, payload, "bumpTexture")?.level,
set: (value, material, index, payload) => {
const texture = GetTexture(material, payload, "bumpTexture");
if (texture) {
texture.level = value;
}
},
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
getPropertyName: [() => "level"],
},
extensions: {
KHR_texture_transform: GenerateTextureMap("bumpTexture"),
},
},
occlusionTexture: {
strength: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).ambientTextureStrength,
set: (value, material, index, payload) => {
const mat = GetMaterial(material, index, payload);
if (mat) {
mat.ambientTextureStrength = value;
}
},
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
getPropertyName: [() => "ambientTextureStrength"],
},
extensions: {
KHR_texture_transform: GenerateTextureMap("ambientTexture"),
},
},
pbrMetallicRoughness: {
baseColorFactor: {
type: "Color4",
get: (material, index, payload) => {
const mat = GetMaterial(material, index, payload);
return Color4.FromColor3(mat.albedoColor, mat.alpha);
},
set: (value, material, index, payload) => {
const mat = GetMaterial(material, index, payload);
mat.albedoColor.set(value.r, value.g, value.b);
mat.alpha = value.a;
},
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
// This is correct on the animation level, but incorrect as a single property of a type Color4
getPropertyName: [() => "albedoColor", () => "alpha"],
},
baseColorTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("albedoTexture"),
},
},
metallicFactor: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).metallic,
set: (value, material, index, payload) => {
const mat = GetMaterial(material, index, payload);
if (mat) {
mat.metallic = value;
}
},
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
getPropertyName: [() => "metallic"],
},
roughnessFactor: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).roughness,
set: (value, material, index, payload) => {
const mat = GetMaterial(material, index, payload);
if (mat) {
mat.roughness = value;
}
},
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
getPropertyName: [() => "roughness"],
},
metallicRoughnessTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("metallicTexture"),
},
},
},
extensions: {
KHR_materials_anisotropy: {
anisotropyStrength: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload)?.anisotropy?.intensity,
set: (value, material, index, payload) => {
const mat = GetMaterial(material, index, payload);
if (mat) {
mat.anisotropy.intensity = value;
}
},
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
getPropertyName: [() => "anisotropy.intensity"],
},
anisotropyRotation: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload)?.anisotropy?.angle,
set: (value, material, index, payload) => {
const mat = GetMaterial(material, index, payload);
if (mat) {
mat.anisotropy.angle = value;
}
},
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
getPropertyName: [() => "anisotropy.angle"],
},
anisotropyTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("anisotropy", "texture"),
},
},
},
KHR_materials_clearcoat: {
clearcoatFactor: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).clearCoat.intensity,
set: (value, material, index, payload) => {
GetMaterial(material, index, payload).clearCoat.intensity = value;
},
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
getPropertyName: [() => "clearCoat.intensity"],
},
clearcoatRoughnessFactor: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).clearCoat.roughness,
set: (value, material, index, payload) => {
GetMaterial(material, index, payload).clearCoat.roughness = value;
},
getTarget: (material, index, payload) => GetMaterial(material, index, payload),
getPropertyName: [() => "clearCoat.roughness"],
},
clearcoatTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("clearCoat", "texture"),
},
},
clearcoatNormalTexture: {
scale: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).clearCoat.bumpTexture?.level,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).clearCoat.bumpTexture.level = value),
},
extensions: {
KHR_texture_transform: GenerateTextureMap("clearCoat", "bumpTexture"),
},
},
clearcoatRoughnessTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("clearCoat", "textureRoughness"),
},
},
},
KHR_materials_dispersion: {
dispersion: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).subSurface.dispersion,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).subSurface.dispersion = value),
},
},
KHR_materials_emissive_strength: {
emissiveStrength: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).emissiveIntensity,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).emissiveIntensity = value),
},
},
KHR_materials_ior: {
ior: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).indexOfRefraction,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).indexOfRefraction = value),
},
},
KHR_materials_iridescence: {
iridescenceFactor: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).iridescence.intensity,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).iridescence.intensity = value),
},
iridescenceIor: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).iridescence.indexOfRefraction,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).iridescence.indexOfRefraction = value),
},
iridescenceTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("iridescence", "texture"),
},
},
iridescenceThicknessMaximum: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).iridescence.maximumThickness,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).iridescence.maximumThickness = value),
},
iridescenceThicknessMinimum: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).iridescence.minimumThickness,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).iridescence.minimumThickness = value),
},
iridescenceThicknessTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("iridescence", "thicknessTexture"),
},
},
},
KHR_materials_sheen: {
sheenColorFactor: {
type: "Color3",
get: (material, index, payload) => GetMaterial(material, index, payload).sheen.color,
getTarget: GetMaterial,
set: (value, material, index, payload) => GetMaterial(material, index, payload).sheen.color.copyFrom(value),
},
sheenColorTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("sheen", "texture"),
},
},
sheenRoughnessFactor: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).sheen.intensity,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).sheen.intensity = value),
},
sheenRoughnessTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("sheen", "textureRoughness"),
},
},
},
KHR_materials_specular: {
specularFactor: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).metallicF0Factor,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).metallicF0Factor = value),
getPropertyName: [() => "metallicF0Factor"],
},
specularColorFactor: {
type: "Color3",
get: (material, index, payload) => GetMaterial(material, index, payload).metallicReflectanceColor,
getTarget: GetMaterial,
set: (value, material, index, payload) => GetMaterial(material, index, payload).metallicReflectanceColor.copyFrom(value),
getPropertyName: [() => "metallicReflectanceColor"],
},
specularTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("metallicReflectanceTexture"),
},
},
specularColorTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("reflectanceTexture"),
},
},
},
KHR_materials_transmission: {
transmissionFactor: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).subSurface.refractionIntensity,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).subSurface.refractionIntensity = value),
getPropertyName: [() => "subSurface.refractionIntensity"],
},
transmissionTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("subSurface", "refractionIntensityTexture", {
extensionKey: "KHR_materials_transmission",
texturePath: ["transmissionTexture"],
}),
},
},
},
KHR_materials_diffuse_transmission: {
diffuseTransmissionFactor: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).subSurface.translucencyIntensity,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).subSurface.translucencyIntensity = value),
},
diffuseTransmissionTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("subSurface", "translucencyIntensityTexture", {
extensionKey: "KHR_materials_diffuse_transmission",
texturePath: ["diffuseTransmissionTexture"],
}),
},
},
diffuseTransmissionColorFactor: {
type: "Color3",
get: (material, index, payload) => GetMaterial(material, index, payload).subSurface.translucencyColor,
getTarget: GetMaterial,
set: (value, material, index, payload) => value && GetMaterial(material, index, payload).subSurface.translucencyColor?.copyFrom(value),
},
diffuseTransmissionColorTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("subSurface", "translucencyColorTexture", {
extensionKey: "KHR_materials_diffuse_transmission",
texturePath: ["diffuseTransmissionColorTexture"],
}),
},
},
},
KHR_materials_volume: {
attenuationColor: {
type: "Color3",
get: (material, index, payload) => GetMaterial(material, index, payload).subSurface.tintColor,
getTarget: GetMaterial,
set: (value, material, index, payload) => GetMaterial(material, index, payload).subSurface.tintColor.copyFrom(value),
},
attenuationDistance: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).subSurface.tintColorAtDistance,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).subSurface.tintColorAtDistance = value),
},
thicknessFactor: {
type: "number",
get: (material, index, payload) => GetMaterial(material, index, payload).subSurface.maximumThickness,
getTarget: GetMaterial,
set: (value, material, index, payload) => (GetMaterial(material, index, payload).subSurface.maximumThickness = value),
},
thicknessTexture: {
extensions: {
KHR_texture_transform: GenerateTextureMap("subSurface", "thicknessTexture", { extensionKey: "KHR_materials_volume", texturePath: ["thicknessTexture"] }),
},
},
},
},
},
};
const extensionsTree = {
KHR_lights_punctual: {
lights: {
length: _CreateLengthAccessor((lights) => lights.length, (lights) => lights.map((light) => light._babylonLight)),
__array__: {
__target__: true,
color: {
type: "Color3",
get: (light) => light._babylonLight?.diffuse,
set: (value, light) => light._babylonLight?.diffuse.copyFrom(value),
getTarget: (light) => light._babylonLight,
getPropertyName: [(_light) => "diffuse"],
},
intensity: {
type: "number",
get: (light) => light._babylonLight?.intensity,
set: (value, light) => (light._babylonLight ? (light._babylonLight.intensity = value) : undefined),
getTarget: (light) => light._babylonLight,
getPropertyName: [(_light) => "intensity"],
},
range: {
type: "number",
get: (light) => light._babylonLight?.range,
set: (value, light) => (light._babylonLight ? (light._babylonLight.range = value) : undefined),
getTarget: (light) => light._babylonLight,
getPropertyName: [(_light) => "range"],
},
spot: {
innerConeAngle: {
type: "number",
get: (light) => light._babylonLight?.innerAngle,
set: (value, light) => (light._babylonLight ? (light._babylonLight.innerAngle = value) : undefined),
getTarget: (light) => light._babylonLight,
getPropertyName: [(_light) => "innerConeAngle"],
},
outerConeAngle: {
type: "number",
get: (light) => light._babylonLight?.angle,
set: (value, light) => (light._babylonLight ? (light._babylonLight.angle = value) : undefined),
getTarget: (light) => light._babylonLight,
getPropertyName: [(_light) => "outerConeAngle"],
},
},
},
},
},
EXT_lights_area: {
lights: {
length: _CreateLengthAccessor((lights) => lights.length, (lights) => lights.map((light) => light._babylonLight)),
__array__: {
__target__: true,
color: {
type: "Color3",
get: (light) => light._babylonLight?.diffuse,
set: (value, light) => light._babylonLight?.diffuse.copyFrom(value),
getTarget: (light) => light._babylonLight,
getPropertyName: [(_light) => "diffuse"],
},
intensity: {
type: "number",
get: (light) => light._babylonLight?.intensity,
set: (value, light) => (light._babylonLight ? (light._babylonLight.intensity = value) : undefined),
getTarget: (light) => light._babylonLight,
getPropertyName: [(_light) => "intensity"],
},
size: {
type: "number",
get: (light) => light._babylonLight?.height,
set: (value, light) => (light._babylonLight ? (light._babylonLight.height = value) : undefined),
getTarget: (light) => light._babylonLight,
getPropertyName: [(_light) => "size"],
},
rect: {
aspect: {
type: "number",
get: (light) => light._babylonLight?.width / light._babylonLight?.height,
set: (value, light) => light._babylonLight ? (light._babylonLight.width = value * light._babylonLight.height) : undefined,
getTarget: (light) => light._babylonLight,
getPropertyName: [(_light) => "aspect"],
},
},
},
},
},
EXT_lights_ies: {
lights: {
length: _CreateLengthAccessor((lights) => lights.length, (lights) => lights.map((light) => light._babylonLight)),
},
},
EXT_lights_image_based: {
lights: {
length: _CreateLengthAccessor((lights) => lights.length, (lights) => lights.map((light) => light._babylonTexture)),
__array__: {
__target__: true,
intensity: {
type: "number",
get: (light) => light._babylonTexture?.level,
set: (value, light) => {
if (light._babylonTexture) {
light._babylonTexture.level = value;
}
},
getTarget: (light) => light._babylonTexture,
},
rotation: {
type: "Quaternion",
get: (light) => light._babylonTexture && Quaternion.FromRotationMatrix(light._babylonTexture?.getReflectionTextureMatrix()),
set: (value, light) => {
if (!light._babylonTexture) {
return;
}
// Invert the rotation so that positive rotation is counter-clockwise.
if (!light._babylonTexture.getScene()?.useRightHandedSystem) {
value = Quaternion.Inverse(value);
}
Matrix.FromQuaternionToRef(value, light._babylonTexture.getReflectionTextureMatrix());
},
getTarget: (light) => light._babylonTexture,
},
},
},
},
};
function GetTexture(material, payload, textureType, textureInObject) {
const babylonMaterial = GetMaterial(material);
return textureInObject ? babylonMaterial[textureType][textureInObject] : babylonMaterial[textureType];
}
function GetMaterial(material, _index, payload) {
return material._data?.[payload?.fillMode ?? Constants.MATERIAL_TriangleFillMode]?.babylonMaterial;
}
function _getNodeMorphTargetManager(node) {
const tn = node?._babylonTransformNode;
if (!tn) {
return undefined;
}
// Single primitive: transform node IS the mesh with morphTargetManager
if (tn.morphTargetManager) {
return tn.morphTargetManager;
}
// Multiple primitives: check each primitive mesh and its source
const primMeshes = node._primitiveBabylonMeshes;
if (primMeshes) {
for (const mesh of primMeshes) {
if (mesh?.morphTargetManager) {
return mesh.morphTargetManager;
}
// Check source mesh for instanced meshes
if (mesh?.sourceMesh?.morphTargetManager) {
return mesh.sourceMesh.morphTargetManager;
}
}
}
return undefined;
}
/**
* KHR_interactivity test assets routinely use a glTF hierarchy where the
* **parent** node has no `mesh` but a descendant does. The Khronos morph-weight
* tests query `/nodes/<parent>/weights/*` and expect the result to come from
* the morph targets of the first descendant mesh. To support this we walk the
* Babylon-side scene graph below the queried INode looking for a Mesh that has
* a morphTargetManager.
*
* For multi-primitive meshes (one INode → several Babylon meshes parented to a
* wrapper TransformNode) we also collect the sibling primitives so a `set`
* touches every mesh that shares the manager.
* @param node the glTF node to start the lookup from
* @returns the active morph target manager and every Babylon mesh that shares
* it, or `undefined` when no morph target manager is reachable from the node.
*/
function _findNodeMorphTargets(node) {
const tn = node?._babylonTransformNode;
if (!tn) {
return undefined;
}
// Direct: this node's own mesh has a morph target manager.
const directMtm = _getNodeMorphTargetManager(node);
if (directMtm && node._primitiveBabylonMeshes && node._primitiveBabylonMeshes.length > 0) {
return { mtm: directMtm, meshes: node._primitiveBabylonMeshes };
}
// Fallback: search descendants in the Babylon scene graph for the first
// mesh that has a morph target manager, then collect every sibling mesh
// that shares it (covers the multi-primitive case).
const descendants = tn.getDescendants(false);
for (const desc of descendants) {
const candidate = desc;
const mtm = candidate.morphTargetManager ?? candidate.sourceMesh?.morphTargetManager;
if (!mtm) {
continue;
}
const meshes = [];
const parent = candidate.parent;
if (parent) {
for (const sib of parent.getChildMeshes(true)) {
const sibMtm = sib.morphTargetManager ?? sib.sourceMesh?.morphTargetManager;
if (sibMtm === mtm) {
meshes.push(sib);
}
}
}
if (meshes.length === 0) {
meshes.push(candidate);
}
return { mtm, meshes };
}
return undefined;
}
/**
* Collapse float32-precision artifacts back to the closest "clean" double.
*
* glTF stores numbers as JSON, but tools usually serialize float32 morph weights
* with their full double-precision text — `0.1` becomes `0.10000000149011612`.
* KHR_interactivity tests then compare the read-back weight via strict `math/eq`
* against literals like `0.1`, which fails because the two doubles aren't bitwise
* equal. Rounding the value to 7 significant figures (the precision of a float32)
* recovers the original "clean" double for any value that survived a float32
* round-trip while leaving genuinely high-precision doubles essentially intact.
* @param v the value to round
* @returns the rounded value, or the input unchanged if it is not finite
*/
function _roundFloat32Artifact(v) {
if (!Number.isFinite(v)) {
return v;
}
return parseFloat(v.toPrecision(7));
}
/**
* Read the KHR_texture_transform object stored in the source glTF JSON for a
* texture-info that lives under one of the material's extensions, creating
* empty parent objects on demand so callers can write through it. Returns
* `undefined` when the input shape is incompatible.
* @param material the source IMaterial owning the extension
* @param gltfPath the path describing where the texture-info lives
* @param createMissing when true, create missing parent objects so writes succeed
* @returns the glTF-side KHR_texture_transform object, or undefined
*/
function _gltfTextureTransform(material, gltfPath, createMissing) {
if (!material) {
return undefined;
}
let extensions = material.extensions;
if (!extensions) {
if (!createMissing) {
return undefined;
}
extensions = {};
material.extensions = extensions;
}
let cursor = extensions[gltfPath.extensionKey];
if (!cursor) {
if (!createMissing) {
return undefined;
}
cursor = {};
extensions[gltfPath.extensionKey] = cursor;
}
for (const key of gltfPath.texturePath) {
let next = cursor[key];
if (!next) {
if (!createMissing) {
return undefined;
}
next = {};
cursor[key] = next;
}
cursor = next;
}
if (!cursor.extensions) {
if (!createMissing) {
return undefined;
}
cursor.extensions = {};
}
let xform = cursor.extensions.KHR_texture_transform;
if (!xform) {
if (!createMissing) {
return undefined;
}
xform = {};
cursor.extensions.KHR_texture_transform = xform;
}
return xform;
}
function GenerateTextureMap(textureType, textureInObject, gltfPath) {
return {
offset: {
componentsCount: 2,
// assuming two independent values for u and v, and NOT a Vector2
type: "Vector2",
get: (material, _index, payload) => {
const texture = GetTexture(material, payload, textureType, textureInObject);
if (texture) {
return new Vector2(texture.uOffset, texture.vOffset);
}
if (gltfPath) {
const xform = _gltfTextureTransform(material, gltfPath, false);
const o = xform?.offset;
return new Vector2(o?.[0] ?? 0, o?.[1] ?? 0);
}
return new Vector2(0, 0);
},
getTarget: GetMaterial,
set: (value, material, _index, payload) => {
const texture = GetTexture(material, payload, textureType, textureInObject);
if (texture) {
texture.uOffset = value.x;
texture.vOffset = value.y;
}
if (gltfPath) {
const xform = _gltfTextureTransform(material, gltfPath, true);
if (xform) {
xform.offset = [value.x, value.y];
}
}
},
getPropertyName: [
() => `${textureType}${textureInObject ? "." + textureInObject : ""}.uOffset`,
() => `${textureType}${textureInObject ? "." + textureInObject : ""}.vOffset`,
],
},
rotation: {
type: "number",
get: (material, _index, payload) => {
const texture = GetTexture(material, payload, textureType, textureInObject);
if (texture) {
return texture.wAng;
}
if (gltfPath) {
const xform = _gltfTextureTransform(material, gltfPath, false);
return xform?.rotation ?? 0;
}
return 0;
},
getTarget: GetMaterial,
set: (value, material, _index, payload) => {
const texture = GetTexture(material, payload, textureType, textureInObject);
if (texture) {
texture.wAng = value;
}
if (gltfPath) {
const xform = _gltfTextureTransform(material, gltfPath, true);
if (xform) {
xform.rotation = value;
}
}
},
getPropertyName: [() => `${textureType}${textureInObject ? "." + textureInObject : ""}.wAng`],
},
scale: {
componentsCount: 2,
type: "Vector2",
get: (material, _index, payload) => {
const texture = GetTexture(material, payload, textureType, textureInObject);
if (texture) {
return new Vector2(texture.uScale, texture.vScale);
}
if (gltfPath) {
const xform = _gltfTextureTransform(material, gltfPath, false);
const s = xform?.scale;
return new Vector2(s?.[0] ?? 1, s?.[1] ?? 1);
}
return new Vector2(1, 1);
},
getTarget: GetMaterial,
set: (value, material, index, payload) => {
const texture = GetTexture(material, payload, textureType, textureInObject);
if (texture) {
texture.uScale = value.x;
texture.vScale = value.y;
}
if (gltfPath) {
const xform = _gltfTextureTransform(material, gltfPath, true);
if (xform) {
xform.scale = [value.x, value.y];
}
}
},
getPropertyName: [
() => `${textureType}${textureInObject ? "." + textureInObject : ""}.uScale`,
() => `${textureType}${textureInObject ? "." + textureInObject : ""}.vScale`,
],
},
};
}
const scenesTree = {
length: _CreateLengthAccessor((scenes) => scenes.length, (scenes) => scenes),
__array__: {
__target__: true,
nodes: {
length: _CreateLengthAccessor((nodes) => nodes?.length ?? 0, (nodes) => nodes ?? []),
__array__: {
__target__: true,
type: "string",
// Indexed scene root: the underlying value is the INode index;
// KHR_interactivity expects a ref-typed JSON Pointer string.
get: (nodeIndex) => (typeof nodeIndex === "number" ? `/nodes/${nodeIndex}` : ""),
getTarget: () => ({ __nodeIndex: true }),
isReadOnly: true,
},
},
},
};
const skinsTree = {
length: _CreateLengthAccessor((skins) => skins.length, (skins) => skins.map((skin) => skin._data?.babylonSkeleton)),
__array__: {
__target__: true,
joints: {
length: _CreateLengthAccessor((joints) => joints?.length ?? 0, (joints) => joints ?? []),
__array__: {
__target__: true,
type: "string",
// Indexed skin joint: returns a ref to the joint node.
get: (jointIndex) => (typeof jointIndex === "number" ? `/nodes/${jointIndex}` : ""),
getTarget: () => ({ __nodeIndex: true }),
isReadOnly: true,
},
},
skeleton: {
type: "string",
// Skin's skeleton root: returns a ref to the root node, or empty
// (null ref) when no skeleton root is declared.
get: (skin) => {
const skeleton = skin.skeleton;
return typeof skeleton === "number" ? `/nodes/${skeleton}` : "";
},
getTarget: (skin) => skin,
isReadOnly: true,
},
},
};
const objectModelMapping = {
scene: {
__target__: true,
type: "number",
get: (sceneIndex) => sceneIndex ?? 0,
getTarget: () => ({ __gltfRoot: true }),
isReadOnly: true,
getPropertyName: [() => "scene"],
},
cameras: camerasTree,
nodes: nodesTree,
materials: materialsTree,
extensions: extensionsTree,
animations: animationsTree,
meshes: meshesTree,
scenes: scenesTree,
skins: skinsTree,
};
/**
* get a path-to-object converter for the given glTF tree
* @param gltf the glTF tree to use
* @returns a path-to-object converter for the given glTF tree
*/
function GetPathToObjectConverter(gltf) {
return new GLTFPathToObjectConverter(gltf, objectModelMapping);
}
/**
* This function will return the object accessor for the given key in the object model
* If the key is not found, it will return undefined
* @param key the key to get the mapping for, for example /materials/\{\}/emissiveFactor
* @returns an object accessor for the given key, or undefined if the key is not found
*/
function GetMappingForKey(key) {
// replace every `{}` in key with __array__ to match the object model
const keyParts = key.split("/").map((part) => part.replace(/{}/g, "__array__"));
let current = objectModelMapping;
for (const part of keyParts) {
// make sure part is not empty
if (!part) {
continue;
}
current = current[part];
}
// validate that current is an object accessor
if (current && current.type && current.get) {
return current;
}
return undefined;
}
/**
* Set interpolation for a specific key in the object model
* @param key the key to set, for example /materials/\{\}/emissiveFactor
* @param interpolation the interpolation elements array
*/
function SetInterpolationForKey(key, interpolation) {
// replace every `{}` in key with __array__ to match the object model
const keyParts = key.split("/").map((part) => part.replace(/{}/g, "__array__"));
let current = objectModelMapping;
for (const part of keyParts) {
// make sure part is not empty
if (!part) {
continue;
}
current = current[part];
}
// validate that the current object is an object accessor
if (current && current.type && current.get) {
current.interpolation = interpolation;
}
}
/**
* This will ad a new object accessor in the object model at the given key.
* Note that this will NOT change the typescript types. To do that you will need to change the interface itself (extending it in the module that uses it)
* @param key the key to add the object accessor at. For example /cameras/\{\}/perspective/aspectRatio
* @param accessor the object accessor to add
*/
function AddObjectAccessorToKey(key, accessor) {
// replace every `{}` in key with __array__ to match the object model
const keyParts = key.split("/").map((part) => part.replace(/{}/g, "__array__"));
let current = objectModelMapping;
for (const part of keyParts) {
// make sure part is not empty
if (!part) {
continue;
}
if (!current[part]) {
if (part === "?") {
current.__ignoreObjectTree__ = true;
continue;
}
current[part] = {};
// if the part is __array__ then add the __target__ property
if (part === "__array__") {
current[part].__target__ = true;
}
}
current = current[part];
}
Object.assign(current, accessor);
}
export { AddObjectAccessorToKey as A, FlowGraphInteger as F, GetPathToObjectConverter as G, RegisterFlowGraphInteger as R, SetInterpolationForKey as S, GetMappingForKey as a };
//# sourceMappingURL=objectModelMapping-OlchA9xj.esm.js.map