entity_Entity.ts

import * as mercator from "../mercator";
import * as utils from "../utils/shared";
import {Billboard} from "./Billboard";
import {EntityCollection} from "./EntityCollection";
import type {IBillboardParams} from "./Billboard";
import type {EntityCollectionEvents} from "./EntityCollection";
import {Extent} from "../Extent";
import {Geometry} from "./Geometry";
import {GeoObject} from "./GeoObject";
import type {IGeometryParams} from "./Geometry";
import type {IGeoObjectParams} from "./GeoObject";
import {LonLat} from "../LonLat";
import {Label} from "./Label";
import type {ILabelParams} from "./Label";
import {Vec3} from "../math/Vec3";
import type {NumberArray3} from "../math/Vec3";
import type {NumberArray2} from "../math/Vec2";
import {Planet} from "../scene/Planet";
import {PointCloud} from "./PointCloud";
import {Polyline} from "./Polyline";
import type {IPointCloudParams} from "./PointCloud";
import type {IPolylineParams} from "./Polyline";
import {Ray} from "./Ray";
import type {IRayParams} from "./Ray";
import {Strip} from "./Strip";
import type {IStripParams} from "./Strip";
import {Vector} from "../layer/Vector";
import type {VectorEventsType} from "../layer/Vector";
import {EntityCollectionNode} from "../quadTree/EntityCollectionNode";
import {Quat} from "../math/Quat";
import {clamp} from "../math";

/**
 * Interface for Entity parameters.
 * @typedef {Object} IEntityParams
 * @property {string} [name] - Name of the entity.
 * @property {any} [properties] - Additional properties of the entity.
 * @property {Vec3 | NumberArray3} [cartesian] - Cartesian position.
 * @property {LonLat | NumberArray3 | NumberArray2} [lonlat] - Geographic coordinates.
 * @property {number} [altitude] - Altitude.
 * @property {boolean} [visibility] - Visibility flag.
 * @property {Billboard | IBillboardParams} [billboard] - Billboard object or parameters.
 * @property {Label | ILabelParams} [label] - Label object or parameters.
 * @property {Polyline | IPolylineParams} [polyline] - Polyline object or parameters.
 * @property {Ray | IRayParams} [ray] - Ray object or parameters.
 * @property {PointCloud | IPointCloudParams} [pointCloud] - Point cloud object or parameters.
 * @property {Geometry | IGeometryParams} [geometry] - Geometry object or parameters.
 * @property {GeoObject | IGeoObjectParams} [geoObject] - Geo object or parameters.
 * @property {Strip | IStripParams} [strip] - Strip object or parameters.
 * @property {boolean} [independentPicking] - Independent picking flag.
 * @property {boolean} [relativePosition] - Parent relative position flag, otherwise position is absolute.
 * @property {number} [pitch] - Rotation around local X-axis.
 * @property {number} [yaw] - Rotation around local Y-axis.
 * @property {number} [roll] - Rotation around local Z-axis.
 * @property {number | Vec3 | NumberArray3} [scale] - Scaling factor.
 * @property {boolean} [forceGlobalPosition] - Forces global position for entity make the same position as its parent.
 * @property {boolean} [forceGlobalRotation] - Forces global rotation for the entity make the same rotation as its parent.
 */
export interface IEntityParams {
    name?: string;
    properties?: any;
    cartesian?: Vec3 | NumberArray3;
    lonlat?: LonLat | NumberArray3 | NumberArray2;
    altitude?: number;
    visibility?: boolean;
    billboard?: Billboard | IBillboardParams;
    label?: Label | ILabelParams;
    polyline?: Polyline | IPolylineParams;
    ray?: Ray | IRayParams;
    pointCloud?: PointCloud | IPointCloudParams;
    geometry?: Geometry | IGeometryParams;
    geoObject?: GeoObject | IGeoObjectParams;
    strip?: Strip | IStripParams;
    independentPicking?: boolean;
    relativePosition?: boolean;
    pitch?: number;
    yaw?: number;
    roll?: number;
    scale?: number | Vec3 | NumberArray3;
    forceGlobalPosition?: boolean;
    forceGlobalRotation?: boolean;
    forceGlobalScale?: boolean;
    localPosition?: Vec3 | NumberArray3;
}

/**
 * Entity instances aggregate multiple forms of visualization into a single high-level object.
 * They can be created manually and added to entity collection.
 *
 * @class
 * @param {IEntityParams} [options] - Entity options:
 * @param {string} [options.name] - Name of the entity.
 * @param {any} [options.properties] - Additional properties of the entity.
 * @param {Vec3 | NumberArray3} [options.cartesian] - Cartesian position.
 * @param {LonLat | NumberArray3 | NumberArray2} [options.lonlat] - Geographic coordinates.
 * @param {number} [options.altitude] - Altitude.
 * @param {boolean} [options.visibility] - Visibility flag.
 * @param {Billboard | IBillboardParams} [options.billboard] - Billboard object or parameters.
 * @param {Label | ILabelParams} [options.label] - Label object or parameters.
 * @param {Polyline | IPolylineParams} [options.polyline] - Polyline object or parameters.
 * @param {Ray | IRayParams} [options.ray] - Ray object or parameters.
 * @param {PointCloud | IPointCloudParams} [options.pointCloud] - Point cloud object or parameters.
 * @param {Geometry | IGeometryParams} [options.geometry] - Geometry object or parameters.
 * @param {GeoObject | IGeoObjectParams} [options.geoObject] - Geo object or parameters.
 * @param {Strip | IStripParams} [options.strip] - Strip object or parameters.
 * @param {boolean} [options.independentPicking] - Independent picking flag.
 * @param {boolean} [options.relativePosition] - Parent relative position flag, otherwise position is absolute.
 * @param {number} [options.pitch] - Rotation around local X-axis in radians.
 * @param {number} [options.yaw] - Rotation around local Y-axis in radians.
 * @param {number} [options.roll] - Rotation around local Z-axis in radians.
 * @param {number | Vec3 | NumberArray3} [options.scale] - Scaling factor.
 * @param {boolean} [options.forceGlobalPosition] - Forces global position for the entity make the same position as its parent.
 * @param {boolean} [options.forceGlobalRotation] - Forces global rotation for the entity make the same rotation as its parent.
 * @param {boolean} [options.forceGlobalScale] - Forces global scale for the entity make the same scale as its parent.
 */
class Entity {

    static __counter__: number = 0;

    /**
     * Uniq identifier.
     * @public
     * @readonly
     */
    protected __id: number;

    /**
     * Entity user defined properties.
     * @public
     * @type {Object}
     */
    public properties: any;


    /**
     * Children entities.
     * @public
     * @type {Array.<Entity>}
     */
    public childEntities: Entity[];

    public forceGlobalPosition: boolean;

    public forceGlobalRotation: boolean;

    public forceGlobalScale: boolean;

    /**
     * Parent entity.
     * @public
     * @type {Entity}
     */
    public parent: Entity | null;

    /**
     * Entity cartesian position.
     * @protected
     * @type {Vec3}
     */
    public _cartesian: Vec3;

    /**
     * Entity cartesian is equal root entity absolute cartesian.
     * @protected
     * @type {Vec3}
     */
    protected _rootCartesian: Vec3;

    protected _localPosition: Vec3;
    protected _absoluteLocalPosition: Vec3;

    /**
     * Geodetic entity coordinates.
     * @public
     * @type {LonLat}
     */
    public _lonLat: LonLat;

    /**
     * World Mercator entity coordinates.
     * @public
     * @type {LonLat}
     */
    public _lonLatMerc: LonLat;

    /**
     * Entity visible terrain altitude.
     * @public
     * @type {number}
     */
    public _altitude: number;

    /**
     * Visibility flag.
     * @protected
     * @type {boolean}
     */
    protected _visibility: boolean;

    /**
     * Entity collection that this entity belongs to.
     * @public
     * @type {EntityCollection}
     */
    public _entityCollection: EntityCollection | null;

    /**
     * Entity collection array store index.
     * @public
     * @type {number}
     */
    public _entityCollectionIndex: number;

    /**
     * Assigned vector layer pointer.
     * @public
     * @type {Vector}
     */
    public _layer: Vector | null;

    /**
     * Assigned vector layer entity array index.
     * @public
     * @type {number}
     */
    public _layerIndex: number;

    /**
     * Picking color.
     * @public
     * @type {Vec3}
     */
    public _pickingColor: Vec3;

    public _independentPicking: boolean;

    protected _featureConstructorArray: Record<string, [any, Function]>;

    /**
     * Billboard entity.
     * @public
     * @type {Billboard | null}
     */
    public billboard: Billboard | null;

    /**
     * Text label entity.
     * @public
     * @type {Label | null}
     */
    public label: Label | null;

    /**
     * Polyline entity.
     * @public
     * @type {Polyline | null}
     */
    public polyline: Polyline | null;

    /**
     * Ray entity.
     * @public
     * @type {Ray | null}
     */
    public ray: Ray | null;

    /**
     * PointCloud entity.
     * @public
     * @type {PointCloud | null}
     */
    public pointCloud: PointCloud | null;

    /**
     * Geometry entity(available for vector layer only).
     * @public
     * @type {Geometry | null}
     */
    public geometry: Geometry | null;

    /**
     * Geo object entity
     * @public
     * @type {Geometry | null}
     */
    public geoObject: GeoObject | null;

    /**
     * Strip entity.
     * @public
     * @type {Strip | null}
     */
    public strip: Strip | null;

    public _nodePtr?: EntityCollectionNode;

    protected _relativePosition: boolean;
    protected _pitchRad: number;
    protected _yawRad: number;
    protected _rollRad: number;
    protected _scale: Vec3;
    protected _qFrame: Quat;
    protected _qRot: Quat;
    public _absoluteQRot: Quat;

    constructor(options: IEntityParams = {}) {

        options.properties = options.properties || {};

        this.__id = Entity.__counter__++;

        this.properties = options.properties || {};

        this.properties.name = this.properties.name != undefined ? this.properties.name : "";

        this.childEntities = [];

        this.parent = null;

        this.forceGlobalPosition = options.forceGlobalPosition || false;

        this.forceGlobalRotation = options.forceGlobalRotation || false;

        this.forceGlobalScale = options.forceGlobalScale || false;

        this._cartesian = utils.createVector3(options.cartesian);

        this._rootCartesian = new Vec3();

        this._localPosition = utils.createVector3(options.localPosition);
        this._absoluteLocalPosition = new Vec3();

        this._lonLat = utils.createLonLat(options.lonlat);

        this._lonLatMerc = new LonLat();

        this._altitude = options.altitude || 0.0;

        this._visibility = options.visibility != undefined ? options.visibility : true;

        this._entityCollection = null;

        this._entityCollectionIndex = -1;

        this._layer = null;

        this._layerIndex = -1;

        this._pickingColor = new Vec3(0, 0, 0);

        this._independentPicking = options.independentPicking || false;

        this._relativePosition = options.relativePosition || false;

        this._pitchRad = options.pitch || 0;
        this._yawRad = options.yaw || 0;
        this._rollRad = options.roll || 0;

        this._scale = utils.createVector3(options.scale, new Vec3(1, 1, 1));

        this._qFrame = Quat.IDENTITY;
        this._qRot = Quat.IDENTITY;
        this._absoluteQRot = Quat.IDENTITY;

        this._featureConstructorArray = {
            billboard: [Billboard, this.setBillboard],
            label: [Label, this.setLabel],
            polyline: [Polyline, this.setPolyline],
            pointCloud: [PointCloud, this.setPointCloud],
            geometry: [Geometry, this.setGeometry],
            geoObject: [GeoObject, this.setGeoObject],
            strip: [Strip, this.setStrip],
            ray: [Ray, this.setRay]
        };

        this.billboard = this._createOptionFeature<Billboard, IBillboardParams>("billboard", options.billboard);

        this.label = this._createOptionFeature<Label, ILabelParams>("label", options.label);

        this.polyline = this._createOptionFeature<Polyline, IPolylineParams>("polyline", options.polyline);

        this.ray = this._createOptionFeature<Ray, IRayParams>("ray", options.ray);

        this.pointCloud = this._createOptionFeature<PointCloud, IPolylineParams>("pointCloud", options.pointCloud);

        this.geometry = this._createOptionFeature<Geometry, IGeometryParams>("geometry", options.geometry);

        this.geoObject = this._createOptionFeature<GeoObject, IGeoObjectParams>("geoObject", options.geoObject);

        this.strip = this._createOptionFeature<Strip, IStripParams>("strip", options.strip);
    }

    /**
     * Returns root entity object.
     * @public
     * @return {Entity}
     */
    public get rootEntity(): Entity {
        let pn: Entity | null = this;
        while (pn) {
            if (!pn.parent) {
                return pn;
            }
            pn = pn.parent;
        }
        return this;
    }

    /**
     * Sets relative position property
     * @param isRelative
     */
    public set relativePosition(isRelative: boolean) {

        if (isRelative !== this._relativePosition) {

            let cart = this.getAbsoluteCartesian(),
                pitch = this.getAbsolutePitch(),
                yaw = this.getAbsoluteYaw(),
                roll = this.getAbsoluteRoll();

            this._relativePosition = isRelative;

            // probably need to take root this.rootEntity
            if (this.parent) {
                this._rootCartesian.copy(this.parent._rootCartesian);
            }

            if (!isRelative) {
                this.setCartesian3v(cart);
                this.setPitch(pitch);
                this.setYaw(yaw);
                this.setRoll(roll);
            } else if (this.parent) {
                this.setAbsoluteCartesian3v(cart);
                this.setAbsolutePitch(pitch);
                this.setAbsoluteYaw(yaw);
                this.setAbsoluteRoll(roll);
            }
        }
    }

    /**
     * Gets relative position property
     * @public
     * @returns{boolean}
     */
    public get relativePosition(): boolean {
        return this._relativePosition;
    }

    /**
     * Gets current entity collection container.
     * @public
     * @returns {EntityCollection | null}
     */
    public get entityCollection(): EntityCollection | null {
        return this._entityCollection;
    }

    /**
     * Gets entity uniq id
     * @public
     * @returns {number}
     */
    public get id(): number {
        return this.__id;
    }

    /**
     * Checks if the given entity is equal to the current entity.
     * @param {Entity} entity - The entity to compare.
     * @returns {boolean} True if entities are equal, otherwise false.
     */
    public isEqual(entity: Entity): boolean {
        return this.__id === entity.__id;
    }

    /**
     * Gets the layer index of the entity.
     * @returns {number} The layer index.
     */
    public get layerIndex(): number {
        return this._layerIndex;
    }

    /**
     * Gets the instance class name of the entity.
     * @returns {string} The instance name "Entity".
     */
    public get instanceName(): string {
        return "Entity";
    }

    protected _createOptionFeature<T, K>(
        featureName: string,
        options?: T | K
    ): T | null {
        if (options) {
            let c = this._featureConstructorArray[featureName];
            return c[1].call(this, new c[0](options)) as T;
        }
        return null;
    }

    /**
     * Gets the collection index of the entity.
     * @returns {number} The entity collection index.
     */
    public getCollectionIndex(): number {
        return this._entityCollectionIndex;
    }

    /**
     * Adds current entity into the specified entity collection.
     * @public
     * @param {EntityCollection | Vector} collection - Specified entity collection or vector layer.
     * @param {boolean} [rightNow=false] - Entity insertion option for vector layer.
     * @returns {Entity} - This object.
     */
    public addTo(collection: EntityCollection | Vector, rightNow: boolean = false): Entity {
        collection.add(this, rightNow);
        return this;
    }

    /**
     * Removes current entity from its collection or layer.
     * @public
     */
    public remove() {
        this._layer && this._layer.removeEntity(this);
        this._entityCollection && this._entityCollection.removeEntity(this);
    }

    /**
     * Sets the entity visibility.
     * @public
     * @param {boolean} visibility - Entity visibility.
     */
    public setVisibility(visibility: boolean) {
        this._visibility = visibility;

        // billboards
        this.billboard && this.billboard.setVisibility(visibility);

        // geoObject
        this.geoObject && this.geoObject.setVisibility(visibility);

        // labels
        this.label && this.label.setVisibility(visibility);

        // polyline
        this.polyline && this.polyline.setVisibility(visibility);

        // ray
        this.ray && this.ray.setVisibility(visibility);

        // geometry
        this.geometry && this.geometry.setVisibility(visibility);

        for (let i = 0; i < this.childEntities.length; i++) {
            this.childEntities[i].setVisibility(visibility);
        }
    }

    /**
     * Returns entity visibility.
     * @public
     * @returns {boolean} -
     */
    public getVisibility() {
        return this._visibility;
    }

    /**
     * Sets entity cartesian position.
     * @public
     * @param {Vec3} cartesian - Cartesian position in 3d space.
     */
    public setCartesian3v(cartesian: Vec3) {
        this.setCartesian(cartesian.x, cartesian.y, cartesian.z);
    }

    /**
     * Gets scale factor
     * @public
     * @returns {Vec3}
     */
    public getScale(): Vec3 {
        return this._scale;
    }

    /**
     * Sets XYZ axis scale for the inner object such as GeoObject
     * @public
     * @param {Vec3} scale - Scale factor
     */
    public setScale3v(scale: Vec3) {

        this._scale.copy(scale);

        this.geoObject && this.geoObject.setScale3v(this._scale);

        for (let i = 0; i < this.childEntities.length; i++) {
            let chi = this.childEntities[i];
            if (chi.forceGlobalScale) {
                chi.setScale3v(this._scale);
            } else {
                chi.setScale3v(this.childEntities[i].getScale());
            }
        }
    }

    /**
     * Sets scale for the inner object such as GeoObject
     * @public
     * @param {number} val - Scale factor
     */
    public setScale(val: number) {

        this._scale.set(val, val, val);

        this.geoObject && this.geoObject.setScale(val);

        for (let i = 0; i < this.childEntities.length; i++) {
            let chi = this.childEntities[i];
            if (chi.forceGlobalScale) {
                chi.setScale(val);
            } else {
                chi.setScale3v(this.childEntities[i].getScale());
            }
        }
    }

    /**
     * Gets the absolute rotation direction of the entity.
     * @public
     * @returns {Quat} The absolute rotation quaternion.
     */
    public getAbsoluteRotation(): Quat {
        return this._absoluteQRot.clone();
    }

    /**
     * Gets the local rotation of the entity. For the root entity it is equal to the absolute rotation.
     * @public
     * @returns {Quat} The rotation quaternion.
     */
    public getRotation(): Quat {
        return this._qRot;
    }

    /**
     * Rotates the entity to look at a given point in world coordinates.
     * @public
     * @param {Vec3} cart - The target position to look at.
     */
    public setLook3v(cart: Vec3) {
        let lq = new Quat();
        let p0 = this.getAbsoluteCartesian();
        let rot;
        if (this._entityCollection) {
            let up = (this._entityCollection.renderNode as Planet).ellipsoid.getSurfaceNormal3v(p0);
            rot = lq.setLookRotation(cart.sub(p0), up).conjugate();

        } else {
            rot = lq.setLookRotation(cart.sub(p0), Vec3.UP).conjugate();
        }
        this.setAbsoluteRotation(rot);
    }

    /**
     * Rotates the entity to look at a given geographic coordinate.
     * @public
     * @param {LonLat} lonLat - The longitude and latitude to look at.
     */
    public setLookLonLat(lonLat: LonLat) {
        if (this._entityCollection) {
            let cart = (this._entityCollection.renderNode as Planet).ellipsoid.lonLatToCartesian(lonLat);
            this.setLook3v(cart);
        }
    }

    /**
     * Sets the absolute rotation of the entity.
     * @public
     * @param {Quat} rot - The new absolute rotation quaternion.
     */
    public setAbsoluteRotation(rot: Quat) {
        this._absoluteQRot.copy(rot);
        this._updatePitchYawRoll();
    }

    /**
     * Sets the local rotation of the entity.
     * @param {Quat} rot - The new rotation quaternion.
     */
    public setRotation(rot: Quat) {
        //this._qRot.copy(rot);
        //
        //@todo when necessary
        //
    }

    /**
     * Sets the pitch rotation of the entity.
     * @param {number} val - The new pitch angle in radians.
     */
    public setPitch(val: number) {
        this._pitchRad = val;
        this._updateAbsolutePosition();
    }

    /**
     * Sets the yaw rotation of the entity.
     * @param {number} val - The new yaw angle in radians.
     */
    public setYaw(val: number) {
        this._yawRad = val;
        this._updateAbsolutePosition();
    }

    /**
     * Sets the roll rotation of the entity.
     * @public
     * @param {number} val - The new roll angle in radians.
     */
    public setRoll(val: number) {
        this._rollRad = val;
        this._updateAbsolutePosition();
    }

    /**
     * Gets the pitch angle of the entity.
     * @public
     * @returns {number} The pitch angle in radians.
     */
    public getPitch(): number {
        return this._pitchRad;
    }

    /**
     * Gets the yaw angle of the entity.
     * @public
     * @returns {number} The yaw angle in radians.
     */
    public getYaw(): number {
        return this._yawRad;
    }

    /**
     * Gets the roll angle of the entity.
     * @public
     * @returns {number} The roll angle in radians.
     */
    public getRoll(): number {
        return this._rollRad;
    }

    /**
     * Sets the absolute pitch of the entity.
     * @public
     * @param {number} val - The absolute pitch angle in radians.
     */
    public setAbsolutePitch(val: number) {
        if (this._relativePosition) {
            this._absoluteQRot.setPitchYawRoll(val, this.getAbsoluteYaw(), this.getAbsoluteRoll(), this._qFrame);
            this._updatePitchYawRoll();
        } else {
            this.setPitch(val);
        }
    }

    /**
     * Sets the absolute yaw of the entity.
     * @public
     * @param {number} val - The absolute yaw angle in radians.
     */
    public setAbsoluteYaw(val: number) {
        if (this._relativePosition) {
            this._absoluteQRot.setPitchYawRoll(this.getAbsolutePitch(), val, this.getAbsoluteRoll(), this._qFrame);
            this._updatePitchYawRoll();
        } else {
            this.setYaw(val);
        }
    }

    /**
     * Sets the absolute roll of the entity.
     * @public
     * @param {number} val - The absolute roll angle in radians.
     */
    public setAbsoluteRoll(val: number) {
        if (this._relativePosition) {
            this._absoluteQRot.setPitchYawRoll(this.getAbsolutePitch(), this.getAbsoluteYaw(), val, this._qFrame);
            this._updatePitchYawRoll();
        } else {
            this.setRoll(val);
        }
    }

    /**
     * Gets the absolute pitch angle of the entity.
     * @public
     * @returns {number} The absolute pitch angle in radians.
     */
    public getAbsolutePitch(): number {
        if (this.parent && this._relativePosition) {
            return this._qFrame.conjugate().inverse().mul(this._absoluteQRot).getPitch();
        }

        return this._pitchRad;
    }

    /**
     * Gets the absolute yaw angle of the entity.
     * @public
     * @returns {number} The absolute yaw angle in radians.
     */
    public getAbsoluteYaw(): number {
        if (this.parent && this._relativePosition) {
            return this._qFrame.conjugate().inverse().mul(this._absoluteQRot).getYaw();
        }
        return this._yawRad;
    }

    /**
     * Gets the absolute roll angle of the entity.
     * @public
     * @returns {number} The absolute roll angle in radians.
     */
    public getAbsoluteRoll(): number {
        if (this.parent && this._relativePosition) {
            return this._qFrame.conjugate().inverse().mul(this._absoluteQRot).getRoll();
        }
        return this._rollRad;
    }

    protected _getScaleByDistance(): number {
        let scd = 1;
        if (this._entityCollection) {
            let scaleByDistance = this._entityCollection.scaleByDistance;
            let lookLength = 1;
            if (this._entityCollection.renderNode && this._entityCollection.renderNode.renderer) {
                lookLength = this._entityCollection.renderNode.renderer.activeCamera.eye.distance(this._rootCartesian);
            }
            //the same in the shader
            scd = scaleByDistance[2] * clamp(lookLength, scaleByDistance[0], scaleByDistance[1]) / scaleByDistance[0];
        }
        return scd;
    }

    /**
     * Sets the absolute cartesian position of the entity.
     * @public
     * @param {number} x - X coordinate.
     * @param {number} y - Y coordinate.
     * @param {number} z - Z coordinate.
     */
    public setAbsoluteCartesian(x: number, y: number, z: number) {
        this.setAbsoluteCartesian3v(new Vec3(x, y, z));
    }

    /**
     * Sets the absolute cartesian position of the entity using a Vec3.
     * @public
     * @param {Vec3} absolutCartesian - The absolute cartesian position.
     */
    public setAbsoluteCartesian3v(absolutCartesian: Vec3) {
        let pos = absolutCartesian;

        if (this.parent && this._relativePosition) {
            let scd = this._getScaleByDistance();
            pos = absolutCartesian.sub(this.parent.getAbsoluteCartesian()).scale(1 / scd);
            pos = this.parent._absoluteQRot.conjugate().mulVec3(pos);
        }

        this.setCartesian3v(pos);
    }

    /**
     * Returns absolute cartesian position.
     * @public
     * @returns {Vec3} -
     */
    public getAbsoluteCartesian(): Vec3 {
        if (this.parent && this._relativePosition) {
            let scd = this._getScaleByDistance();
            return this._rootCartesian.add(this._absoluteLocalPosition.scaleTo(scd));
        }
        return this._cartesian.clone();
    }

    /**
     * Sets entity cartesian position.
     * @public
     * @param {number} x - 3d space X - position.
     * @param {number} y - 3d space Y - position.
     * @param {number} z - 3d space Z - position.
     */
    public setCartesian(x: number, y: number, z: number) {

        this._cartesian.set(x, y, z);

        this._updateAbsolutePosition();

        for (let i = 0; i < this.childEntities.length; i++) {
            let chi = this.childEntities[i];
            if (chi._relativePosition) {
                chi.setCartesian3v(chi.getCartesian());
            } else if (chi.forceGlobalPosition) {
                chi.setCartesian(x, y, z);
            }
        }

        this._updateLonLat();

        //ec && ec.events.dispatch(ec.events.entitymove, this);
    }

    protected _updatePitchYawRoll() {
        if (this.parent) {
            this._qRot = this.parent._absoluteQRot.conjugate().mul(this._absoluteQRot);

            this._pitchRad = this._qRot.getPitch();
            this._yawRad = this._qRot.getYaw();
            this._rollRad = this._qRot.getRoll();

            // this._pitch = this._pitchRad * DEGREES;
            // this._yaw = this._yawRad * DEGREES;
            // this._roll = this._rollRad * DEGREES;

            if (this.geoObject) {
                this.geoObject.setRotation(this._absoluteQRot);
            }

            for (let i = 0; i < this.childEntities.length; i++) {
                this.childEntities[i]._updateAbsolutePosition();
            }
        }
    }

    public _updateAbsolutePosition() {

        let parent = this.parent;

        if (parent && this._relativePosition) {
            this._qFrame.copy(parent._qFrame);
            this._rootCartesian.copy(parent._rootCartesian);

            this._qRot.setPitchYawRoll(this._pitchRad, this._yawRad, this._rollRad);
            parent._absoluteQRot.mulRes(this._qRot, this._absoluteQRot);

            let rotCart = parent._absoluteQRot.mulVec3(this._cartesian.add(this._localPosition));
            parent._absoluteLocalPosition.addRes(rotCart, this._absoluteLocalPosition);
        } else {
            this._qFrame = Quat.IDENTITY;
            if (this._entityCollection && this._entityCollection.renderNode) {
                this._qFrame = this._entityCollection.renderNode.getFrameRotation(this._cartesian);
            }
            if (parent && this.forceGlobalRotation) {
                this._qRot.setPitchYawRoll(parent._pitchRad, parent._yawRad, parent._rollRad, this._qFrame);
            } else {
                this._qRot.setPitchYawRoll(this._pitchRad, this._yawRad, this._rollRad, this._qFrame);
            }
            this._absoluteQRot.copy(this._qRot);
            this._rootCartesian.copy(this._cartesian);
            this._absoluteLocalPosition.copy(this._localPosition);
        }

        if (this.geoObject) {
            this.geoObject.setRotation(this._absoluteQRot);
            this.geoObject.setPosition3v(this._rootCartesian);
            this.geoObject.setLocalPosition3v(this._absoluteLocalPosition);
        }

        this.billboard && this.billboard.setPosition3v(this._rootCartesian);
        this.label && this.label.setPosition3v(this._rootCartesian);

        for (let i = 0, len = this.childEntities.length; i < len; i++) {
            this.childEntities[i]._updateAbsolutePosition();
        }

        this._updateLonLat();
    }

    /**
     * Sets entity cartesian position without event dispatching.
     * @public
     * @param {Vec3} cartesian - Cartesian position in 3d space.
     * @param {boolean} skipLonLat - skip geodetic calculation.
     */
    public _setCartesian3vSilent(cartesian: Vec3, skipLonLat: boolean = false) {

        this._cartesian.copy(cartesian);

        this._updateAbsolutePosition();

        for (let i = 0; i < this.childEntities.length; i++) {
            this.childEntities[i].setCartesian(this._cartesian.x, this._cartesian.y, this._cartesian.z);
        }

        if (!skipLonLat) {
            this._updateLonLat();
        }
    }

    protected _updateLonLat() {
        let ec = this._entityCollection;

        if (ec && ec.renderNode && (ec.renderNode as Planet).ellipsoid) {
            //let cart = this._rootCartesian.add(this._absoluteLocalPosition);
            this._lonLat = (ec.renderNode as Planet).ellipsoid.cartesianToLonLat(this.getAbsoluteCartesian());

            if (Math.abs(this._lonLat.lat) < mercator.MAX_LAT) {
                this._lonLatMerc = this._lonLat.forwardMercator();
            } else {
                this._lonLatMerc.lon = this._lonLatMerc.lat = 0;
            }
        }
    }

    /**
     * Gets entity geodetic coordinates.
     * @public
     * @returns {LonLat} -
     */
    public getLonLat(): LonLat {
        return this._lonLat.clone();
    }

    /**
     * Sets geodetic coordinates of the entity point object.
     * @public
     * @param {LonLat} lonlat - coordinates.
     */
    public setLonLat(lonlat: LonLat) {
        let l = this._lonLat;

        l.lon = lonlat.lon;
        l.lat = lonlat.lat;
        l.height = lonlat.height;

        let ec = this._entityCollection;
        if (ec && ec.renderNode && (ec.renderNode as Planet).ellipsoid) {
            if (Math.abs(l.lat) < mercator.MAX_LAT) {
                this._lonLatMerc = l.forwardMercator();
            } else {
                //this._lonLatMerc = null;
            }

            let temp = new Vec3();
            (ec.renderNode as Planet).ellipsoid.lonLatToCartesianRes(l, temp);

            this.setAbsoluteCartesian3v(temp);
        }
    }

    /**
     * Sets geodetic coordinates of the entity point object.
     * @public
     * @param {number} lon - Longitude.
     * @param {number} lat - Latitude
     * @param {number} [height] - Height
     */
    public setLonLat2(lon: number, lat: number, height?: number) {
        let l = this._lonLat;

        l.lon = lon;
        l.lat = lat;
        l.height = height != undefined ? height : l.height;

        let ec = this._entityCollection;
        if (ec && ec.renderNode && (ec.renderNode as Planet).ellipsoid) {
            if (Math.abs(l.lat) < mercator.MAX_LAT) {
                this._lonLatMerc = l.forwardMercator();
            } else {
                this._lonLatMerc.lon = this._lonLatMerc.lat = this._lonLatMerc.height = 0;
            }

            let temp = new Vec3();
            (ec.renderNode as Planet).ellipsoid.lonLatToCartesianRes(l, temp);

            this.setAbsoluteCartesian3v(temp);
        }
    }

    /**
     * Sets entity altitude over the planet.
     * @public
     * @param {number} altitude - Altitude.
     */
    public setAltitude(altitude: number) {
        this._altitude = altitude;
    }

    /**
     * Sets entity altitude over the planet.
     * @public
     * @return {number} Altitude.
     */
    public getAltitude(): number {
        return this._altitude;
    }

    /**
     * Returns cartesian position.
     * @public
     * @returns {Vec3} -
     */
    public getCartesian(): Vec3 {
        return this._cartesian.clone();
    }

    /**
     * Sets entity billboard.
     * @public
     * @param {Billboard} billboard - Billboard object.
     * @returns {Billboard} -
     */
    public setBillboard(billboard: Billboard): Billboard {
        if (this.billboard) {
            this.billboard.remove();
        }
        this.billboard = billboard;
        this.billboard._entity = this;
        this.billboard.setPosition3v(this._cartesian);
        this.billboard.setVisibility(this._visibility);
        this._entityCollection && this._entityCollection.billboardHandler.add(billboard);
        return billboard;
    }

    /**
     * Sets entity label.
     * @public
     * @param {Label} label - Text label.
     * @returns {Label} -
     */
    public setLabel(label: Label): Label {
        if (this.label) {
            this.label.remove();
        }
        this.label = label;
        this.label._entity = this;
        this.label.setPosition3v(this._cartesian);
        this.label.setVisibility(this._visibility);
        this._entityCollection && this._entityCollection.labelHandler.add(label);
        return label;
    }

    /**
     * Sets entity ray.
     * @public
     * @param {Ray} ray - Ray object.
     * @returns {Ray} -
     */
    public setRay(ray: Ray): Ray {
        if (this.ray) {
            this.ray.remove();
        }
        this.ray = ray;
        this.ray._entity = this;
        this.ray.setVisibility(this._visibility);
        this._entityCollection && this._entityCollection.rayHandler.add(ray);
        return ray;
    }

    /**
     * Sets entity polyline.
     * @public
     * @param {Polyline} polyline - Polyline object.
     * @returns {Polyline} -
     */
    public setPolyline(polyline: Polyline): Polyline {
        if (this.polyline) {
            this.polyline.remove();
        }
        this.polyline = polyline;
        this.polyline._entity = this;
        this.polyline.setVisibility(this._visibility);
        this._entityCollection && this._entityCollection.polylineHandler.add(polyline);
        return polyline;
    }

    /**
     * Sets entity pointCloud.
     * @public
     * @param {PointCloud} pointCloud - PointCloud object.
     * @returns {PointCloud} -
     */
    public setPointCloud(pointCloud: PointCloud): PointCloud {
        if (this.pointCloud) {
            this.pointCloud.remove();
        }
        this.pointCloud = pointCloud;
        this.pointCloud._entity = this;
        this.pointCloud.setVisibility(this._visibility);
        this._entityCollection && this._entityCollection.pointCloudHandler.add(pointCloud);
        return pointCloud;
    }

    /**
     * Sets entity geometry.
     * @public
     * @param {Geometry} geometry - Geometry object.
     * @returns {Geometry} -
     */
    public setGeometry(geometry: Geometry): Geometry {
        if (this.geometry) {
            this.geometry.remove();
        }
        this.geometry = geometry;
        this.geometry._entity = this;
        this.geometry.setVisibility(this._visibility);
        let layer = this._layer;
        if (this._layer) {
            this._layer.removeEntity(this);
        }
        layer && layer.add(this);
        return geometry;
    }

    /**
     * Sets entity geoObject.
     * @public
     * @param {GeoObject} geoObject - GeoObject.
     * @returns {GeoObject} -
     */
    public setGeoObject(geoObject: GeoObject): GeoObject {
        if (this.geoObject) {
            this.geoObject.remove();
        }
        this.geoObject = geoObject;
        this.geoObject._entity = this;
        this.geoObject.setPosition3v(this._cartesian);
        this.geoObject.setVisibility(this._visibility);
        this._entityCollection && this._entityCollection.geoObjectHandler.add(geoObject);
        return geoObject;
    }

    /**
     * Sets entity strip.
     * @public
     * @param {Strip} strip - Strip object.
     * @returns {Strip} -
     */
    public setStrip(strip: Strip): Strip {
        if (this.strip) {
            this.strip.remove();
        }
        this.strip = strip;
        this.strip._entity = this;
        this.strip.setVisibility(this._visibility);
        this._entityCollection && this._entityCollection.stripHandler.add(strip);
        return strip;
    }

    /**
     * Gets layer container
     * @public
     * @returns {Vector | null}
     */
    public get layer(): Vector | null {
        return this._layer;
    }

    public get rendererEvents(): VectorEventsType | EntityCollectionEvents | null {
        if (this._layer) {
            return this._layer.events;
        } else if (this._entityCollection) {
            return this._entityCollection.events;
        }
        return null;
    }

    /**
     * Append child entity.
     * @public
     * @param {Entity} entity - Child entity.
     */
    public appendChild(entity: Entity) {
        entity._entityCollection = this._entityCollection;
        if (!entity._independentPicking) {
            entity._pickingColor = this._pickingColor;
        }
        entity.parent = this;
        this.childEntities.push(entity);
        this._entityCollection && this._entityCollection.appendChildEntity(entity);
    }

    /**
     * Appends entity items(billboard, label etc.) picking color.
     * @public
     */
    public setPickingColor() {
        let c = this._pickingColor;

        this.billboard && this.billboard.setPickingColor3v(c);

        this.label && this.label.setPickingColor3v(c);

        this.polyline && this.polyline.setPickingColor3v(c);

        this.ray && this.ray.setPickingColor3v(c);

        this.strip && this.strip.setPickingColor3v(c);

        this.geoObject && this.geoObject.setPickingColor3v(c);

        for (let i = 0; i < this.childEntities.length; i++) {
            this.childEntities[i].setPickingColor();
        }
    }

    /**
     * Return geodetic extent.
     * @public
     * @returns {Extent} -
     */
    public getExtent(): Extent {

        let res;
        let c = this._lonLat;

        if (this.billboard || this.label) {
            res = new Extent(new LonLat(c.lon, c.lat), new LonLat(c.lon, c.lat));
        } else {
            res = new Extent(new LonLat(180.0, 90.0), new LonLat(-180.0, -90.0));
        }

        let sw = res.southWest,
            ne = res.northEast;

        if (this.polyline) {
            let e = this.polyline.getExtent();
            if (e.southWest.lon < sw.lon) sw.lon = e.southWest.lon;
            if (e.southWest.lat < sw.lat) sw.lat = e.southWest.lat;
            if (e.northEast.lon > ne.lon) ne.lon = e.northEast.lon;
            if (e.northEast.lat > ne.lat) ne.lat = e.northEast.lat;
        }

        if (this.geometry) {
            let e = this.geometry.getExtent();
            if (e.southWest.lon < sw.lon) sw.lon = e.southWest.lon;
            if (e.southWest.lat < sw.lat) sw.lat = e.southWest.lat;
            if (e.northEast.lon > ne.lon) ne.lon = e.northEast.lon;
            if (e.northEast.lat > ne.lat) ne.lat = e.northEast.lat;
        }

        for (let i = 0; i < this.childEntities.length; i++) {
            let e = this.childEntities[i].getExtent();
            if (e.southWest.lon < sw.lon) sw.lon = e.southWest.lon;
            if (e.southWest.lat < sw.lat) sw.lat = e.southWest.lat;
            if (e.northEast.lon > ne.lon) ne.lon = e.northEast.lon;
            if (e.northEast.lat > ne.lat) ne.lat = e.northEast.lat;
        }

        return res;
    }
}

export {Entity};