/* @license
 * Copyright 2019 Google LLC. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the 'License');
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an 'AS IS' BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { Vector3 } from 'three';
import { SkeletonUtils } from 'three/examples/jsm/utils/SkeletonUtils.js';
import { cubeUVChunk } from './shader-chunk/cube_uv_reflection_fragment.glsl.js';
import { lightsChunk } from './shader-chunk/lights_physical_fragment.glsl.js';
/**
 * This is a patch to Three.js' handling of PMREM environments. This patch
 * has to be applied after cloning because Three.js does not seem to clone
 * the onBeforeCompile method.
 */
const updateShader = (shader) => {
    shader.fragmentShader =
        shader.fragmentShader
            .replace('#include <cube_uv_reflection_fragment>', cubeUVChunk)
            .replace('#include <lights_physical_fragment>', lightsChunk);
};
/**
 * Fully clones a parsed GLTF, including correct cloning of any SkinnedMesh
 * objects.
 *
 * NOTE(cdata): This is necessary due to limitations of the Three.js clone
 * routine on scenes. Without it, models with skeletal animations will not be
 * cloned properly.
 *
 * @see https://github.com/mrdoob/three.js/issues/5878
 */
export const cloneGltf = (gltf) => {
    const clone = Object.assign(Object.assign({}, gltf), { scene: SkeletonUtils.clone(gltf.scene) });
    const specularGlossiness = gltf.parser.extensions['KHR_materials_pbrSpecularGlossiness'];
    /**
     * Creates a clone of the given material, and applies a patch to the
     * shader program.
     */
    const cloneAndPatchMaterial = (material) => {
        const clone = material.isGLTFSpecularGlossinessMaterial ?
            specularGlossiness.cloneMaterial(material) :
            material.clone();
        clone.onBeforeCompile = updateShader;
        // This is a fix for NormalTangentMirrorTest. Remove when
        // https://github.com/mrdoob/three.js/issues/11438 is solved.
        if (!clone.vertexTangents && clone.normalScale) {
            clone.normalScale.y *= -1;
        }
        return clone;
    };
    clone.scene.traverse((node) => {
        // Set a high renderOrder while we're here to ensure the model
        // always renders on top of the skysphere
        node.renderOrder = 1000;
        if (specularGlossiness != null && node.isMesh) {
            node.onBeforeRender = specularGlossiness.refreshUniforms;
        }
        // Materials aren't cloned when cloning meshes; geometry
        // and materials are copied by reference. This is necessary
        // for the same model to be used twice with different
        // environment maps.
        if (Array.isArray(node.material)) {
            node.material = node.material.map(cloneAndPatchMaterial);
        }
        else if (node.material != null) {
            node.material = cloneAndPatchMaterial(node.material);
        }
    });
    return clone;
};
/**
 * Moves Three.js objects from one parent to another
 */
export const moveChildren = (from, to) => {
    while (from.children.length) {
        to.add(from.children.shift());
    }
};
/**
 * Performs a reduction across all the vertices of the input model and all its
 * children. The supplied function takes the reduced value and a vertex and
 * returns the newly reduced value. The value is initialized as zero.
 *
 * Adapted from Three.js, @see https://github.com/mrdoob/three.js/blob/7e0a78beb9317e580d7fa4da9b5b12be051c6feb/src/math/Box3.js#L241
 */
export const reduceVertices = (model, func) => {
    let value = 0;
    const vector = new Vector3();
    model.traverse((object) => {
        let i, l;
        object.updateWorldMatrix(false, false);
        let geometry = object.geometry;
        if (geometry !== undefined) {
            if (geometry.isGeometry) {
                let vertices = geometry.vertices;
                for (i = 0, l = vertices.length; i < l; i++) {
                    vector.copy(vertices[i]);
                    vector.applyMatrix4(object.matrixWorld);
                    value = func(value, vector);
                }
            }
            else if (geometry.isBufferGeometry) {
                let attribute = geometry.attributes.position;
                if (attribute !== undefined) {
                    for (i = 0, l = attribute.count; i < l; i++) {
                        vector.fromBufferAttribute(attribute, i)
                            .applyMatrix4(object.matrixWorld);
                        value = func(value, vector);
                    }
                }
            }
        }
    });
    return value;
};
//# sourceMappingURL=ModelUtils.js.map