@cesium/engine
Version:
CesiumJS is a JavaScript library for creating 3D globes and 2D maps in a web browser without a plugin.
1,149 lines (1,074 loc) • 38.2 kB
JavaScript
import addExtensionsUsed from "./addExtensionsUsed.js";
import addToArray from "./addToArray.js";
import findAccessorMinMax from "./findAccessorMinMax.js";
import ForEach from "./ForEach.js";
import getAccessorByteStride from "./getAccessorByteStride.js";
import numberOfComponentsForType from "./numberOfComponentsForType.js";
import moveTechniqueRenderStates from "./moveTechniqueRenderStates.js";
import moveTechniquesToExtension from "./moveTechniquesToExtension.js";
import removeUnusedElements from "./removeUnusedElements.js";
import updateAccessorComponentTypes from "./updateAccessorComponentTypes.js";
import removeExtension from "./removeExtension.js";
import Cartesian3 from "../../Core/Cartesian3.js";
import Cartesian4 from "../../Core/Cartesian4.js";
import clone from "../../Core/clone.js";
import ComponentDatatype from "../../Core/ComponentDatatype.js";
import defined from "../../Core/defined.js";
import Matrix4 from "../../Core/Matrix4.js";
import Quaternion from "../../Core/Quaternion.js";
import WebGLConstants from "../../Core/WebGLConstants.js";
const updateFunctions = {
0.8: glTF08to10,
"1.0": glTF10to20,
"2.0": undefined,
};
/**
* Update the glTF version to the latest version (2.0), or targetVersion if specified.
* Applies changes made to the glTF spec between revisions so that the core library
* only has to handle the latest version.
*
* @param {object} gltf A javascript object containing a glTF asset.
* @param {object} [options] Options for updating the glTF.
* @param {string} [options.targetVersion] The glTF will be upgraded until it hits the specified version.
* @param {string[]} [options.baseColorTextureNames] Names of uniforms that indicate base color textures.
* @param {string[]} [options.baseColorFactorNames] Names of uniforms that indicate base color factors.
* @returns {object} The updated glTF asset.
*
* @private
*/
function updateVersion(gltf, options) {
options = options ?? {};
const targetVersion = options.targetVersion;
let version = gltf.version;
gltf.asset = gltf.asset ?? {
version: "1.0",
};
gltf.asset.version = gltf.asset.version ?? "1.0";
version = (version ?? gltf.asset.version).toString();
// Invalid version
if (!Object.prototype.hasOwnProperty.call(updateFunctions, version)) {
// Try truncating trailing version numbers, could be a number as well if it is 0.8
if (defined(version)) {
version = version.substring(0, 3);
}
// Default to 1.0 if it cannot be determined
if (!Object.prototype.hasOwnProperty.call(updateFunctions, version)) {
version = "1.0";
}
}
let updateFunction = updateFunctions[version];
while (defined(updateFunction)) {
if (version === targetVersion) {
break;
}
updateFunction(gltf, options);
version = gltf.asset.version;
updateFunction = updateFunctions[version];
}
if (!options.keepLegacyExtensions) {
convertTechniquesToPbr(gltf, options);
convertMaterialsCommonToPbr(gltf);
}
return gltf;
}
function updateInstanceTechniques(gltf) {
const materials = gltf.materials;
for (const materialId in materials) {
if (Object.prototype.hasOwnProperty.call(materials, materialId)) {
const material = materials[materialId];
const instanceTechnique = material.instanceTechnique;
if (defined(instanceTechnique)) {
material.technique = instanceTechnique.technique;
material.values = instanceTechnique.values;
delete material.instanceTechnique;
}
}
}
}
function setPrimitiveModes(gltf) {
const meshes = gltf.meshes;
for (const meshId in meshes) {
if (Object.prototype.hasOwnProperty.call(meshes, meshId)) {
const mesh = meshes[meshId];
const primitives = mesh.primitives;
if (defined(primitives)) {
const primitivesLength = primitives.length;
for (let i = 0; i < primitivesLength; ++i) {
const primitive = primitives[i];
const defaultMode = primitive.primitive ?? WebGLConstants.TRIANGLES;
primitive.mode = primitive.mode ?? defaultMode;
delete primitive.primitive;
}
}
}
}
}
function updateNodes(gltf) {
const nodes = gltf.nodes;
const axis = new Cartesian3();
const quat = new Quaternion();
for (const nodeId in nodes) {
if (Object.prototype.hasOwnProperty.call(nodes, nodeId)) {
const node = nodes[nodeId];
if (defined(node.rotation)) {
const rotation = node.rotation;
Cartesian3.fromArray(rotation, 0, axis);
Quaternion.fromAxisAngle(axis, rotation[3], quat);
node.rotation = [quat.x, quat.y, quat.z, quat.w];
}
const instanceSkin = node.instanceSkin;
if (defined(instanceSkin)) {
node.skeletons = instanceSkin.skeletons;
node.skin = instanceSkin.skin;
node.meshes = instanceSkin.meshes;
delete node.instanceSkin;
}
}
}
}
function updateAnimations(gltf) {
const animations = gltf.animations;
const accessors = gltf.accessors;
const bufferViews = gltf.bufferViews;
const buffers = gltf.buffers;
const updatedAccessors = {};
const axis = new Cartesian3();
const quat = new Quaternion();
for (const animationId in animations) {
if (Object.prototype.hasOwnProperty.call(animations, animationId)) {
const animation = animations[animationId];
const channels = animation.channels;
const parameters = animation.parameters;
const samplers = animation.samplers;
if (defined(channels)) {
const channelsLength = channels.length;
for (let i = 0; i < channelsLength; ++i) {
const channel = channels[i];
if (channel.target.path === "rotation") {
const accessorId = parameters[samplers[channel.sampler].output];
if (defined(updatedAccessors[accessorId])) {
continue;
}
updatedAccessors[accessorId] = true;
const accessor = accessors[accessorId];
const bufferView = bufferViews[accessor.bufferView];
const buffer = buffers[bufferView.buffer];
const source = buffer.extras._pipeline.source;
const byteOffset =
source.byteOffset + bufferView.byteOffset + accessor.byteOffset;
const componentType = accessor.componentType;
const count = accessor.count;
const componentsLength = numberOfComponentsForType(accessor.type);
const length = accessor.count * componentsLength;
const typedArray = ComponentDatatype.createArrayBufferView(
componentType,
source.buffer,
byteOffset,
length,
);
for (let j = 0; j < count; j++) {
const offset = j * componentsLength;
Cartesian3.unpack(typedArray, offset, axis);
const angle = typedArray[offset + 3];
Quaternion.fromAxisAngle(axis, angle, quat);
Quaternion.pack(quat, typedArray, offset);
}
}
}
}
}
}
}
function removeTechniquePasses(gltf) {
const techniques = gltf.techniques;
for (const techniqueId in techniques) {
if (Object.prototype.hasOwnProperty.call(techniques, techniqueId)) {
const technique = techniques[techniqueId];
const passes = technique.passes;
if (defined(passes)) {
const passName = technique.pass ?? "defaultPass";
if (Object.prototype.hasOwnProperty.call(passes, passName)) {
const pass = passes[passName];
const instanceProgram = pass.instanceProgram;
technique.attributes =
technique.attributes ?? instanceProgram.attributes;
technique.program = technique.program ?? instanceProgram.program;
technique.uniforms = technique.uniforms ?? instanceProgram.uniforms;
technique.states = technique.states ?? pass.states;
}
delete technique.passes;
delete technique.pass;
}
}
}
}
function glTF08to10(gltf) {
if (!defined(gltf.asset)) {
gltf.asset = {};
}
const asset = gltf.asset;
asset.version = "1.0";
// Profile should be an object, not a string
if (typeof asset.profile === "string") {
const split = asset.profile.split(" ");
asset.profile = {
api: split[0],
version: split[1],
};
} else {
asset.profile = {};
}
// Version property should be in asset, not on the root element
if (defined(gltf.version)) {
delete gltf.version;
}
// material.instanceTechnique properties should be directly on the material
updateInstanceTechniques(gltf);
// primitive.primitive should be primitive.mode
setPrimitiveModes(gltf);
// Node rotation should be quaternion, not axis-angle
// node.instanceSkin is deprecated
updateNodes(gltf);
// Animations that target rotations should be quaternion, not axis-angle
updateAnimations(gltf);
// technique.pass and techniques.passes are deprecated
removeTechniquePasses(gltf);
// gltf.allExtensions -> extensionsUsed
if (defined(gltf.allExtensions)) {
gltf.extensionsUsed = gltf.allExtensions;
delete gltf.allExtensions;
}
// gltf.lights -> khrMaterialsCommon.lights
if (defined(gltf.lights)) {
const extensions = gltf.extensions ?? {};
gltf.extensions = extensions;
const materialsCommon = extensions.KHR_materials_common ?? {};
extensions.KHR_materials_common = materialsCommon;
materialsCommon.lights = gltf.lights;
delete gltf.lights;
addExtensionsUsed(gltf, "KHR_materials_common");
}
}
function removeAnimationSamplersIndirection(gltf) {
const animations = gltf.animations;
for (const animationId in animations) {
if (Object.prototype.hasOwnProperty.call(animations, animationId)) {
const animation = animations[animationId];
const parameters = animation.parameters;
if (defined(parameters)) {
const samplers = animation.samplers;
for (const samplerId in samplers) {
if (Object.prototype.hasOwnProperty.call(samplers, samplerId)) {
const sampler = samplers[samplerId];
sampler.input = parameters[sampler.input];
sampler.output = parameters[sampler.output];
}
}
delete animation.parameters;
}
}
}
}
function objectToArray(object, mapping) {
const array = [];
for (const id in object) {
if (Object.prototype.hasOwnProperty.call(object, id)) {
const value = object[id];
mapping[id] = array.length;
array.push(value);
if (!defined(value.name)) {
value.name = id;
}
}
}
return array;
}
function objectsToArrays(gltf) {
let i;
const globalMapping = {
accessors: {},
animations: {},
buffers: {},
bufferViews: {},
cameras: {},
images: {},
materials: {},
meshes: {},
nodes: {},
programs: {},
samplers: {},
scenes: {},
shaders: {},
skins: {},
textures: {},
techniques: {},
};
// Map joint names to id names
let jointName;
const jointNameToId = {};
const nodes = gltf.nodes;
for (const id in nodes) {
if (Object.prototype.hasOwnProperty.call(nodes, id)) {
jointName = nodes[id].jointName;
if (defined(jointName)) {
jointNameToId[jointName] = id;
}
}
}
// Convert top level objects to arrays
for (const topLevelId in gltf) {
if (
Object.prototype.hasOwnProperty.call(gltf, topLevelId) &&
defined(globalMapping[topLevelId])
) {
const objectMapping = {};
const object = gltf[topLevelId];
gltf[topLevelId] = objectToArray(object, objectMapping);
globalMapping[topLevelId] = objectMapping;
}
}
// Remap joint names to array indexes
for (jointName in jointNameToId) {
if (Object.prototype.hasOwnProperty.call(jointNameToId, jointName)) {
jointNameToId[jointName] = globalMapping.nodes[jointNameToId[jointName]];
}
}
// Fix references
if (defined(gltf.scene)) {
gltf.scene = globalMapping.scenes[gltf.scene];
}
ForEach.bufferView(gltf, function (bufferView) {
if (defined(bufferView.buffer)) {
bufferView.buffer = globalMapping.buffers[bufferView.buffer];
}
});
ForEach.accessor(gltf, function (accessor) {
if (defined(accessor.bufferView)) {
accessor.bufferView = globalMapping.bufferViews[accessor.bufferView];
}
});
ForEach.shader(gltf, function (shader) {
const extensions = shader.extensions;
if (defined(extensions)) {
const binaryGltf = extensions.KHR_binary_glTF;
if (defined(binaryGltf)) {
shader.bufferView = globalMapping.bufferViews[binaryGltf.bufferView];
delete extensions.KHR_binary_glTF;
}
if (Object.keys(extensions).length === 0) {
delete shader.extensions;
}
}
});
ForEach.program(gltf, function (program) {
if (defined(program.vertexShader)) {
program.vertexShader = globalMapping.shaders[program.vertexShader];
}
if (defined(program.fragmentShader)) {
program.fragmentShader = globalMapping.shaders[program.fragmentShader];
}
});
ForEach.technique(gltf, function (technique) {
if (defined(technique.program)) {
technique.program = globalMapping.programs[technique.program];
}
ForEach.techniqueParameter(technique, function (parameter) {
if (defined(parameter.node)) {
parameter.node = globalMapping.nodes[parameter.node];
}
const value = parameter.value;
if (typeof value === "string") {
parameter.value = {
index: globalMapping.textures[value],
};
}
});
});
ForEach.mesh(gltf, function (mesh) {
ForEach.meshPrimitive(mesh, function (primitive) {
if (defined(primitive.indices)) {
primitive.indices = globalMapping.accessors[primitive.indices];
}
ForEach.meshPrimitiveAttribute(
primitive,
function (accessorId, semantic) {
primitive.attributes[semantic] = globalMapping.accessors[accessorId];
},
);
if (defined(primitive.material)) {
primitive.material = globalMapping.materials[primitive.material];
}
});
});
ForEach.node(gltf, function (node) {
let children = node.children;
if (defined(children)) {
const childrenLength = children.length;
for (i = 0; i < childrenLength; ++i) {
children[i] = globalMapping.nodes[children[i]];
}
}
if (defined(node.meshes)) {
// Split out meshes on nodes
const meshes = node.meshes;
const meshesLength = meshes.length;
if (meshesLength > 0) {
node.mesh = globalMapping.meshes[meshes[0]];
for (i = 1; i < meshesLength; ++i) {
const meshNode = {
mesh: globalMapping.meshes[meshes[i]],
};
const meshNodeId = addToArray(gltf.nodes, meshNode);
if (!defined(children)) {
children = [];
node.children = children;
}
children.push(meshNodeId);
}
}
delete node.meshes;
}
if (defined(node.camera)) {
node.camera = globalMapping.cameras[node.camera];
}
if (defined(node.skin)) {
node.skin = globalMapping.skins[node.skin];
}
if (defined(node.skeletons)) {
// Assign skeletons to skins
const skeletons = node.skeletons;
const skeletonsLength = skeletons.length;
if (skeletonsLength > 0 && defined(node.skin)) {
const skin = gltf.skins[node.skin];
skin.skeleton = globalMapping.nodes[skeletons[0]];
}
delete node.skeletons;
}
if (defined(node.jointName)) {
delete node.jointName;
}
});
ForEach.skin(gltf, function (skin) {
if (defined(skin.inverseBindMatrices)) {
skin.inverseBindMatrices =
globalMapping.accessors[skin.inverseBindMatrices];
}
const jointNames = skin.jointNames;
if (defined(jointNames)) {
const joints = [];
const jointNamesLength = jointNames.length;
for (i = 0; i < jointNamesLength; ++i) {
joints[i] = jointNameToId[jointNames[i]];
}
skin.joints = joints;
delete skin.jointNames;
}
});
ForEach.scene(gltf, function (scene) {
const sceneNodes = scene.nodes;
if (defined(sceneNodes)) {
const sceneNodesLength = sceneNodes.length;
for (i = 0; i < sceneNodesLength; ++i) {
sceneNodes[i] = globalMapping.nodes[sceneNodes[i]];
}
}
});
ForEach.animation(gltf, function (animation) {
const samplerMapping = {};
animation.samplers = objectToArray(animation.samplers, samplerMapping);
ForEach.animationSampler(animation, function (sampler) {
sampler.input = globalMapping.accessors[sampler.input];
sampler.output = globalMapping.accessors[sampler.output];
});
ForEach.animationChannel(animation, function (channel) {
channel.sampler = samplerMapping[channel.sampler];
const target = channel.target;
if (defined(target)) {
target.node = globalMapping.nodes[target.id];
delete target.id;
}
});
});
ForEach.material(gltf, function (material) {
if (defined(material.technique)) {
material.technique = globalMapping.techniques[material.technique];
}
ForEach.materialValue(material, function (value, name) {
if (typeof value === "string") {
material.values[name] = {
index: globalMapping.textures[value],
};
}
});
const extensions = material.extensions;
if (defined(extensions)) {
const materialsCommon = extensions.KHR_materials_common;
if (defined(materialsCommon) && defined(materialsCommon.values)) {
ForEach.materialValue(materialsCommon, function (value, name) {
if (typeof value === "string") {
materialsCommon.values[name] = {
index: globalMapping.textures[value],
};
}
});
}
}
});
ForEach.image(gltf, function (image) {
const extensions = image.extensions;
if (defined(extensions)) {
const binaryGltf = extensions.KHR_binary_glTF;
if (defined(binaryGltf)) {
image.bufferView = globalMapping.bufferViews[binaryGltf.bufferView];
image.mimeType = binaryGltf.mimeType;
delete extensions.KHR_binary_glTF;
}
if (Object.keys(extensions).length === 0) {
delete image.extensions;
}
}
});
ForEach.texture(gltf, function (texture) {
if (defined(texture.sampler)) {
texture.sampler = globalMapping.samplers[texture.sampler];
}
if (defined(texture.source)) {
texture.source = globalMapping.images[texture.source];
}
});
}
function removeAnimationSamplerNames(gltf) {
ForEach.animation(gltf, function (animation) {
ForEach.animationSampler(animation, function (sampler) {
delete sampler.name;
});
});
}
function removeEmptyArrays(gltf) {
for (const topLevelId in gltf) {
if (Object.prototype.hasOwnProperty.call(gltf, topLevelId)) {
const array = gltf[topLevelId];
if (Array.isArray(array) && array.length === 0) {
delete gltf[topLevelId];
}
}
}
ForEach.node(gltf, function (node) {
if (defined(node.children) && node.children.length === 0) {
delete node.children;
}
});
}
function stripAsset(gltf) {
const asset = gltf.asset;
delete asset.profile;
delete asset.premultipliedAlpha;
}
const knownExtensions = {
CESIUM_RTC: true,
KHR_materials_common: true,
WEB3D_quantized_attributes: true,
};
function requireKnownExtensions(gltf) {
const extensionsUsed = gltf.extensionsUsed;
gltf.extensionsRequired = gltf.extensionsRequired ?? [];
if (defined(extensionsUsed)) {
const extensionsUsedLength = extensionsUsed.length;
for (let i = 0; i < extensionsUsedLength; ++i) {
const extension = extensionsUsed[i];
if (defined(knownExtensions[extension])) {
gltf.extensionsRequired.push(extension);
}
}
}
}
function removeBufferType(gltf) {
ForEach.buffer(gltf, function (buffer) {
delete buffer.type;
});
}
function removeTextureProperties(gltf) {
ForEach.texture(gltf, function (texture) {
delete texture.format;
delete texture.internalFormat;
delete texture.target;
delete texture.type;
});
}
function requireAttributeSetIndex(gltf) {
ForEach.mesh(gltf, function (mesh) {
ForEach.meshPrimitive(mesh, function (primitive) {
ForEach.meshPrimitiveAttribute(
primitive,
function (accessorId, semantic) {
if (semantic === "TEXCOORD") {
primitive.attributes.TEXCOORD_0 = accessorId;
} else if (semantic === "COLOR") {
primitive.attributes.COLOR_0 = accessorId;
}
},
);
delete primitive.attributes.TEXCOORD;
delete primitive.attributes.COLOR;
});
});
ForEach.technique(gltf, function (technique) {
ForEach.techniqueParameter(technique, function (parameter) {
const semantic = parameter.semantic;
if (defined(semantic)) {
if (semantic === "TEXCOORD") {
parameter.semantic = "TEXCOORD_0";
} else if (semantic === "COLOR") {
parameter.semantic = "COLOR_0";
}
}
});
});
}
const knownSemantics = {
POSITION: true,
NORMAL: true,
TANGENT: true,
};
const indexedSemantics = {
COLOR: "COLOR",
JOINT: "JOINTS",
JOINTS: "JOINTS",
TEXCOORD: "TEXCOORD",
WEIGHT: "WEIGHTS",
WEIGHTS: "WEIGHTS",
};
function underscoreApplicationSpecificSemantics(gltf) {
const mappedSemantics = {};
ForEach.mesh(gltf, function (mesh) {
ForEach.meshPrimitive(mesh, function (primitive) {
/*eslint-disable no-unused-vars*/
ForEach.meshPrimitiveAttribute(
primitive,
function (accessorId, semantic) {
if (semantic.charAt(0) !== "_") {
const setIndex = semantic.search(/_[0-9]+/g);
let strippedSemantic = semantic;
let suffix = "_0";
if (setIndex >= 0) {
strippedSemantic = semantic.substring(0, setIndex);
suffix = semantic.substring(setIndex);
}
let newSemantic;
const indexedSemantic = indexedSemantics[strippedSemantic];
if (defined(indexedSemantic)) {
newSemantic = indexedSemantic + suffix;
mappedSemantics[semantic] = newSemantic;
} else if (!defined(knownSemantics[strippedSemantic])) {
newSemantic = `_${semantic}`;
mappedSemantics[semantic] = newSemantic;
}
}
},
);
for (const semantic in mappedSemantics) {
if (Object.prototype.hasOwnProperty.call(mappedSemantics, semantic)) {
const mappedSemantic = mappedSemantics[semantic];
const accessorId = primitive.attributes[semantic];
if (defined(accessorId)) {
delete primitive.attributes[semantic];
primitive.attributes[mappedSemantic] = accessorId;
}
}
}
});
});
ForEach.technique(gltf, function (technique) {
ForEach.techniqueParameter(technique, function (parameter) {
const mappedSemantic = mappedSemantics[parameter.semantic];
if (defined(mappedSemantic)) {
parameter.semantic = mappedSemantic;
}
});
});
}
function clampCameraParameters(gltf) {
ForEach.camera(gltf, function (camera) {
const perspective = camera.perspective;
if (defined(perspective)) {
const aspectRatio = perspective.aspectRatio;
if (defined(aspectRatio) && aspectRatio === 0.0) {
delete perspective.aspectRatio;
}
const yfov = perspective.yfov;
if (defined(yfov) && yfov === 0.0) {
perspective.yfov = 1.0;
}
}
});
}
function computeAccessorByteStride(gltf, accessor) {
return defined(accessor.byteStride) && accessor.byteStride !== 0
? accessor.byteStride
: getAccessorByteStride(gltf, accessor);
}
function requireByteLength(gltf) {
ForEach.buffer(gltf, function (buffer) {
if (!defined(buffer.byteLength)) {
buffer.byteLength = buffer.extras._pipeline.source.length;
}
});
ForEach.accessor(gltf, function (accessor) {
const bufferViewId = accessor.bufferView;
if (defined(bufferViewId)) {
const bufferView = gltf.bufferViews[bufferViewId];
const accessorByteStride = computeAccessorByteStride(gltf, accessor);
const accessorByteEnd =
accessor.byteOffset + accessor.count * accessorByteStride;
bufferView.byteLength = Math.max(
bufferView.byteLength ?? 0,
accessorByteEnd,
);
}
});
}
function moveByteStrideToBufferView(gltf) {
let i;
let j;
let bufferView;
const bufferViews = gltf.bufferViews;
const bufferViewHasVertexAttributes = {};
ForEach.accessorContainingVertexAttributeData(gltf, function (accessorId) {
const accessor = gltf.accessors[accessorId];
if (defined(accessor.bufferView)) {
bufferViewHasVertexAttributes[accessor.bufferView] = true;
}
});
// Map buffer views to a list of accessors
const bufferViewMap = {};
ForEach.accessor(gltf, function (accessor) {
if (defined(accessor.bufferView)) {
bufferViewMap[accessor.bufferView] =
bufferViewMap[accessor.bufferView] ?? [];
bufferViewMap[accessor.bufferView].push(accessor);
}
});
// Split accessors with different byte strides
for (const bufferViewId in bufferViewMap) {
if (Object.prototype.hasOwnProperty.call(bufferViewMap, bufferViewId)) {
bufferView = bufferViews[bufferViewId];
const accessors = bufferViewMap[bufferViewId];
accessors.sort(function (a, b) {
return a.byteOffset - b.byteOffset;
});
let currentByteOffset = 0;
let currentIndex = 0;
const accessorsLength = accessors.length;
for (i = 0; i < accessorsLength; ++i) {
let accessor = accessors[i];
const accessorByteStride = computeAccessorByteStride(gltf, accessor);
const accessorByteOffset = accessor.byteOffset;
const accessorByteLength = accessor.count * accessorByteStride;
delete accessor.byteStride;
const hasNextAccessor = i < accessorsLength - 1;
const nextAccessorByteStride = hasNextAccessor
? computeAccessorByteStride(gltf, accessors[i + 1])
: undefined;
if (accessorByteStride !== nextAccessorByteStride) {
const newBufferView = clone(bufferView, true);
if (bufferViewHasVertexAttributes[bufferViewId]) {
newBufferView.byteStride = accessorByteStride;
}
newBufferView.byteOffset += currentByteOffset;
newBufferView.byteLength =
accessorByteOffset + accessorByteLength - currentByteOffset;
const newBufferViewId = addToArray(bufferViews, newBufferView);
for (j = currentIndex; j <= i; ++j) {
accessor = accessors[j];
accessor.bufferView = newBufferViewId;
accessor.byteOffset = accessor.byteOffset - currentByteOffset;
}
// Set current byte offset to next accessor's byte offset
currentByteOffset = hasNextAccessor
? accessors[i + 1].byteOffset
: undefined;
currentIndex = i + 1;
}
}
}
}
// Remove unused buffer views
removeUnusedElements(gltf, ["accessor", "bufferView", "buffer"]);
}
function requirePositionAccessorMinMax(gltf) {
ForEach.accessorWithSemantic(gltf, "POSITION", function (accessorId) {
const accessor = gltf.accessors[accessorId];
if (!defined(accessor.min) || !defined(accessor.max)) {
const minMax = findAccessorMinMax(gltf, accessor);
accessor.min = minMax.min;
accessor.max = minMax.max;
}
});
}
function isNodeEmpty(node) {
return (
(!defined(node.children) || node.children.length === 0) &&
(!defined(node.meshes) || node.meshes.length === 0) &&
!defined(node.camera) &&
!defined(node.skin) &&
!defined(node.skeletons) &&
!defined(node.jointName) &&
(!defined(node.translation) ||
Cartesian3.fromArray(node.translation).equals(Cartesian3.ZERO)) &&
(!defined(node.scale) ||
Cartesian3.fromArray(node.scale).equals(new Cartesian3(1.0, 1.0, 1.0))) &&
(!defined(node.rotation) ||
Cartesian4.fromArray(node.rotation).equals(
new Cartesian4(0.0, 0.0, 0.0, 1.0),
)) &&
(!defined(node.matrix) ||
Matrix4.fromColumnMajorArray(node.matrix).equals(Matrix4.IDENTITY)) &&
!defined(node.extensions) &&
!defined(node.extras)
);
}
function deleteNode(gltf, nodeId) {
// Remove from list of nodes in scene
ForEach.scene(gltf, function (scene) {
const sceneNodes = scene.nodes;
if (defined(sceneNodes)) {
const sceneNodesLength = sceneNodes.length;
for (let i = sceneNodesLength; i >= 0; --i) {
if (sceneNodes[i] === nodeId) {
sceneNodes.splice(i, 1);
return;
}
}
}
});
// Remove parent node's reference to this node, and delete the parent if also empty
ForEach.node(gltf, function (parentNode, parentNodeId) {
if (defined(parentNode.children)) {
const index = parentNode.children.indexOf(nodeId);
if (index > -1) {
parentNode.children.splice(index, 1);
if (isNodeEmpty(parentNode)) {
deleteNode(gltf, parentNodeId);
}
}
}
});
delete gltf.nodes[nodeId];
}
function removeEmptyNodes(gltf) {
ForEach.node(gltf, function (node, nodeId) {
if (isNodeEmpty(node)) {
deleteNode(gltf, nodeId);
}
});
return gltf;
}
function requireAnimationAccessorMinMax(gltf) {
ForEach.animation(gltf, function (animation) {
ForEach.animationSampler(animation, function (sampler) {
const accessor = gltf.accessors[sampler.input];
if (!defined(accessor.min) || !defined(accessor.max)) {
const minMax = findAccessorMinMax(gltf, accessor);
accessor.min = minMax.min;
accessor.max = minMax.max;
}
});
});
}
function validatePresentAccessorMinMax(gltf) {
ForEach.accessor(gltf, function (accessor) {
if (defined(accessor.min) || defined(accessor.max)) {
const minMax = findAccessorMinMax(gltf, accessor);
if (defined(accessor.min)) {
accessor.min = minMax.min;
}
if (defined(accessor.max)) {
accessor.max = minMax.max;
}
}
});
}
function glTF10to20(gltf) {
gltf.asset = gltf.asset ?? {};
gltf.asset.version = "2.0";
// material.instanceTechnique properties should be directly on the material. instanceTechnique is a gltf 0.8 property but is seen in some 1.0 models.
updateInstanceTechniques(gltf);
// animation.samplers now refers directly to accessors and animation.parameters should be removed
removeAnimationSamplersIndirection(gltf);
// Remove empty nodes and re-assign referencing indices
removeEmptyNodes(gltf);
// Top-level objects are now arrays referenced by index instead of id
objectsToArrays(gltf);
// Animation.sampler objects cannot have names
removeAnimationSamplerNames(gltf);
// asset.profile no longer exists
stripAsset(gltf);
// Move known extensions from extensionsUsed to extensionsRequired
requireKnownExtensions(gltf);
// bufferView.byteLength and buffer.byteLength are required
requireByteLength(gltf);
// byteStride moved from accessor to bufferView
moveByteStrideToBufferView(gltf);
// accessor.min and accessor.max must be defined for accessors containing POSITION attributes
requirePositionAccessorMinMax(gltf);
// An animation sampler's input accessor must have min and max properties defined
requireAnimationAccessorMinMax(gltf);
// When an acccessor has a min- or max, then it is recomputed, to capture the actual
// value, and not use the (possibly imprecise) value from the input
validatePresentAccessorMinMax(gltf);
// buffer.type is unnecessary and should be removed
removeBufferType(gltf);
// Remove format, internalFormat, target, and type
removeTextureProperties(gltf);
// TEXCOORD and COLOR attributes must be written with a set index (TEXCOORD_#)
requireAttributeSetIndex(gltf);
// Add underscores to application-specific parameters
underscoreApplicationSpecificSemantics(gltf);
// Accessors referenced by JOINTS_0 and WEIGHTS_0 attributes must have correct component types
updateAccessorComponentTypes(gltf);
// Clamp camera parameters
clampCameraParameters(gltf);
// Move legacy technique render states to material properties and add KHR_blend extension blending functions
moveTechniqueRenderStates(gltf);
// Add material techniques to KHR_techniques_webgl extension, removing shaders, programs, and techniques
moveTechniquesToExtension(gltf);
// Remove empty arrays
removeEmptyArrays(gltf);
}
// It's not possible to upgrade glTF 1.0 shaders to 2.0 PBR materials in a generic way,
// but we can look for certain uniform names that are commonly found in glTF 1.0 assets
// and create PBR materials out of those.
const defaultBaseColorTextureNames = [
"u_tex",
"u_diffuse",
"u_emission",
"u_diffuse_tex",
];
const defaultBaseColorFactorNames = ["u_diffuse", "u_diffuse_mat"];
function initializePbrMaterial(material) {
material.pbrMetallicRoughness = defined(material.pbrMetallicRoughness)
? material.pbrMetallicRoughness
: {};
material.pbrMetallicRoughness.roughnessFactor = 1.0;
material.pbrMetallicRoughness.metallicFactor = 0.0;
}
function isTexture(value) {
return defined(value.index);
}
function isVec4(value) {
return Array.isArray(value) && value.length === 4;
}
function srgbToLinear(srgb) {
const linear = new Array(4);
linear[3] = srgb[3];
for (let i = 0; i < 3; i++) {
const c = srgb[i];
if (c <= 0.04045) {
// eslint-disable-next-line no-loss-of-precision
linear[i] = srgb[i] * 0.07739938080495356037151702786378;
} else {
linear[i] = Math.pow(
// eslint-disable-next-line no-loss-of-precision
(c + 0.055) * 0.94786729857819905213270142180095,
2.4,
);
}
}
return linear;
}
function convertTechniquesToPbr(gltf, options) {
options = options ?? {};
const baseColorTextureNames =
options.baseColorTextureNames ?? defaultBaseColorTextureNames;
const baseColorFactorNames =
options.baseColorFactorNames ?? defaultBaseColorFactorNames;
// Future work: convert other values like emissive, specular, etc. Only handling diffuse right now.
ForEach.material(gltf, function (material) {
ForEach.materialValue(material, function (value, name) {
if (baseColorTextureNames.indexOf(name) !== -1 && isTexture(value)) {
initializePbrMaterial(material);
material.pbrMetallicRoughness.baseColorTexture = value;
} else if (baseColorFactorNames.indexOf(name) !== -1 && isVec4(value)) {
initializePbrMaterial(material);
material.pbrMetallicRoughness.baseColorFactor = srgbToLinear(value);
}
});
});
removeExtension(gltf, "KHR_techniques_webgl");
removeExtension(gltf, "KHR_blend");
}
function assignAsBaseColor(material, baseColor) {
if (defined(baseColor)) {
if (isVec4(baseColor)) {
material.pbrMetallicRoughness.baseColorFactor = srgbToLinear(baseColor);
} else if (isTexture(baseColor)) {
material.pbrMetallicRoughness.baseColorTexture = baseColor;
}
}
}
function assignAsEmissive(material, emissive) {
if (defined(emissive)) {
if (isVec4(emissive)) {
material.emissiveFactor = emissive.slice(0, 3);
} else if (isTexture(emissive)) {
material.emissiveTexture = emissive;
}
}
}
function convertMaterialsCommonToPbr(gltf) {
// Future work: convert KHR_materials_common lights to KHR_lights_punctual
ForEach.material(gltf, function (material) {
const materialsCommon = (material.extensions ?? {}).KHR_materials_common;
if (!defined(materialsCommon)) {
// Nothing to do
return;
}
const values = materialsCommon.values ?? {};
const ambient = values.ambient;
const diffuse = values.diffuse;
const emission = values.emission;
const transparency = values.transparency;
// These actually exist on the extension object, not the values object despite what's shown in the spec
const doubleSided = materialsCommon.doubleSided;
const transparent = materialsCommon.transparent;
// Ignore specular and shininess for now because the conversion to PBR
// isn't straightforward and depends on the technique
initializePbrMaterial(material);
const technique = materialsCommon.technique;
if (technique === "CONSTANT") {
// Add the KHR_materials_unlit extension
addExtensionsUsed(gltf, "KHR_materials_unlit");
material.extensions = defined(material.extensions)
? material.extensions
: {};
material.extensions["KHR_materials_unlit"] = {};
// The CONSTANT technique does not support 'diffuse', so
// assign either the 'emission' or the 'ambient' as the
// base color
assignAsBaseColor(material, emission);
assignAsBaseColor(material, ambient);
} else {
// Assign the 'diffuse' as the base color, and
// the 'ambient' or 'emissive' as the emissive
// part if they are present.
assignAsBaseColor(material, diffuse);
assignAsEmissive(material, ambient);
assignAsEmissive(material, emission);
}
if (defined(doubleSided)) {
material.doubleSided = doubleSided;
}
if (defined(transparency)) {
if (defined(material.pbrMetallicRoughness.baseColorFactor)) {
material.pbrMetallicRoughness.baseColorFactor[3] *= transparency;
} else {
material.pbrMetallicRoughness.baseColorFactor = [1, 1, 1, transparency];
}
}
if (defined(transparent)) {
material.alphaMode = transparent ? "BLEND" : "OPAQUE";
}
});
removeExtension(gltf, "KHR_materials_common");
}
export default updateVersion;