import { Accessor, Document, Primitive, Transform, vec3 } from '@gltf-transform/core'; const NAME = 'colorspace'; export interface ColorspaceOptions { inputEncoding: string; } /** * Options: * - **inputEncoding**: Currently accepts only `"sRGB"`. Required. */ export function colorspace (options: ColorspaceOptions): Transform { return (doc: Document): void => { const logger = doc.getLogger(); if (options.inputEncoding === 'linear') { logger.info(`${NAME}: Vertex colors already linear. Skipping conversion.`); return; } if (options.inputEncoding !== 'sRGB') { logger.error( `${NAME}: Unknown input encoding "${options.inputEncoding}" – should be "sRGB" or ` + '"linear". Skipping conversion.' ); return; } const converted = new Set(); // Source: THREE.Color function sRGBToLinear( c: number ): number { return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 ); } function updatePrimitive(primitive: Primitive): void { const color = [0, 0, 0] as vec3; let attribute: Accessor; for (let i = 0; (attribute = primitive.getAttribute(`COLOR_${i}`)); i++) { if (converted.has(attribute)) continue; for (let j = 0; j < attribute.getCount(); j++) { attribute.getElement(j, color); color[0] = sRGBToLinear(color[0]); color[1] = sRGBToLinear(color[1]); color[2] = sRGBToLinear(color[2]); attribute.setElement(j, color); } converted.add(attribute); } } doc.getRoot() .listMeshes() .forEach((mesh) => mesh.listPrimitives().forEach(updatePrimitive)); logger.debug(`${NAME}: Complete.`); }; }