Source: loader/GLTFExtensions.js

/* eslint camelcase: "off" */
import PointLight from '../light/PointLight';
import DirectionalLight from '../light/DirectionalLight';
import SpotLight from '../light/SpotLight';
import Color from '../math/Color';
import math from '../math/math';
import * as util from '../utils/util';
import ShaderMaterial from '../material/ShaderMaterial';
import semantic from '../material/semantic';
import constants from '../constants';

const {
    SAMPLER_2D
} = constants;

export {
    default as ALI_amc_mesh_compression
} from './AliAMCExtension';

/**
 * GLTFExtension Handler 接口
 * @interface IGLTFExtensionHandler
 */

/**
 * 解析元素扩展
 * @function
 * @name IGLTFExtensionHandler#[parse]
 * @param {Object} [extensionData] 扩展数据
 * @param {GLTFParser} [parser] parser
 * @param {Object} [element] parse的元素,e.g. material, mesh, geometry
 * @param {Object} [options]
 * @return {Object} [result] 一般需要返回原始元素或者替换的新的元素
 */

/**
 * 解析全局扩展,在资源加载后执行
 * @function
 * @name IGLTFExtensionHandler#[parseOnLoad]
 * @param {Object} [extensionData] 扩展数据
 * @param {GLTFParser} [parser] parser
 * @param {Object} [element] parse的元素,这里为 null
 * @param {Object} [options]
 */

/**
 * 解析全局扩展,在所有元素解析结束后执行
 * @function
 * @name IGLTFExtensionHandler#[parseOnEnd]
 * @param {Object} [extensionData] 扩展数据
 * @param {GLTFParser} [parser] parser
 * @param {GLTFModel} [element] parse的元素,这里为加载后的model,{node, scene, meshes, json, cameras, lights, textures, materials}
 * @param {Object} [options]
 */

/**
 * 初始化全局扩展,在加载前执行,可进行添加需要加载的资源
 * @function
 * @name IGLTFExtensionHandler#[init]
 * @param {GLTFLoader} [gltfLoader]
 * @param {GLTFParser} [parser]
 */

/**
 * 获取扩展用到的贴图信息, parser.isLoadAllTextures 为 false 时生效
 * @function
 * @name IGLTFExtensionHandler#[getUsedTextureNameMap]
 * @param {Object} [extensionData] 扩展数据
 * @param {Object} [map] used texture map
 * @example
 * getUsedTextureNameMap(extension, map) {
 *     if (extension.diffuseTexture) {
 *         map[extension.diffuseTexture.index] = true;
 *     }
 * }
 */


export const WEB3D_quantized_attributes = {
    unQuantizeData(data, decodeMat) {
        if (!decodeMat) {
            return data;
        }

        const matSize = Math.sqrt(decodeMat.length);
        const itemLen = matSize - 1;
        const result = new Float32Array(data.length);
        const tempArr = [];
        data.traverse((d, i) => {
            if (d.toArray) {
                d.toArray(tempArr);
            } else {
                tempArr[0] = d;
            }
            const idx = i * itemLen;
            for (let j = 0; j < matSize; j++) {
                result[idx + j] = 0;
                for (let k = 0; k < matSize; k++) {
                    let v = k === itemLen ? 1 : tempArr[k];
                    result[idx + j] += decodeMat[k * matSize + j] * v;
                }
            }
        });
        data.data = result;
        data.stride = 0;
        data.offset = 0;
        return data;
    },
    parse(quantizeInfo, parser, result, options) {
        let decodeMat = quantizeInfo.decodeMatrix;
        if (options.isDecode) {
            result = WEB3D_quantized_attributes.unQuantizeData(result, decodeMat);
        } else {
            result.decodeMat = decodeMat;
        }
        return result;
    }
};

export const HILO_animation_clips = {
    parseOnEnd(animClips, parser, model) {
        if (!model.anim || parser.isMultiAnim) {
            return model;
        }
        for (let name in animClips) {
            let clip = animClips[name];
            model.anim.addClip(name, clip[0], clip[1]);
        }
        return model;
    }
};
export const ALI_animation_clips = HILO_animation_clips;

export const ALI_bounding_box = {
    parse(bounds, parser, model) {
        bounds.center = bounds.max.map((a, i) => (a + bounds.min[i]) / 2);
        bounds.width = bounds.max[0] - bounds.min[0];
        bounds.height = bounds.max[1] - bounds.min[1];
        bounds.depth = bounds.max[2] - bounds.min[2];
        bounds.size = Math.sqrt(bounds.width ** 2 + bounds.height ** 2 + bounds.depth ** 2);
        model.bounds = bounds;
        return model;
    }
};

export const KHR_materials_pbrSpecularGlossiness = {
    getUsedTextureNameMap(extension, map) {
        if (extension.diffuseTexture) {
            map[extension.diffuseTexture.index] = true;
        }
        if (extension.specularGlossinessTexture) {
            map[extension.specularGlossinessTexture.index] = true;
        }
    },
    parse(info, parser, material) {
        if (info.diffuseFactor) {
            material.baseColor.fromArray(info.diffuseFactor);
        }
        if (info.diffuseTexture) {
            material.baseColorMap = parser.getTexture(info.diffuseTexture);
        }

        if (info.specularFactor) {
            material.specular.fromArray(info.specularFactor);
            material.specular.a = 1;
        }
        if ('glossinessFactor' in info) {
            material.glossiness = info.glossinessFactor;
        }
        if (info.specularGlossinessTexture) {
            material.specularGlossinessMap = parser.getTexture(info.specularGlossinessTexture);
        }
        material.isSpecularGlossiness = true;

        return material;
    }
};

export const KHR_materials_clearcoat = {
    getUsedTextureNameMap(extension, map) {
        if (extension.clearcoatTexture) {
            map[extension.clearcoatTexture.index] = true;
        }
        if (extension.clearcoatRoughnessTexture) {
            map[extension.clearcoatRoughnessTexture.index] = true;
        }
        if (extension.clearcoatNormalTexture) {
            map[extension.clearcoatNormalTexture.index] = true;
        }
    },
    parse(info, parser, material) {
        if (info.clearcoatFactor) {
            material.clearcoatFactor = info.clearcoatFactor;
        }

        if (info.clearcoatTexture) {
            material.clearcoatMap = parser.getTexture(info.clearcoatTexture);
        }

        if (info.clearcoatRoughnessFactor) {
            material.clearcoatRoughnessFactor = info.clearcoatRoughnessFactor;
        }

        if (info.clearcoatRoughnessTexture) {
            material.clearcoatRoughnessMap = parser.getTexture(info.clearcoatRoughnessTexture);
        }

        if (info.clearcoatNormalTexture) {
            material.clearcoatNormalMap = parser.getTexture(info.clearcoatNormalTexture);
        }

        return material;
    }
};

export const KHR_lights_punctual = {
    parse(info, parser, node) {
        if (!parser.isUseExtension(parser.json, 'KHR_lights_punctual') || !parser.json.extensions.KHR_lights_punctual.lights) {
            return node;
        }

        const lightInfo = parser.json.extensions.KHR_lights_punctual.lights[info.light];

        if (!lightInfo) {
            return node;
        }

        let light;
        const color = new Color(1, 1, 1, 1);
        if (lightInfo.color) {
            color.r = lightInfo.color[0];
            color.g = lightInfo.color[1];
            color.b = lightInfo.color[2];
        }

        const amount = lightInfo.intensity !== undefined ? lightInfo.intensity : 1;
        const name = lightInfo.name || '';

        // spot light
        const spotInfo = lightInfo.spot || {};
        const cutoff = spotInfo.innerConeAngle !== undefined ? math.radToDeg(spotInfo.innerConeAngle) : 0;
        const outerCutoff = spotInfo.outerConeAngle !== undefined ? math.radToDeg(spotInfo.outerConeAngle) : 45;
        const range = lightInfo.range || 0;
        switch (lightInfo.type) {
            case 'directional':
                light = new DirectionalLight({
                    color,
                    amount,
                    name,
                    range
                });
                light.direction.set(0, 0, -1);
                break;
            case 'point':
                light = new PointLight({
                    color,
                    amount,
                    name,
                    range
                });
                break;
            case 'spot':
                light = new SpotLight({
                    color,
                    amount,
                    name,
                    range,
                    cutoff,
                    outerCutoff
                });
                light.direction.set(0, 0, -1);
                break;
            default:
                return node;
        }

        if (light) {
            node.addChild(light);
            parser.lights.push(light);
        }
        return node;
    }
};

export const KHR_techniques_webgl = {
    init(loader, parser) {
        const actions = [];
        const extensions = parser.json.extensions || {};
        const KHR_techniques_webgl = extensions.KHR_techniques_webgl || {};

        const programs = KHR_techniques_webgl.programs || [];
        const shaders = KHR_techniques_webgl.shaders || [];
        const techniques = KHR_techniques_webgl.techniques || [];

        parser.shaders = {};
        shaders.forEach((shader, index) => {
            let uri = util.getRelativePath(parser.src, shader.uri);
            if (parser.preHandlerShaderURI) {
                uri = parser.preHandlerShaderURI(uri, index, shader);
            }
            actions.push(loader.loadRes(uri).then((shaderText) => {
                parser.shaders[index] = shaderText;
            }));
        });

        parser.programs = {};
        programs.forEach((program, index) => {
            parser.programs[index] = Object.assign({}, program);
        });

        parser.techniques = {};
        techniques.forEach((technique, index) => {
            const newTechnique = parser.techniques[index] = Object.assign({}, technique);
            const textureInfos = newTechnique.textureInfos = {};
            const uniforms = technique.uniforms || {};
            for (let name in uniforms) {
                const uniform = uniforms[name];
                if (uniform.type === SAMPLER_2D) {
                    textureInfos[name] = uniform.value || {};
                }
            }
        });

        return Promise.all(actions);
    },
    getUsedTextureNameMap(extension, map, parser) {
        const techniques = parser.techniques;
        const technique = techniques[extension.technique];
        const values = extension.values || {};
        if (technique) {
            const textureInfos = technique.textureInfos;
            for (let name in textureInfos) {
                let imageIndex;
                if (values[name] && values[name].index !== undefined) {
                    imageIndex = values[name].index;
                } else if (textureInfos[name].index !== undefined) {
                    imageIndex = textureInfos[name].index;
                }

                if (imageIndex !== undefined) {
                    map[imageIndex] = true;
                }
            }
        }
    },
    parse(info, parser, material) {
        const textures = parser.textures || [];

        const techniqueInfo = parser.techniques[info.technique];
        if (!techniqueInfo) {
            return material;
        }
        const programInfo = parser.programs[techniqueInfo.program];
        if (!programInfo) {
            return material;
        }

        const fragmentText = parser.shaders[programInfo.fragmentShader];
        const vertexText = parser.shaders[programInfo.vertexShader];

        const uniformsInfo = techniqueInfo.uniforms || {};
        const attributesInfo = techniqueInfo.attributes || {};
        const valuesInfo = info.values || {};

        const attributes = {};
        const uniforms = {};

        for (let uniformName in uniformsInfo) {
            const uniformDef = uniformsInfo[uniformName] || {};
            let uniformValue = valuesInfo[uniformName];
            if (uniformValue === undefined) {
                uniformValue = uniformDef.value;
            }
            let uniformObject;
            if (uniformValue !== undefined) {
                if (uniformDef.type === SAMPLER_2D) {
                    const textureIndex = uniformValue.index || 0;
                    uniformObject = {
                        get(mesh, material, programInfo) {
                            return semantic.handlerTexture(textures[textureIndex], programInfo.textureIndex);
                        }
                    };
                } else {
                    uniformObject = {
                        get() {
                            return uniformValue;
                        }
                    };
                }
            } else if (uniformDef.semantic && semantic[uniformDef.semantic]) {
                const semanticFunc = semantic[uniformDef.semantic];
                const nodeIndex = uniformDef.node;
                let node;
                if (nodeIndex !== undefined) {
                    uniformObject = {
                        get(mesh, material, programInfo) {
                            if (node === undefined) {
                                node = parser.node.getChildByFn((node) => {
                                    return node.animationId === nodeIndex;
                                }) || mesh;
                            }
                            return semanticFunc.get(node, material, programInfo);
                        }
                    };
                } else {
                    uniformObject = uniformDef.semantic;
                }
            }
            uniforms[uniformName] = uniformObject;
        }

        for (let attributeName in attributesInfo) {
            const attributeValue = attributesInfo[attributeName] || {};
            if (attributeValue.semantic) {
                attributes[attributeName] = attributeValue.semantic;
            }
        }

        const shaderMaterial = new ShaderMaterial({
            needBasicUnifroms: false,
            needBasicAttributes: false,
            useHeaderCache: true,
            premultiplyAlpha: false,
            vs: vertexText,
            fs: fragmentText,
            attributes,
            uniforms,
        });

        if (info.premultiplyAlpha !== undefined) {
            shaderMaterial.premultiplyAlpha = info.premultiplyAlpha;
        }

        if (info.defines) {
            shaderMaterial.getCustomRenderOption = function() {
                return info.defines;
            };
        }

        if (programInfo.name !== undefined) {
            shaderMaterial.shaderName = programInfo.name;
            shaderMaterial.shaderCacheId = `KHR_techniques_webgl_${programInfo.name}`;
        }

        return shaderMaterial;
    }
};