import BABYLON from 'babylonjs';
import ActHist from './acthist';

const { Axis, Color3, Mesh, Space, StandardMaterial, Vector3 } = BABYLON;

export default class EditControl {

    meshPicked = null;
    canvas = null;
    scene = null;
    mainCamera = null;
    theParent = null;
    local = true;
    snapT = false;
    snapR = false;
    transSnap = 1;
    rotSnap = Math.PI / 18;
    axesLen = 0.4;
    axesScale = 1;
    redMat = null;
    greenMat = null;
    blueMat = null;
    whiteMat = null;
    yellowMat = null;
    actHist = null;
    renderer = null;
    pointerdown = null;
    pointerup = null;
    pointermove = null;

    constructor (mesh, camera, canvas, scale) {
        this.meshPicked = mesh;
        this.canvas = canvas;
        this.axesScale = scale;

        this.scene = mesh.getScene();
        this.mainCamera = camera;
        this.actHist = new ActHist(mesh, 10);

        mesh.computeWorldMatrix(true);

        this.theParent = new Mesh('EditControl', this.scene);
        this.theParent.position = this.meshPicked.position;
        this.theParent.visibility = 0;
        this.theParent.isPickable = false;

        this.createMaterials(this.scene);
        this.createGuideAxes();
        this.guideCtl.parent = this.theParent;
        this.createPickPlane();
        this.pickPlane.parent = this.theParent;

        this.pointerdown = this::this.onPointerDown;
        this.pointerup = this::this.onPointerUp;
        this.pointermove = this::this.onPointerMove;

        canvas.addEventListener('mousedown', this.pointerdown, false);
        canvas.addEventListener('mouseup', this.pointerup, false);
        canvas.addEventListener('mousemove', this.pointermove, false);

        this.setLocalAxes(mesh);
        this.renderer = this::this.renderLoopProcess;
        this.scene.registerBeforeRender(this.renderer);
    }

    renderLoopProcess () {
        this.setAxesScale();
        this.theParent.position = this.meshPicked.position;
        this.onPointerOver();
    }

    switchTo (mesh) {
        mesh.computeWorldMatrix(true);
        this.meshPicked = mesh;
        this.setLocalAxes(mesh);
        this.actHist = new ActHist(mesh, 10);
    }

    setUndoCount (c) {
        this.actHist.setCapacity(c);
    }

    undo () {
        this.actHist.undo();
        this.meshPicked.computeWorldMatrix(true);
        this.setLocalAxes(this.meshPicked);
    }

    redo () {
        this.actHist.redo();
        this.meshPicked.computeWorldMatrix(true);
        this.setLocalAxes(this.meshPicked);
    }

    detach () {
        this.canvas.removeEventListener('mousedown', this.pointerdown, false);
        this.canvas.removeEventListener('mouseup', this.pointerup, false);
        this.canvas.removeEventListener('mousemove', this.pointermove, false);
        this.scene.unregisterBeforeRender(this.renderer);
        this.disposeAll();
    }

    disposeAll () {
        this.theParent.dispose();
        this.disposeMaterials();
        this.actHist = null;
    }

    pDown = false;

    axisPicked = null;

    onPointerDown (evt) {
        evt.preventDefault();
        this.pDown = true;

        if (evt.button !== 0)
            return;

        const pickResult = this.scene.pick(this.scene.pointerX, this.scene.pointerY, mesh => {
            if (this.transEnabled) {
                if (mesh === this.tX || mesh === this.tY || mesh === this.tZ)
                    return true;
            }
            if (this.rotEnabled) {
                if (mesh === this.rX || mesh === this.rY || mesh === this.rZ)
                    return true;
            }
            if (this.scaleEnabled) {
                if (mesh === this.sX || mesh === this.sY || mesh === this.sZ || mesh === this.sAll)
                    return true;
            }
            return false;
        }, null, this.mainCamera);

        if (pickResult.hit) {
            this.setAxisVisiblity(0);
            this.axisPicked = pickResult.pickedMesh;
            this.axisPicked.getChildren()[0].visibility = 1;

            const name = this.axisPicked.name;

            if (name === 'X') this.bXaxis.visibility = 1;
            if (name === 'Y') this.bYaxis.visibility = 1;
            if (name === 'Z') this.bZaxis.visibility = 1;
            if (name === 'ALL') {
                this.bXaxis.visibility = 1;
                this.bYaxis.visibility = 1;
                this.bZaxis.visibility = 1;
            }

            this.editing = true;
            this.prevPos = this.getPosOnPickPlane();

            window.setTimeout(this::this.detachControl, 0, this.mainCamera, this.canvas);
        }
    }

    isEditing () {
        return this.editing;
    }

    detachControl (cam, can) {
        const camera = cam;
        const canvas = can;

        camera.detachControl(canvas);
    }

    prevOverMesh = null;

    onPointerOver () {
        if (this.pDown)
            return;

        const pickResult = this.scene.pick(this.scene.pointerX, this.scene.pointerY, mesh => {
            if (this.transEnabled) {
                if (mesh === this.tX || mesh === this.tY || mesh === this.tZ)
                    return true;
            }
            if (this.rotEnabled) {
                if (mesh === this.rX || mesh === this.rY || mesh === this.rZ)
                    return true;
            }
            if (this.scaleEnabled) {
                if (mesh === this.sX || mesh === this.sY || mesh === this.sZ || mesh === this.sAll)
                    return true;
            }
            return false;
        }, null, this.mainCamera);

        if (pickResult.hit) {
            if (pickResult.pickedMesh !== this.prevOverMesh) {
                if (this.prevOverMesh) {
                    this.prevOverMesh.visibility = 0;
                    this.restoreColor(this.prevOverMesh);
                }

                this.prevOverMesh = pickResult.pickedMesh;

                if (this.rotEnabled) {
                    this.prevOverMesh.getChildren()[0].color = Color3.White();
                }
                else {
                    this.prevOverMesh.getChildren()[0].material = this.whiteMat
                }

                if (this.prevOverMesh.name === 'X') {
                    this.xaxis.color = Color3.White();
                }

                if (this.prevOverMesh.name === 'Y') {
                    this.yaxis.color = Color3.White();
                }

                if (this.prevOverMesh.name === 'Z') {
                    this.zaxis.color = Color3.White();
                }
            }
        }
        else {
            if (this.prevOverMesh) {
                this.restoreColor(this.prevOverMesh);
                this.prevOverMesh = null;
            }
        }
    }

    restoreColor (mesh) {
        let col, mat;

        if (mesh.name === 'X') {
            col = Color3.Red();
            mat = this.redMat;
            this.xaxis.color = Color3.Red();
        }
        else if (this.prevOverMesh.name === 'Y') {
            col = Color3.Green();
            mat = this.greenMat;
            this.yaxis.color = Color3.Green();
        }
        else if (this.prevOverMesh.name === 'Z') {
            col = Color3.Blue();
            mat = this.blueMat;
            this.zaxis.color = Color3.Blue();
        }
        else {
            col = Color3.Yellow();
            mat = this.yellowMat;
        }

        if (this.rotEnabled) {
            this.prevOverMesh.getChildren()[0].color = col;
        }
        else {
            this.prevOverMesh.getChildren()[0].material = mat;
        }
    }

    editing = false;

    onPointerUp (e) {
        this.pDown = false;

        if (this.editing) {
            this.mainCamera.attachControl(this.canvas);
            this.editing = false;
            this.setAxisVisiblity(1);
            this.hideBaxis();
            this.restoreColor(this.prevOverMesh);
            this.prevOverMesh = null;
            this.actHist.add();
        }
    }

    prevPos = null;

    snapX = 0;
    snapY = 0;
    snapZ = 0;
    snapRX = 0;
    snapRY = 0;
    snapRZ = 0;

    onPointerMove () {
        if (!this.pDown || !this.editing)
            return;

        const newPos = this.getPosOnPickPlane();

        if (!newPos)
            return;

        if (this.transEnabled)
            this.doTranslation(newPos);

        if (this.scaleEnabled && this.local)
            this.doScaling(newPos);

        if (this.rotEnabled)
            this.doRotation(newPos);

        this.prevPos = newPos;
    }

    doTranslation (newPos) {
        const diff = newPos.subtract(this.prevPos);
        let dl = diff.length();
        let space;

        if (this.local)
            space = Space.LOCAL;
        else
            space = Space.WORLD;

        if (this.axisPicked === this.tX) {
            if (this.local)
                dl = Vector3.Dot(diff, this.localX) / (this.localX.length() * this.meshPicked.scaling.x);
            else
                dl = diff.x;

            if (this.snapT) {
                this.snapX += dl;
                dl = 0;
                let scale = 1;

                if (this.local)
                    scale = this.meshPicked.scaling.x;
                if (Math.abs(this.snapX) > this.transSnap / scale) {
                    if (this.snapX > 0)
                        dl = this.transSnap / scale;
                    else
                        dl = -this.transSnap / scale;

                    this.snapX = 0;
                }
            }

            if (this.local)
                this.meshPicked.translate(Axis.X, dl, space);
            else
                this.meshPicked.position.x += dl;
        }
        if (this.axisPicked === this.tY) {
            if (this.local)
                dl = Vector3.Dot(diff, this.localY) / (this.localY.length() * this.meshPicked.scaling.y);
            else
                dl = diff.y;

            if (this.snapT) {
                this.snapY += dl;
                dl = 0;
                let scale = 1;

                if (this.local)
                    scale = this.meshPicked.scaling.y;
                if (Math.abs(this.snapY) > this.transSnap / scale) {
                    if (this.snapY > 0)
                        dl = this.transSnap / scale;
                    else
                        dl = -this.transSnap / scale;

                    this.snapY = 0;
                }
            }

            if (this.local)
                this.meshPicked.translate(Axis.Y, dl, space);
            else
                this.meshPicked.position.y += dl;
        }
        if (this.axisPicked === this.tZ) {
            if (this.local)
                dl = Vector3.Dot(diff, this.localZ) / (this.localZ.length() * this.meshPicked.scaling.z);
            else
                dl = diff.z;

            if (this.snapT) {
                this.snapZ += dl;
                dl = 0;
                let scale = 1;

                if (this.local)
                    scale = this.meshPicked.scaling.z;
                if (Math.abs(this.snapZ) > this.transSnap / scale) {
                    if (this.snapZ > 0)
                        dl = this.transSnap / scale;
                    else
                        dl = -this.transSnap / scale;

                    this.snapZ = 0;
                }
            }

            if (this.local)
                this.meshPicked.translate(Axis.Z, dl, space);
            else
                this.meshPicked.position.z += dl;
        }
    }

    doScaling (newPos) {
        const ppm = this.prevPos.subtract(this.meshPicked.position);
        const diff = newPos.subtract(this.prevPos);
        let r = diff.length() / ppm.length();

        if (this.axisPicked === this.sX) {
            const dot = Vector3.Dot(diff, this.localX);
            if (dot >= 0)
                this.meshPicked.scaling.x *= 1 + r;
            else
                this.meshPicked.scaling.x *= 1 - r;
        }

        if (this.axisPicked === this.sY) {
            const dot = Vector3.Dot(diff, this.localY);
            if (dot >= 0)
                this.meshPicked.scaling.y *= 1 + r;
            else
                this.meshPicked.scaling.y *= 1 - r;
        }

        if (this.axisPicked === this.sZ) {
            const dot = Vector3.Dot(diff, this.localZ);
            if (dot >= 0)
                this.meshPicked.scaling.z *= 1 + r;
            else
                this.meshPicked.scaling.z *= 1 - r;
        }

        if (this.axisPicked === this.sAll) {
            const dot = Vector3.Dot(diff, this.mainCamera.upVector);
            r = diff.length() / 5;

            if (dot < 0) {
                r = -1;
            }

            this.meshPicked.scaling.x *= 1 + r;
            this.meshPicked.scaling.y *= 1 + r;
            this.meshPicked.scaling.z *= 1 + r;
        }
    }

    doRotation (newPos) {
        const cN = Vector3.TransformNormal(Axis.Z, this.mainCamera.getWorldMatrix());

        if (this.axisPicked === this.rX) {
			let angle = this.getAngle(this.prevPos, newPos, this.meshPicked.position, cN);

            if (this.snapR) {
				this.snapRX += angle;
				angle = 0;

                if (Math.abs(this.snapRX) >= this.rotSnap) {
					if (this.snapRX > 0)
						this.angle = this.rotSnap;
					else
						angle = -this.rotSnap;

                    this.snapRX = 0;
				}
			}

            if (this.local) {
				if (Vector3.Dot(this.localX, cN) < 0)
					angle = -1 * angle;

                this.meshPicked.rotate(Axis.X, angle, Space.LOCAL);
			}
            else
				this.meshPicked.rotate(new Vector3(cN.x, 0, 0), angle, Space.WORLD);

            this.setLocalAxes(this.meshPicked);
		}
        if (this.axisPicked === this.rY) {
			let angle = this.getAngle(this.prevPos, newPos, this.meshPicked.position, cN);

            if (this.snapR) {
				this.snapRY += angle;
				angle = 0;

				if (Math.abs(this.snapRY) >= this.rotSnap) {
					if (this.snapRY > 0)
						angle = this.rotSnap;
					else
						angle = -this.rotSnap;

					this.snapRY = 0;
				}
			}

			if (this.local) {
				if (Vector3.Dot(this.localY, cN) < 0)
					angle = -1 * angle;

				this.meshPicked.rotate(Axis.Y, angle, Space.LOCAL);
			}
            else
				this.meshPicked.rotate(new Vector3(0, cN.y, 0), angle, Space.WORLD);

            this.setLocalAxes(this.meshPicked);
		}

        if (this.axisPicked === this.rZ) {
			let angle = this.getAngle(this.prevPos, newPos, this.meshPicked.position, cN);

            if (this.snapR) {
				this.snapRZ += angle;
				angle = 0;

				if (Math.abs(this.snapRZ) >= this.rotSnap) {
					if (this.snapRZ > 0)
						angle = this.rotSnap;
					else
						angle = -this.rotSnap;

					this.snapRZ = 0;
				}
			}

			if (this.local) {
				if (Vector3.Dot(this.localZ, cN) < 0)
					angle = -1 * angle;
				this.meshPicked.rotate(Axis.Z, angle, Space.LOCAL);
			} else
				this.meshPicked.rotate(new Vector3(0, 0, cN.z), angle, Space.WORLD);

			this.setLocalAxes(this.meshPicked);
		}

		this.meshPicked.rotation = this.meshPicked.rotationQuaternion.toEulerAngles();
    }

    getPosOnPickPlane () {
        const pickinfo = this.scene.pick(this.scene.pointerX, this.scene.pointerY, mesh => {
            return mesh === this.pickPlane;
        }, null, this.mainCamera);

        if (pickinfo.hit) {
            return pickinfo.pickedPoint;
        }

        return null;
    }

    hideBaxis () {
        this.bXaxis.visibility = 0;
        this.bYaxis.visibility = 0;
        this.bZaxis.visibility = 0;
    }

    setAxisVisiblity (v) {
        if (this.transEnabled) {
			this.tEndX.visibility = v;
			this.tEndY.visibility = v;
			this.tEndZ.visibility = v;
		}

		if (this.rotEnabled) {
			this.rEndX.visibility = v;
			this.rEndY.visibility = v;
			this.rEndZ.visibility = v;
		}

		if (this.scaleEnabled) {
			this.sEndX.visibility = v;
			this.sEndY.visibility = v;
			this.sEndZ.visibility = v;
			this.sEndAll.visibility = v;
		}
    }

    transEnabled = false;

    isTranslationEnabled () {
        return this.transEnabled;
    }

    enableTranslation () {
        if (!this.tX) {
			this.createTransAxes();
			this.tCtl.parent = this.theParent;
		}

		if (!this.transEnabled) {
			this.tEndX.visibility = 1;
			this.tEndY.visibility = 1;
			this.tEndZ.visibility = 1;
			this.transEnabled = true;

			this.disableRotation();
			this.disableScaling();
		}
    }

    disableTranslation () {
		if (this.transEnabled) {
			this.tEndX.visibility = 0;
			this.tEndY.visibility = 0;
			this.tEndZ.visibility = 0;
			this.transEnabled = false;
		}
	}

    rotEnabled = false;

    isRotationEnabled () {
        return this.rotEnabled;
    }

    enableRotation () {
        if (!this.rX) {
            this.createRotAxes();
            this.rCtl.parent = this.theParent;
        }

        if (!this.rotEnabled) {
            this.rEndX.visibility = 1;
			this.rEndY.visibility = 1;
			this.rEndZ.visibility = 1;
			this.rotEnabled = true;

			this.disableTranslation();
			this.disableScaling();
        }
    }

    disableRotation () {
		if (this.rotEnabled) {
			this.rEndX.visibility = 0;
			this.rEndY.visibility = 0;
			this.rEndZ.visibility = 0;
			this.rotEnabled = false;
		}
	}

    scaleEnabled = false;

	isScaleEnabled(){
		return this.scaleEnabled;
	}

	enableScaling() {
		if (!this.sX) {
			this.createScaleAxes();
			this.sCtl.parent = this.theParent;
		}

		if (!this.scaleEnabled) {
			this.sEndX.visibility = 1;
			this.sEndY.visibility = 1;
			this.sEndZ.visibility = 1;
			this.sEndAll.visibility = 1;
			this.scaleEnabled = true;

			this.disableTranslation();
			this.disableRotation();
		}
	}

	disableScaling () {
		if (this.scaleEnabled) {
			this.sEndX.visibility = 0;
			this.sEndY.visibility = 0;
			this.sEndZ.visibility = 0;
			this.sEndAll.visibility = 0;
			this.scaleEnabled = false;
		}
	}

    bXaxis = null;
    bYaxis = null;
    bZaxis = null;
    xaxis = null;
    yaxis = null;
    zaxis = null;
    guideCtl = null;

    createGuideAxes () {
        const l = this.axesLen * this.axesScale;

        this.guideCtl = new Mesh('guideCtl', this.scene);

        this.bXaxis = Mesh.CreateLines('xAxis', [new Vector3(-100, 0, 0), new Vector3(100, 0, 0)], this.scene);
        this.bYaxis = Mesh.CreateLines('xAxis', [new Vector3(0, -100, 0), new Vector3(0, 100, 0)], this.scene);
        this.bZaxis = Mesh.CreateLines('zAxis', [new Vector3(0, 0, -100), new Vector3(0, 0, 100)], this.scene);

        this.bXaxis.parent = this.guideCtl;
        this.bYaxis.parent = this.guideCtl;
        this.bZaxis.parent = this.guideCtl;

        this.bXaxis.color = Color3.Red();
        this.bYaxis.color = Color3.Green();
        this.bZaxis.color = Color3.Blue();

        this.bXaxis.renderingGroupId = 1;
        this.bYaxis.renderingGroupId = 1;
        this.bZaxis.renderingGroupId = 1;

        this.hideBaxis();

        this.xaxis = Mesh.CreateLines('xAxis', [new Vector3(0, 0, 0), new Vector3(l, 0, 0)], this.scene);
        this.yaxis = Mesh.CreateLines('yAxis', [new Vector3(0, 0, 0), new Vector3(0, l, 0)], this.scene);
        this.zaxis = Mesh.CreateLines('zAxis', [new Vector3(0, 0, 0), new Vector3(0, 0, l)], this.scene);

        this.xaxis.parent = this.guideCtl;
        this.yaxis.parent = this.guideCtl;
        this.zaxis.parent = this.guideCtl;

        this.xaxis.color = Color3.Red();
        this.yaxis.color = Color3.Green();
        this.zaxis.color = Color3.Blue();

        this.xaxis.renderingGroupId = 2;
        this.yaxis.renderingGroupId = 2;
        this.zaxis.renderingGroupId = 2;
    }

    pickPlane = null;

    createPickPlane () {
        this.pickPlane = Mesh.CreatePlane('axisPlane', 200, this.scene);
        this.pickPlane.isPickable = false;
        this.pickPlane.visibility = 0;
        this.pickPlane.billboardMode = Mesh.BILLBOARDMODE_ALL;
        this.pickPlane.renderingGroupId = 1;
    }

    tCtl = null;
    tX = null;
    tY = null;
    tZ = null;
    tEndX = null;
    tEndY = null;
    tEndZ = null;

    createTransAxes () {
        const r = 0.04;
        const l = this.axesLen * this.axesScale;

        this.tCtl = new Mesh('tarnsCtl', this.scene);

		this.tX = this.extrudeBox(r / 2, l);
		this.tX.name = 'X';
		this.tY = this.tX.clone('Y');
		this.tZ = this.tX.clone('Z');

		this.tX.material = this.redMat;
		this.tY.material = this.greenMat;
		this.tZ.material = this.blueMat;

		this.tX.parent = this.tCtl;
		this.tY.parent = this.tCtl;
		this.tZ.parent = this.tCtl;

		this.tX.rotation.y = 1.57;
		this.tY.rotation.x -= 1.57;

		this.tX.visibility = 0;
		this.tY.visibility = 0;
		this.tZ.visibility = 0;

		this.tX.renderingGroupId = 1;
		this.tY.renderingGroupId = 1;
		this.tZ.renderingGroupId = 1;

		this.tX.isPickable = false;
		this.tY.isPickable = false;
		this.tZ.isPickable = false;

        const cl = l * this.axesScale / 4;
        const cr = r * this.axesScale;

        this.tEndX = Mesh.CreateCylinder('tEndX', cl, 0, cr, 6, 1, this.scene);
		this.tEndY = this.tEndX.clone('tEndY');
		this.tEndZ = this.tEndX.clone('tEndZ');

		this.tEndX.rotation.x = 1.57;
		this.tEndY.rotation.x = 1.57;
		this.tEndZ.rotation.x = 1.57;

		this.tEndX.parent = this.tX;
		this.tEndY.parent = this.tY;
		this.tEndZ.parent = this.tZ;

		this.tEndX.position.z = l - cl / 2;
		this.tEndY.position.z = l - cl / 2;
		this.tEndZ.position.z = l - cl / 2;

		this.tEndX.material = this.redMat;
		this.tEndY.material = this.greenMat;
		this.tEndZ.material = this.blueMat;

		this.tEndX.renderingGroupId = 1;
		this.tEndY.renderingGroupId = 1;
		this.tEndZ.renderingGroupId = 1;

		this.tEndX.isPickable = false;
		this.tEndY.isPickable = false;
		this.tEndZ.isPickable = false;
    }

    rCtl = null;
    rX = null;
    rY = null;
    rZ = null;
    rEndX = null;
    rEndY = null;
    rEndZ = null;

    createRotAxes () {
        const r = 0.04;
        const d = this.axesLen * this.axesScale * 2;

        this.rCtl = new Mesh('rotCtl', this.scene);

		this.rX = Mesh.CreateTorus('X', d, r, 30, this.scene);
		this.rY = this.rX.clone('Y');
		this.rZ = this.rX.clone('Z');

		this.rX.material = this.redMat;
		this.rY.material = this.greenMat;
		this.rZ.material = this.blueMat;

		this.rX.parent = this.rCtl;
		this.rY.parent = this.rCtl;
		this.rZ.parent = this.rCtl;

		this.rX.rotation.z -= 1.57;
		this.rZ.rotation.x = 1.57;

		this.rX.visibility = 0;
		this.rY.visibility = 0;
		this.rZ.visibility = 0;

		this.rX.renderingGroupId = 1;
		this.rY.renderingGroupId = 1;
		this.rZ.renderingGroupId = 1;

		this.rX.isPickable = false;
		this.rY.isPickable = false;
		this.rZ.isPickable = false;

        const cl = d;

        this.rEndX = this.createCircle(cl / 2);
        this.rEndY = this.rEndX.clone('');
        this.rEndZ = this.rEndX.clone('');

        this.rEndX.parent = this.rX;
        this.rEndY.parent = this.rY;
        this.rEndZ.parent = this.rZ;

        this.rEndX.rotation.x = 1.57;
		this.rEndY.rotation.x = 1.57;
		this.rEndZ.rotation.x = 1.57;

		this.rEndX.color = Color3.Red();
		this.rEndY.color = Color3.Green();
		this.rEndZ.color = Color3.Blue();

		this.rEndX.renderingGroupId = 1;
		this.rEndY.renderingGroupId = 1;
		this.rEndZ.renderingGroupId = 1;

        // TODO: This is broken; isPickable is only a getter
		/*this.rEndX.isPickable = false;
		this.rEndY.isPickable = false;
		this.rEndZ.isPickable = false;*/
    }

    extrudeBox (w, l) {
        const shape = [
            new Vector3(w, w, 0),
            new Vector3(-w, w, 0),
            new Vector3(-w, -w, 0),
            new Vector3(w, -w, 0),
            new Vector3(w, w, 0)
        ];
        const path = [
            new Vector3(0, 0, 0),
            new Vector3(0, 0, l)
        ];
        const box = Mesh.ExtrudeShape('', shape, path, 1, 0, 2, this.scene);

        return box;
    }

    createCircle (r) {
        const points = [];
		let x, y;
		let a = 3.14 / 180;
		let p = 0;
		for (let i = 0; i <= 360; i = i + 10) {
			x = r * Math.cos(i * a);
			if (i === 90)
				y = r;
			else if (i === 270)
				y = -r;
			else
				y = r * Math.sin(i * a);

			points[p] = new Vector3(x, y, 0);
			p++;
		}
		const circle = Mesh.CreateLines('', points, this.scene);
		return circle;
    }

    sCtl = null;
    sX = null;
    sY = null;
    sZ = null;
    sAll = null;
    sEndX = null;
    sEndY = null;
    sEndZ = null;
    sEndAll = null;

    createScaleAxes () {
        const r = 0.04;
        const l = this.axesLen * this.axesScale;

        this.sCtl = new Mesh('sCtl', this.scene);
		this.sAll = Mesh.CreateBox('ALL', r * 2, this.scene);

		this.sX = this.extrudeBox(r / 2, l);
		this.sX.name = 'X';
		this.sY = this.sX.clone('Y');
		this.sZ = this.sX.clone('Z');

		this.sX.material = this.redMat;
		this.sY.material = this.greenMat;
		this.sZ.material = this.blueMat;
		this.sAll.material = this.yellowMat;

		this.sX.parent = this.sCtl;
		this.sY.parent = this.sCtl;
		this.sZ.parent = this.sCtl;
		this.sAll.parent = this.sCtl;

		this.sX.rotation.y = 1.57;
		this.sY.rotation.x -= 1.57;

		this.sX.visibility = 0;
		this.sY.visibility = 0;
		this.sZ.visibility = 0;
		this.sAll.visibility = 0;

		this.sX.renderingGroupId = 1;
		this.sY.renderingGroupId = 1;
		this.sZ.renderingGroupId = 1;
		this.sAll.renderingGroupId = 1;

		this.sX.isPickable = false;
		this.sY.isPickable = false;
		this.sZ.isPickable = false;
		this.sAll.isPickable = false;

        const cr = r * this.axesScale;

		this.sEndX = Mesh.CreateBox('', cr, this.scene);
		this.sEndY = this.sEndX.clone('');
		this.sEndZ = this.sEndX.clone('');
		this.sEndAll = this.sEndX.clone('');

		this.sEndX.parent = this.sX;
		this.sEndY.parent = this.sY;
		this.sEndZ.parent = this.sZ;
		this.sEndAll.parent = this.sAll;

		this.sEndX.position.z = l - cr / 2;
		this.sEndY.position.z = l - cr / 2;
		this.sEndZ.position.z = l - cr / 2;

		this.sEndX.material = this.redMat;
		this.sEndY.material = this.greenMat;
		this.sEndZ.material = this.blueMat;
		this.sEndAll.material = this.yellowMat;

		this.sEndX.renderingGroupId = 1;
		this.sEndY.renderingGroupId = 1;
		this.sEndZ.renderingGroupId = 1;
		this.sEndAll.renderingGroupId = 1;

		this.sEndX.isPickable = false;
		this.sEndY.isPickable = false;
		this.sEndZ.isPickable = false;
		this.sEndAll.isPickable = false;
    }

    localX = null;
    localY = null;
    localZ = null;
    localRot = null;

    setLocalAxes (mesh) {
        const meshMatrix = mesh.getWorldMatrix();
		const pos = mesh.position;

		this.localX = Vector3.TransformCoordinates(Axis.X, meshMatrix).subtract(pos);
		this.localY = Vector3.TransformCoordinates(Axis.Y, meshMatrix).subtract(pos);
		this.localZ = Vector3.TransformCoordinates(Axis.Z, meshMatrix).subtract(pos);
		this.localRot = Vector3.RotationFromAxis(this.localX, this.localY, this.localZ);

        if (this.local)
			this.theParent.rotation.copyFrom(this.localRot);
    }

    setLocal(l) {
		if (this.local === l)
			return;

		this.local = l;

		if (this.local)
			this.theParent.rotation.copyFrom(this.localRot);
		else
			this.theParent.rotation.copyFrom(Vector3.Zero());
	}

    isLocal (){
		return this.local;
	}

	setTransSnap (s) {
		this.snapT = s;
	}

	setRotSnap (s) {
		this.snapR = s;
	}

	setTransSnapValue (t) {
		this.transSnap = t;
	}

	setRotSnapValue (r) {
		this.rotSnap = r;
	}

    setAxesScale () {
        const distFromCamera = 2;
        const toParent = this.theParent.position.subtract(this.mainCamera.position)
        const cameraNormal = Vector3.TransformNormal(Axis.Z, this.mainCamera.getWorldMatrix());
        const parentOnNormal = Vector3.Dot(toParent, cameraNormal) / cameraNormal.length();
        const s = parentOnNormal / distFromCamera;
        const scale = new Vector3(s, s, s);

        this.theParent.scaling = scale;
    }

    getAngle (p1, p2, p, cN) {
        const v1 = p1.subtract(p);
		const v2 = p2.subtract(p);
		const n = Vector3.Cross(v1, v2);
		let angle = Math.asin(n.length() / (v1.length() * v2.length()));

		if (Vector3.Dot(n, cN) < 0) {
			angle = -1 * angle;
		}

		return angle;
    }

    createMaterials (scene) {
        this.redMat = this.getStandardMaterial('redMat', Color3.Red(), scene);
		this.greenMat = this.getStandardMaterial('greenMat', Color3.Green(), scene);
		this.blueMat = this.getStandardMaterial('blueMat', Color3.Blue(), scene);
		this.whiteMat = this.getStandardMaterial('whiteMat', Color3.White(), scene);
		this.yellowMat = this.getStandardMaterial('whiteMat', Color3.Yellow(), scene);
    }

    disposeMaterials () {
        this.redMat.dispose();
		this.greenMat.dispose();
		this.blueMat.dispose();
		this.whiteMat.dispose();
		this.yellowMat.dispose();
    }

    getStandardMaterial (name, col, scene) {
        const mat = new StandardMaterial(name, scene);
        mat.emissiveColor = col;
        mat.diffuseColor = Color3.Black();
        mat.specularColor = Color3.Black();

        return mat;
    }

}
