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 { Ray } from "three";
import { Gizmos } from "../../engine/engine_gizmos.js";
import { serializable } from "../../engine/engine_serialization_decorator.js";
import { getTempVector } from "../../engine/engine_three_utils.js";
import { getParam } from "../../engine/engine_utils.js";
import { Behaviour } from "../Component.js";
const debug = getParam("debugcursor");
/**
 * The CursorFollow component makes the object follow the cursor (or touch) position on screen.
 *
 * - Example: [Look At Cursor sample](https://engine.needle.tools/samples/look-at-cursor-interactive-3d-header/). This sample combines the CursorFollow component with a LookAt component to create an interactive 3D header that looks at the cursor.
 *
 * @summary Makes the object follow the cursor position on screen
 * @category Web
 * @group Components
 * @component
 */
export class CursorFollow extends Behaviour {
    // testing this for compilation
    static NAME = "CursorFollow";
    /**
     * Damping for the movement, set to 0 for instant movement
     * @default 0
     */
    damping = 0;
    /**
     * When enabled the object will follow the cursor even outside of the needle-engine canvas. This is useful for example for look at effects where you have a small needle-engine element on your page and you want the 3D object to keep looking at the cursor even when it's outside of the canvas.
     * @default true
     */
    useFullPage = true;
    /**
     * If true, the initial distance to the camera is maintained when following the cursor.
     * @default true
     */
    keepDistance = true;
    /**
     * If true, the object will attempt to snap to the surface of other objects in the scene using a raycast.
     */
    snapToSurface = false;
    _distance = -1;
    updateDistance(force = false) {
        if (!force && (this.keepDistance && this._distance !== -1)) {
            return;
        }
        this._distance = this.gameObject.worldPosition.distanceTo(this.context.mainCamera.worldPosition);
    }
    /** @internal */
    awake() {
        this._distance = -1;
    }
    /** @internal */
    onEnable() {
        this._distance = -1;
        window.addEventListener('pointermove', this._onPointerMove);
    }
    /** @internal */
    onDisable() {
        window.removeEventListener('pointermove', this._onPointerMove);
    }
    _ndc_x = 0;
    _ndc_y = 0;
    _onPointerMove = (e) => {
        if (!this.useFullPage)
            return;
        const x = e.clientX;
        const y = e.clientY;
        const domx = this.context.domX;
        const domy = this.context.domY;
        const domw = this.context.domWidth;
        const domh = this.context.domHeight;
        this._ndc_x = (x - domx) / domw * 2 - 1;
        this._ndc_y = -(y - domy) / domh * 2 + 1;
    };
    /** @internal */
    lateUpdate() {
        // continuously update distance in case camera or object moves
        this.updateDistance();
        const x = this.useFullPage ? this._ndc_x : this.context.input.mousePositionRC.x;
        const y = this.useFullPage ? this._ndc_y : this.context.input.mousePositionRC.y;
        // follow cursor in screenspace but maintain initial distance from camera
        const camera = this.context.mainCamera;
        const cameraPosition = camera.worldPosition;
        // create ray from camera through cursor position
        const rayDirection = getTempVector(x, y, 1).unproject(camera);
        rayDirection.sub(cameraPosition).normalize();
        // position object at initial distance along the ray
        const newPosition = getTempVector(rayDirection).multiplyScalar(this._distance).add(cameraPosition);
        let _position = newPosition;
        if (this.damping > 0) {
            const pos = this.gameObject.worldPosition;
            pos.lerp(newPosition, this.context.time.deltaTime / this.damping);
            this.gameObject.worldPosition = pos;
            _position = pos;
        }
        else {
            this.gameObject.worldPosition = newPosition;
        }
        if (this.snapToSurface) {
            ray.origin = _position;
            ray.direction = rayDirection.multiplyScalar(-1);
            const hits = this.context.physics.raycastFromRay(ray);
            if (hits?.length) {
                const hit = hits[0];
                if (this.damping > 0) {
                    this.gameObject.worldPosition = _position.lerp(hit.point, this.context.time.deltaTime / this.damping);
                }
                else {
                    this.gameObject.worldPosition = hit.point;
                }
                if (debug) {
                    Gizmos.DrawLine(hit.point, hit.normal.add(hit.point), 0x00FF00);
                }
            }
        }
    }
}
__decorate([
    serializable()
], CursorFollow.prototype, "damping", void 0);
__decorate([
    serializable()
], CursorFollow.prototype, "useFullPage", void 0);
__decorate([
    serializable()
], CursorFollow.prototype, "keepDistance", void 0);
__decorate([
    serializable()
], CursorFollow.prototype, "snapToSurface", void 0);
const ray = new Ray();
//# sourceMappingURL=CursorFollow.js.map