diff --git a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_animation_pointer.map.ts b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_animation_pointer.map.ts new file mode 100644 index 00000000000..d2c288225e0 --- /dev/null +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_animation_pointer.map.ts @@ -0,0 +1,509 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Animation } from "core/Animations/animation"; +import type { AnimationGroup } from "core/Animations/animationGroup"; +import { Quaternion, Vector3, Matrix } from "core/Maths/math.vector"; +import { Color3 } from "core/Maths/math.color"; +import type { IGLTF } from "../glTFLoaderInterfaces"; +import { Material } from "core/Materials/material"; +import type { IAnimatable } from "core/Animations/animatable.interface"; +import type { Mesh } from "core/Meshes/mesh"; +import type { Nullable } from "core/types"; + +export type GetGltfNodeTargetFn = (source: IGLTF, indices: string) => any; +type GetValueFn = (target: any, source: Float32Array, offset: number, scale?: number) => any; + +export interface IAnimationPointerPropertyInfos { + type: number; + name: string; + get: GetValueFn; + isValid(target: any): boolean; + buildAnimations(target: any, fps: number, keys: any[], group: AnimationGroup, animationTargetOverride: Nullable, params?: any): void; +} + +const parseIntIndex = (str: string) => { + const targetIndex = parseInt(str); + if (isNaN(targetIndex)) { + return -1; + } + return targetIndex; +}; + +const getGltfNode: GetGltfNodeTargetFn = (gltf: IGLTF, index: string) => { + if (gltf.nodes) { + const i = parseIntIndex(index); + if (i != -1) { + return gltf.nodes[i]; + } + } + return null; +}; + +const getGltfMaterial: GetGltfNodeTargetFn = (gltf: IGLTF, index: string) => { + if (gltf.materials) { + const i = parseIntIndex(index); + if (i != -1) { + return gltf.materials[i]; + } + } + return null; +}; + +const getGltfCamera: GetGltfNodeTargetFn = (gltf: IGLTF, index: string) => { + if (gltf.cameras) { + const i = parseIntIndex(index); + if (i != -1) { + return gltf.cameras[i]; + } + } + return null; +}; + +const getGltfExtension: GetGltfNodeTargetFn = (gltf: IGLTF, index: string) => { + if (gltf.extensions && index) { + return gltf.extensions[index]; + } + return null; +}; + +const getMatrix: GetValueFn = (_target: any, source: Float32Array, offset: number, scale?: number) => { + return scale ? Matrix.FromArray(source, offset).scale(scale) : Matrix.FromArray(source, offset); +}; + +const getVector3: GetValueFn = (_target: any, source: Float32Array, offset: number, scale?: number) => { + return scale ? Vector3.FromArray(source, offset).scaleInPlace(scale) : Vector3.FromArray(source, offset); +}; + +const getQuaternion: GetValueFn = (_target: any, source: Float32Array, offset: number, scale?: number) => { + return scale ? Quaternion.FromArray(source, offset).scaleInPlace(scale) : Quaternion.FromArray(source, offset); +}; + +const getColor3: GetValueFn = (_target: any, source: Float32Array, offset: number, scale?: number) => { + return scale ? Color3.FromArray(source, offset).scale(scale) : Color3.FromArray(source, offset); +}; + +const getAlpha: GetValueFn = (_target: any, source: Float32Array, offset: number, scale?: number) => { + return scale ? source[offset + 3] * scale : source[offset + 3]; +}; + +const getFloat: GetValueFn = (_target: any, source: Float32Array, offset: number, scale?: number) => { + return scale ? source[offset] * scale : source[offset]; +}; + +const getMinusFloat: GetValueFn = (_target: any, source: Float32Array, offset: number, scale?: number) => { + return -(scale ? source[offset] * scale : source[offset]); +}; + +const getNextFloat: GetValueFn = (_target: any, source: Float32Array, offset: number, scale?: number) => { + return scale ? source[offset + 1] * scale : source[offset + 1]; +}; + +const getFloatBy2: GetValueFn = (_target: any, source: Float32Array, offset: number, scale?: number) => { + return (scale ? source[offset] * scale : source[offset]) * 2; +}; + +const getWeights: GetValueFn = (target: any, source: Float32Array, offset: number, scale?: number) => { + if (target._numMorphTargets) { + const value = new Array(target._numMorphTargets!); + for (let i = 0; i < target._numMorphTargets!; i++) { + value[i] = scale ? source[offset++] * scale : source[offset++]; + } + return value; + } + return null; +}; + +abstract class AbstractAnimationPointerPropertyInfos implements IAnimationPointerPropertyInfos { + public constructor(public type: number, public name: string, public get: GetValueFn) {} + + protected _buildAnimation( + animatable: Nullable, + fps: number, + keys: any[], + babylonAnimationGroup: AnimationGroup, + animationTargetOverride: Nullable = null + ) { + if (animatable || animationTargetOverride) { + const animationName = `${babylonAnimationGroup!.name}_channel${babylonAnimationGroup.targetedAnimations.length}_${this.name}`; + const babylonAnimation = new Animation(animationName, this.name, fps, this.type); + babylonAnimation.setKeys(keys); + + if (animationTargetOverride != null && animationTargetOverride.animations != null) { + animationTargetOverride.animations.push(babylonAnimation); + babylonAnimationGroup.addTargetedAnimation(babylonAnimation, animationTargetOverride); + } else if (animatable) { + animatable.animations = animatable.animations || Array(1); + animatable.animations.push(babylonAnimation); + babylonAnimationGroup.addTargetedAnimation(babylonAnimation, animatable); + } + } + } + public isValid(_target: any): boolean { + return true; + } + + public abstract buildAnimations(target: any, fps: number, keys: any[], group: AnimationGroup, animationTargetOverride: Nullable, params?: any): void; +} + +class TransformNodeAnimationPointerPropertyInfos extends AbstractAnimationPointerPropertyInfos { + public constructor(type: number, name: string, get: GetValueFn = getVector3) { + super(type, name, get); + } + public isValid(target: any): boolean { + return target._babylonTransformNode !== null && target._babylonTransformNode !== undefined; + } + + public buildAnimations(target: any, fps: number, keys: any[], group: AnimationGroup, animationTargetOverride: Nullable = null): void { + return this._buildAnimation(target._babylonTransformNode, fps, keys, group, animationTargetOverride); + } +} + +class CameraAnimationPointerPropertyInfos extends AbstractAnimationPointerPropertyInfos { + public constructor(type: number, name: string, get: GetValueFn = getFloat) { + super(type, name, get); + } + + public isValid(target: any): boolean { + return target._babylonCamera != null && target._babylonCamera !== undefined; + } + + public buildAnimations(target: any, fps: number, keys: any[], group: AnimationGroup, animationTargetOverride: Nullable = null): void { + return this._buildAnimation(target._babylonCamera, fps, keys, group, animationTargetOverride); + } +} + +class MaterialAnimationPointerPropertyInfos extends AbstractAnimationPointerPropertyInfos { + public constructor(type: number, name: string, get: GetValueFn = getFloat, public fillMode: any = Material.TriangleFillMode) { + super(type, name, get); + } + + public isValid(target: any): boolean { + const data = target._data; + if (data) { + const c = data[this.fillMode]; + if (c) { + return c.babylonMaterial !== null && c.babylonMaterial !== undefined; + } + } + return false; + } + + public buildAnimations(target: any, fps: number, keys: any[], group: AnimationGroup, animationTargetOverride: Nullable = null): void { + return this._buildAnimation(target._data[this.fillMode].babylonMaterial, fps, keys, group, animationTargetOverride); + } +} + +class LightAnimationPointerPropertyInfos extends AbstractAnimationPointerPropertyInfos { + public constructor(type: number, name: string, get: GetValueFn = getFloat) { + super(type, name, get); + } + + public isValid(target: any): boolean { + return target && target.length != 0; + } + + // note : the extensions array store directly the BabylonLight reference + public buildAnimations(target: any, fps: number, keys: any[], group: AnimationGroup, animationTargetOverride: Nullable = null, params: any): void { + const i = parseIntIndex(params[1]); + const l = i >= 0 && i < target.lights.length ? target.lights[i] : null; + return this._buildAnimation(l._babylonLight, fps, keys, group, animationTargetOverride); + } +} + +class WeightAnimationPointerPropertyInfos extends AbstractAnimationPointerPropertyInfos { + public constructor(type: number, name: string, get: GetValueFn = getWeights) { + super(type, name, get); + } + public isValid(target: any): boolean { + return target._numMorphTargets; + } + public buildAnimations(targetNode: any, fps: number, keys: any[], babylonAnimationGroup: AnimationGroup, _animationTargetOverride: Nullable = null): void { + if (targetNode._numMorphTargets) { + for (let targetIndex = 0; targetIndex < targetNode._numMorphTargets; targetIndex++) { + const animationName = `${babylonAnimationGroup.name}_channel${babylonAnimationGroup.targetedAnimations.length}`; + const babylonAnimation = new Animation(animationName, this.name, fps, this.type); + babylonAnimation.setKeys( + keys.map((key) => ({ + frame: key.frame, + inTangent: key.inTangent ? key.inTangent[targetIndex] : undefined, + value: key.value[targetIndex], + outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined, + interpolation: key.interpolation, + })) + ); + + if (targetNode._primitiveBabylonMeshes) { + for (const m of targetNode._primitiveBabylonMeshes) { + const babylonMesh = m as Mesh; + if (babylonMesh.morphTargetManager) { + const morphTarget = babylonMesh.morphTargetManager.getTarget(targetIndex); + const babylonAnimationClone = babylonAnimation.clone(); + morphTarget.animations.push(babylonAnimationClone); + babylonAnimationGroup.addTargetedAnimation(babylonAnimationClone, morphTarget); + } + } + } + } + } + } +} + +const CoreAnimationNodesPointerMap: any = { + getTarget: getGltfNode, + hasIndex: true, + matrix: { + properties: [new TransformNodeAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_MATRIX, "matrix", getMatrix)], + }, + translation: { + properties: [new TransformNodeAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_VECTOR3, "position")], + }, + rotation: { + properties: [new TransformNodeAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_QUATERNION, "rotationQuaternion", getQuaternion)], + }, + scale: { + properties: [new TransformNodeAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_VECTOR3, "scaling")], + }, + weights: { + getStride: (target: any) => { + return target._numMorphTargets; + }, + properties: [new WeightAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "influence")], + }, +}; + +const CoreAnimationCamerasPointerMap: any = { + hasIndex: true, + getTarget: getGltfCamera, + orthographic: { + xmag: { + properties: [ + new CameraAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "orthoLeft", getMinusFloat), + new CameraAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "orthoRight", getNextFloat), + ], + }, + ymag: { + properties: [ + new CameraAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "orthoBottom", getMinusFloat), + new CameraAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "orthoTop", getNextFloat), + ], + }, + zfar: { + properties: [new CameraAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "maxZ")], + }, + znear: { + properties: [new CameraAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "minZ")], + }, + }, + perspective: { + aspectRatio: { + // not supported. + }, + yfov: { + properties: [new CameraAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "fov")], + }, + zfar: { + properties: [new CameraAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "maxZ")], + }, + znear: { + properties: [new CameraAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "minZ")], + }, + }, +}; + +const CoreAnimationMaterialsPointerMap: any = { + hasIndex: true, + getTarget: getGltfMaterial, + pbrMetallicRoughness: { + baseColorFactor: { + properties: [ + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_COLOR3, "albedoColor", getColor3), + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "alpha", getAlpha), + ], + }, + metallicFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "metallic")], + }, + roughnessFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "roughness")], + }, + baseColorTexture: { + extensions: { + KHR_texture_transform: { + scale: { + properties: [ + // MAY introduce set scale(Vector2) into texture. + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "albedoTexture.uScale"), + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "albedoTexture.vScale", getNextFloat), + ], + }, + offset: { + properties: [ + // MAY introduce set offset(Vector2) into texture. + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "albedoTexture.uOffset"), + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "albedoTexture.vOffset", getNextFloat), + ], + }, + rotation: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "albedoTexture.wAng", getMinusFloat)], + }, + }, + }, + }, + }, + emissiveFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_COLOR3, "emissiveColor", getColor3)], + }, + normalTexture: { + scale: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "bumpTexture.level")], + }, + }, + occlusionTexture: { + strength: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "ambientTextureStrength")], + }, + extensions: { + KHR_texture_transform: { + scale: { + properties: [ + // MAY introduce set scale(Vector2) into texture. + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "ambientTexture.uScale"), + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "ambientTexture.vScale", getNextFloat), + ], + }, + offset: { + properties: [ + // MAY introduce set offset(Vector2) into texture. + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "ambientTexture.uOffset"), + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "ambientTexture.vOffset", getNextFloat), + ], + }, + rotation: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "ambientTexture.wAng", getMinusFloat)], + }, + }, + }, + }, + emissiveTexture: { + extensions: { + KHR_texture_transform: { + scale: { + properties: [ + // MAY introduce set scale(Vector2) into texture. + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "emissiveTexture.uScale"), + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "emissiveTexture.vScale", getNextFloat), + ], + }, + offset: { + properties: [ + // MAY introduce set offset(Vector2) into texture. + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "emissiveTexture.uOffset"), + new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "emissiveTexture.vOffset", getNextFloat), + ], + }, + rotation: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "emissiveTexture.wAng", getMinusFloat)], + }, + }, + }, + }, + extensions: { + KHR_materials_ior: { + ior: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "indexOfRefraction")], + }, + }, + KHR_materials_clearcoat: { + clearcoatFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "clearCoat.intensity")], + }, + clearcoatRoughnessFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "clearCoat.roughness")], + }, + }, + KHR_materials_sheen: { + sheenColorFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_COLOR3, "sheen.color", getColor3)], + }, + sheenRoughnessFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "sheen.roughness")], + }, + }, + KHR_materials_specular: { + specularFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "metallicF0Factor")], + }, + specularColorFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_COLOR3, "metallicReflectanceColor", getColor3)], + }, + }, + KHR_materials_emissive_strength: { + emissiveStrength: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "emissiveIntensity")], + }, + }, + KHR_materials_transmission: { + transmissionFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "subSurface.refractionIntensity")], + }, + }, + KHR_materials_volume: { + attenuationColor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_COLOR3, "subSurface.tintColor", getColor3)], + }, + attenuationDistance: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "subSurface.tintColorAtDistance")], + }, + thicknessFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "subSurface.maximumThickness")], + }, + }, + KHR_materials_iridescence: { + iridescenceFactor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "iridescence.intensity")], + }, + iridescenceIor: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "iridescence.indexOfRefraction")], + }, + iridescenceThicknessMinimum: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "iridescence.minimumThickness")], + }, + iridescenceThicknessMaximum: { + properties: [new MaterialAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "iridescence.maximumThickness")], + }, + }, + }, +}; + +const CoreAnimationExtensionsPointerMap: any = { + getTarget: getGltfExtension, + KHR_lights_punctual: { + isIndex: true, + lights: { + hasIndex: true, // we have an array of light into the extension. + color: { + properties: [new LightAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_COLOR3, "diffuse", getColor3)], + }, + intensity: { + properties: [new LightAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "intensity")], + }, + range: { + properties: [new LightAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "range")], + }, + spot: { + innerConeAngle: { + properties: [new LightAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "innerAngle", getFloatBy2)], + }, + outerConeAngle: { + properties: [new LightAnimationPointerPropertyInfos(Animation.ANIMATIONTYPE_FLOAT, "angle", getFloatBy2)], + }, + }, + }, + }, +}; + +export const CoreAnimationPointerMap: any = { + nodes: CoreAnimationNodesPointerMap, + materials: CoreAnimationMaterialsPointerMap, + cameras: CoreAnimationCamerasPointerMap, + extensions: CoreAnimationExtensionsPointerMap, +}; diff --git a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_animation_pointer.ts b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_animation_pointer.ts new file mode 100644 index 00000000000..1830d02c892 --- /dev/null +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_animation_pointer.ts @@ -0,0 +1,324 @@ +import type { IGLTFLoaderExtension } from "../glTFLoaderExtension"; +import { ArrayItem, GLTFLoader } from "../glTFLoader"; +import type { Nullable } from "core/types"; +import { AnimationGroup } from "core/Animations/animationGroup"; +import type { IAnimatable } from "core/Animations/animatable.interface"; +import type { IAnimation, IAnimationChannel, _IAnimationSamplerData, IAnimationSampler } from "../glTFLoaderInterfaces"; + +import { AnimationChannelTargetPath, AnimationSamplerInterpolation } from "babylonjs-gltf2interface"; +import { AnimationKeyInterpolation } from "core/Animations/animationKey"; +import { CoreAnimationPointerMap } from "./KHR_animation_pointer.map"; +import type { GetGltfNodeTargetFn, IAnimationPointerPropertyInfos } from "./KHR_animation_pointer.map"; +import { getDataAccessorElementCount } from "../glTFUtilities"; + +const NAME = GLTFLoader._KHRAnimationPointerName; + +interface IAnimationChannelTarget { + stride?: number; + target: any; + properties: Array; + params: any; +} + +/** + * [Specification PR](https://github.com/KhronosGroup/glTF/pull/2147) + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_animation_pointer implements IGLTFLoaderExtension { + /** + * used to gently ignore invalid pointer. If false, invalid pointer will throw exception. + */ + public ignoreInvalidPointer: boolean = true; + + /** + * The name of this extension. + */ + public readonly name = NAME; + + private _loader: GLTFLoader; + + /** + * @param loader + * @hidden + */ + constructor(loader: GLTFLoader) { + this._loader = loader; + } + + /** + * Defines whether this extension is enabled. + */ + public get enabled(): boolean { + return this._loader.isExtensionUsed(NAME); + } + + /** @hidden */ + public dispose() { + (this._loader as any) = null; + } + + /** + * according to specification, + * It is not allowed to animate a glTFid property, as it does change the structure of the glTF in general + * It is not allowed to animate a name property in general. + * @param property + * @hidden + */ + public accept(property: string): boolean { + return property != "name"; + } + + public loadAnimationAsync(context: string, animation: IAnimation): Nullable> { + // ensure an animation group is present. + if (!animation._babylonAnimationGroup) { + this._loader.babylonScene._blockEntityCollection = !!this._loader._assetContainer; + const group = new AnimationGroup(animation.name || `animation${animation.index}`, this._loader.babylonScene); + group._parentContainer = this._loader._assetContainer; + this._loader.babylonScene._blockEntityCollection = false; + animation._babylonAnimationGroup = group; + } + const babylonAnimationGroup = animation._babylonAnimationGroup; + + const promises = new Array>(); + ArrayItem.Assign(animation.channels); + ArrayItem.Assign(animation.samplers); + + for (const channel of animation.channels) { + promises.push(this._loadAnimationChannelAsync(`${context}/channels/${channel.index}`, context, animation, channel)); + } + + return Promise.all(promises).then(() => { + babylonAnimationGroup.normalize(0); + return babylonAnimationGroup; + }); + } + + /** + * @hidden Loads a glTF animation channel. + * @param context The context when loading the asset + * @param animationContext The context of the animation when loading the asset + * @param animation The glTF animation property + * @param channel The glTF animation channel property + * @param animationTargetOverride The babylon animation channel target override property. My be null. + * @returns A void promise when the channel load is complete + */ + public _loadAnimationChannelAsync( + context: string, + animationContext: string, + animation: IAnimation, + channel: IAnimationChannel, + animationTargetOverride: Nullable = null + ): Promise { + if (channel.target.path != AnimationChannelTargetPath.POINTER) { + throw new Error(`${context}/target/path: Invalid value (${channel.target.path})`); + } + + if (channel.target.node != undefined) { + // According to KHR_animation_pointer specification + // If this extension is used, the animation.channel.target.node must not be set. + // Because the node is defined, the channel is ignored and not animated due to the specification. + return Promise.resolve(); + } + + const pointer = channel.target.extensions?.KHR_animation_pointer?.pointer; + if (!pointer) { + throw new Error(`${context}/target/extensions/${this.name}: Pointer is missing`); + } + + const sampler = ArrayItem.Get(`${context}/sampler`, animation.samplers, channel.sampler); + + return this._loadAnimationSamplerAsync(`${context}/samplers/${channel.sampler}`, sampler).then((data) => { + // this is where we process the pointer. + const animationTarget = this._parseAnimationPointer(`${context}/extensions/${this.name}/pointer`, pointer); + + if (!animationTarget) { + return; + } + // build the keys + // build the animations into the group + const babylonAnimationGroup = animation._babylonAnimationGroup; + if (!babylonAnimationGroup) { + return; + } + + const outputAccessor = ArrayItem.Get(`${context}/output`, this._loader.gltf.accessors, sampler.output); + // stride is the size of each element stored into the output buffer. + const stride = animationTarget.stride ?? getDataAccessorElementCount(outputAccessor.type); + const fps = this._loader.parent.targetFps; + + // we extract the corresponding values from the read value. + // the reason for that is one GLTF value may be dispatched to several Babylon properties + // one of example is baseColorFactor which is a Color4 under GLTF and dispatched to + // - albedoColor as Color3(Color4.r,Color4.g,Color4.b) + // - alpha as Color4.a + for (const propertyInfo of animationTarget.properties) { + // Ignore animations that have no animation valid targets. + if (!propertyInfo.isValid(animationTarget.target)) { + return; + } + + // build the keys. + const keys = new Array(data.input.length); + let outputOffset = 0; + + switch (data.interpolation) { + case AnimationSamplerInterpolation.STEP: { + for (let frameIndex = 0; frameIndex < data.input.length; frameIndex++) { + keys[frameIndex] = { + frame: data.input[frameIndex] * fps, + value: propertyInfo.get(animationTarget.target, data.output, outputOffset), + interpolation: AnimationKeyInterpolation.STEP, + }; + outputOffset += stride; + } + break; + } + case AnimationSamplerInterpolation.CUBICSPLINE: { + const invfps = 1 / fps; + for (let frameIndex = 0; frameIndex < data.input.length; frameIndex++) { + const k: any = { + frame: data.input[frameIndex] * fps, + }; + + (k.inTangent = propertyInfo.get(animationTarget.target, data.output, outputOffset, invfps)), (outputOffset += stride); + (k.value = propertyInfo.get(animationTarget.target, data.output, outputOffset)), (outputOffset += stride); + (k.outTangent = propertyInfo.get(animationTarget.target, data.output, outputOffset, invfps)), (outputOffset += stride); + + keys[frameIndex] = k; + } + break; + } + case AnimationSamplerInterpolation.LINEAR: + default: { + for (let frameIndex = 0; frameIndex < data.input.length; frameIndex++) { + keys[frameIndex] = { + frame: data.input[frameIndex] * fps, + value: propertyInfo.get(animationTarget.target, data.output, outputOffset), + }; + outputOffset += stride; + } + break; + } + } + + // each properties has its own build animation process. + // these logics are located into KHR_animation_pointer.map.ts + propertyInfo.buildAnimations(animationTarget.target, fps, keys, babylonAnimationGroup, animationTargetOverride, animationTarget.params); + } + }); + } + + private _loadAnimationSamplerAsync(context: string, sampler: IAnimationSampler): Promise<_IAnimationSamplerData> { + if (sampler._data) { + return sampler._data; + } + + const interpolation = sampler.interpolation || AnimationSamplerInterpolation.LINEAR; + switch (interpolation) { + case AnimationSamplerInterpolation.STEP: + case AnimationSamplerInterpolation.LINEAR: + case AnimationSamplerInterpolation.CUBICSPLINE: { + break; + } + default: { + throw new Error(`${context}/interpolation: Invalid value (${sampler.interpolation})`); + } + } + + const inputAccessor = ArrayItem.Get(`${context}/input`, this._loader.gltf.accessors, sampler.input); + const outputAccessor = ArrayItem.Get(`${context}/output`, this._loader.gltf.accessors, sampler.output); + sampler._data = Promise.all([ + this._loader._loadFloatAccessorAsync(`/accessors/${inputAccessor.index}`, inputAccessor), + this._loader._loadFloatAccessorAsync(`/accessors/${outputAccessor.index}`, outputAccessor), + ]).then(([inputData, outputData]) => { + return { + input: inputData, + interpolation: interpolation, + output: outputData, + }; + }); + + return sampler._data; + } + + /** + * parsing animation pointer is the core of animation channel. + * Animation pointer is a Json pointer, which mean it locate an item into the json hierarchy. + * Consequentely the pointer has the following BNF + + * := + * := "nodes" | "materials" | "meshes" | "cameras" | "extensions" + * := | + * := | + * := "extensions" + * := | + * := "/" + * := W+ + * := D+ + * + * examples of pointer are + * - "/nodes/0/rotation" + * - "/materials/2/emissiveFactor" + * - "/materials/2/pbrMetallicRoughness/baseColorFactor" + * - "/materials/2/extensions/KHR_materials_emissive_strength/emissiveStrength" + * @param context + * @param pointer + * @return + */ + private _parseAnimationPointer(context: string, pointer: string): Nullable { + const sep = "/"; + if (pointer.charAt(0) == sep) { + pointer = pointer.substring(1); + } + const parts = pointer.split(sep); + // we have a least 3 part + if (parts.length >= 3) { + let node = CoreAnimationPointerMap; // the map of possible path + const indices = []; + let getTarget: Nullable = null; + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + node = node[part]; + + if (!node) { + // nothing to do so far + break; + } + + if (node.getTarget) { + getTarget = node.getTarget; + } + + if (node.hasIndex) { + indices.push(parts[++i]); + // move to the next part + continue; + } + + if (node.isIndex) { + indices.push(part); + // move to the next part + continue; + } + + if (node.properties && getTarget) { + const t = getTarget(this._loader.gltf, indices[0]); + if (t != null) { + return { + target: t, + stride: node.getStride ? node.getStride(t) : undefined, + properties: node.properties, + params: indices, + }; + } + } + } + } + if (this.ignoreInvalidPointer) { + return null; + } + throw new Error(`${context} invalid pointer. ${pointer}`); + } +} + +GLTFLoader.RegisterExtension(NAME, (loader) => new KHR_animation_pointer(loader)); diff --git a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index e49e9667876..60d84dee56e 100644 --- a/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -8,9 +8,9 @@ import { SpotLight } from "core/Lights/spotLight"; import { Light } from "core/Lights/light"; import type { TransformNode } from "core/Meshes/transformNode"; -import type { IKHRLightsPunctual_LightReference, IKHRLightsPunctual_Light, IKHRLightsPunctual } from "babylonjs-gltf2interface"; +import type { IKHRLightsPunctual_LightReference } from "babylonjs-gltf2interface"; import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; -import type { INode } from "../glTFLoaderInterfaces"; +import type { INode, IKHRLight } from "../glTFLoaderInterfaces"; import type { IGLTFLoaderExtension } from "../glTFLoaderExtension"; import { GLTFLoader, ArrayItem } from "../glTFLoader"; @@ -30,8 +30,9 @@ export class KHR_lights implements IGLTFLoaderExtension { */ public enabled: boolean; + /** hidden */ private _loader: GLTFLoader; - private _lights?: IKHRLightsPunctual_Light[]; + private _lights?: IKHRLight[]; /** * @param loader @@ -52,7 +53,7 @@ export class KHR_lights implements IGLTFLoaderExtension { public onLoading(): void { const extensions = this._loader.gltf.extensions; if (extensions && extensions[this.name]) { - const extension = extensions[this.name] as IKHRLightsPunctual; + const extension = extensions[this.name] as any; this._lights = extension.lights; } } @@ -104,6 +105,7 @@ export class KHR_lights implements IGLTFLoaderExtension { babylonLight.parent = babylonMesh; this._loader._babylonLights.push(babylonLight); + light._babylonLight = babylonLight; GLTFLoader.AddPointerMetadata(babylonLight, extensionContext); diff --git a/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts b/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts index 52f3f11b1d7..0f0cab8eeae 100644 --- a/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts +++ b/packages/dev/loaders/src/glTF/2.0/Extensions/index.ts @@ -20,6 +20,7 @@ export * from "./KHR_mesh_quantization"; export * from "./KHR_texture_basisu"; export * from "./KHR_texture_transform"; export * from "./KHR_xmp_json_ld"; +export * from "./KHR_animation_pointer"; export * from "./MSFT_audio_emitter"; export * from "./MSFT_lod"; export * from "./MSFT_minecraftMesh"; diff --git a/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts b/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts index e5a5460b747..65743a2b5fd 100644 --- a/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts +++ b/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts @@ -6,7 +6,6 @@ import { Tools } from "core/Misc/tools"; import { Camera } from "core/Cameras/camera"; import { FreeCamera } from "core/Cameras/freeCamera"; import { AnimationGroup } from "core/Animations/animationGroup"; -import { Animation } from "core/Animations/animation"; import { Bone } from "core/Bones/bone"; import { Skeleton } from "core/Bones/skeleton"; import { Material } from "core/Materials/material"; @@ -29,7 +28,6 @@ import { AccessorType, CameraType, AnimationChannelTargetPath, - AnimationSamplerInterpolation, AccessorComponentType, MaterialAlphaMode, TextureMinFilter, @@ -38,7 +36,6 @@ import { MeshPrimitiveMode, } from "babylonjs-gltf2interface"; import type { - _IAnimationSamplerData, IGLTF, ISampler, INode, @@ -48,8 +45,6 @@ import type { ISkin, ICamera, IAnimation, - IAnimationChannel, - IAnimationSampler, IBuffer, IBufferView, IMaterialPbrMetallicRoughness, @@ -64,9 +59,6 @@ import type { import type { IGLTFLoaderExtension } from "./glTFLoaderExtension"; import type { IGLTFLoader, IGLTFLoaderData } from "../glTFFileLoader"; import { GLTFFileLoader, GLTFLoaderState, GLTFLoaderCoordinateSystemMode, GLTFLoaderAnimationStartMode } from "../glTFFileLoader"; -import type { IAnimationKey } from "core/Animations/animationKey"; -import { AnimationKeyInterpolation } from "core/Animations/animationKey"; -import type { IAnimatable } from "core/Animations/animatable.interface"; import type { IDataBuffer } from "core/Misc/dataReader"; import { DecodeBase64UrlToBinary, IsBase64DataUrl, LoadFileError } from "core/Misc/fileTools"; import { Logger } from "core/Misc/logger"; @@ -130,6 +122,11 @@ export class ArrayItem { * The glTF 2.0 loader */ export class GLTFLoader implements IGLTFLoader { + /** @hidden */ + // note : KHR_animation_pointer is used to load animation in ALL case, turning everything + // into pointer. This is the reason why this value is located here. + public static readonly _KHRAnimationPointerName = "KHR_animation_pointer"; + /** @hidden */ public readonly _completePromises = new Array>(); @@ -1463,6 +1460,8 @@ export class GLTFLoader implements IGLTFLoader { GLTFLoader.AddPointerMetadata(babylonCamera, context); this._parent.onCameraLoadedObservable.notifyObservers(babylonCamera); assign(babylonCamera); + // register the camera to be used later. + camera._babylonCamera = babylonCamera; this.logClose(); @@ -1501,238 +1500,34 @@ export class GLTFLoader implements IGLTFLoader { * @returns A promise that resolves with the loaded Babylon animation group when the load is complete */ public loadAnimationAsync(context: string, animation: IAnimation): Promise { - const promise = this._extensionsLoadAnimationAsync(context, animation); - if (promise) { - return promise; + // turn everything into pointer + for (const channel of animation.channels) { + if (channel.target.path == AnimationChannelTargetPath.POINTER) { + continue; + } + // decorate the channel with a KHR_animation_pointer extension. + channel.target.extensions = channel.target.extensions || {}; + channel.target.extensions.KHR_animation_pointer = { + pointer: `/nodes/${channel.target.node}/${channel.target.path}`, + }; + channel.target.path = AnimationChannelTargetPath.POINTER; + delete channel.target.node; + // ensure to declare extension used. + this._gltf.extensionsUsed = this._gltf.extensionsUsed || []; + if (this._gltf.extensionsUsed.indexOf(GLTFLoader._KHRAnimationPointerName) === -1) { + this._gltf.extensionsUsed.push(GLTFLoader._KHRAnimationPointerName); + } } + // create the animation group to be passed to extension. this._babylonScene._blockEntityCollection = !!this._assetContainer; const babylonAnimationGroup = new AnimationGroup(animation.name || `animation${animation.index}`, this._babylonScene); babylonAnimationGroup._parentContainer = this._assetContainer; this._babylonScene._blockEntityCollection = false; animation._babylonAnimationGroup = babylonAnimationGroup; - const promises = new Array>(); - - ArrayItem.Assign(animation.channels); - ArrayItem.Assign(animation.samplers); - - for (const channel of animation.channels) { - promises.push(this._loadAnimationChannelAsync(`${context}/channels/${channel.index}`, context, animation, channel, babylonAnimationGroup)); - } - - return Promise.all(promises).then(() => { - babylonAnimationGroup.normalize(0); - return babylonAnimationGroup; - }); - } - - /** - * @hidden Loads a glTF animation channel. - * @param context The context when loading the asset - * @param animationContext The context of the animation when loading the asset - * @param animation The glTF animation property - * @param channel The glTF animation channel property - * @param babylonAnimationGroup The babylon animation group property - * @param animationTargetOverride The babylon animation channel target override property. My be null. - * @returns A void promise when the channel load is complete - */ - public _loadAnimationChannelAsync( - context: string, - animationContext: string, - animation: IAnimation, - channel: IAnimationChannel, - babylonAnimationGroup: AnimationGroup, - animationTargetOverride: Nullable = null - ): Promise { - if (channel.target.node == undefined) { - return Promise.resolve(); - } - - const targetNode = ArrayItem.Get(`${context}/target/node`, this._gltf.nodes, channel.target.node); - - // Ignore animations that have no animation targets. - if ( - (channel.target.path === AnimationChannelTargetPath.WEIGHTS && !targetNode._numMorphTargets) || - (channel.target.path !== AnimationChannelTargetPath.WEIGHTS && !targetNode._babylonTransformNode) - ) { - return Promise.resolve(); - } - - const sampler = ArrayItem.Get(`${context}/sampler`, animation.samplers, channel.sampler); - return this._loadAnimationSamplerAsync(`${animationContext}/samplers/${channel.sampler}`, sampler).then((data) => { - let targetPath: string; - let animationType: number; - switch (channel.target.path) { - case AnimationChannelTargetPath.TRANSLATION: { - targetPath = "position"; - animationType = Animation.ANIMATIONTYPE_VECTOR3; - break; - } - case AnimationChannelTargetPath.ROTATION: { - targetPath = "rotationQuaternion"; - animationType = Animation.ANIMATIONTYPE_QUATERNION; - break; - } - case AnimationChannelTargetPath.SCALE: { - targetPath = "scaling"; - animationType = Animation.ANIMATIONTYPE_VECTOR3; - break; - } - case AnimationChannelTargetPath.WEIGHTS: { - targetPath = "influence"; - animationType = Animation.ANIMATIONTYPE_FLOAT; - break; - } - default: { - throw new Error(`${context}/target/path: Invalid value (${channel.target.path})`); - } - } - - let outputBufferOffset = 0; - let getNextOutputValue: (scale: number) => Vector3 | Quaternion | Array; - switch (targetPath) { - case "position": { - getNextOutputValue = (scale) => { - const value = Vector3.FromArray(data.output, outputBufferOffset).scaleInPlace(scale); - outputBufferOffset += 3; - return value; - }; - break; - } - case "rotationQuaternion": { - getNextOutputValue = (scale) => { - const value = Quaternion.FromArray(data.output, outputBufferOffset).scaleInPlace(scale); - outputBufferOffset += 4; - return value; - }; - break; - } - case "scaling": { - getNextOutputValue = (scale) => { - const value = Vector3.FromArray(data.output, outputBufferOffset).scaleInPlace(scale); - outputBufferOffset += 3; - return value; - }; - break; - } - case "influence": { - getNextOutputValue = (scale) => { - const value = new Array(targetNode._numMorphTargets!); - for (let i = 0; i < targetNode._numMorphTargets!; i++) { - value[i] = data.output[outputBufferOffset++] * scale; - } - return value; - }; - break; - } - } - - let getNextKey: (frameIndex: number) => IAnimationKey; - switch (data.interpolation) { - case AnimationSamplerInterpolation.STEP: { - getNextKey = (frameIndex) => ({ - frame: data.input[frameIndex] * this.parent.targetFps, - value: getNextOutputValue(1), - interpolation: AnimationKeyInterpolation.STEP, - }); - break; - } - case AnimationSamplerInterpolation.LINEAR: { - getNextKey = (frameIndex) => ({ - frame: data.input[frameIndex] * this.parent.targetFps, - value: getNextOutputValue(1), - }); - break; - } - case AnimationSamplerInterpolation.CUBICSPLINE: { - const invTargetFps = 1 / this.parent.targetFps; - getNextKey = (frameIndex) => ({ - frame: data.input[frameIndex] * this.parent.targetFps, - inTangent: getNextOutputValue(invTargetFps), - value: getNextOutputValue(1), - outTangent: getNextOutputValue(invTargetFps), - }); - break; - } - } - - const keys = new Array(data.input.length); - for (let frameIndex = 0; frameIndex < data.input.length; frameIndex++) { - keys[frameIndex] = getNextKey!(frameIndex); - } - - if (targetPath === "influence") { - for (let targetIndex = 0; targetIndex < targetNode._numMorphTargets!; targetIndex++) { - const animationName = `${babylonAnimationGroup.name}_channel${babylonAnimationGroup.targetedAnimations.length}`; - const babylonAnimation = new Animation(animationName, targetPath, this.parent.targetFps, animationType); - babylonAnimation.setKeys( - keys.map((key) => ({ - frame: key.frame, - inTangent: key.inTangent ? key.inTangent[targetIndex] : undefined, - value: key.value[targetIndex], - outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined, - interpolation: key.interpolation, - })) - ); - - this._forEachPrimitive(targetNode, (babylonAbstractMesh: AbstractMesh) => { - const babylonMesh = babylonAbstractMesh as Mesh; - if (babylonMesh.morphTargetManager) { - const morphTarget = babylonMesh.morphTargetManager.getTarget(targetIndex); - const babylonAnimationClone = babylonAnimation.clone(); - morphTarget.animations.push(babylonAnimationClone); - babylonAnimationGroup.addTargetedAnimation(babylonAnimationClone, morphTarget); - } - }); - } - } else { - const animationName = `${babylonAnimationGroup.name}_channel${babylonAnimationGroup.targetedAnimations.length}`; - const babylonAnimation = new Animation(animationName, targetPath, this.parent.targetFps, animationType); - babylonAnimation.setKeys(keys); - - if (animationTargetOverride != null && animationTargetOverride.animations != null) { - animationTargetOverride.animations.push(babylonAnimation); - babylonAnimationGroup.addTargetedAnimation(babylonAnimation, animationTargetOverride); - } else { - targetNode._babylonTransformNode!.animations.push(babylonAnimation); - babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonTransformNode!); - } - } - }); - } - - private _loadAnimationSamplerAsync(context: string, sampler: IAnimationSampler): Promise<_IAnimationSamplerData> { - if (sampler._data) { - return sampler._data; - } - - const interpolation = sampler.interpolation || AnimationSamplerInterpolation.LINEAR; - switch (interpolation) { - case AnimationSamplerInterpolation.STEP: - case AnimationSamplerInterpolation.LINEAR: - case AnimationSamplerInterpolation.CUBICSPLINE: { - break; - } - default: { - throw new Error(`${context}/interpolation: Invalid value (${sampler.interpolation})`); - } - } - - const inputAccessor = ArrayItem.Get(`${context}/input`, this._gltf.accessors, sampler.input); - const outputAccessor = ArrayItem.Get(`${context}/output`, this._gltf.accessors, sampler.output); - sampler._data = Promise.all([ - this._loadFloatAccessorAsync(`/accessors/${inputAccessor.index}`, inputAccessor), - this._loadFloatAccessorAsync(`/accessors/${outputAccessor.index}`, outputAccessor), - ]).then(([inputData, outputData]) => { - return { - input: inputData, - interpolation: interpolation, - output: outputData, - }; - }); - - return sampler._data; + const promise = this._extensionsLoadAnimationAsync(context, animation); + return promise ?? Promise.resolve(animation._babylonAnimationGroup); } /** diff --git a/packages/dev/loaders/src/glTF/2.0/glTFLoaderInterfaces.ts b/packages/dev/loaders/src/glTF/2.0/glTFLoaderInterfaces.ts index 763ea2ecb07..ad02a95fb1a 100644 --- a/packages/dev/loaders/src/glTF/2.0/glTFLoaderInterfaces.ts +++ b/packages/dev/loaders/src/glTF/2.0/glTFLoaderInterfaces.ts @@ -5,6 +5,8 @@ import type { TransformNode } from "core/Meshes/transformNode"; import type { Buffer, VertexBuffer } from "core/Buffers/buffer"; import type { AbstractMesh } from "core/Meshes/abstractMesh"; import type { Mesh } from "core/Meshes/mesh"; +import type { Camera } from "core/Cameras/camera"; +import type { Light } from "core/Lights/light"; import type * as GLTF2 from "babylonjs-gltf2interface"; @@ -83,7 +85,18 @@ export interface IBufferView extends GLTF2.IBufferView, IArrayItem { /** * Loader interface with additional members. */ -export interface ICamera extends GLTF2.ICamera, IArrayItem {} +export interface IKHRLight extends GLTF2.IKHRLightsPunctual_Light { + /** @hidden */ + _babylonLight: Light; +} + +/** + * Loader interface with additional members. + */ +export interface ICamera extends GLTF2.ICamera, IArrayItem { + /** @hidden */ + _babylonCamera: Camera; +} /** * Loader interface with additional members. diff --git a/packages/dev/loaders/src/glTF/2.0/glTFUtilities.ts b/packages/dev/loaders/src/glTF/2.0/glTFUtilities.ts new file mode 100644 index 00000000000..385b51be067 --- /dev/null +++ b/packages/dev/loaders/src/glTF/2.0/glTFUtilities.ts @@ -0,0 +1,24 @@ +import { AccessorType } from "babylonjs-gltf2interface"; + +/** + * Used internally to determine how much data to be gather from input buffer at each key frame. + * @param accessorType the accessor type + * @returns the number of item to be gather at each keyframe + */ +export function getDataAccessorElementCount(accessorType: AccessorType) { + switch (accessorType) { + case AccessorType.SCALAR: + return 1; + case AccessorType.VEC2: + return 2; + case AccessorType.VEC3: + return 3; + case AccessorType.VEC4: + case AccessorType.MAT2: + return 4; + case AccessorType.MAT3: + return 9; + case AccessorType.MAT4: + return 16; + } +} diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index 0cdcc60e1a5..f50d59d8fd8 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -463,6 +463,8 @@ export class _GLTFAnimation { } } } + } else { + // this is the place for the KHR_animation_pointer. } } morphAnimationMeshes.forEach((mesh) => { diff --git a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts index 4e4b53c8b1e..d00a1305a79 100644 --- a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts +++ b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts @@ -73,7 +73,9 @@ declare module BABYLON.GLTF2 { } /** - * The name of the node's TRS property to modify, or the weights of the Morph Targets it instantiates + * The name of the node's TRS property to modify, + * or the weights of the Morph Targets it instantiates, + * or pointer is use of KHR_animation_pointer extension */ const enum AnimationChannelTargetPath { /** @@ -92,6 +94,10 @@ declare module BABYLON.GLTF2 { * Weights */ WEIGHTS = "weights", + /** + * Pointer + */ + POINTER = "pointer", } /** @@ -393,7 +399,7 @@ declare module BABYLON.GLTF2 { /** * The index of the node to target */ - node: number; + node?: number; /** * The name of the node's TRS property to modify, or the weights of the Morph Targets it instantiates */