import { Vector3, Disposable, randomUUID, DRef, Quaternion, DWeakRef, Matrix4x4, nextPowerOf2 } from '@zephyr3d/base';
import { BoundingBox } from '../utility/bounding_volume.js';
import { getDevice } from '../app/api.js';

/**
 * Standardized humanoid joint names for consistent skeleton mapping across models and animations.
 *
 * These names align with common conventions used in 3D modeling and animation tools, facilitating interoperability and reuse of assets.
 *
 * @public
 */ var HumanoidBodyRig = /*#__PURE__*/ function(HumanoidBodyRig) {
    HumanoidBodyRig["Hips"] = "Hips";
    HumanoidBodyRig["Spine"] = "Spine";
    HumanoidBodyRig["Chest"] = "Chest";
    HumanoidBodyRig["UpperChest"] = "UpperChest";
    HumanoidBodyRig["Neck"] = "Neck";
    HumanoidBodyRig["Head"] = "Head";
    HumanoidBodyRig["LeftShoulder"] = "LeftShoulder";
    HumanoidBodyRig["LeftUpperArm"] = "LeftUpperArm";
    HumanoidBodyRig["LeftLowerArm"] = "LeftLowerArm";
    HumanoidBodyRig["LeftHand"] = "LeftHand";
    HumanoidBodyRig["RightShoulder"] = "RightShoulder";
    HumanoidBodyRig["RightUpperArm"] = "RightUpperArm";
    HumanoidBodyRig["RightLowerArm"] = "RightLowerArm";
    HumanoidBodyRig["RightHand"] = "RightHand";
    HumanoidBodyRig["LeftUpperLeg"] = "LeftUpperLeg";
    HumanoidBodyRig["LeftLowerLeg"] = "LeftLowerLeg";
    HumanoidBodyRig["LeftFoot"] = "LeftFoot";
    HumanoidBodyRig["LeftToes"] = "LeftToes";
    HumanoidBodyRig["RightUpperLeg"] = "RightUpperLeg";
    HumanoidBodyRig["RightLowerLeg"] = "RightLowerLeg";
    HumanoidBodyRig["RightFoot"] = "RightFoot";
    HumanoidBodyRig["RightToes"] = "RightToes";
    return HumanoidBodyRig;
}({});
/**
 * Standardized humanoid hand joint names for consistent skeleton mapping across models and animations.
 *
 * These names align with common conventions used in 3D modeling and animation tools, facilitating interoperability and reuse of assets.
 *
 * @public
 */ var HumanoidHandRig = /*#__PURE__*/ function(HumanoidHandRig) {
    HumanoidHandRig["ThumbProximal"] = "ThumbProximal";
    HumanoidHandRig["ThumbIntermediate"] = "ThumbIntermediate";
    HumanoidHandRig["ThumbDistal"] = "ThumbDistal";
    HumanoidHandRig["IndexProximal"] = "IndexProximal";
    HumanoidHandRig["IndexIntermediate"] = "IndexIntermediate";
    HumanoidHandRig["IndexDistal"] = "IndexDistal";
    HumanoidHandRig["MiddleProximal"] = "MiddleProximal";
    HumanoidHandRig["MiddleIntermediate"] = "MiddleIntermediate";
    HumanoidHandRig["MiddleDistal"] = "MiddleDistal";
    HumanoidHandRig["RingProximal"] = "RingProximal";
    HumanoidHandRig["RingIntermediate"] = "RingIntermediate";
    HumanoidHandRig["RingDistal"] = "RingDistal";
    HumanoidHandRig["PinkyProximal"] = "PinkyProximal";
    HumanoidHandRig["PinkyIntermediate"] = "PinkyIntermediate";
    HumanoidHandRig["PinkyDistal"] = "PinkyDistal";
    return HumanoidHandRig;
}({});
const tmpV0 = new Vector3();
const tmpV1 = new Vector3();
const tmpV2 = new Vector3();
const tmpV3 = new Vector3();
function jointPattern(all, none, any) {
    return {
        all,
        any: any?.length ? any : undefined,
        none: none?.length ? none : undefined
    };
}
function sideJointPatterns(side, patterns) {
    const sideTokens = side === 'left' ? [
        'left',
        'l'
    ] : [
        'right',
        'r'
    ];
    return sideTokens.flatMap((sideToken)=>patterns.map((pattern)=>({
                all: [
                    sideToken,
                    ...pattern.all
                ],
                any: pattern.any?.length ? [
                    ...pattern.any
                ] : undefined,
                none: pattern.none?.length ? [
                    ...pattern.none
                ] : undefined
            })));
}
/**
 * Skeleton for skinned animation.
 *
 * Responsibilities:
 * - Maintains joint transforms: inverse bind, bind pose, and current skinning matrices.
 * - Provides a texture containing joint matrices for GPU skinning.
 * - Applies skinning state to associated meshes each frame.
 * - Computes animated axis-aligned bounding boxes using representative skinned vertices.
 *
 * Joint matrix texture layout:
 * - Texture format: `rgba32f`.
 * - Stored as a 2-layered ring buffer: current and previous joint transforms to support
 *   temporal addressing if needed. Offsets are tracked in `_jointOffsets[0]` (current)
 *   and `_jointOffsets[1]` (previous).
 *
 * Usage:
 * - Construct with joints, bind data, meshes and submesh bounding info.
 * - Call `apply()` each frame to update joint texture, bind to meshes, and update bounds.
 * - Call `reset()` to clear skinning on meshes.
 *
 * @public
 */ class Skeleton extends Disposable {
    /** @internal Global weak registry keyed by persistentId for serialization/lookup. */ static _registry = new Map();
    /** @internal */ _id;
    /** @internal */ _joints;
    /** @internal */ _inverseBindMatrices;
    /** @internal */ _bindPose;
    /** @internal */ _jointMatrices;
    /** @internal */ _jointOffsets;
    /** @internal */ _jointMatrixArray;
    /** @internal */ _jointTexture;
    /** @internal */ _playing;
    /** @internal */ _modifiers;
    /** @internal */ _lastUpdateTime;
    /** @internal */ _humanoidJointMapping;
    /** @internal */ _humanoidRootRotation;
    /**
   * Create a skeleton instance.
   *
   * @param joints - Joint scene nodes (one per joint), ordered to match skin data.
   * @param inverseBindMatrices - Inverse bind matrices for each joint.
   * @param bindPoseMatrices - Bind pose matrices for each joint (model-space).
   */ constructor(joints, inverseBindMatrices, bindPose){
        super();
        this._id = randomUUID();
        this._joints = joints;
        this._inverseBindMatrices = inverseBindMatrices;
        this._bindPose = bindPose;
        this._jointTexture = new DRef();
        this._playing = false;
        this._modifiers = [];
        this._lastUpdateTime = 0;
        this.computeBindPose();
        this.updateJointMatrices(0);
        let skeletonRoot = this.findRootJoint(this._joints);
        if (!skeletonRoot || !this._joints.includes(skeletonRoot)) {
            throw new Error('Skeleton root must be included in the joint list');
        }
        this._humanoidJointMapping = Skeleton.tryExtractHumanoidJoints(skeletonRoot);
        this._humanoidRootRotation = Quaternion.identity();
        if (this._humanoidJointMapping) {
            let p = this._humanoidJointMapping.body["Hips"];
            while(this._joints.includes(p.parent)){
                Quaternion.multiply(p.parent.rotation, this._humanoidRootRotation, this._humanoidRootRotation);
                p = p.parent;
            }
        }
        Skeleton._registry.set(this._id, new DWeakRef(this));
    }
    /**
   * Lookup a skeleton from the global registry by persistent id.
   *
   * @param id - The persistent UUID to search for.
   * @returns The skeleton if alive, otherwise `null`.
   * @internal
   */ static findSkeletonById(id) {
        const m = this._registry.get(id);
        if (m && !m.get()) {
            this._registry.delete(id);
            return null;
        }
        return m ? m.get() : null;
    }
    /** Gets joint nodes */ get joints() {
        return this._joints;
    }
    /** Gets the humanoid joint mapping */ get humanoidJointMapping() {
        return this._humanoidJointMapping;
    }
    /** Root rotation of humanoid hips bone */ get humanoidRootRotation() {
        return this._humanoidRootRotation;
    }
    /** @internal */ get inverseBindMatrices() {
        return this._inverseBindMatrices;
    }
    /** @internal */ get bindPose() {
        return this._bindPose;
    }
    get playing() {
        return this._playing;
    }
    set playing(b) {
        this._playing = b;
    }
    get persistentId() {
        return this._id;
    }
    set persistentId(val) {
        if (val !== this._id) {
            const m = Skeleton._registry.get(this._id);
            if (!m || m.get() !== this) {
                throw new Error('Registry skeleton mismatch');
            }
            Skeleton._registry.delete(this._id);
            this._id = val;
            Skeleton._registry.set(this._id, m);
        }
    }
    /**
   * Texture containing joint matrices for GPU skinning.
   *
   * Each matrix is stored in 4 texels (one row per texel, RGBA = 4 floats).
   */ get jointTexture() {
        return this._jointTexture.get();
    }
    /**
   * Get joint index by joint node
   * @param joint - joint node
   * @returns The index of the joint
   */ getJointIndex(joint) {
        return this._joints.indexOf(joint);
    }
    /**
   * Get joint index by joint name
   * @param jointName - joint name
   * @returns The index of the joint
   */ getJointIndexByName(jointName) {
        return this._joints.findIndex((joint)=>joint.name === jointName);
    }
    /**
   * Update joint matrices from either provided transforms or the joints' world matrices.
   *
   * - Lazily creates the joint texture and its backing arrays on first call.
   * - Advances the ring buffer offset in `_jointOffsets` to write a new "current" set.
   * - For each joint:
   *   - Optionally premultiplies by `worldMatrix` (to transform into model space).
   *   - Computes skinning matrix: ( M_\{skin\} = M_\{joint\} \\times M_\{inverseBind\} ).
   *
   * Note: This method only writes into the CPU-side array; callers like `computeJoints()`
   * update the GPU texture.
   *
   * @param jointTransforms - Optional per-joint transforms to use instead of node world matrices.
   * @param worldMatrix - Optional world-to-model transform applied before inverse bind.
   * @internal
   */ updateJointMatrices(deltaTime) {
        this.applyModifiers(deltaTime);
        if (!this._jointTexture.get()) {
            this._createJointTexture();
        }
        if (this._jointOffsets[0] === 0) {
            this._jointOffsets[0] = 1;
            this._jointOffsets[1] = 1;
        } else {
            this._jointOffsets[1] = this._jointOffsets[0];
            this._jointOffsets[0] = this._joints.length - this._jointOffsets[0] + 2;
        }
        for(let i = 0; i < this._joints.length; i++){
            Matrix4x4.multiply(this._joints[i].worldMatrix, this._inverseBindMatrices[i], this._jointMatrices[i + this._jointOffsets[0] - 1]);
        }
    }
    /**
   * Reset skeleton to bind pose
   *
   * @internal
   */ computeBindPose() {
        for(let i = 0; i < this._joints.length; i++){
            const joint = this._joints[i];
            const bindpose = this._bindPose[i];
            joint.position.set(bindpose.position);
            joint.rotation.set(bindpose.rotation);
            //joint.rotation.identity();
            joint.scale.set(bindpose.scale);
        }
    }
    /**
   * Compute current joint matrices from the nodes and upload them to the joint texture.
   *
   * @internal
   */ apply(deltaTime) {
        this.updateJointMatrices(deltaTime);
        const tex = this.jointTexture;
        tex.update(this._jointMatrixArray, 0, 0, tex.width, tex.height);
    }
    /**
   * Apply all enabled modifiers.
   *
   * Modifiers are applied after the base animation/bind pose layer,
   * allowing procedural modifications like IK, spring physics, or manual overrides.
   *
   * @param deltaTime - Time elapsed since last frame (in seconds)
   * @internal
   */ applyModifiers(deltaTime) {
        for (const modifier of this._modifiers){
            modifier.apply(this, deltaTime);
        }
    }
    /**
   * Get all modifiers attached to this skeleton.
   *
   * @public
   */ get modifiers() {
        return this._modifiers;
    }
    /**
   * Reset all meshes to an unskinned state and clear animated bounds.
   *
   * @internal
   */ reset() {
        //this.updateJointMatrices(this._bindPoseMatrices);
        this._playing = false;
    }
    /**
   * Compute the animated bounding box for a single mesh using its representative vertices.
   *
   * For each representative vertex:
   * - Blends the vertex by up to 4 joint matrices using provided weights.
   * - Transforms to the mesh's local space using `invWorldMatrix`.
   * - Expands the bounding box.
   *
   * @param info - Precomputed bounding data (representative vertices, indices, weights).
   * @param invWorldMatrix - Mesh inverse world matrix to convert to model/local space.
   * @internal
   */ computeBoundingBox(info, invWorldMatrix) {
        info.boundingBox.beginExtend();
        for(let i = 0; i < info.boundingVertices.length; i++){
            this._jointMatrices[info.boundingVertexBlendIndices[i * 4 + 0] + this._jointOffsets[0] - 1].transformPointAffine(info.boundingVertices[i], tmpV0).scaleBy(info.boundingVertexJointWeights[i * 4 + 0]);
            this._jointMatrices[info.boundingVertexBlendIndices[i * 4 + 1] + this._jointOffsets[0] - 1].transformPointAffine(info.boundingVertices[i], tmpV1).scaleBy(info.boundingVertexJointWeights[i * 4 + 1]);
            this._jointMatrices[info.boundingVertexBlendIndices[i * 4 + 2] + this._jointOffsets[0] - 1].transformPointAffine(info.boundingVertices[i], tmpV2).scaleBy(info.boundingVertexJointWeights[i * 4 + 2]);
            this._jointMatrices[info.boundingVertexBlendIndices[i * 4 + 3] + this._jointOffsets[0] - 1].transformPointAffine(info.boundingVertices[i], tmpV3).scaleBy(info.boundingVertexJointWeights[i * 4 + 3]);
            tmpV0.addBy(tmpV1).addBy(tmpV2).addBy(tmpV3);
            invWorldMatrix.transformPointAffine(tmpV0, tmpV0);
            info.boundingBox.extend(tmpV0);
        }
    }
    /**
   * Dispose GPU resources and references held by the skeleton.
   *
   * - Disposes the joint texture.
   * - Clears matrix arrays and joint references.
   */ onDispose() {
        super.onDispose();
        this._jointTexture.dispose();
        const m = Skeleton._registry.get(this._id);
        if (m?.get() === this) {
            Skeleton._registry.delete(this._id);
            m.dispose();
        }
    }
    /**
   * Initialize joint texture and CPU-side matrix storage.
   *
   * Layout details:
   * - Texture size is the next power-of-two able to contain all matrices plus two offset texels.
   * - `_jointMatrixArray` holds:
   *   - First 2 vec4s: ring buffer offsets `[current, previous, 0, 0]`.
   *   - Followed by 2×N matrices (current and previous), each as 16 floats.
   * - `_jointMatrices` is a view into `_jointMatrixArray` providing Matrix4x4 objects per slot.
   *
   * @internal
   */ _createJointTexture() {
        const textureWidth = nextPowerOf2(Math.max(4, Math.ceil(Math.sqrt((this._joints.length * 2 + 1) * 4))));
        const device = getDevice();
        this._jointTexture.set(device.createTexture2D('rgba32f', textureWidth, textureWidth, {
            mipmapping: false,
            samplerOptions: {
                magFilter: 'nearest',
                minFilter: 'nearest'
            }
        }));
        this._jointMatrixArray = new Float32Array(textureWidth * textureWidth * 4);
        const buffer = this._jointMatrixArray.buffer;
        this._jointOffsets = new Float32Array(buffer);
        this._jointOffsets[0] = 0;
        this._jointOffsets[1] = 0;
        this._jointMatrices = Array.from({
            length: this._joints.length * 2
        }).map((val, index)=>new Matrix4x4(buffer, (index + 1) * 16 * Float32Array.BYTES_PER_ELEMENT));
    }
    /**
   * Build representative skinned bounding data for a submesh.
   *
   * Strategy:
   * - For all vertices, compute their skinned position (using current ring buffer slot).
   * - Track the indices of the min/max extents along x, y, z (6 indices total).
   * - Store:
   *   - The 6 representative positions in object space.
   *   - Their 4 joint indices and weights (flattened).
   *   - An empty BoundingBox to be filled during animation.
   *
   * @param meshData - Raw submesh attributes (positions, blend indices, weights).
   * @returns Skinned bounding box info used during per-frame updates.
   */ getBoundingInfo(data) {
        const indices = [
            0,
            0,
            0,
            0,
            0,
            0
        ];
        let minx = Number.MAX_VALUE;
        let maxx = -Number.MAX_VALUE;
        let miny = Number.MAX_VALUE;
        let maxy = -Number.MAX_VALUE;
        let minz = Number.MAX_VALUE;
        let maxz = -Number.MAX_VALUE;
        const v = data.positions;
        const vert = new Vector3();
        const tmpV0 = new Vector3();
        const tmpV1 = new Vector3();
        const tmpV2 = new Vector3();
        const tmpV3 = new Vector3();
        const numVertices = Math.floor(v.length / 3);
        for(let i = 0; i < numVertices; i++){
            vert.setXYZ(v[i * 3], v[i * 3 + 1], v[i * 3 + 2]);
            this._jointMatrices[data.blendIndices[i * 4 + 0] + this._jointOffsets[0] - 1].transformPointAffine(vert, tmpV0).scaleBy(data.weights[i * 4 + 0]);
            this._jointMatrices[data.blendIndices[i * 4 + 1] + this._jointOffsets[0] - 1].transformPointAffine(vert, tmpV1).scaleBy(data.weights[i * 4 + 1]);
            this._jointMatrices[data.blendIndices[i * 4 + 2] + this._jointOffsets[0] - 1].transformPointAffine(vert, tmpV2).scaleBy(data.weights[i * 4 + 2]);
            this._jointMatrices[data.blendIndices[i * 4 + 3] + this._jointOffsets[0] - 1].transformPointAffine(vert, tmpV3).scaleBy(data.weights[i * 4 + 3]);
            tmpV0.addBy(tmpV1).addBy(tmpV2).addBy(tmpV3);
            if (tmpV0.x < minx) {
                minx = tmpV0.x;
                indices[0] = i;
            }
            if (tmpV0.x > maxx) {
                maxx = tmpV0.x;
                indices[1] = i;
            }
            if (tmpV0.y < miny) {
                miny = tmpV0.y;
                indices[2] = i;
            }
            if (tmpV0.y > maxy) {
                maxy = tmpV0.y;
                indices[3] = i;
            }
            if (tmpV0.z < minz) {
                minz = tmpV0.z;
                indices[4] = i;
            }
            if (tmpV0.z > maxz) {
                maxz = tmpV0.z;
                indices[5] = i;
            }
        }
        const info = {
            boundingVertexBlendIndices: new Float32Array(Array.from({
                length: 6 * 4
            }).map((val, index)=>data.blendIndices[indices[index >> 2] * 4 + index % 4])),
            boundingVertexJointWeights: new Float32Array(Array.from({
                length: 6 * 4
            }).map((val, index)=>data.weights[indices[index >> 2] * 4 + index % 4])),
            boundingVertices: Array.from({
                length: 6
            }).map((val, index)=>new Vector3(data.positions[indices[index] * 3], data.positions[indices[index] * 3 + 1], data.positions[indices[index] * 3 + 2])),
            boundingBox: new BoundingBox()
        };
        return info;
    }
    /**
   * Skin all vertices into mesh-local space for the current skeleton pose.
   *
   * This is used by systems like cloth that need the animated mesh pose on CPU,
   * but render with GPU skinning disabled to avoid double deformation.
   *
   * @param positions - Bind-pose/object-space input positions, xyz packed.
   * @param blendIndices - 4 joint indices per vertex.
   * @param weights - 4 joint weights per vertex.
   * @param invWorldMatrix - Mesh inverse world matrix to convert skinned world positions back to mesh-local space.
   * @param out - Optional output array to reuse.
   * @returns The skinned mesh-local positions.
   * @internal
   */ skinPositionsToLocal(positions, blendIndices, weights, invWorldMatrix, out) {
        const result = out && out.length === positions.length ? out : new Float32Array(positions.length);
        const matrixOffset = this._jointOffsets[0] - 1;
        for(let i = 0; i + 2 < positions.length; i += 3){
            const vertexIndex = i / 3 >> 0;
            const x = positions[i];
            const y = positions[i + 1];
            const z = positions[i + 2];
            let skinnedX = 0;
            let skinnedY = 0;
            let skinnedZ = 0;
            let weightSum = 0;
            for(let j = 0; j < 4; j++){
                const weight = Number(weights[vertexIndex * 4 + j]) || 0;
                if (weight <= 0) {
                    continue;
                }
                const jointIndex = (Number(blendIndices[vertexIndex * 4 + j]) || 0) + matrixOffset;
                const matrix = this._jointMatrices[jointIndex];
                if (!matrix) {
                    continue;
                }
                skinnedX += (matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12]) * weight;
                skinnedY += (matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13]) * weight;
                skinnedZ += (matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14]) * weight;
                weightSum += weight;
            }
            if (weightSum <= 1e-6) {
                skinnedX = x;
                skinnedY = y;
                skinnedZ = z;
            }
            result[i] = invWorldMatrix[0] * skinnedX + invWorldMatrix[4] * skinnedY + invWorldMatrix[8] * skinnedZ + invWorldMatrix[12];
            result[i + 1] = invWorldMatrix[1] * skinnedX + invWorldMatrix[5] * skinnedY + invWorldMatrix[9] * skinnedZ + invWorldMatrix[13];
            result[i + 2] = invWorldMatrix[2] * skinnedX + invWorldMatrix[6] * skinnedY + invWorldMatrix[10] * skinnedZ + invWorldMatrix[14];
        }
        return result;
    }
    findRootJoint(joints) {
        let root = null;
        for (const joint of joints){
            if (!root) {
                root = joint;
            }
            while(!root.isParentOf(joint)){
                root = root.parent;
            }
            if (!root) {
                break;
            }
        }
        return root;
    }
    static normalizeHumanoidJointName(name) {
        return name.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2').replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/([a-zA-Z])(\d+)/g, '$1 $2').replace(/(\d+)([a-zA-Z])/g, '$1 $2').replace(/[_:\/\\.-]+/g, ' ').replace(/[^a-zA-Z0-9]+/g, ' ').toLowerCase().replace(/\bforearm\b/g, 'fore arm').replace(/\bupperarm\b/g, 'upper arm').replace(/\blowerarm\b/g, 'lower arm').replace(/\bupleg\b/g, 'up leg').replace(/\bupperleg\b/g, 'upper leg').replace(/\blowerleg\b/g, 'lower leg').replace(/\btoebase\b/g, 'toe base').replace(/\bupperchest\b/g, 'upper chest').replace(/\btoes\b/g, 'toe').replace(/\blittle\b/g, 'pinky').replace(/\bcollar\b/g, 'clavicle').replace(/\s+/g, ' ').trim();
    }
    static getHumanoidJointNodeDepth(root, node) {
        let depth = 0;
        let current = node;
        while(current && current !== root){
            depth++;
            current = current.parent;
        }
        return depth;
    }
    static collectHumanoidJointNodeInfos(root) {
        const nodes = [];
        this.iterateNode(root, (node)=>{
            const normalizedName = this.normalizeHumanoidJointName(node.name || '');
            if (!normalizedName) {
                return false;
            }
            const tokens = normalizedName.split(' ').filter(Boolean);
            if (!tokens.length) {
                return false;
            }
            nodes.push({
                node,
                depth: this.getHumanoidJointNodeDepth(root, node),
                tokens,
                tokenSet: new Set(tokens)
            });
            return false;
        });
        return nodes;
    }
    static matchesHumanoidJointPattern(info, pattern) {
        if (pattern.all?.some((token)=>!info.tokenSet.has(token))) {
            return false;
        }
        if (pattern.any?.length && !pattern.any.some((token)=>info.tokenSet.has(token))) {
            return false;
        }
        if (pattern.none?.some((token)=>info.tokenSet.has(token))) {
            return false;
        }
        return true;
    }
    static scoreHumanoidJointPattern(info, pattern, priority) {
        const matchedTokens = new Set(pattern.all ?? []);
        for (const token of pattern.any ?? []){
            if (info.tokenSet.has(token)) {
                matchedTokens.add(token);
            }
        }
        let extraTokenCount = 0;
        for (const token of info.tokens){
            if (!matchedTokens.has(token)) {
                extraTokenCount++;
            }
        }
        return priority * 100000 + matchedTokens.size * 100 - extraTokenCount * 5 - info.depth;
    }
    static collectHumanoidJointCandidates(nodes, patterns, used) {
        const candidates = new Map();
        for(let index = 0; index < patterns.length; index++){
            const pattern = patterns[index];
            const priority = patterns.length - index;
            for (const info of nodes){
                if (used.has(info.node) || !this.matchesHumanoidJointPattern(info, pattern)) {
                    continue;
                }
                const score = this.scoreHumanoidJointPattern(info, pattern, priority);
                const current = candidates.get(info.node);
                if (current === undefined || score > current) {
                    candidates.set(info.node, score);
                }
            }
        }
        return Array.from(candidates, ([node, score])=>({
                node,
                score
            })).sort((a, b)=>b.score - a.score);
    }
    static findBestHumanoidJoint(nodes, patterns, used) {
        return this.collectHumanoidJointCandidates(nodes, patterns, used)[0]?.node ?? null;
    }
    static matchHumanoidJointProfile(nodes, profile, used, optional) {
        const result = {};
        const reserved = new Set();
        for (const joint of Object.keys(profile)){
            const node = this.findBestHumanoidJoint(nodes, profile[joint], new Set([
                ...used,
                ...reserved
            ]));
            if (!node) {
                if (optional?.has(joint)) {
                    continue;
                }
                return null;
            }
            result[joint] = node;
            reserved.add(node);
        }
        for (const node of reserved){
            used.add(node);
        }
        return result;
    }
    static validateHumanoidBodyPartial(body) {
        const validateAncestor = (ancestor, descendant)=>!body[ancestor] || !body[descendant] || this.isSameOrAncestor(body[ancestor], body[descendant]);
        return validateAncestor("Hips", "Spine") && validateAncestor("Hips", "Neck") && validateAncestor("Hips", "LeftUpperLeg") && validateAncestor("Hips", "RightUpperLeg") && validateAncestor("Spine", "Chest") && validateAncestor("Spine", "UpperChest") && validateAncestor("Spine", "Neck") && validateAncestor("Spine", "Head") && validateAncestor("Spine", "LeftShoulder") && validateAncestor("Spine", "RightShoulder") && validateAncestor("Chest", "UpperChest") && validateAncestor("Chest", "Neck") && validateAncestor("Chest", "Head") && validateAncestor("Chest", "LeftShoulder") && validateAncestor("Chest", "RightShoulder") && validateAncestor("UpperChest", "Neck") && validateAncestor("UpperChest", "Head") && validateAncestor("UpperChest", "LeftShoulder") && validateAncestor("UpperChest", "RightShoulder") && validateAncestor("Neck", "Head") && validateAncestor("LeftShoulder", "LeftUpperArm") && validateAncestor("LeftUpperArm", "LeftLowerArm") && validateAncestor("LeftLowerArm", "LeftHand") && validateAncestor("RightShoulder", "RightUpperArm") && validateAncestor("RightUpperArm", "RightLowerArm") && validateAncestor("RightLowerArm", "RightHand") && validateAncestor("LeftUpperLeg", "LeftLowerLeg") && validateAncestor("LeftLowerLeg", "LeftFoot") && validateAncestor("LeftFoot", "LeftToes") && validateAncestor("RightUpperLeg", "RightLowerLeg") && validateAncestor("RightLowerLeg", "RightFoot") && validateAncestor("RightFoot", "RightToes");
    }
    static matchHumanoidBodyProfile(nodes, profile, used, optional) {
        const joints = Object.keys(profile);
        const candidates = new Map();
        for (const joint of joints){
            candidates.set(joint, this.collectHumanoidJointCandidates(nodes, profile[joint], used));
        }
        const current = {};
        const reserved = new Set();
        let bestScore = Number.NEGATIVE_INFINITY;
        let bestResult = null;
        const search = (index, score)=>{
            if (index >= joints.length) {
                if (score > bestScore) {
                    bestScore = score;
                    bestResult = {
                        ...current
                    };
                }
                return;
            }
            const joint = joints[index];
            let matched = false;
            for (const candidate of candidates.get(joint) ?? []){
                if (reserved.has(candidate.node)) {
                    continue;
                }
                current[joint] = candidate.node;
                reserved.add(candidate.node);
                if (this.validateHumanoidBodyPartial(current)) {
                    matched = true;
                    search(index + 1, score + candidate.score);
                }
                reserved.delete(candidate.node);
                delete current[joint];
            }
            if (optional?.has(joint)) {
                search(index + 1, score);
            } else if (!matched) {
                return;
            }
        };
        search(0, 0);
        if (!bestResult) {
            return null;
        }
        for (const node of Object.values(bestResult)){
            used.add(node);
        }
        return bestResult;
    }
    static getHumanoidIntermediateChain(ancestor, descendant) {
        if (!this.isSameOrAncestor(ancestor, descendant)) {
            return null;
        }
        const chain = [];
        let current = descendant.parent;
        while(current && current !== ancestor){
            chain.unshift(current);
            current = current.parent;
        }
        return chain;
    }
    static completeHumanoidBody(body) {
        const requiredJoints = [
            "Hips",
            "Spine",
            "Neck",
            "Head",
            "LeftShoulder",
            "LeftUpperArm",
            "LeftLowerArm",
            "LeftHand",
            "RightShoulder",
            "RightUpperArm",
            "RightLowerArm",
            "RightHand",
            "LeftUpperLeg",
            "LeftLowerLeg",
            "LeftFoot",
            "LeftToes",
            "RightUpperLeg",
            "RightLowerLeg",
            "RightFoot",
            "RightToes"
        ];
        for (const joint of requiredJoints){
            if (!body[joint]) {
                return null;
            }
        }
        const spine = body["Spine"];
        const neck = body["Neck"];
        let chest = body["Chest"];
        let upperChest = body["UpperChest"];
        if (!chest || !upperChest) {
            const spineChain = this.getHumanoidIntermediateChain(spine, neck);
            if (!spineChain || spineChain.length === 0) {
                chest ||= spine;
                upperChest ||= chest;
            } else if (spineChain.length === 1) {
                chest ||= spineChain[0];
                upperChest ||= chest;
            } else {
                chest ||= spineChain[0];
                upperChest ||= spineChain[spineChain.length - 1];
            }
        }
        if (!chest || !upperChest) {
            return null;
        }
        return {
            ["Hips"]: body["Hips"],
            ["Spine"]: spine,
            ["Chest"]: chest,
            ["UpperChest"]: upperChest,
            ["Neck"]: neck,
            ["Head"]: body["Head"],
            ["LeftShoulder"]: body["LeftShoulder"],
            ["LeftUpperArm"]: body["LeftUpperArm"],
            ["LeftLowerArm"]: body["LeftLowerArm"],
            ["LeftHand"]: body["LeftHand"],
            ["RightShoulder"]: body["RightShoulder"],
            ["RightUpperArm"]: body["RightUpperArm"],
            ["RightLowerArm"]: body["RightLowerArm"],
            ["RightHand"]: body["RightHand"],
            ["LeftUpperLeg"]: body["LeftUpperLeg"],
            ["LeftLowerLeg"]: body["LeftLowerLeg"],
            ["LeftFoot"]: body["LeftFoot"],
            ["LeftToes"]: body["LeftToes"],
            ["RightUpperLeg"]: body["RightUpperLeg"],
            ["RightLowerLeg"]: body["RightLowerLeg"],
            ["RightFoot"]: body["RightFoot"],
            ["RightToes"]: body["RightToes"]
        };
    }
    static completeHumanoidHand(hand) {
        for (const joint of Object.values(HumanoidHandRig)){
            if (!hand[joint]) {
                return null;
            }
        }
        return {
            ["ThumbProximal"]: hand["ThumbProximal"],
            ["ThumbIntermediate"]: hand["ThumbIntermediate"],
            ["ThumbDistal"]: hand["ThumbDistal"],
            ["IndexProximal"]: hand["IndexProximal"],
            ["IndexIntermediate"]: hand["IndexIntermediate"],
            ["IndexDistal"]: hand["IndexDistal"],
            ["MiddleProximal"]: hand["MiddleProximal"],
            ["MiddleIntermediate"]: hand["MiddleIntermediate"],
            ["MiddleDistal"]: hand["MiddleDistal"],
            ["RingProximal"]: hand["RingProximal"],
            ["RingIntermediate"]: hand["RingIntermediate"],
            ["RingDistal"]: hand["RingDistal"],
            ["PinkyProximal"]: hand["PinkyProximal"],
            ["PinkyIntermediate"]: hand["PinkyIntermediate"],
            ["PinkyDistal"]: hand["PinkyDistal"]
        };
    }
    static tryExtractOptionalHumanoidHand(nodes, side, used) {
        const handCandidates = this.matchHumanoidJointProfile(nodes, this.createCommonHumanoidHandProfile(side), used);
        if (!handCandidates) {
            return undefined;
        }
        return this.completeHumanoidHand(handCandidates) ?? undefined;
    }
    static isSameOrAncestor(parent, child) {
        return parent === child || this.isParentOf(parent, child);
    }
    static validateHumanoidHandHierarchy(hand, joints) {
        const chains = [
            [
                "ThumbProximal",
                "ThumbIntermediate",
                "ThumbDistal"
            ],
            [
                "IndexProximal",
                "IndexIntermediate",
                "IndexDistal"
            ],
            [
                "MiddleProximal",
                "MiddleIntermediate",
                "MiddleDistal"
            ],
            [
                "RingProximal",
                "RingIntermediate",
                "RingDistal"
            ],
            [
                "PinkyProximal",
                "PinkyIntermediate",
                "PinkyDistal"
            ]
        ];
        return chains.every(([proximal, intermediate, distal])=>this.isSameOrAncestor(hand, joints[proximal]) && this.isSameOrAncestor(joints[proximal], joints[intermediate]) && this.isSameOrAncestor(joints[intermediate], joints[distal]));
    }
    static validateHumanoidJointExtraction(result) {
        const body = result.body;
        return this.isSameOrAncestor(body["Hips"], body["Spine"]) && this.isSameOrAncestor(body["Spine"], body["Chest"]) && this.isSameOrAncestor(body["Chest"], body["UpperChest"]) && this.isSameOrAncestor(body["UpperChest"], body["Neck"]) && this.isSameOrAncestor(body["Neck"], body["Head"]) && this.isSameOrAncestor(body["UpperChest"], body["LeftShoulder"]) && this.isSameOrAncestor(body["LeftShoulder"], body["LeftUpperArm"]) && this.isSameOrAncestor(body["LeftUpperArm"], body["LeftLowerArm"]) && this.isSameOrAncestor(body["LeftLowerArm"], body["LeftHand"]) && this.isSameOrAncestor(body["UpperChest"], body["RightShoulder"]) && this.isSameOrAncestor(body["RightShoulder"], body["RightUpperArm"]) && this.isSameOrAncestor(body["RightUpperArm"], body["RightLowerArm"]) && this.isSameOrAncestor(body["RightLowerArm"], body["RightHand"]) && this.isSameOrAncestor(body["Hips"], body["LeftUpperLeg"]) && this.isSameOrAncestor(body["LeftUpperLeg"], body["LeftLowerLeg"]) && this.isSameOrAncestor(body["LeftLowerLeg"], body["LeftFoot"]) && this.isSameOrAncestor(body["LeftFoot"], body["LeftToes"]) && this.isSameOrAncestor(body["Hips"], body["RightUpperLeg"]) && this.isSameOrAncestor(body["RightUpperLeg"], body["RightLowerLeg"]) && this.isSameOrAncestor(body["RightLowerLeg"], body["RightFoot"]) && this.isSameOrAncestor(body["RightFoot"], body["RightToes"]) && (!result.leftHand || this.validateHumanoidHandHierarchy(body["LeftHand"], result.leftHand)) && (!result.rightHand || this.validateHumanoidHandHierarchy(body["RightHand"], result.rightHand));
    }
    static createCommonHumanoidHandProfile(side) {
        return {
            ["ThumbProximal"]: sideJointPatterns(side, [
                {
                    all: [
                        'thumb',
                        'proximal'
                    ]
                },
                {
                    all: [
                        'thumb',
                        '1'
                    ]
                },
                {
                    all: [
                        'finger',
                        '0'
                    ]
                }
            ]),
            ["ThumbIntermediate"]: sideJointPatterns(side, [
                {
                    all: [
                        'thumb',
                        'intermediate'
                    ]
                },
                {
                    all: [
                        'thumb',
                        '2'
                    ]
                },
                {
                    all: [
                        'finger',
                        '01'
                    ]
                }
            ]),
            ["ThumbDistal"]: sideJointPatterns(side, [
                {
                    all: [
                        'thumb',
                        'distal'
                    ]
                },
                {
                    all: [
                        'thumb',
                        '3'
                    ]
                },
                {
                    all: [
                        'finger',
                        '02'
                    ]
                }
            ]),
            ["IndexProximal"]: sideJointPatterns(side, [
                {
                    all: [
                        'index',
                        'proximal'
                    ]
                },
                {
                    all: [
                        'index',
                        '1'
                    ]
                },
                {
                    all: [
                        'finger',
                        '1'
                    ]
                }
            ]),
            ["IndexIntermediate"]: sideJointPatterns(side, [
                {
                    all: [
                        'index',
                        'intermediate'
                    ]
                },
                {
                    all: [
                        'index',
                        '2'
                    ]
                },
                {
                    all: [
                        'finger',
                        '11'
                    ]
                }
            ]),
            ["IndexDistal"]: sideJointPatterns(side, [
                {
                    all: [
                        'index',
                        'distal'
                    ]
                },
                {
                    all: [
                        'index',
                        '3'
                    ]
                },
                {
                    all: [
                        'finger',
                        '12'
                    ]
                }
            ]),
            ["MiddleProximal"]: sideJointPatterns(side, [
                {
                    all: [
                        'middle',
                        'proximal'
                    ]
                },
                {
                    all: [
                        'middle',
                        '1'
                    ]
                },
                {
                    all: [
                        'finger',
                        '2'
                    ]
                }
            ]),
            ["MiddleIntermediate"]: sideJointPatterns(side, [
                {
                    all: [
                        'middle',
                        'intermediate'
                    ]
                },
                {
                    all: [
                        'middle',
                        '2'
                    ]
                },
                {
                    all: [
                        'finger',
                        '21'
                    ]
                }
            ]),
            ["MiddleDistal"]: sideJointPatterns(side, [
                {
                    all: [
                        'middle',
                        'distal'
                    ]
                },
                {
                    all: [
                        'middle',
                        '3'
                    ]
                },
                {
                    all: [
                        'finger',
                        '22'
                    ]
                }
            ]),
            ["RingProximal"]: sideJointPatterns(side, [
                {
                    all: [
                        'ring',
                        'proximal'
                    ]
                },
                {
                    all: [
                        'ring',
                        '1'
                    ]
                },
                {
                    all: [
                        'finger',
                        '3'
                    ]
                }
            ]),
            ["RingIntermediate"]: sideJointPatterns(side, [
                {
                    all: [
                        'ring',
                        'intermediate'
                    ]
                },
                {
                    all: [
                        'ring',
                        '2'
                    ]
                },
                {
                    all: [
                        'finger',
                        '31'
                    ]
                }
            ]),
            ["RingDistal"]: sideJointPatterns(side, [
                {
                    all: [
                        'ring',
                        'distal'
                    ]
                },
                {
                    all: [
                        'ring',
                        '3'
                    ]
                },
                {
                    all: [
                        'finger',
                        '32'
                    ]
                }
            ]),
            ["PinkyProximal"]: sideJointPatterns(side, [
                {
                    all: [
                        'pinky',
                        'proximal'
                    ]
                },
                {
                    all: [
                        'pinky',
                        '1'
                    ]
                },
                {
                    all: [
                        'finger',
                        '4'
                    ]
                }
            ]),
            ["PinkyIntermediate"]: sideJointPatterns(side, [
                {
                    all: [
                        'pinky',
                        'intermediate'
                    ]
                },
                {
                    all: [
                        'pinky',
                        '2'
                    ]
                },
                {
                    all: [
                        'finger',
                        '41'
                    ]
                }
            ]),
            ["PinkyDistal"]: sideJointPatterns(side, [
                {
                    all: [
                        'pinky',
                        'distal'
                    ]
                },
                {
                    all: [
                        'pinky',
                        '3'
                    ]
                },
                {
                    all: [
                        'finger',
                        '42'
                    ]
                }
            ])
        };
    }
    static createStandardHumanoidBodyProfile() {
        return {
            ["Hips"]: [
                jointPattern([
                    'hips'
                ]),
                jointPattern([
                    'pelvis'
                ])
            ],
            ["Spine"]: [
                jointPattern([
                    'spine'
                ], [
                    '1',
                    '2',
                    '3',
                    'chest',
                    'upper'
                ])
            ],
            ["Chest"]: [
                jointPattern([
                    'chest'
                ], [
                    'upper'
                ]),
                jointPattern([
                    'spine',
                    '1'
                ])
            ],
            ["UpperChest"]: [
                jointPattern([
                    'upper',
                    'chest'
                ]),
                jointPattern([
                    'spine',
                    '2'
                ]),
                jointPattern([
                    'spine',
                    '3'
                ])
            ],
            ["Neck"]: [
                jointPattern([
                    'neck'
                ], [
                    '1',
                    '2'
                ])
            ],
            ["Head"]: [
                jointPattern([
                    'head'
                ], [
                    'top',
                    'end',
                    'nub'
                ])
            ],
            ["LeftShoulder"]: sideJointPatterns('left', [
                {
                    all: [
                        'shoulder'
                    ]
                },
                {
                    all: [
                        'clavicle'
                    ]
                }
            ]),
            ["LeftUpperArm"]: sideJointPatterns('left', [
                {
                    all: [
                        'upper',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                },
                {
                    all: [
                        'arm'
                    ],
                    none: [
                        'fore',
                        'lower',
                        'hand',
                        'twist',
                        'shoulder'
                    ]
                }
            ]),
            ["LeftLowerArm"]: sideJointPatterns('left', [
                {
                    all: [
                        'lower',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                },
                {
                    all: [
                        'fore',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                }
            ]),
            ["LeftHand"]: sideJointPatterns('left', [
                {
                    all: [
                        'hand'
                    ],
                    none: [
                        'thumb',
                        'index',
                        'middle',
                        'ring',
                        'pinky'
                    ]
                }
            ]),
            ["RightShoulder"]: sideJointPatterns('right', [
                {
                    all: [
                        'shoulder'
                    ]
                },
                {
                    all: [
                        'clavicle'
                    ]
                }
            ]),
            ["RightUpperArm"]: sideJointPatterns('right', [
                {
                    all: [
                        'upper',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                },
                {
                    all: [
                        'arm'
                    ],
                    none: [
                        'fore',
                        'lower',
                        'hand',
                        'twist',
                        'shoulder'
                    ]
                }
            ]),
            ["RightLowerArm"]: sideJointPatterns('right', [
                {
                    all: [
                        'lower',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                },
                {
                    all: [
                        'fore',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                }
            ]),
            ["RightHand"]: sideJointPatterns('right', [
                {
                    all: [
                        'hand'
                    ],
                    none: [
                        'thumb',
                        'index',
                        'middle',
                        'ring',
                        'pinky'
                    ]
                }
            ]),
            ["LeftUpperLeg"]: sideJointPatterns('left', [
                {
                    all: [
                        'upper',
                        'leg'
                    ]
                },
                {
                    all: [
                        'up',
                        'leg'
                    ]
                },
                {
                    all: [
                        'thigh'
                    ]
                }
            ]),
            ["LeftLowerLeg"]: sideJointPatterns('left', [
                {
                    all: [
                        'lower',
                        'leg'
                    ]
                },
                {
                    all: [
                        'leg'
                    ],
                    none: [
                        'upper',
                        'up',
                        'thigh',
                        'foot',
                        'toe'
                    ]
                },
                {
                    all: [
                        'calf'
                    ]
                },
                {
                    all: [
                        'shin'
                    ]
                }
            ]),
            ["LeftFoot"]: sideJointPatterns('left', [
                {
                    all: [
                        'foot'
                    ],
                    none: [
                        'toe'
                    ]
                }
            ]),
            ["LeftToes"]: sideJointPatterns('left', [
                {
                    all: [
                        'toe',
                        'base'
                    ]
                },
                {
                    all: [
                        'toe'
                    ],
                    none: [
                        'end'
                    ]
                }
            ]),
            ["RightUpperLeg"]: sideJointPatterns('right', [
                {
                    all: [
                        'upper',
                        'leg'
                    ]
                },
                {
                    all: [
                        'up',
                        'leg'
                    ]
                },
                {
                    all: [
                        'thigh'
                    ]
                }
            ]),
            ["RightLowerLeg"]: sideJointPatterns('right', [
                {
                    all: [
                        'lower',
                        'leg'
                    ]
                },
                {
                    all: [
                        'leg'
                    ],
                    none: [
                        'upper',
                        'up',
                        'thigh',
                        'foot',
                        'toe'
                    ]
                },
                {
                    all: [
                        'calf'
                    ]
                },
                {
                    all: [
                        'shin'
                    ]
                }
            ]),
            ["RightFoot"]: sideJointPatterns('right', [
                {
                    all: [
                        'foot'
                    ],
                    none: [
                        'toe'
                    ]
                }
            ]),
            ["RightToes"]: sideJointPatterns('right', [
                {
                    all: [
                        'toe',
                        'base'
                    ]
                },
                {
                    all: [
                        'toe'
                    ],
                    none: [
                        'end'
                    ]
                }
            ])
        };
    }
    static createMixamoHumanoidBodyProfile() {
        return {
            ["Hips"]: [
                jointPattern([
                    'hips'
                ])
            ],
            ["Spine"]: [
                jointPattern([
                    'spine'
                ], [
                    '1',
                    '2',
                    '3'
                ])
            ],
            ["Chest"]: [
                jointPattern([
                    'spine',
                    '1'
                ]),
                jointPattern([
                    'chest'
                ], [
                    'upper'
                ])
            ],
            ["UpperChest"]: [
                jointPattern([
                    'spine',
                    '2'
                ]),
                jointPattern([
                    'spine',
                    '3'
                ]),
                jointPattern([
                    'upper',
                    'chest'
                ])
            ],
            ["Neck"]: [
                jointPattern([
                    'neck'
                ], [
                    '1',
                    '2'
                ])
            ],
            ["Head"]: [
                jointPattern([
                    'head'
                ], [
                    'top',
                    'end',
                    'nub'
                ])
            ],
            ["LeftShoulder"]: sideJointPatterns('left', [
                {
                    all: [
                        'shoulder'
                    ]
                },
                {
                    all: [
                        'clavicle'
                    ]
                }
            ]),
            ["LeftUpperArm"]: sideJointPatterns('left', [
                {
                    all: [
                        'arm'
                    ],
                    none: [
                        'upper',
                        'fore',
                        'lower',
                        'hand',
                        'twist',
                        'shoulder'
                    ]
                },
                {
                    all: [
                        'upper',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                }
            ]),
            ["LeftLowerArm"]: sideJointPatterns('left', [
                {
                    all: [
                        'fore',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                },
                {
                    all: [
                        'lower',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                }
            ]),
            ["LeftHand"]: sideJointPatterns('left', [
                {
                    all: [
                        'hand'
                    ],
                    none: [
                        'thumb',
                        'index',
                        'middle',
                        'ring',
                        'pinky'
                    ]
                }
            ]),
            ["RightShoulder"]: sideJointPatterns('right', [
                {
                    all: [
                        'shoulder'
                    ]
                },
                {
                    all: [
                        'clavicle'
                    ]
                }
            ]),
            ["RightUpperArm"]: sideJointPatterns('right', [
                {
                    all: [
                        'arm'
                    ],
                    none: [
                        'upper',
                        'fore',
                        'lower',
                        'hand',
                        'twist',
                        'shoulder'
                    ]
                },
                {
                    all: [
                        'upper',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                }
            ]),
            ["RightLowerArm"]: sideJointPatterns('right', [
                {
                    all: [
                        'fore',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                },
                {
                    all: [
                        'lower',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                }
            ]),
            ["RightHand"]: sideJointPatterns('right', [
                {
                    all: [
                        'hand'
                    ],
                    none: [
                        'thumb',
                        'index',
                        'middle',
                        'ring',
                        'pinky'
                    ]
                }
            ]),
            ["LeftUpperLeg"]: sideJointPatterns('left', [
                {
                    all: [
                        'up',
                        'leg'
                    ]
                },
                {
                    all: [
                        'upper',
                        'leg'
                    ]
                },
                {
                    all: [
                        'thigh'
                    ]
                }
            ]),
            ["LeftLowerLeg"]: sideJointPatterns('left', [
                {
                    all: [
                        'leg'
                    ],
                    none: [
                        'upper',
                        'up',
                        'thigh',
                        'foot',
                        'toe'
                    ]
                },
                {
                    all: [
                        'lower',
                        'leg'
                    ]
                },
                {
                    all: [
                        'calf'
                    ]
                },
                {
                    all: [
                        'shin'
                    ]
                }
            ]),
            ["LeftFoot"]: sideJointPatterns('left', [
                {
                    all: [
                        'foot'
                    ],
                    none: [
                        'toe'
                    ]
                }
            ]),
            ["LeftToes"]: sideJointPatterns('left', [
                {
                    all: [
                        'toe',
                        'base'
                    ]
                },
                {
                    all: [
                        'toe'
                    ],
                    none: [
                        'end'
                    ]
                }
            ]),
            ["RightUpperLeg"]: sideJointPatterns('right', [
                {
                    all: [
                        'up',
                        'leg'
                    ]
                },
                {
                    all: [
                        'upper',
                        'leg'
                    ]
                },
                {
                    all: [
                        'thigh'
                    ]
                }
            ]),
            ["RightLowerLeg"]: sideJointPatterns('right', [
                {
                    all: [
                        'leg'
                    ],
                    none: [
                        'upper',
                        'up',
                        'thigh',
                        'foot',
                        'toe'
                    ]
                },
                {
                    all: [
                        'lower',
                        'leg'
                    ]
                },
                {
                    all: [
                        'calf'
                    ]
                },
                {
                    all: [
                        'shin'
                    ]
                }
            ]),
            ["RightFoot"]: sideJointPatterns('right', [
                {
                    all: [
                        'foot'
                    ],
                    none: [
                        'toe'
                    ]
                }
            ]),
            ["RightToes"]: sideJointPatterns('right', [
                {
                    all: [
                        'toe',
                        'base'
                    ]
                },
                {
                    all: [
                        'toe'
                    ],
                    none: [
                        'end'
                    ]
                }
            ])
        };
    }
    static createBipedHumanoidBodyProfile() {
        return {
            ["Hips"]: [
                jointPattern([
                    'pelvis'
                ]),
                jointPattern([
                    'hips'
                ])
            ],
            ["Spine"]: [
                jointPattern([
                    'spine'
                ], [
                    '1',
                    '2',
                    '3'
                ])
            ],
            ["Chest"]: [
                jointPattern([
                    'spine',
                    '1'
                ]),
                jointPattern([
                    'chest'
                ], [
                    'upper'
                ])
            ],
            ["UpperChest"]: [
                jointPattern([
                    'spine',
                    '2'
                ]),
                jointPattern([
                    'spine',
                    '3'
                ]),
                jointPattern([
                    'upper',
                    'chest'
                ])
            ],
            ["Neck"]: [
                jointPattern([
                    'neck'
                ], [
                    '1',
                    '2'
                ])
            ],
            ["Head"]: [
                jointPattern([
                    'head'
                ], [
                    'top',
                    'end',
                    'nub'
                ])
            ],
            ["LeftShoulder"]: sideJointPatterns('left', [
                {
                    all: [
                        'clavicle'
                    ]
                },
                {
                    all: [
                        'shoulder'
                    ]
                }
            ]),
            ["LeftUpperArm"]: sideJointPatterns('left', [
                {
                    all: [
                        'upper',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                },
                {
                    all: [
                        'arm'
                    ],
                    none: [
                        'fore',
                        'lower',
                        'hand',
                        'twist',
                        'shoulder',
                        'clavicle'
                    ]
                }
            ]),
            ["LeftLowerArm"]: sideJointPatterns('left', [
                {
                    all: [
                        'fore',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                },
                {
                    all: [
                        'lower',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                }
            ]),
            ["LeftHand"]: sideJointPatterns('left', [
                {
                    all: [
                        'hand'
                    ],
                    none: [
                        'finger',
                        'thumb',
                        'index',
                        'middle',
                        'ring',
                        'pinky'
                    ]
                }
            ]),
            ["RightShoulder"]: sideJointPatterns('right', [
                {
                    all: [
                        'clavicle'
                    ]
                },
                {
                    all: [
                        'shoulder'
                    ]
                }
            ]),
            ["RightUpperArm"]: sideJointPatterns('right', [
                {
                    all: [
                        'upper',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                },
                {
                    all: [
                        'arm'
                    ],
                    none: [
                        'fore',
                        'lower',
                        'hand',
                        'twist',
                        'shoulder',
                        'clavicle'
                    ]
                }
            ]),
            ["RightLowerArm"]: sideJointPatterns('right', [
                {
                    all: [
                        'fore',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                },
                {
                    all: [
                        'lower',
                        'arm'
                    ],
                    none: [
                        'twist'
                    ]
                }
            ]),
            ["RightHand"]: sideJointPatterns('right', [
                {
                    all: [
                        'hand'
                    ],
                    none: [
                        'finger',
                        'thumb',
                        'index',
                        'middle',
                        'ring',
                        'pinky'
                    ]
                }
            ]),
            ["LeftUpperLeg"]: sideJointPatterns('left', [
                {
                    all: [
                        'thigh'
                    ]
                },
                {
                    all: [
                        'upper',
                        'leg'
                    ]
                },
                {
                    all: [
                        'up',
                        'leg'
                    ]
                }
            ]),
            ["LeftLowerLeg"]: sideJointPatterns('left', [
                {
                    all: [
                        'calf'
                    ]
                },
                {
                    all: [
                        'lower',
                        'leg'
                    ]
                },
                {
                    all: [
                        'leg'
                    ],
                    none: [
                        'upper',
                        'up',
                        'thigh',
                        'foot',
                        'toe'
                    ]
                }
            ]),
            ["LeftFoot"]: sideJointPatterns('left', [
                {
                    all: [
                        'foot'
                    ],
                    none: [
                        'toe'
                    ]
                }
            ]),
            ["LeftToes"]: sideJointPatterns('left', [
                {
                    all: [
                        'toe',
                        '0'
                    ]
                },
                {
                    all: [
                        'toe'
                    ],
                    none: [
                        'end'
                    ]
                }
            ]),
            ["RightUpperLeg"]: sideJointPatterns('right', [
                {
                    all: [
                        'thigh'
                    ]
                },
                {
                    all: [
                        'upper',
                        'leg'
                    ]
                },
                {
                    all: [
                        'up',
                        'leg'
                    ]
                }
            ]),
            ["RightLowerLeg"]: sideJointPatterns('right', [
                {
                    all: [
                        'calf'
                    ]
                },
                {
                    all: [
                        'lower',
                        'leg'
                    ]
                },
                {
                    all: [
                        'leg'
                    ],
                    none: [
                        'upper',
                        'up',
                        'thigh',
                        'foot',
                        'toe'
                    ]
                }
            ]),
            ["RightFoot"]: sideJointPatterns('right', [
                {
                    all: [
                        'foot'
                    ],
                    none: [
                        'toe'
                    ]
                }
            ]),
            ["RightToes"]: sideJointPatterns('right', [
                {
                    all: [
                        'toe',
                        '0'
                    ]
                },
                {
                    all: [
                        'toe'
                    ],
                    none: [
                        'end'
                    ]
                }
            ])
        };
    }
    static isParentOf(parent, child) {
        while(child && child !== parent){
            child = child.parent;
        }
        return child === parent;
    }
    static iterateNode(root, callback) {
        if (callback(root)) {
            return true;
        }
        for (const child of root.children){
            if (this.iterateNode(child, callback)) {
                return true;
            }
        }
        return false;
    }
    static tryExtractHumanoidJointsByBodyProfile(root, bodyProfile) {
        const nodes = this.collectHumanoidJointNodeInfos(root);
        const used = new Set();
        const bodyCandidates = this.matchHumanoidBodyProfile(nodes, bodyProfile, used, new Set([
            "Chest",
            "UpperChest"
        ]));
        if (!bodyCandidates) {
            return null;
        }
        const body = this.completeHumanoidBody(bodyCandidates);
        if (!body) {
            return null;
        }
        const leftHand = this.tryExtractOptionalHumanoidHand(nodes, 'left', used);
        const rightHand = this.tryExtractOptionalHumanoidHand(nodes, 'right', used);
        const result = {
            body,
            leftHand,
            rightHand
        };
        return this.validateHumanoidJointExtraction(result) ? result : null;
    }
    /**
   * Attempt to extract humanoid joint mappings from the skeleton's joints based on their names.
   *
   * This method looks for joints with names matching the standardized `HumanoidBodyRig` and `HumanoidHandRig` enums.
   * If a complete mapping is found, it returns an object containing the mapped joints for the body and hands.
   * If any required joint is missing, it returns `null`.
   *
   * This method tries to find the best match for humanoid rigs, designed to work with Mixamo, VRoid, Unity Humanoid,
   * Biped, and similar skeletons. It is not guaranteed to work with all models, and may require manual adjustments or
   * custom modifiers for non-standard rigs.
   *
   * @param root - The root scene node to search for humanoid joints.
   * @returns An object containing the mapped body and hand joints if a complete humanoid rig is detected, otherwise `null`.
   */ static tryExtractHumanoidJoints(root) {
        return this.tryExtractHumanoidJointsMixamo(root) ?? this.tryExtractHumanoidJointsVRM(root) ?? this.tryExtractHumanoidJointsUnityHumanoid(root) ?? this.tryExtractHumanoidJointsBiped(root);
    }
    static tryExtractHumanoidJointsMixamo(root) {
        return this.tryExtractHumanoidJointsByBodyProfile(root, this.createMixamoHumanoidBodyProfile());
    }
    static tryExtractHumanoidJointsVRM(root) {
        return this.tryExtractHumanoidJointsByBodyProfile(root, this.createStandardHumanoidBodyProfile());
    }
    static tryExtractHumanoidJointsUnityHumanoid(root) {
        return this.tryExtractHumanoidJointsByBodyProfile(root, this.createStandardHumanoidBodyProfile());
    }
    static tryExtractHumanoidJointsBiped(root) {
        return this.tryExtractHumanoidJointsByBodyProfile(root, this.createBipedHumanoidBodyProfile());
    }
}

export { HumanoidBodyRig, HumanoidHandRig, Skeleton };
//# sourceMappingURL=skeleton.js.map
