import { as as Animation, V as Vector3, M as Matrix, d as Tools, ao as Quaternion, bf as BVHFileLoaderMetadata, ax as RegisterSceneLoaderPlugin } from './index-CxR0Yf_7.js';
import { A as AssetContainer } from './assetContainer-7GPvH2S_.js';
import { B as Bone } from './bone-C6xIrJX4.js';
import { S as Skeleton } from './skeleton-DzANFt7R.js';
import './index-BvBk1Iap.js';
import './rawTexture-Bto4WZO2.js';

const _XPosition = "Xposition";
const _YPosition = "Yposition";
const _ZPosition = "Zposition";
const _XRotation = "Xrotation";
const _YRotation = "Yrotation";
const _ZRotation = "Zrotation";
const _HierarchyNode = "HIERARCHY";
const _MotionNode = "MOTION";
class LoaderContext {
    constructor(skeleton) {
        this.loopMode = Animation.ANIMATIONLOOPMODE_CYCLE;
        this.list = [];
        this.root = CreateBVHNode();
        this.numFrames = 0;
        this.frameRate = 0;
        this.skeleton = skeleton;
    }
}
function CreateBVHNode() {
    return {
        name: "",
        type: "",
        offset: new Vector3(),
        channels: [],
        children: [],
        frames: [],
        parent: null,
    };
}
function CreateBVHKeyFrame() {
    return {
        frame: 0,
        position: new Vector3(),
        rotation: new Quaternion(),
    };
}
/**
 * Converts the BVH node's offset to a Babylon matrix
 * @param node - The BVH node to convert
 * @returns The converted matrix
 */
function BoneOffset(node) {
    const x = node.offset.x;
    const y = node.offset.y;
    const z = node.offset.z;
    return Matrix.Translation(x, y, z);
}
/**
 * Creates animations for the BVH node
 * @param node - The BVH node to create animations for
 * @param context - The loader context
 * @returns The created animations
 */
function CreateAnimations(node, context) {
    if (node.frames.length === 0) {
        return [];
    }
    const animations = [];
    // Create position animation if there are position channels
    const hasPosition = node.channels.some((c) => c === _XPosition || c === _YPosition || c === _ZPosition);
    // Create rotation animation if there are rotation channels
    const hasRotation = node.channels.some((c) => c === _XRotation || c === _YRotation || c === _ZRotation);
    const posAnim = new Animation(`${node.name}_pos`, "position", context.frameRate, Animation.ANIMATIONTYPE_VECTOR3, context.loopMode);
    const rotAnim = new Animation(`${node.name}_rot`, "rotationQuaternion", context.frameRate, Animation.ANIMATIONTYPE_QUATERNION, context.loopMode);
    const posKeys = [];
    const rotKeys = [];
    for (let i = 0; i < node.frames.length; i++) {
        const frame = node.frames[i];
        if (hasPosition && frame.position) {
            posKeys.push({
                frame: frame.frame,
                value: frame.position.clone(),
            });
        }
        if (hasRotation) {
            rotKeys.push({
                frame: frame.frame,
                value: frame.rotation.clone(),
            });
        }
    }
    if (posKeys.length > 0) {
        posAnim.setKeys(posKeys);
        animations.push(posAnim);
    }
    if (rotKeys.length > 0) {
        rotAnim.setKeys(rotKeys);
        animations.push(rotAnim);
    }
    return animations;
}
/**
 * Converts a BVH node to a Babylon bone
 * @param node - The BVH node to convert
 * @param parent - The parent bone
 * @param context - The loader context
 */
function ConvertNode(node, parent, context) {
    const matrix = BoneOffset(node);
    const bone = new Bone(node.name, context.skeleton, parent, matrix);
    // Create animation for this bone
    const animations = CreateAnimations(node, context);
    for (const animation of animations) {
        if (animation.getKeys() && animation.getKeys().length > 0) {
            bone.animations.push(animation);
        }
    }
    for (const child of node.children) {
        ConvertNode(child, bone, context);
    }
}
/**
 * Recursively reads data from a single frame into the bone hierarchy.
 * The bone hierarchy has to be structured in the same order as the BVH file.
 * keyframe data is stored in bone.frames.
 * @param data - splitted string array (frame values), values are shift()ed
 * @param frameNumber - playback time for this keyframe
 * @param bone - the bone to read frame data from
 * @param tokenIndex - the index of the token to read
 */
function ReadFrameData(data, frameNumber, bone, tokenIndex) {
    if (bone.type === "ENDSITE") {
        // end sites have no motion data
        return;
    }
    // add keyframe
    const keyframe = CreateBVHKeyFrame();
    keyframe.frame = frameNumber;
    keyframe.position = new Vector3();
    keyframe.rotation = new Quaternion();
    bone.frames.push(keyframe);
    let combinedRotation = Matrix.Identity();
    // parse values for each channel in node
    for (let i = 0; i < bone.channels.length; ++i) {
        const channel = bone.channels[i];
        const value = data[tokenIndex.i++];
        if (!value) {
            continue;
        }
        const parsedValue = parseFloat(value.trim());
        if (channel.endsWith("position")) {
            switch (channel) {
                case _XPosition:
                    keyframe.position.x = parsedValue;
                    break;
                case _YPosition:
                    keyframe.position.y = parsedValue;
                    break;
                case _ZPosition:
                    keyframe.position.z = parsedValue;
                    break;
            }
        }
        else if (channel.endsWith("rotation")) {
            const angle = Tools.ToRadians(parsedValue);
            let rotationMatrix;
            switch (channel) {
                case _XRotation:
                    rotationMatrix = Matrix.RotationX(angle);
                    break;
                case _YRotation:
                    rotationMatrix = Matrix.RotationY(angle);
                    break;
                case _ZRotation:
                    rotationMatrix = Matrix.RotationZ(angle);
                    break;
            }
            combinedRotation = rotationMatrix.multiply(combinedRotation);
        }
    }
    Quaternion.FromRotationMatrixToRef(combinedRotation, keyframe.rotation);
    // parse child nodes
    for (const child of bone.children) {
        ReadFrameData(data, frameNumber, child, tokenIndex);
    }
}
/**
 * Recursively parses the HIERARCHY section of the BVH file
 * @param lines - all lines of the file. lines are consumed as we go along
 * @param firstLine - line containing the node type and name e.g. "JOINT hip"
 * @param parent - the parent node for hierarchy
 * @param context - the loader context containing the list of nodes and other data
 * @returns a BVH node including children
 */
function ReadNode(lines, firstLine, parent, context) {
    const node = CreateBVHNode();
    node.parent = parent;
    context.list.push(node);
    // parse node type and name.
    let tokens = firstLine.trim().split(/\s+/);
    if (tokens[0].toUpperCase() === "END" && tokens[1].toUpperCase() === "SITE") {
        node.type = "ENDSITE";
        node.name = "ENDSITE"; // bvh end sites have no name
    }
    else {
        node.name = tokens[1];
        node.type = tokens[0].toUpperCase();
    }
    // opening bracket
    if (lines.shift()?.trim() != "{") {
        throw new Error("Expected opening { after type & name");
    }
    // parse OFFSET
    const tokensSplit = lines.shift()?.trim().split(/\s+/);
    if (!tokensSplit) {
        throw new Error("Unexpected end of file: missing OFFSET");
    }
    tokens = tokensSplit;
    if (tokens[0].toUpperCase() != "OFFSET") {
        throw new Error("Expected OFFSET, but got: " + tokens[0]);
    }
    if (tokens.length != 4) {
        throw new Error("OFFSET: Invalid number of values");
    }
    const offset = new Vector3(parseFloat(tokens[1]), parseFloat(tokens[2]), parseFloat(tokens[3]));
    if (isNaN(offset.x) || isNaN(offset.y) || isNaN(offset.z)) {
        throw new Error("OFFSET: Invalid values");
    }
    node.offset = offset;
    // parse CHANNELS definitions
    if (node.type != "ENDSITE") {
        tokens = lines.shift()?.trim().split(/\s+/);
        if (!tokens) {
            throw new Error("Unexpected end of file: missing CHANNELS");
        }
        if (tokens[0].toUpperCase() != "CHANNELS") {
            throw new Error("Expected CHANNELS definition");
        }
        const numChannels = parseInt(tokens[1]);
        // Skip CHANNELS and the number of channels
        node.channels = tokens.splice(2, numChannels);
        node.children = [];
    }
    // read children
    while (lines.length > 0) {
        const line = lines.shift()?.trim();
        if (line === "}") {
            // Finish reading the node
            return node;
        }
        else if (line) {
            node.children.push(ReadNode(lines, line, node, context));
        }
    }
    throw new Error("Unexpected end of file: missing closing brace");
}
/**
 * Reads a BVH file, returns a skeleton
 * @param text - The BVH file content
 * @param scene - The scene to add the skeleton to
 * @param assetContainer - The asset container to add the skeleton to
 * @param loadingOptions - The loading options
 * @returns The skeleton
 */
function ReadBvh(text, scene, assetContainer, loadingOptions) {
    const lines = text.split("\n");
    const { loopMode } = loadingOptions;
    scene._blockEntityCollection = !!assetContainer;
    const skeleton = new Skeleton("", "", scene);
    skeleton._parentContainer = assetContainer;
    scene._blockEntityCollection = false;
    const context = new LoaderContext(skeleton);
    context.loopMode = loopMode;
    // read model structure
    const firstLine = lines.shift();
    if (!firstLine || firstLine.trim().toUpperCase() !== _HierarchyNode) {
        throw new Error("HIERARCHY expected");
    }
    const nodeLine = lines.shift();
    if (!nodeLine) {
        throw new Error("Unexpected end of file after HIERARCHY");
    }
    const root = ReadNode(lines, nodeLine.trim(), null, context);
    // read motion data
    const motionLine = lines.shift();
    if (!motionLine || motionLine.trim().toUpperCase() !== _MotionNode) {
        throw new Error("MOTION expected");
    }
    const framesLine = lines.shift();
    if (!framesLine) {
        throw new Error("Unexpected end of file before frame count");
    }
    const framesTokens = framesLine.trim().split(/[\s]+/);
    if (framesTokens.length < 2) {
        throw new Error("Invalid frame count line");
    }
    // number of frames
    const numFrames = parseInt(framesTokens[1]);
    if (isNaN(numFrames)) {
        throw new Error("Failed to read number of frames.");
    }
    context.numFrames = numFrames;
    // frame time
    const frameTimeLine = lines.shift();
    if (!frameTimeLine) {
        throw new Error("Unexpected end of file before frame time");
    }
    const frameTimeTokens = frameTimeLine.trim().split(/[\s]+/);
    if (frameTimeTokens.length < 3) {
        throw new Error("Invalid frame time line");
    }
    const frameTime = parseFloat(frameTimeTokens[2]);
    if (isNaN(frameTime)) {
        throw new Error("Failed to read frame time.");
    }
    if (frameTime <= 0) {
        throw new Error("Failed to read frame time. Invalid value " + frameTime);
    }
    context.frameRate = 1 / frameTime;
    // read frame data line by line
    for (let i = 0; i < numFrames; ++i) {
        const frameLine = lines.shift();
        if (!frameLine) {
            continue;
        }
        const tokens = frameLine.trim().split(/[\s]+/) || [];
        ReadFrameData(tokens, i, root, { i: 0 });
    }
    context.root = root;
    ConvertNode(context.root, null, context);
    context.skeleton.returnToRest();
    return context.skeleton;
}

/**
 * @experimental
 * BVH file type loader.
 * This is a babylon scene loader plugin.
 */
class BVHFileLoader {
    /**
     * Creates loader for bvh motion files
     * @param loadingOptions - Options for the bvh loader
     */
    constructor(loadingOptions) {
        /**
         * Name of the loader ("bvh")
         */
        this.name = BVHFileLoaderMetadata.name;
        /** @internal */
        this.extensions = BVHFileLoaderMetadata.extensions;
        this._loadingOptions = { ...BVHFileLoader._DefaultLoadingOptions, ...(loadingOptions ?? {}) };
    }
    static get _DefaultLoadingOptions() {
        return {
            loopMode: Animation.ANIMATIONLOOPMODE_CYCLE,
        };
    }
    /** @internal */
    createPlugin(options) {
        return new BVHFileLoader(options[BVHFileLoaderMetadata.name]);
    }
    /**
     * If the data string can be loaded directly.
     * @param data - direct load data
     * @returns if the data can be loaded directly
     */
    canDirectLoad(data) {
        return this.isBvhHeader(data);
    }
    isBvhHeader(text) {
        return text.split("\n")[0] == "HIERARCHY";
    }
    isNotBvhHeader(text) {
        return !this.isBvhHeader(text);
    }
    /**
     * Imports  from the loaded gaussian splatting data and adds them to the scene
     * @param _meshesNames a string or array of strings of the mesh names that should be loaded from the file
     * @param scene the scene the meshes should be added to
     * @param data the bvh data to load
     * @returns a promise containing the loaded skeletons and animations
     */
    // eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax
    importMeshAsync(_meshesNames, scene, data) {
        if (typeof data !== "string") {
            // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
            return Promise.reject("BVH loader expects string data.");
        }
        if (this.isNotBvhHeader(data)) {
            // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
            return Promise.reject("BVH loader expects HIERARCHY header.");
        }
        try {
            const skeleton = ReadBvh(data, scene, null, this._loadingOptions);
            return Promise.resolve({
                meshes: [],
                particleSystems: [],
                skeletons: [skeleton],
                animationGroups: [],
                transformNodes: [],
                geometries: [],
                lights: [],
                spriteManagers: [],
            });
        }
        catch (e) {
            // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
            return Promise.reject(e);
        }
    }
    /**
     * Imports all objects from the loaded bvh data and adds them to the scene
     * @param scene the scene the objects should be added to
     * @param data the bvh data to load
     * @returns a promise which completes when objects have been loaded to the scene
     */
    // eslint-disable-next-line no-restricted-syntax, @typescript-eslint/promise-function-async
    loadAsync(scene, data) {
        if (typeof data !== "string") {
            // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
            return Promise.reject("BVH loader expects string data.");
        }
        if (this.isNotBvhHeader(data)) {
            // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
            return Promise.reject("BVH loader expects HIERARCHY header.");
        }
        // eslint-disable-next-line github/no-then
        return this.importMeshAsync(null, scene, data).then(() => {
            // return void
        });
    }
    /**
     * Load into an asset container.
     * @param scene The scene to load into
     * @param data The data to import
     * @returns The loaded asset container
     */
    // eslint-disable-next-line @typescript-eslint/promise-function-async, no-restricted-syntax
    loadAssetContainerAsync(scene, data) {
        if (typeof data !== "string") {
            // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
            return Promise.reject("BVH loader expects string data.");
        }
        if (this.isNotBvhHeader(data)) {
            // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
            return Promise.reject("BVH loader expects HIERARCHY header.");
        }
        const assetContainer = new AssetContainer(scene);
        try {
            const skeleton = ReadBvh(data, scene, assetContainer, this._loadingOptions);
            assetContainer.skeletons.push(skeleton);
            return Promise.resolve(assetContainer);
        }
        catch (e) {
            // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
            return Promise.reject(e);
        }
    }
}
RegisterSceneLoaderPlugin(new BVHFileLoader());

export { BVHFileLoader };
//# sourceMappingURL=bvhFileLoader-DIfP346p.js.map
