import { AnimationTrack } from './animationtrack.js';
import { createGeometryCacheState, mixGeometryCacheBoundingBox, ensureGeometryCacheMeshBinding } from './geometry_cache_utils.js';

/**
 * Geometry cache track backed by explicit per-frame vertex data.
 *
 * @public
 */ class FixedGeometryCacheTrack extends AnimationTrack {
    _times;
    _frames;
    _state;
    _meshBindings;
    constructor(times, frames, embedded){
        super(embedded);
        this._times = times ?? new Float32Array();
        this._frames = frames ?? [];
        this._state = this._frames.length > 0 ? createGeometryCacheState(this._frames[0]) : null;
        this._meshBindings = new WeakMap();
    }
    get times() {
        return this._times;
    }
    set times(value) {
        this._times = value ?? new Float32Array();
    }
    get frames() {
        return this._frames;
    }
    set frames(value) {
        this._frames = value ?? [];
        this._state = this._frames.length > 0 ? createGeometryCacheState(this._frames[0]) : null;
    }
    calculateState(_target, currentTime) {
        if (this._frames.length === 0) {
            throw new Error('FixedGeometryCacheTrack.calculateState(): no frames available');
        }
        if (this._frames.length === 1 || this._times.length <= 1) {
            return this.copyFrameToState(this._frames[0]);
        }
        if (currentTime <= this._times[0]) {
            return this.copyFrameToState(this._frames[0]);
        }
        const lastIndex = this._times.length - 1;
        if (currentTime >= this._times[lastIndex]) {
            return this.copyFrameToState(this._frames[lastIndex]);
        }
        let right = 1;
        while(right < this._times.length && this._times[right] < currentTime){
            right++;
        }
        const left = Math.max(0, right - 1);
        const start = this._times[left];
        const end = this._times[right];
        const t = end > start ? (currentTime - start) / (end - start) : 0;
        return this.interpolateFrames(this._frames[left], this._frames[right], t);
    }
    applyState(target, state) {
        const mesh = target;
        const binding = this.ensureMeshBinding(mesh, state);
        binding.positionBuffer.bufferSubData(0, state.positions);
        if (binding.normalBuffer && state.normals) {
            binding.normalBuffer.bufferSubData(0, state.normals);
        }
        mesh.setAnimatedBoundingBox(state.boundingBox);
    }
    mixState(a, b, t) {
        if (!this._state) {
            throw new Error('FixedGeometryCacheTrack.mixState(): invalid state');
        }
        const positions = new Float32Array(a.positions.length);
        for(let i = 0; i < positions.length; i++){
            positions[i] = a.positions[i] + (b.positions[i] - a.positions[i]) * t;
        }
        let normals = null;
        if (a.normals && b.normals && a.normals.length === b.normals.length) {
            normals = new Float32Array(a.normals.length);
            for(let i = 0; i < normals.length; i++){
                normals[i] = a.normals[i] + (b.normals[i] - a.normals[i]) * t;
            }
        }
        return {
            positions,
            normals,
            boundingBox: mixGeometryCacheBoundingBox(a.boundingBox, b.boundingBox, t)
        };
    }
    getBlendId() {
        return 'fixed-geometry-cache';
    }
    getDuration() {
        return this._times.length > 0 ? this._times[this._times.length - 1] : 0;
    }
    reset(target) {
        if (this._frames.length > 0) {
            this.applyState(target, this.copyFrameToState(this._frames[0]));
        }
    }
    copyFrameToState(frame) {
        if (!this._state) {
            this._state = createGeometryCacheState(frame);
        }
        this._state.positions.set(frame.positions);
        if (frame.normals) {
            if (!this._state.normals || this._state.normals.length !== frame.normals.length) {
                this._state.normals = new Float32Array(frame.normals.length);
            }
            this._state.normals.set(frame.normals);
        } else {
            this._state.normals = null;
        }
        this._state.boundingBox.minPoint.set(frame.boundingBox.minPoint);
        this._state.boundingBox.maxPoint.set(frame.boundingBox.maxPoint);
        return this._state;
    }
    interpolateFrames(a, b, t) {
        if (!this._state) {
            this._state = createGeometryCacheState(a);
        }
        for(let i = 0; i < this._state.positions.length; i++){
            this._state.positions[i] = a.positions[i] + (b.positions[i] - a.positions[i]) * t;
        }
        if (a.normals && b.normals && a.normals.length === b.normals.length) {
            if (!this._state.normals || this._state.normals.length !== a.normals.length) {
                this._state.normals = new Float32Array(a.normals.length);
            }
            for(let i = 0; i < this._state.normals.length; i++){
                this._state.normals[i] = a.normals[i] + (b.normals[i] - a.normals[i]) * t;
            }
        } else {
            this._state.normals = null;
        }
        this._state.boundingBox.minPoint.setXYZ(a.boundingBox.minPoint.x + (b.boundingBox.minPoint.x - a.boundingBox.minPoint.x) * t, a.boundingBox.minPoint.y + (b.boundingBox.minPoint.y - a.boundingBox.minPoint.y) * t, a.boundingBox.minPoint.z + (b.boundingBox.minPoint.z - a.boundingBox.minPoint.z) * t);
        this._state.boundingBox.maxPoint.setXYZ(a.boundingBox.maxPoint.x + (b.boundingBox.maxPoint.x - a.boundingBox.maxPoint.x) * t, a.boundingBox.maxPoint.y + (b.boundingBox.maxPoint.y - a.boundingBox.maxPoint.y) * t, a.boundingBox.maxPoint.z + (b.boundingBox.maxPoint.z - a.boundingBox.maxPoint.z) * t);
        return this._state;
    }
    ensureMeshBinding(mesh, state) {
        return ensureGeometryCacheMeshBinding(this._meshBindings, mesh, state, 'FixedGeometryCacheTrack.applyState()');
    }
}

export { FixedGeometryCacheTrack };
//# sourceMappingURL=fixed_geometry_cache_track.js.map
