var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { Component, Mesh, MeshIndexType, MeshAttribute, } from '@wonderlandengine/api';
import { property } from '@wonderlandengine/api/decorators.js';
import { vec3 } from 'gl-matrix';
const direction = vec3.create();
const offset = vec3.create();
const normal = vec3.create();
const UP = vec3.fromValues(0, 1, 0);
/**
 * Dynamic mesh-based trail
 *
 * This component keeps track of the world position of the object it's added to.
 * At a fixed interval the world position is stored as start and end points of the trail segments.
 *
 * The trail tapers off along its length. UV texture coordinates are setup such that the
 * U-axis covers the width of the trail and the V-axis covers the length of the trail.
 * This allows the trail's appearance to be defined using a texture.
 */
class Trail extends Component {
    static TypeName = 'trail';
    /** The material to apply to the trail mesh */
    material = null;
    /** The number of segments in the trail mesh */
    segments = 50;
    /** The time interval before recording a new point */
    interval = 0.1;
    /** The width of the trail (in world space) */
    width = 1.0;
    /** Whether or not the trail should taper off */
    taper = true;
    /**
     * The maximum delta time in seconds, above which the trail resets.
     * This prevents the trail from jumping around when updates happen
     * infrequently (e.g. when the tab doesn't have focus).
     */
    resetThreshold = 0.5;
    _currentPointIndex = 0;
    _timeTillNext = 0;
    _points = [];
    _trailContainer = null;
    _meshComp = null;
    _mesh = null;
    _indexData = null;
    start() {
        this._points = new Array(this.segments + 1);
        for (let i = 0; i < this._points.length; ++i) {
            this._points[i] = vec3.create();
        }
        /* The points array is circular, so keep track of its head */
        this._timeTillNext = this.interval;
        this._trailContainer = this.engine.scene.addObject();
        /* Workaround for new objects sometimes not being set dirty before 1.4.6 */
        this._trailContainer.setDirty();
        this._meshComp = this._trailContainer.addComponent('mesh');
        this._meshComp.material = this.material;
        /* Each point will have two vertices; one on either side */
        const vertexCount = 2 * this._points.length;
        /* Each segment consists of two triangles */
        this._indexData = new Uint32Array(6 * this.segments);
        for (let i = 0, v = 0; i < vertexCount - 2; i += 2, v += 6) {
            this._indexData
                .subarray(v, v + 6)
                .set([i + 1, i + 0, i + 2, i + 2, i + 3, i + 1]);
        }
        this._mesh = new Mesh(this.engine, {
            vertexCount: vertexCount,
            indexData: this._indexData,
            indexType: MeshIndexType.UnsignedInt,
        });
        this._meshComp.mesh = this._mesh;
    }
    updateVertices() {
        if (!this._mesh)
            return;
        const positions = this._mesh.attribute(MeshAttribute.Position);
        const texCoords = this._mesh.attribute(MeshAttribute.TextureCoordinate);
        const normals = this._mesh.attribute(MeshAttribute.Normal);
        vec3.set(direction, 0, 0, 0);
        for (let i = 0; i < this._points.length; ++i) {
            const curr = this._points[(this._currentPointIndex + i + 1) % this._points.length];
            const next = this._points[(this._currentPointIndex + i + 2) % this._points.length];
            /* The last point has no next, so re-use the direction of the previous segment */
            if (i !== this._points.length - 1) {
                vec3.sub(direction, next, curr);
            }
            vec3.cross(offset, UP, direction);
            vec3.normalize(offset, offset);
            const timeFraction = 1.0 - this._timeTillNext / this.interval;
            const fraction = (i - timeFraction) / this.segments;
            vec3.scale(offset, offset, ((this.taper ? fraction : 1.0) * this.width) / 2.0);
            positions.set(i * 2, [
                curr[0] - offset[0],
                curr[1] - offset[1],
                curr[2] - offset[2],
            ]);
            positions.set(i * 2 + 1, [
                curr[0] + offset[0],
                curr[1] + offset[1],
                curr[2] + offset[2],
            ]);
            if (normals) {
                vec3.cross(normal, direction, offset);
                vec3.normalize(normal, normal);
                normals.set(i * 2, normal);
                normals.set(i * 2 + 1, normal);
            }
            if (texCoords) {
                texCoords.set(i * 2, [0, fraction]);
                texCoords.set(i * 2 + 1, [1, fraction]);
            }
        }
        /* Notify WLE that the mesh has changed */
        this._mesh.update();
    }
    resetTrail() {
        this.object.getPositionWorld(this._points[0]);
        for (let i = 1; i < this._points.length; ++i) {
            vec3.copy(this._points[i], this._points[0]);
        }
        this._currentPointIndex = 0;
        this._timeTillNext = this.interval;
    }
    update(dt) {
        this._timeTillNext -= dt;
        if (dt > this.resetThreshold) {
            this.resetTrail();
        }
        if (this._timeTillNext < 0) {
            this._currentPointIndex = (this._currentPointIndex + 1) % this._points.length;
            this._timeTillNext = (this._timeTillNext % this.interval) + this.interval;
        }
        this.object.getPositionWorld(this._points[this._currentPointIndex]);
        this.updateVertices();
    }
    onActivate() {
        this.resetTrail();
        if (this._meshComp)
            this._meshComp.active = true;
    }
    onDeactivate() {
        if (this._meshComp)
            this._meshComp.active = false;
    }
    onDestroy() {
        if (this._trailContainer)
            this._trailContainer.destroy();
        if (this._mesh)
            this._mesh.destroy();
    }
}
__decorate([
    property.material()
], Trail.prototype, "material", void 0);
__decorate([
    property.int(50)
], Trail.prototype, "segments", void 0);
__decorate([
    property.float(0.1)
], Trail.prototype, "interval", void 0);
__decorate([
    property.float(1.0)
], Trail.prototype, "width", void 0);
__decorate([
    property.bool(true)
], Trail.prototype, "taper", void 0);
__decorate([
    property.float(1.0)
], Trail.prototype, "resetThreshold", void 0);
export { Trail };
//# sourceMappingURL=trail.js.map