control/entityEditor/RotateEntity.tstypescript
import { Entity, type IEntityParams } from "../../entity/Entity";
import { Vec3 } from "../../math/Vec3";
import { RADIANS } from "../../math";
import { Quat } from "../../math/Quat";
import { SEL_X_COLOR, SEL_Y_COLOR, SEL_Z_COLOR, X_COLOR, Y_COLOR, Z_COLOR } from "./colors";

const POLYLINE_THICKNESS = 3.8;
const SEG_SIZE = 360;
const VISIBLESPHERE_DOT_THRESHOLD = 0.95;

export interface IRotationEntityParams extends IEntityParams {}

const pitchCoords = new Array(SEG_SIZE);
const yawCoords = new Array(SEG_SIZE);
const rollCoords = new Array(SEG_SIZE);

export class RotateEntity extends Entity {
    constructor(params: IRotationEntityParams = {}) {
        super(params);
        this._init();
    }

    private _init() {
        const length = SEG_SIZE;

        let pitch = new Entity({
            independentPicking: true,
            polyline: {
                path3v: [Array.from({ length }, (_, i) => new Vec3())],
                thickness: POLYLINE_THICKNESS,
                color: [X_COLOR],
                isClosed: true
            },
            properties: {
                opName: "rotate_pitch",
                noEdit: true,
                style: {
                    color: X_COLOR,
                    selectColor: SEL_X_COLOR
                }
            }
        });

        let yaw = new Entity({
            independentPicking: true,
            polyline: {
                path3v: [Array.from({ length }, (_, i) => new Vec3())],
                thickness: POLYLINE_THICKNESS,
                color: [Y_COLOR],
                isClosed: true
            },
            properties: {
                opName: "rotate_yaw",
                noEdit: true,
                style: {
                    color: Y_COLOR,
                    selectColor: SEL_Y_COLOR
                }
            }
        });

        let roll = new Entity({
            independentPicking: true,
            polyline: {
                path3v: [Array.from({ length }, (_, i) => new Vec3())],
                thickness: POLYLINE_THICKNESS,
                color: [Z_COLOR],
                isClosed: true
            },
            properties: {
                opName: "rotate_roll",
                noEdit: true,
                style: {
                    color: Z_COLOR,
                    selectColor: SEL_Z_COLOR
                }
            }
        });

        this.appendChild(pitch);
        this.appendChild(yaw);
        this.appendChild(roll);
    }

    public override setCartesian3v(cart: Vec3): void;
    public override setCartesian3v(cart: Vec3, rotation: Quat): void;
    public override setCartesian3v(cart: Vec3, rotation?: Quat) {
        super.setCartesian3v(cart);

        if (this._entityCollection && this._entityCollection.scene) {
            let rn = this._entityCollection.scene;
            let qRot = rotation || rn.getFrameRotation(cart).conjugate();

            this._updateGizmo(cart, qRot, qRot);
        }
    }

    public setYawCartesian3v(cart: Vec3, yaw: number) {
        super.setCartesian3v(cart);

        if (this._entityCollection && this._entityCollection.scene) {
            let rn = this._entityCollection.scene;
            let frameRotation = rn.getFrameRotation(cart);
            let qYaw = frameRotation.conjugate();
            let qRot = Quat.zRotation(0).mul(Quat.xRotation(0)).mul(Quat.yRotation(yaw)).mul(frameRotation).conjugate();

            this._updateGizmo(cart, qRot, qYaw);
        }
    }

    protected _updateGizmo(cart: Vec3, qRot: Quat, qYaw: Quat) {
        let rn = this._entityCollection!.scene!;
        let cam = rn.renderer!.activeCamera;

        let radiusDist = cam.isOrthographic ? cam.focusDistance : cam.eye.distance(cart);
        let r = radiusDist * 0.15;

        for (let i = 0, j = 0, step = 360 / SEG_SIZE; i < SEG_SIZE; i += step, j++) {
            let a = i * RADIANS,
                cos_a = Math.cos(a),
                sin_a = Math.sin(a);

            let pitch_p = qRot
                .mulVec3(new Vec3(0, sin_a, cos_a))
                .normalize()
                .scale(r)
                .add(cart);
            let yaw_p = qYaw
                .mulVec3(new Vec3(cos_a, 0, sin_a))
                .normalize()
                .scale(r)
                .add(cart);
            let roll_p = qRot
                .mulVec3(new Vec3(cos_a, sin_a, 0))
                .normalize()
                .scale(r)
                .add(cart);

            pitchCoords[j] = pitch_p;
            yawCoords[j] = yaw_p;
            rollCoords[j] = roll_p;
        }

        this.childEntities[0].polyline!.setPath3v([pitchCoords], undefined, true);
        this.childEntities[1].polyline!.setPath3v([yawCoords], undefined, true);
        this.childEntities[2].polyline!.setPath3v([rollCoords], undefined, true);

        // Gets whole circle visibility
        let dir_pitch = qRot.mulVec3(new Vec3(1, 0, 0)).normalize();
        this._entityCollection!.setVisibleSphere(
            cart,
            Math.abs(dir_pitch.dot(cam.getForward())) > VISIBLESPHERE_DOT_THRESHOLD ? 0.0 : r
        );
    }
}