import { AnimationClip } from "./AnimationClip.js";
import { AnimationTrack } from "./AnimationTrack.js";
import { applyRootMotion, extractRootMotion } from "./RootMotion.js";
export class LocomotionController {
    clip;
    rootMotionTrack;
    strideAmplitude;
    rootMotionScale;
    state;
    constructor(options) {
        if (!Number.isFinite(options.clip.duration) || options.clip.duration <= 0) {
            throw new Error("LocomotionController requires a clip with positive finite duration.");
        }
        this.clip = options.clip;
        this.rootMotionTrack = options.rootMotionTrack ?? "root.position";
        this.strideAmplitude = options.strideAmplitude ?? 1;
        this.rootMotionScale = options.rootMotionScale ?? 1;
        this.state = {
            speed: options.speed ?? 1,
            inPlace: options.inPlace ?? false,
            paused: options.paused ?? false,
            pathRadius: options.pathRadius ?? 1
        };
    }
    setSpeed(speed) {
        if (!Number.isFinite(speed) || speed < 0) {
            throw new Error("LocomotionController speed must be finite and non-negative.");
        }
        this.state.speed = speed;
        return this;
    }
    setInPlace(inPlace) {
        this.state.inPlace = inPlace;
        return this;
    }
    setPaused(paused) {
        this.state.paused = paused;
        return this;
    }
    setPathRadius(pathRadius) {
        if (!Number.isFinite(pathRadius) || pathRadius <= 0) {
            throw new Error("LocomotionController pathRadius must be positive and finite.");
        }
        this.state.pathRadius = pathRadius;
        return this;
    }
    sample(elapsedSeconds) {
        if (!Number.isFinite(elapsedSeconds) || elapsedSeconds < 0) {
            throw new Error("LocomotionController elapsed time must be finite and non-negative.");
        }
        const scaledTime = this.state.paused ? 0 : elapsedSeconds * this.state.speed;
        const previousTime = Math.max(0, scaledTime - 1 / 60);
        const clipTime = wrapTime(scaledTime, this.clip.duration);
        const previousClipTime = wrapTime(previousTime, this.clip.duration);
        const target = { position: [0, 0, 0] };
        const rootMotion = extractRootMotion(this.clip, {
            target: this.rootMotionTrack,
            fromTime: previousClipTime,
            toTime: clipTime,
            loop: true
        });
        applyRootMotion(target, rootMotion, this.state.inPlace ? 0 : this.rootMotionScale);
        const rootMotionDistance = this.state.inPlace ? 0 : scaledTime * this.rootMotionScale;
        const pathAngle = this.state.inPlace ? -0.9 : rootMotionDistance / Math.max(0.1, this.state.pathRadius);
        return {
            clipName: this.clip.name,
            clipTime,
            worldX: this.state.inPlace ? 0 : Math.sin(pathAngle) * this.state.pathRadius,
            worldZ: this.state.inPlace ? 0 : Math.cos(pathAngle) * this.state.pathRadius,
            heading: this.state.inPlace ? 0 : pathAngle + Math.PI / 2,
            stride: Math.sin((clipTime / this.clip.duration) * Math.PI * 2) * this.strideAmplitude,
            rootMotionDistance: Math.hypot(target.position[0], target.position[2]) + rootMotionDistance
        };
    }
}
export function createRootMotionWalkClip(options = {}) {
    const name = options.name ?? "a3d-procedural-root-walk";
    const duration = options.duration ?? 1.2;
    const distance = options.distance ?? 1.28;
    const target = options.rootMotionTrack ?? "root.position";
    if (!Number.isFinite(duration) || duration <= 0 || !Number.isFinite(distance) || distance <= 0) {
        throw new Error("Root-motion walk clip duration and distance must be positive finite values.");
    }
    return new AnimationClip({
        name,
        duration,
        tracks: [
            new AnimationTrack({
                target,
                valueType: "vector3",
                keyframes: [
                    { time: 0, value: [0, 0, 0], interpolation: "linear" },
                    { time: duration * 0.25, value: [0, 0, distance * 0.25], interpolation: "linear" },
                    { time: duration * 0.5, value: [0, 0, distance * 0.5], interpolation: "linear" },
                    { time: duration * 0.75, value: [0, 0, distance * 0.75], interpolation: "linear" },
                    { time: duration, value: [0, 0, distance], interpolation: "linear" }
                ]
            })
        ]
    });
}
function wrapTime(time, duration) {
    const wrapped = time % duration;
    return wrapped < 0 ? wrapped + duration : wrapped;
}
//# sourceMappingURL=LocomotionController.js.map