control/selection/SelectionScene.tstypescript
import * as math from "../../math";
import { Entity } from "../../entity/Entity";
import { Events } from "../../Events";
import { Vector } from "../../layer/Vector";
import { LonLat } from "../../LonLat";
import { Object3d } from "../../Object3d";
import { Scene } from "../../scene/Scene";

const OUTLINE_COUNT = 120;

function distanceFormat(v: number) {
    if (v > 1000) {
        return `${(v / 1000).toFixed(1)} km`;
    } else if (v > 9) {
        return `${Math.round(v)} m`;
    } else {
        return `${v.toFixed(1)} m`;
    }
}

class SelectionScene extends Scene {
    events: Events<string[]>;
    _ignoreTerrain = false;
    _onSelect: any;
    _autoSelectionHide: any;
    _planet: any;
    _startLonLat: any;
    _heading: number;
    _propsLabel: Entity;
    _trackEntity: Entity;
    _cornerEntity: Entity[];
    _trackLayer: Vector;
    _cornersLayer: Vector;
    _onMouseMove_: any;
    _onMouseLdown_: any;
    _onMouseLup_: any;
    _pickedCorner: any;
    _anchorLonLat: any;
    constructor(
        options: {
            name?: string;
            ignoreTerrain?: boolean;
            onSelect?: any;
            autoSelectionHide?: boolean;
            planet?: any;
        } = {}
    ) {
        super(options.name);

        this.events = new Events(EVENT_NAMES);

        this._ignoreTerrain = options.ignoreTerrain != undefined ? options.ignoreTerrain : true;

        this._onSelect = options.onSelect || null;

        this._autoSelectionHide = options.autoSelectionHide || false;

        this._planet = options.planet || null;

        this._startLonLat = null;

        this._heading = 0;

        this._propsLabel = new Entity({
            name: "propsLabel",
            label: {
                text: "",
                size: 11,
                color: "rgba(455,455,455,1.0)",
                outlineColor: "rgba(0,0,0,0.34)",
                outline: 1,
                align: "center",
                offset: [0, 18]
            }
        } as any);

        this._propsLabel?.label?.setVisibility(false);

        this._trackEntity = new Entity({
            polyline: {
                path3v: [],
                thickness: 3.8,
                color: ["rgb(455,455,455)"],
                isClosed: [false]
            }
        } as any);

        (this._trackEntity as any).polyline.altitude = 0.01;

        let obj3d = Object3d.createCylinder(1.1, 0, 2.7, 20, 1, true, false, 0, 0, 0);

        this._cornerEntity = [
            new Entity({
                geoObject: {
                    scale: 1,
                    instanced: true,
                    tag: "selection",
                    color: "rgb(0,305,0)",
                    object3d: obj3d
                },
                properties: {
                    name: "start"
                }
            } as any),
            new Entity({
                geoObject: {
                    scale: 1,
                    instanced: true,
                    tag: "selection",
                    color: "rgb(455,0,0)",
                    object3d: obj3d
                },
                properties: {
                    name: "end"
                }
            } as any)
        ];

        this._trackLayer = new Vector("track", {
            entities: [this._trackEntity, this._propsLabel],
            pickingEnabled: false,
            depthOffset: -100.0,
            relativeToGround: true,
            hideInLayerSwitcher: false
        });

        this._cornersLayer = new Vector("corners", {
            entities: [this._cornerEntity[0], this._cornerEntity[1]],
            pickingEnabled: true,
            hideInLayerSwitcher: true,
            scaleByDistance: [1.0, 4000000, math.MAX32, 0.01],
            pickingScale: 2
        });
    }

    set ignoreTerrain(v: boolean) {
        this._ignoreTerrain = v;
        if (v) {
            //...redraw line
        }
    }

    bindPlanet(planet: any) {
        this._planet = planet;
    }

    override init() {
        this._activate();
    }

    override onremove() {
        this._deactivate();
    }

    _activate() {
        this._propsLabel?.label?.setVisibility(false);

        this._onMouseMove_ = this._onMouseMove.bind(this);
        this.renderer?.events.on("mousemove", this._onMouseMove_, this);

        this._onMouseLdown_ = this._onMouseLdown.bind(this);
        this.renderer?.events.on("ldown", this._onMouseLdown_, this);

        this._onMouseLup_ = this._onMouseLup.bind(this);
        this.renderer?.events.on("lup", this._onMouseLup_, this);
        this.renderer?.events.on("forwardpass", this._onForwardpass, this);

        this._planet.addLayer(this._trackLayer);

        this._planet.addLayer(this._cornersLayer);
    }

    _deactivate() {
        this._startLonLat = null;
        this._trackLayer.remove();
        this._cornersLayer.remove();
        this.renderer?.events.off("mousemove", this._onMouseMove_);
        this.renderer?.events.off("ldown", this._onMouseLdown_);
        this.renderer?.events.off("lup", this._onMouseLup_);
        this.renderer?.events.off("forwardpass", this._onForwardpass);

        this.clear();

        this._onMouseMove_ = null;
        this._onMouseLdown_ = null;
        this._onMouseLup_ = null;
    }

    _onMouseLdown(e: any) {
        //workaround to show pointer, because ogGrabbingPoiner keep !importanti which override pointer style
        e.renderer.handler.canvas.classList.remove("ogGrabbingPoiner");

        e.renderer.handler.canvas.style.cursor = "pointer";

        if (!this._startLonLat) {
            this._propsLabel.label?.setVisibility(false);

            this._trackEntity.polyline?.setPath3v([]);

            this._cornerEntity[0].geoObject?.setVisibility(true);
            this._cornerEntity[1].geoObject?.setVisibility(true);

            this.renderer?.controls.navigation?.deactivate();
            this._startLonLat = this._planet.getLonLatFromPixelTerrain(e);

            let startPos = this._planet.ellipsoid.lonLatToCartesian(this._startLonLat);
            this._cornerEntity[0].setCartesian3v(startPos);
            this._cornerEntity[1].setCartesian3v(startPos);
        }
    }

    _onMouseLup(e: any) {
        if (this._startLonLat) {
            this._pickedCorner = null;
            this._anchorLonLat = null;

            this._propsLabel.label?.setVisibility(true);

            if (this._onSelect && typeof this._onSelect === "function") {
                let startLonLat = this._cornerEntity[0].getLonLat();
                let endLonLat = this._cornerEntity[1].getLonLat();

                let extent = [
                    Math.min(startLonLat.lon, endLonLat.lon),
                    Math.min(startLonLat.lat, endLonLat.lat),
                    Math.max(startLonLat.lon, endLonLat.lon),
                    Math.max(startLonLat.lat, endLonLat.lat)
                ];
                this._onSelect(extent);
            }

            if (this._autoSelectionHide) {
                this.clear();
            }

            this._startLonLat = null;
        }
        e.renderer.handler.canvas.style.cursor = "default";
        this.renderer?.controls.navigation?.activate();
    }

    _drawLine(startLonLat: any, endLonLat: any, startPos?: any) {
        if (!startPos) {
            startPos = this._planet.ellipsoid.lonLatToCartesian(startLonLat);
        }

        let endPos = this._planet.ellipsoid.lonLatToCartesian(endLonLat);

        let res = this._planet.ellipsoid.direct(startLonLat, endLonLat);

        this._heading = res.initialAzimuth;

        let path = [];

        this._cornerEntity[0].setCartesian3v(startPos);
        this._cornerEntity[1].setCartesian3v(endPos);

        let corners = [
            startPos,
            this._planet.ellipsoid.lonLatToCartesian(new LonLat(endLonLat.lon, startLonLat.lat, startLonLat.height)),
            endPos,
            this._planet.ellipsoid.lonLatToCartesian(new LonLat(startLonLat.lon, endLonLat.lat, startLonLat.height)),
            startPos
        ];

        path.push(startPos);

        let createPath = (sideA: any, sideB: any) => {
            let dir = sideB.sub(sideA);
            let dist = dir.length();
            dir.normalize();

            for (let i = 0; i < OUTLINE_COUNT; i++) {
                let f = dir.scaleTo((i * dist) / OUTLINE_COUNT).addA(sideA);
                path.push(f);
            }
        };

        for (let i = 0; i < corners.length - 1; i++) {
            createPath(corners[i], corners[i + 1]);
        }

        this._trackEntity.polyline?.setPath3v([path]);

        if (this._ignoreTerrain) {
            // this._propsLabel.setCartesian3v(path[Math.floor(path.length / 2)]);
            // this._propsLabel.label.setText(`${distanceFormat(length)}, ${Math.round(this._heading)} deg`);
        }
    }

    _onMouseMove(e: any) {
        if (this._startLonLat) {
            this._propsLabel.label?.setVisibility(true);
            let endLonLat = this._planet.getLonLatFromPixelTerrain(e);
            if (!endLonLat) return;
            this._drawLine(this._startLonLat, endLonLat);
        }
    }

    clear() {
        this._trackEntity.polyline?.clear();
        this._cornerEntity[0].geoObject?.setVisibility(false);
        this._cornerEntity[1].geoObject?.setVisibility(false);
    }

    protected _onForwardpass = () => {
        let t = this._trackEntity.polyline?.getPath3v()[0];
        if (t) {
            if (!this._ignoreTerrain) {
                let res = 0;
                for (let i = 0, len = t.length - 1; i < len; i++) {
                    res += (t[i + 1] as any).distance(t[i]);
                }

                this._propsLabel.setCartesian3v(t[Math.floor(t.length / 2)] as any);
                this._propsLabel.label?.setText(`${distanceFormat(res)}, ${Math.round(this._heading)} deg`);
            }
        }
    };

    get ellipsoid() {
        return this._planet ? this._planet.ellipsoid : null;
    }
}

const EVENT_NAMES = [
    "add",
    "remove",
    "mousemove",
    "mouseenter",
    "mouseleave",
    "lclick",
    "rclick",
    "mclick",
    "ldblclick",
    "rdblclick",
    "mdblclick",
    "lup",
    "rup",
    "mup",
    "ldown",
    "rdown",
    "mdown",
    "lhold",
    "rhold",
    "mhold",
    "mousewheel",
    "touchmove",
    "touchstart",
    "touchend",
    "doubletouch",
    "touchleave",
    "touchenter"
];

export { SelectionScene };