gpu-curtains
Version:
gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.
663 lines (662 loc) • 29.9 kB
JavaScript
import { KeyframesAnimation } from "../animations/KeyframesAnimation.mjs";
//#region src/extras/gltf/GLTFPointerAnimationsManager.ts
/**
* Additional class to help manage glTF pointer animations defined by the [KHR_animation_pointer](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_animation_pointer) extension.
*
* @example
* ```javascript
* const gltfLoader = new GLTFLoader()
* const pointerAnimationsManager = new GLTFPointerAnimationsManager()
* const gltf = await gltfLoader.loadFromUrl('path/to/model.gltf')
*
* // create a gltfScenesManager from the resulting 'gltf' object
* // assuming 'renderer' is a valid camera renderer or curtains instance
* const gltfScenesManager = new GLTFScenesManager({ renderer, gltf })
*
* // create the pointer animations
* pointerAnimationsManager.createPointerAnimations(gltfScenesManager)
*
* // add the meshes
* gltfScenesManager.addMeshes()
* ```
*/
var GLTFPointerAnimationsManager = class {
/**
* {@link GLTFPointerAnimationsManager} constructor.
*/
constructor() {
this.gltfScenesManager = null;
this.resetAnimationsMaps();
}
/** Reset the {@link materialAnimations} map. */
resetAnimationsMaps() {
this.materialAnimations = /* @__PURE__ */ new Map();
}
/**
* Add an {@link Object3D} as a {@link TargetsAnimationsManager} target.
* @param object - {@link Object3D} to add.
* @param targetsAnimation - {@link TargetsAnimationsManager} to add to.
*/
addObjectToTargetAnimation(object, targetsAnimation) {
if (!targetsAnimation.targets.find((t) => t.object.object3DIndex === object.object3DIndex)) targetsAnimation.addTarget(object);
}
/**
* Get the {@link PointerAnimationType | animation type} and animated property from a given pointer animation channel.
* @param propertyPaths - Array of strings parsed from the pointer channel extension path.
* @returns - The correct animation type and property.
*/
getAnimationTypeAndProperty(propertyPaths) {
let animatedProperty = propertyPaths[propertyPaths.length - 1];
let animationType = "materials";
if (propertyPaths.includes("nodes")) animationType = "nodes";
else if (propertyPaths.includes("cameras")) animationType = "cameras";
else if (propertyPaths.includes("lights")) animationType = "lights";
else if (propertyPaths.includes("normalTexture") && animatedProperty === "scale" && !propertyPaths.includes("KHR_texture_transform")) {
animationType = "materials";
animatedProperty = "normalScale";
} else if (propertyPaths.includes("clearcoatNormalTexture") && animatedProperty === "scale" && !propertyPaths.includes("KHR_texture_transform")) {
animationType = "materials";
animatedProperty = "clearcoatNormalScale";
} else if (propertyPaths.includes("occlusionTexture") && animatedProperty === "strength") {
animationType = "materials";
animatedProperty = "occlusionIntensity";
} else if (propertyPaths.find((p) => p.indexOf("texture") !== -1) || propertyPaths.find((p) => p.indexOf("Texture") !== -1)) animationType = "textures";
return {
animationType,
animatedProperty
};
}
/**
* Get any camera animations {@link KeyframesAnimationValueType | value type} and key (property) to use for the {@link KeyframesAnimation}.
* @param animatedProperty - Animated property from the pointer channel extension path.
* @returns - The camera animations {@link KeyframesAnimationValueType | value type} and key (property) to animate.
*/
getCleanCameraProperties(animatedProperty) {
switch (animatedProperty) {
case "znear": return {
type: "scalar",
key: "near"
};
case "zfar": return {
type: "scalar",
key: "far"
};
case "yfov": return {
type: "scalar",
key: "fov"
};
case "aspectRatio": return {
type: "scalar",
key: "forceAspect"
};
case "xmag": return {
type: "scalar",
key: "left"
};
case "ymag": return {
type: "scalar",
key: "top"
};
default: return {
type: null,
key: null
};
}
}
/**
* Get any light animations {@link KeyframesAnimationValueType | value type} and key (property) to use for the {@link KeyframesAnimation}.
* @param animatedProperty - Animated property from the pointer channel extension path.
* @returns - The light animations {@link KeyframesAnimationValueType | value type} and key (property) to animate.
*/
getCleanLightProperties(animatedProperty) {
switch (animatedProperty) {
case "color": return {
type: "vec3",
key: animatedProperty
};
case "intensity":
case "range":
case "innerConeAngle":
case "outerConeAngle": return {
type: "scalar",
key: animatedProperty
};
default: return {
type: null,
key: null
};
}
}
/**
* Get any material animations {@link KeyframesAnimationValueType | value type} and key (property) to use for the {@link KeyframesAnimation}.
* @param animatedProperty - Animated property from the pointer channel extension path.
* @returns - The material animations {@link KeyframesAnimationValueType | value type} and {@link PointerAnimatedMaterialProperty | material key (property)} to animate.
*/
getCleanMaterialProperties(animatedProperty) {
return (() => {
switch (animatedProperty) {
case "alphaCutoff":
case "occlusionIntensity":
case "clearcoatRoughness":
case "dispersion":
case "ior":
case "attenuationDistance":
case "normalScale":
case "clearcoatNormalScale":
case "iridescenceThicknessMinimum":
case "iridescenceThicknessMaximum": return {
type: "scalar",
key: animatedProperty
};
case "attenuationColor": return {
type: "vec3",
key: animatedProperty
};
case "emissiveFactor": return {
type: "vec3",
key: "emissiveColor"
};
case "emissiveStrength": return {
type: "scalar",
key: "emissiveIntensity"
};
case "metallicFactor": return {
type: "scalar",
key: "metallic"
};
case "roughnessFactor": return {
type: "scalar",
key: "roughness"
};
case "anisotropyStrength": return {
type: "scalar",
key: "anisotropy"
};
case "clearcoatFactor": return {
type: "scalar",
key: "clearcoat"
};
case "iridescenceFactor": return {
type: "scalar",
key: "iridescence"
};
case "iridescenceIor": return {
type: "scalar",
key: "iridescenceIOR"
};
case "sheenColorFactor": return {
type: "vec3",
key: "sheenColor"
};
case "sheenRoughnessFactor": return {
type: "scalar",
key: "sheenRoughness"
};
case "specularFactor": return {
type: "scalar",
key: "specular"
};
case "specularColorFactor": return {
type: "vec3",
key: "specularColor"
};
case "transmissionFactor": return {
type: "scalar",
key: "transmission"
};
case "thicknessFactor": return {
type: "scalar",
key: "thickness"
};
case "anisotropyRotation": return {
type: "scalar",
key: "anisotropyVector"
};
default: return {
type: null,
key: null
};
}
})();
}
/**
* Get an array of {@link MediaTexture} from a given array of available {@link ShaderTextureDescriptor} corresponding to the given glTF texture name input.
* @param textureName - glTF texture name to use to retrieve the textures.
* @param texturesDescriptors - Array of available {@link ShaderTextureDescriptor}.
* @returns - Array of matching {@link MediaTexture}.
*/
getCleanTextures(textureName, texturesDescriptors) {
const getMixedTextures = (textureName, texturesDescriptors) => {
const descriptor = texturesDescriptors.find((t) => t.texture.options.name === textureName);
const textures = [];
if (descriptor) textures.push(descriptor.texture);
else {
if (textureName === "specularTexture" || textureName === "specularColorTexture") {
const specDesc = texturesDescriptors.find((t) => t.texture.options.name === "specularTexture");
if (specDesc) textures.push(specDesc);
else {
if (textureName === "specularTexture") {
const specFactorDesc = texturesDescriptors.find((t) => t.texture.options.name === "specularFactorTexture");
if (specFactorDesc) textures.push(specFactorDesc.texture);
}
if (textureName === "specularColorTexture") {
const specColorDesc = texturesDescriptors.find((t) => t.texture.options.name === "specularColorTexture");
if (specColorDesc) textures.push(specColorDesc.texture);
}
}
}
if (textureName === "transmissionTexture" || textureName === "thicknessTexture") {
const trthDesc = texturesDescriptors.find((t) => t.texture.options.name === "transmissionThicknessTexture");
if (trthDesc) textures.push(trthDesc.texture);
else {
if (textureName === "transmissionTexture") {
const trDesc = texturesDescriptors.find((t) => t.texture.options.name === "transmissionTexture");
if (trDesc) textures.push(trDesc.texture);
}
if (textureName === "thicknessTexture") {
const thDesc = texturesDescriptors.find((t) => t.texture.options.name === "thicknessTexture");
if (thDesc) textures.push(thDesc.texture);
}
}
}
if (textureName === "sheenColorTexture" || textureName === "sheenRoughnessTexture") {
const sheenDesc = texturesDescriptors.find((t) => t.texture.options.name === "sheenTexture");
if (sheenDesc) textures.push(sheenDesc.texture);
else {
if (textureName === "sheenColorTexture") {
const sheenColorDesc = texturesDescriptors.find((t) => t.texture.options.name === "sheenColorTexture");
if (sheenColorDesc) textures.push(sheenColorDesc.texture);
}
if (textureName === "sheenRoughnessTexture") {
const sheenRoughDesc = texturesDescriptors.find((t) => t.texture.options.name === "sheenRoughnessTexture");
if (sheenRoughDesc) textures.push(sheenRoughDesc.texture);
}
}
}
if (textureName === "clearcoatTexture" || textureName === "clearcoatRoughnessTexture") {
const ccDesc = texturesDescriptors.find((t) => t.texture.options.name === "clearcoatTexture");
if (ccDesc) textures.push(ccDesc.texture);
else {
if (textureName === "clearcoatTexture") {
const ccFactorDesc = texturesDescriptors.find((t) => t.texture.options.name === "clearcoatFactorTexture");
if (ccFactorDesc) textures.push(ccFactorDesc.texture);
}
if (textureName === "clearcoatRoughnessTexture") {
const ccRoughDesc = texturesDescriptors.find((t) => t.texture.options.name === "clearcoatRoughnessTexture");
if (ccRoughDesc) textures.push(ccRoughDesc.texture);
}
}
}
if (textureName === "iridescenceTexture" || textureName === "iridescenceThicknessTexture") {
const irDesc = texturesDescriptors.find((t) => t.texture.options.name === "iridescenceTexture");
if (irDesc) textures.push(irDesc.texture);
else {
if (textureName === "iridescenceTexture") {
const irFactorDesc = texturesDescriptors.find((t) => t.texture.options.name === "iridescenceFactorTexture");
if (irFactorDesc) textures.push(irFactorDesc.texture);
}
if (textureName === "iridescenceThicknessTexture") {
const irThickDesc = texturesDescriptors.find((t) => t.texture.options.name === "iridescenceThicknessTexture");
if (irThickDesc) textures.push(irThickDesc.texture);
}
}
}
if (textureName === "diffuseTransmissionTexture" || textureName === "diffuseTransmissionColorTexture") {
const difDesc = texturesDescriptors.find((t) => t.texture.options.name === "diffuseTransmissionTexture");
if (difDesc) textures.push(difDesc.texture);
else {
if (textureName === "diffuseTransmissionTexture") {
const difFactorDesc = texturesDescriptors.find((t) => t.texture.options.name === "diffuseTransmissionFactorTexture");
if (difFactorDesc) textures.push(difFactorDesc.texture);
}
if (textureName === "diffuseTransmissionColorTexture") {
const difColorDesc = texturesDescriptors.find((t) => t.texture.options.name === "diffuseTransmissionColorTexture");
if (difColorDesc) textures.push(difColorDesc.texture);
}
}
}
}
return textures;
};
return (() => {
switch (textureName) {
case "baseColorTexture":
case "metallicRoughnessTexture":
case "normalTexture":
case "occlusionTexture":
case "emissiveTexture":
case "anisotropyTexture":
case "clearcoatNormalTexture":
const descriptor = texturesDescriptors.find((t) => t.texture.options.name === textureName);
return descriptor ? [descriptor.texture] : [];
case "specularTexture":
case "specularColorTexture":
case "transmissionTexture":
case "thicknessTexture":
case "sheenColorTexture":
case "sheenRoughnessTexture":
case "clearcoatTexture":
case "clearcoatRoughnessTexture":
case "iridescenceTexture":
case "iridescenceThicknessTexture":
case "diffuseTransmissionTexture":
case "diffuseTransmissionColorTexture": return getMixedTextures(textureName, texturesDescriptors);
default: return [];
}
})();
}
/**
* Create all the necessary pointer {@link KeyframesAnimation} for a given {@link GLTFScenesManager} instance.
*
* Parse the animations channels, and for each one:
* - Get the animation path and use it to extract the {@link PointerAnimationType | animation type} and animated property.
* - Based on the {@link PointerAnimationType | animation type}, create the corresponding {@link KeyframesAnimation} and handle the actual value update (except for materials, where it's done inside {@link registerMeshAnimations} method).
*
* @param gltfScenesManager - {@link GLTFScenesManager} instance to parse for pointer animations.
*/
createPointerAnimations(gltfScenesManager = null) {
if (!gltfScenesManager) return;
if (this.gltfScenesManager) this.gltfScenesManager.pointerAnimationsManager = null;
this.gltfScenesManager = gltfScenesManager;
this.gltfScenesManager.pointerAnimationsManager = this;
this.resetAnimationsMaps();
if (this.gltfScenesManager.gltf.animations) this.gltfScenesManager.scenesManager.animations.forEach((targetsAnimation, i) => {
const animation = this.gltfScenesManager.gltf.animations[i];
const channels = animation.channels.filter((channel) => channel.target.path === "pointer");
if (channels && channels.length) channels.forEach((channel) => {
let propertyPath = channel.target.extensions.KHR_animation_pointer.pointer;
if (propertyPath.startsWith("/extensions/KHR_lights_punctual/")) propertyPath = "/" + propertyPath.substring(32);
const splitedPropertyPaths = propertyPath.split("/");
splitedPropertyPaths.shift();
const { animatedProperty, animationType } = this.getAnimationTypeAndProperty(splitedPropertyPaths);
const propertyIndex = parseInt(splitedPropertyPaths[1]);
if (animationType === "nodes") {
if (animatedProperty === "rotation" || animatedProperty === "scale" || animatedProperty === "translation" || animatedProperty === "visible") {
const node = this.gltfScenesManager.gltf.nodes[propertyIndex];
const sceneNode = this.gltfScenesManager.scenesManager.nodes.get(propertyIndex);
this.addObjectToTargetAnimation(sceneNode, targetsAnimation);
const animName = node.name ? `${node.name} pointer animation` : `${animatedProperty} pointer animation ${propertyIndex}`;
const label = animation.name ? `${animation.name} ${animName}` : `Animation ${i} ${animName}`;
const sampler = animation.samplers[channel.sampler];
const { keyframes, values } = this.gltfScenesManager.getAnimationKeyframesValues(sampler);
const nodeProperties = (() => {
switch (animatedProperty) {
case "translation": return {
inputValue: sceneNode.position,
type: "vec3",
path: animatedProperty
};
case "rotation": return {
inputValue: sceneNode.quaternion,
type: "quaternion",
path: animatedProperty
};
case "scale": return {
inputValue: sceneNode.scale,
type: "vec3",
path: animatedProperty
};
case "visible": return {
inputValue: 0,
type: "scalar",
path: "pointer"
};
default: return {
inputValue: null,
type: null,
path: null
};
}
})();
const keyframesAnimation = new KeyframesAnimation({
label,
inputIndex: sampler.input,
keyframes,
values,
path: nodeProperties.path,
type: nodeProperties.type,
interpolation: sampler.interpolation,
inputValue: nodeProperties.inputValue
});
targetsAnimation.addTargetAnimation(sceneNode, keyframesAnimation);
if (animatedProperty === "visible") keyframesAnimation.onAfterUpdate = () => {
sceneNode.visible = !!keyframesAnimation.inputValue;
};
}
} else if (animationType === "cameras") {
const isOrthographic = splitedPropertyPaths.includes("orthographic");
const gltfCamera = this.gltfScenesManager.gltf.cameras[propertyIndex];
const sampler = animation.samplers[channel.sampler];
const path = channel.target.path;
const { keyframes, values } = this.gltfScenesManager.getAnimationKeyframesValues(sampler);
const cameraProperties = this.getCleanCameraProperties(animatedProperty);
if (cameraProperties.key) if (isOrthographic) {
const camera = this.gltfScenesManager.scenesManager.cameras[propertyIndex];
this.addObjectToTargetAnimation(camera, targetsAnimation);
const animName = gltfCamera.name ? `${gltfCamera.name} animation` : `Orthographic camera ${propertyIndex} animation`;
const keyframesAnimation = new KeyframesAnimation({
label: animation.name ? `${animation.name} ${animName}` : `Animation ${i} ${animName}`,
inputIndex: sampler.input,
keyframes,
values,
path,
type: cameraProperties.type,
interpolation: sampler.interpolation,
...cameraProperties.type === "scalar" && { inputValue: 0 }
});
if (keyframesAnimation.type === "scalar") keyframesAnimation.onAfterUpdate = () => {
const value = keyframesAnimation.inputValue;
if (cameraProperties.key === "left") {
camera.left = -value;
camera.right = value;
this.gltfScenesManager.renderer.updateCameraViewport();
} else if (cameraProperties.key === "top") {
camera.top = value;
camera.bottom = -value;
this.gltfScenesManager.renderer.updateCameraViewport();
} else camera[cameraProperties.key] = value;
};
else keyframesAnimation.inputValue = camera[cameraProperties.key];
targetsAnimation.addTargetAnimation(camera, keyframesAnimation);
} else {
const camera = this.gltfScenesManager.scenesManager.cameras[propertyIndex];
this.addObjectToTargetAnimation(camera, targetsAnimation);
const animName = gltfCamera.name ? `${gltfCamera.name} animation` : `Perspective camera ${propertyIndex} animation`;
const keyframesAnimation = new KeyframesAnimation({
label: animation.name ? `${animation.name} ${animName}` : `Animation ${i} ${animName}`,
inputIndex: sampler.input,
keyframes,
values,
path,
type: cameraProperties.type,
interpolation: sampler.interpolation,
...cameraProperties.type === "scalar" && { inputValue: 0 }
});
if (keyframesAnimation.type === "scalar") keyframesAnimation.onAfterUpdate = () => {
const value = keyframesAnimation.inputValue;
if (cameraProperties.key === "fov") camera.fov = value * 180 / Math.PI;
else if (cameraProperties.key === "forceAspect") {
camera[cameraProperties.key] = value;
this.gltfScenesManager.renderer.updateCameraViewport();
} else camera[cameraProperties.key] = value;
};
else keyframesAnimation.inputValue = camera[cameraProperties.key];
targetsAnimation.addTargetAnimation(camera, keyframesAnimation);
}
} else if (animationType === "lights") {
const light = this.gltfScenesManager.scenesManager.lights[propertyIndex];
const gltfLight = this.gltfScenesManager.gltf.extensions["KHR_lights_punctual"].lights[propertyIndex];
this.addObjectToTargetAnimation(light, targetsAnimation);
const lightProperties = this.getCleanLightProperties(animatedProperty);
const animName = `${light.options.label} ${lightProperties.key} animation`;
const label = animation.name ? `${animation.name} ${animName}` : `Animation ${i} ${animName}`;
const sampler = animation.samplers[channel.sampler];
const path = channel.target.path;
const { keyframes, values } = this.gltfScenesManager.getAnimationKeyframesValues(sampler);
if (lightProperties.key) {
const keyframesAnimation = new KeyframesAnimation({
label,
inputIndex: sampler.input,
keyframes,
values,
path,
type: lightProperties.type,
interpolation: sampler.interpolation,
...lightProperties.type === "scalar" && { inputValue: 0 }
});
const innerConeAngle = gltfLight.type === "spot" ? gltfLight.spot.innerConeAngle !== void 0 ? gltfLight.spot.innerConeAngle : 0 : 0;
const outerConeAngle = gltfLight.type === "spot" ? gltfLight.spot.outerConeAngle !== void 0 ? gltfLight.spot.outerConeAngle : Math.PI / 4 : Math.PI / 4;
const getPenumbra = (innerConeAngle, outerConeAngle) => {
return 1 - innerConeAngle / outerConeAngle;
};
light.userData.innerConeAngle = innerConeAngle;
light.userData.outerConeAngle = outerConeAngle;
if (keyframesAnimation.type === "scalar") keyframesAnimation.onAfterUpdate = () => {
const value = keyframesAnimation.inputValue;
if (lightProperties.key === "innerConeAngle") {
light.userData.innerConeAngle = value;
light.penumbra = getPenumbra(light.userData.innerConeAngle, light.userData.outerConeAngle);
} else if (lightProperties.key === "outerConeAngle") {
light.userData.outerConeAngle = value;
light.penumbra = getPenumbra(light.userData.innerConeAngle, light.userData.outerConeAngle);
light.angle = value;
} else light[lightProperties.key] = value;
};
else keyframesAnimation.inputValue = light[lightProperties.key];
targetsAnimation.addTargetAnimation(light, keyframesAnimation);
}
} else if (animationType === "materials" || animationType === "textures") {
const primitiveInstance = this.gltfScenesManager.getPrimitiveInstanceFromGLTFMaterial(propertyIndex);
if (primitiveInstance) {
const { meshDescriptor } = primitiveInstance;
const targetObject = meshDescriptor.nodes[0];
this.addObjectToTargetAnimation(targetObject, targetsAnimation);
const sampler = animation.samplers[channel.sampler];
const path = channel.target.path;
const { keyframes, values } = this.gltfScenesManager.getAnimationKeyframesValues(sampler);
if (animationType === "materials") {
let animationMap = this.materialAnimations.get(meshDescriptor);
if (!animationMap) {
animationMap = /* @__PURE__ */ new Map();
this.materialAnimations.set(meshDescriptor, animationMap);
}
if (animatedProperty === "baseColorFactor") {
const colorValues = new values.constructor(keyframes.length * 3);
const alphaValues = new values.constructor(keyframes.length);
for (let i = 0, c = 0, a = 0; i < values.length; i += 4, c += 3, a++) {
colorValues[c] = values[i];
colorValues[c + 1] = values[i + 1];
colorValues[c + 2] = values[i + 2];
alphaValues[a] = values[i + 3];
}
const colorAnimName = `${meshDescriptor.parameters.label} color animation`;
const colorKeyframesAnimation = new KeyframesAnimation({
label: animation.name ? `${animation.name} ${colorAnimName}` : `Animation ${i} ${colorAnimName}`,
inputIndex: sampler.input,
keyframes,
values: colorValues,
path,
type: "vec3",
interpolation: sampler.interpolation
});
targetsAnimation.addTargetAnimation(targetObject, colorKeyframesAnimation);
animationMap.set("color", colorKeyframesAnimation);
const alphaAnimName = `${meshDescriptor.parameters.label} opacity animation`;
const alphaKeyframesAnimation = new KeyframesAnimation({
label: animation.name ? `${animation.name} ${alphaAnimName}` : `Animation ${i} ${alphaAnimName}`,
inputIndex: sampler.input,
keyframes,
values: alphaValues,
path,
type: "scalar",
interpolation: sampler.interpolation,
inputValue: 0
});
targetsAnimation.addTargetAnimation(targetObject, alphaKeyframesAnimation);
animationMap.set("opacity", alphaKeyframesAnimation);
} else {
const materialProperties = this.getCleanMaterialProperties(animatedProperty);
const animName = `${meshDescriptor.parameters.label} ${materialProperties.key} animation`;
const label = animation.name ? `${animation.name} ${animName}` : `Animation ${i} ${animName}`;
if (materialProperties.key) {
const keyframesAnimation = new KeyframesAnimation({
label,
inputIndex: sampler.input,
keyframes,
values,
path,
type: materialProperties.type,
interpolation: sampler.interpolation,
...materialProperties.type === "scalar" && { inputValue: 0 }
});
targetsAnimation.addTargetAnimation(meshDescriptor.nodes[0], keyframesAnimation);
animationMap.set(materialProperties.key, keyframesAnimation);
}
}
} else {
const textureName = splitedPropertyPaths.find((s) => s.indexOf("Texture") !== -1);
const animatedTextures = this.getCleanTextures(textureName, meshDescriptor.texturesDescriptors);
const normalTextureDesc = meshDescriptor.texturesDescriptors.find((desc) => desc.texture.options.name === "normalTexture");
const clearcoatNormalTextureDesc = meshDescriptor.texturesDescriptors.find((desc) => desc.texture.options.name === "clearcoatNormalTexture");
if (textureName === "normalTexture" && clearcoatNormalTextureDesc) animatedTextures.push(clearcoatNormalTextureDesc.texture);
else if (textureName === "clearcoatNormalTexture" && normalTextureDesc) animatedTextures.push(normalTextureDesc.texture);
if (animatedTextures.length) animatedTextures.forEach((texture) => {
if (texture.options.useTransform) {
const animName = `${texture.options.label} ${animatedProperty} animation`;
const keyframesAnimation = new KeyframesAnimation({
label: animation.name ? `${animation.name} ${animName}` : `Animation ${i} ${animName}`,
inputIndex: sampler.input,
keyframes,
values,
path,
type: animatedProperty === "rotation" ? "scalar" : "vec2",
interpolation: sampler.interpolation,
...animatedProperty === "rotation" && { inputValue: 0 }
});
if (keyframesAnimation.type === "scalar") keyframesAnimation.onAfterUpdate = () => {
texture[animatedProperty] = keyframesAnimation.inputValue;
};
else keyframesAnimation.inputValue = texture[animatedProperty];
targetsAnimation.addTargetAnimation(targetObject, keyframesAnimation);
}
});
}
}
}
});
});
}
/**
* Handle the {@link PointerMaterialAnimations | pointer material animations} from the {@link materialAnimations} map after the {@link GLTFScenesManager} meshes have been created.
*
* Since material animations need an actual {@link LitMesh} to apply the animation, they actually need to be registered once the mesh has been created.
*
* This method is called internally by {@link GLTFScenesManager}.
*
* @param meshDescriptor - Reference {@link MeshDescriptor} to use as {@link materialAnimations} Map key.
* @param mesh - {@link LitMesh} that will have its material uniform animated.
*/
registerMeshAnimations(meshDescriptor, mesh) {
const meshDescriptorAnimationMap = this.materialAnimations.get(meshDescriptor);
if (meshDescriptorAnimationMap && meshDescriptorAnimationMap.size) {
const geometry = meshDescriptor.parameters.geometry;
const normalYMultiplier = geometry && !!geometry.getAttributeByName("tangent") ? 1 : -1;
meshDescriptorAnimationMap.forEach((animation, property) => {
if (animation.type === "scalar") animation.onAfterUpdate = () => {
const value = animation.inputValue;
if (mesh.uniforms.material[property]) if (property === "normalScale") mesh.uniforms.material.normalScale.value.set(value, value * normalYMultiplier);
else if (property === "clearcoatNormalScale") mesh.uniforms.material.clearcoatNormalScale.value.set(value, value * normalYMultiplier);
else if (property === "anisotropyVector") mesh.uniforms.material.anisotropyVector.value.set(Math.cos(value), Math.sin(value));
else mesh.uniforms.material[property].value = value;
else if (mesh.uniforms.material.iridescenceThicknessRange) {
if (property === "iridescenceThicknessMinimum") mesh.uniforms.material.iridescenceThicknessRange.value.x = value;
else if (property === "iridescenceThicknessMaximum") mesh.uniforms.material.iridescenceThicknessRange.value.y = value;
}
};
else if (mesh.uniforms.material[property]) animation.inputValue = mesh.uniforms.material[property].value;
});
}
}
};
//#endregion
export { GLTFPointerAnimationsManager };