API Docs for:

File: /home/lindsay/xeolabs/xeogl-next/xeogl/examples/js/controls/BIMClipControl2.js

/**

 Helper that visualizes the position and direction of a plane.

 @class ClipControl
 @constructor
 @param cfg {*} Configuration
 @param [cfg.pos=[0,0,0]] {Float32Array} World-space position.
 @param [cfg.dir=[0,0,1]] {Float32Array} World-space direction vector.
 @param [cfg.color=[0.4,0.4,0.4]] {Float32Array} Emmissive color
 @param [cfg.solid=true] {Boolean} Indicates whether or not this helper is filled with color or just wireframe.
 @param [cfg.visible=true] {Boolean} Indicates whether or not this helper is visible.
 @param [cfg.planeSize] {Float32Array} The width and height of the ClipControl plane indicator.
 @param [cfg.autoPlaneSize=false] {Boolean} Indicates whether or not this ClipControl's
 {{#crossLink "ClipControl/planeSize:property"}}{{/crossLink}} is automatically sized to fit within
 the {{#crossLink "Scene/aabb:property"}}Scene's boundary{{/crossLink}}.
 */
(function () {

    "use strict";

    xeogl.ClipControl = xeogl.Component.extend({

        type: "xeogl.ClipControl",

        _init: function (cfg) {

            this._solid = false;
            this._visible = false;
            this._clipStartDir = xeogl.math.vec3();
            this._clipPos = xeogl.math.vec3();

            this._gumballGroup = null;
            this._planeScale = null;

            this._initEntities();

            this.planeSize = cfg.planeSize;
            this.autoPlaneSize = cfg.autoPlaneSize;
            this.color = cfg.color;
            this.solid = cfg.solid;
            this.clip = cfg.clip;
            this.visible = cfg.visible;
            this.active = true;

            this._initEvents();
        },

        _update: (function () {
            var arrowPositions = new Float32Array(6);
            return function () {

                var pos = this._pos;
                var dir = this._dir;

                // Rebuild arrow geometry

                arrowPositions[0] = pos[0];
                arrowPositions[1] = pos[1];
                arrowPositions[2] = pos[2];
                arrowPositions[3] = pos[0] + dir[0];
                arrowPositions[4] = pos[1] + dir[1];
                arrowPositions[5] = pos[2] + dir[2];

                //this._display.arrow.geometry.positions = arrowPositions;
            }
        })(),

        _props: {

            /**
             Indicates whether this ClipControl is active or not.

             @property active
             @default true
             @type Boolean
             */
            active: {
                set: function (value) {
                    value = value !== false;
                    if (value === this._active) {
                        return;
                    }
                    this._active = value;
                },
                get: function () {
                    return this._active;
                }
            },

            /**
             The {{#crossLink "Clip"}}{{/crossLink}} attached to this ClipControl.

             @property clip
             @type Clip
             */
            clip: {
                set: function (value) {
                    var self = this;
                    this._attach({name: "clip", type: "xeogl.Clip", component: value});
                    var clip = this._attached.clip;
                    if (clip) { // Reset rotation and translation basis
                        this._setGumballDir(clip.dir);
                        this._setGumballPos(clip.pos);
                    }
                },
                get: function () {
                    return this._attached.clip;
                }
            },

            /**
             * The width and height of the ClipControl plane indicator.
             *
             * Values assigned to this property will be overridden by an auto-computed value when
             * {{#crossLink "ClipControl/autoPlaneSize:property"}}{{/crossLink}} is true.
             *
             * @property planeSize
             * @default [1,1]
             * @type {Float32Array}
             */
            planeSize: {
                set: function (value) {
                    (this._planeSize = this._planeSize || new xeogl.math.vec2()).set(value || [1, 1]);
                    //    this._planeScale.xyz = [this._planeSize[0], this._planeSize[1], 1.0];
                },
                get: function () {
                    return this._planeSize;
                }
            },

            /**
             * Indicates whether this ClipControl's {{#crossLink "ClipControl/planeSize:property"}}{{/crossLink}} is automatically
             * generated or not.
             *
             * When auto-generated, {{#crossLink "ClipControl/planeSize:property"}}{{/crossLink}} will automatically size
             * to fit within the {{#crossLink "Scene/aabb:property"}}Scene's boundary{{/crossLink}}.
             *
             * @property autoPlaneSize
             * @default false
             * @type {Boolean}
             */
            autoPlaneSize: {
                set: function (value) {
                    value = !!value;
                    if (this._autoPlaneSize === value) {
                        return;
                    }
                    this._autoPlaneSize = value;
                    if (this._autoPlaneSize) {
                        if (!this._onSceneAABB) {
                            this._onSceneAABB = this.scene.on("boundary", function () {
                                var aabbDiag = xeogl.math.getAABB3Diag(this.scene.aabb);
                                var clipSize = (aabbDiag * 0.50);
                                this.planeSize = [clipSize, clipSize];
                            }, this);
                        }
                    } else {
                        if (this._onSceneAABB) {
                            this.scene.off(this._onSceneAABB);
                            this._onSceneAABB = null;
                        }
                    }
                },
                get: function () {
                    return this._autoPlaneSize;
                }
            },

            /**
             * Emissive color of this ClipControl.
             *
             * @property color
             * @default [0.4,0.4,0.4]
             * @type {Float32Array}
             */
            color: {
                set: function (value) {
                    (this._color = this._color || new xeogl.math.vec3()).set(value || [0.4, 0.4, 0.4]);
                    this._display.planeWire.material.emissive = this._color;
                },
                get: function () {
                    return this._color;
                }
            },

            /**
             Indicates whether this ClipControl is filled with color or just wireframe.

             @property solid
             @default true
             @type Boolean
             */
            solid: {
                set: function (value) {
                    this._solid = value !== false;
                    this._display.planeSolid.visible = this._solid && this._visible;
                },
                get: function () {
                    return this._solid;
                }
            },

            /**
             * Visibility of this ClipControl.
             *
             * @property visible
             * @type Boolean
             * @default true
             */
            visible: {
                set: function (value) {
                    value = !!value;
                    if (this._visible === value) {
                        return;
                    }
                    this._visible = value;
                    for (var id in this._display) {
                        if (this._display.hasOwnProperty(id)) {
                            this._display[id].visible = value;
                        }
                    }
                },
                get: function () {
                    return this._visible;
                }
            }
        },

        _setGumballPos: function (xyz) {
            this._clipPos.set(xyz);
            this._gumballGroup.position = xyz;
        },

        _setGumballDir: (function () {
            var zeroVec = new Float32Array([0, 0, 1]);
            var quat = new Float32Array(4);
            return function (xyz) {
                this._clipStartDir.set(xyz);
                xeogl.math.vec3PairToQuaternion(zeroVec, xyz, quat);
                this._gumballGroup.quaternion = quat;
            };
        })(),

        _initEntities: function () {

            // Option for xeogl.Group.addChild(), to prevent child xeogl.Objects from inheriting
            // state from their parent xeogl.Group, such as 'pickable', 'visible', 'collidable' etc.
            // Although, the children's transforms are relative to the xeogl.Group.
            const DONT_INHERIT_GROUP_STATE = false;

            // Positions, rotates & scales all Meshes as a group;
            // Meshes still have their relative transforms.
            var gumballGroup = this._gumballGroup = new xeogl.Group(this, {
                //scale: [10, 10, 0],
                position: [0, 0, 0]
            });

            var radius = 1.2;
            var hoopRadius = radius - 0.2;

            var geometries = {

                arrowHead: new xeogl.CylinderGeometry(this, {
                    radiusTop: 0.001,
                    radiusBottom: 0.10,
                    height: 0.2,
                    radialSegments: 16,
                    heightSegments: 1,
                    openEnded: false
                }),
                curve: new xeogl.TorusGeometry(this, {
                    radius: hoopRadius,
                    tube: 0.0175,
                    radialSegments: 64,
                    tubeSegments: 14,
                    arc: (Math.PI * 2.0) / 4.0
                }),
                hoop: new xeogl.TorusGeometry(this, {
                    radius: hoopRadius,
                    tube: 0.0175,
                    radialSegments: 64,
                    tubeSegments: 8,
                    arc: (Math.PI * 2.0)
                }),
                curvePickable: new xeogl.TorusGeometry(this, {
                    radius: hoopRadius,
                    tube: 0.06,
                    radialSegments: 64,
                    tubeSegments: 14,
                    arc: (Math.PI * 2.0) / 4.0
                }),
                axis: new xeogl.CylinderGeometry(scene, {
                    radiusTop: 0.0175,
                    radiusBottom: 0.0175,
                    height: radius,
                    radialSegments: 20,
                    heightSegments: 1,
                    openEnded: false
                })
            };

            var materials = {
                pickable: new xeogl.PhongMaterial(this, {
                    diffuse: [1, 1, 0],
                    alpha: 0, // Invisible
                    alphaMode: "blend"
                }),
                red: new xeogl.PhongMaterial(scene, {
                    diffuse: [1, 0.3, 0.3],
                    ambient: [0.0, 0.0, 0.0],
                    specular: [.6, .6, .3],
                    shininess: 80,
                    lineWidth: 2
                }),
                transparentRed: new xeogl.PhongMaterial(scene, {
                    diffuse: [1, 0.3, 0.3],
                    ambient: [0.0, 0.0, 0.0],
                    specular: [.6, .6, .3],
                    shininess: 80,
                    lineWidth: 2,
                    alpha: 0.6,
                    alphaMode: "blend"
                }),
                highlightRed: new xeogl.GhostMaterial(scene, {
                    edges: false,
                    fill: true,
                    fillColor: [1, 0, 0],
                    fillAlpha: 0.5,
                    vertices: false
                }),
                labelRed: new xeogl.PhongMaterial(scene, {
                    emissive: [1, 0.3, 0.3],
                    ambient: [0.0, 0.0, 0.0],
                    specular: [.6, .6, .3],
                    shininess: 80,
                    lineWidth: 3
                }),
                green: new xeogl.PhongMaterial(scene, {
                    diffuse: [0.3, 1, 0.3],
                    ambient: [0.0, 0.0, 0.0],
                    specular: [.6, .6, .3],
                    shininess: 80,
                    lineWidth: 2
                }),
                highlightGreen: new xeogl.GhostMaterial(scene, {
                    edges: false,
                    fill: true,
                    fillColor: [0, 1, 0],
                    fillAlpha: 0.5,
                    vertices: false
                }),
                transparentGreen: new xeogl.PhongMaterial(scene, {
                    diffuse: [0.3, 1.0, 0.3],
                    ambient: [0.0, 0.0, 0.0],
                    specular: [.6, .6, .3],
                    shininess: 80,
                    lineWidth: 2,
                    alpha: 0.4,
                    alphaMode: "blend"
                }),
                labelGreen: new xeogl.PhongMaterial(scene, { // Green by convention
                    emissive: [0.3, 1, 0.3],
                    ambient: [0.0, 0.0, 0.0],
                    specular: [.6, .6, .3],
                    shininess: 80,
                    lineWidth: 3
                }),
                blue: new xeogl.PhongMaterial(scene, { // Blue by convention
                    diffuse: [0.3, 0.3, 1],
                    ambient: [0.0, 0.0, 0.0],
                    specular: [.6, .6, .3],
                    shininess: 80,
                    lineWidth: 2
                }),
                highlightBlue: new xeogl.GhostMaterial(scene, {
                    edges: false,
                    fill: true,
                    fillColor: [0, 0, 1],
                    fillAlpha: 0.5,
                    vertices: false
                }),
                transparentBlue: new xeogl.PhongMaterial(scene, {
                    diffuse: [0.3, 0.3, 1.0],
                    ambient: [0.0, 0.0, 0.0],
                    specular: [.6, .6, .3],
                    shininess: 80,
                    lineWidth: 2,
                    alpha: 0.4,
                    alphaMode: "blend"
                }),
                labelBlue: new xeogl.PhongMaterial(scene, {
                    emissive: [0.3, 0.3, 1],
                    ambient: [0.0, 0.0, 0.0],
                    specular: [.6, .6, .3],
                    shininess: 80,
                    lineWidth: 3
                }),
                ball: new xeogl.PhongMaterial(scene, {
                    diffuse: [0.5, 0.5, 0.5],
                    ambient: [0.0, 0.0, 0.0],
                    specular: [.6, .6, .3],
                    shininess: 80,
                    lineWidth: 2
                }),
                highlightBall: new xeogl.GhostMaterial(scene, {
                    edges: false,
                    fill: true,
                    fillColor: [0.5, 0.5, 0.5],
                    fillAlpha: 0.5,
                    vertices: false
                }),
                highlightPlane: new xeogl.GhostMaterial(scene, {
                    edges: true,
                    edgeWidth: 3,
                    fill: false,
                    fillColor: [0.5, 0.5, .5],
                    fillAlpha: 0.5,
                    vertices: false
                })
            };

            this._display = {
                planeWire: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: new xeogl.Geometry(this, {
                        primitive: "lines",
                        positions: [
                            1.1, 1.1, 0.0, 1.1, -1.1, 0.0, // 0
                            -1.1, -1.1, 0.0, -1.1, 1.1, 0.0, // 1
                            1.1, 1.1, -0.0, 1.1, -1.1, -0.0, // 2
                            -1.1, -1.1, -0.0, -1.1, 1.1, -0.0 // 3
                        ],
                        indices: [0, 1, 0, 3, 1, 2, 2, 3]
                    }),
                    highlight: true,
                    highlightMaterial: materials.highlightPlane,
                    material: new xeogl.PhongMaterial(this, {
                        emissive: [1, 0, 0],
                        diffuse: [0, 0, 0],
                        lineWidth: 2
                    }),
                    pickable: false,
                    collidable: true,
                    clippable: false
                }), DONT_INHERIT_GROUP_STATE),

                planeSolid: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: new xeogl.Geometry(this, {
                        primitive: "triangles",
                        positions: [
                            1.1, 1.1, 0.0, 1.1, -1.1, 0.0, // 0
                            -1.1, -1.1, 0.0, -1.1, 1.1, 0.0, // 1
                            1.1, 1.1, -0.0, 1.1, -1.1, -0.0, // 2
                            -1.1, -1.1, -0.0, -1.1, 1.1, -0.0 // 3
                        ],
                        indices: [0, 1, 2, 2, 3, 0]
                    }),
                    highlight: true,
                    highlightMaterial: materials.highlightPlane,
                    material: new xeogl.PhongMaterial(this, {
                        emissive: [0, 0, 0],
                        diffuse: [0, 0, 0],
                        specular: [1, 1, 1],
                        shininess: 120,
                        alpha: 0.3,
                        alphaMode: "blend",
                        backfaces: true
                    }),
                    pickable: false,
                    collidable: true,
                    clippable: false,
                    backfaces: true
                }), DONT_INHERIT_GROUP_STATE),

                xRedCurve: gumballGroup.addChild(new xeogl.Mesh(this, { // Red hoop about Y-axis
                    geometry: geometries.curve,
                    material: materials.red,
                    highlight: true,
                    highlightMaterial: materials.highlightRed,
                    matrix: (function () {
                        var rotate2 = xeogl.math.rotationMat4v(90 * xeogl.math.DEGTORAD, [0, 1, 0], xeogl.math.identityMat4());
                        var rotate1 = xeogl.math.rotationMat4v(270 * xeogl.math.DEGTORAD, [1, 0, 0], xeogl.math.identityMat4());
                        return xeogl.math.mulMat4(rotate1, rotate2, xeogl.math.identityMat4());
                    })(),
                    pickable: false,
                    collidable: true,
                    clippable: false,
                    backfaces: true
                }), DONT_INHERIT_GROUP_STATE),

                xRedCurvePickable: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: geometries.curvePickable,
                    material: materials.pickable,
                    matrix: (function () {
                        var rotate2 = xeogl.math.rotationMat4v(90 * xeogl.math.DEGTORAD, [0, 1, 0], xeogl.math.identityMat4());
                        var rotate1 = xeogl.math.rotationMat4v(270 * xeogl.math.DEGTORAD, [1, 0, 0], xeogl.math.identityMat4());
                        return xeogl.math.mulMat4(rotate1, rotate2, xeogl.math.identityMat4());
                    })(),
                    pickable: true,
                    collidable: true,
                    clippable: false
                }), DONT_INHERIT_GROUP_STATE),



                yGreenCurve: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: geometries.curve,
                    material: materials.green,
                    highlight: true,
                    highlightMaterial: materials.highlightGreen,
                    rotation: [-90, 0, 0],
                    pickable: false,
                    collidable: true,
                    clippable: false,
                    backfaces: true
                }), DONT_INHERIT_GROUP_STATE),

                yGreenCurvePickable: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: geometries.curvePickable,
                    material: materials.pickable,
                    rotation: [-90, 0, 0],
                    pickable: true,
                    collidable: true,
                    clippable: false
                }), DONT_INHERIT_GROUP_STATE),

                zBlueCurve: gumballGroup.addChild(new xeogl.Mesh(this, { // Blue hoop about Z-axis
                    geometry: geometries.curve,
                    material: materials.blue,
                    highlight: true,
                    highlightMaterial: materials.highlightBlue,
                    matrix: (function () {
                        var rotate2 = xeogl.math.rotationMat4v(90 * xeogl.math.DEGTORAD, [1, 0, 0], xeogl.math.identityMat4());
                        var rotate1 = xeogl.math.rotationMat4v(90 * xeogl.math.DEGTORAD, [1, 0, 0], xeogl.math.identityMat4());
                        return xeogl.math.mulMat4(rotate2, rotate1, xeogl.math.identityMat4());
                    })(),
                    pickable: false,
                    collidable: true,
                    clippable: false,
                    backfaces: true
                }), DONT_INHERIT_GROUP_STATE),

                zBlueCurvePickable: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: geometries.curvePickable,
                    material: materials.pickable,

                    matrix: (function () {
                        var rotate2 = xeogl.math.rotationMat4v(90 * xeogl.math.DEGTORAD, [1, 0, 0], xeogl.math.identityMat4());
                        var rotate1 = xeogl.math.rotationMat4v(90 * xeogl.math.DEGTORAD, [1, 0, 0], xeogl.math.identityMat4());
                        return xeogl.math.mulMat4(rotate2, rotate1, xeogl.math.identityMat4());
                    })(),
                    pickable: true,
                    collidable: true,
                    clippable: false
                }), DONT_INHERIT_GROUP_STATE),

                ball: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: new xeogl.SphereGeometry(this, {
                        radius: 0.05
                    }),
                    highlight: true,
                    highlightMaterial: materials.highlightBall,
                    material: materials.ball,
                    pickable: false,
                    collidable: true,
                    clippable: false
                }), DONT_INHERIT_GROUP_STATE),

                xRedArrow: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: geometries.arrowHead,
                    material: materials.red,
                    highlight: true,
                    highlightMaterial: materials.highlightRed,
                    matrix: (function () {
                        var translate = xeogl.math.translateMat4c(0, radius + .1, 0, xeogl.math.identityMat4());
                        var rotate = xeogl.math.rotationMat4v(-90 * xeogl.math.DEGTORAD, [0, 0, 1], xeogl.math.identityMat4());
                        return xeogl.math.mulMat4(rotate, translate, xeogl.math.identityMat4());
                    })(),
                    pickable: true,
                    collidable: true,
                    clippable: false
                }), DONT_INHERIT_GROUP_STATE),

                xRedShaft: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: geometries.axis,
                    material: materials.red,
                    highlight: true,
                    highlightMaterial: materials.highlightRed,
                    matrix: (function () {
                        var translate = xeogl.math.translateMat4c(0, radius / 2, 0, xeogl.math.identityMat4());
                        var rotate = xeogl.math.rotationMat4v(-90 * xeogl.math.DEGTORAD, [0, 0, 1], xeogl.math.identityMat4());
                        return xeogl.math.mulMat4(rotate, translate, xeogl.math.identityMat4());
                    })(),
                    pickable: false,
                    collidable: true,
                    clippable: false
                }), DONT_INHERIT_GROUP_STATE),

                yGreenArrow: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: geometries.arrowHead,
                    material: materials.green,
                    highlight: true,
                    highlightMaterial: materials.highlightGreen,
                    matrix: (function () {
                        var translate = xeogl.math.translateMat4c(0, radius + .1, 0, xeogl.math.identityMat4());
                        var rotate = xeogl.math.rotationMat4v(180 * xeogl.math.DEGTORAD, [1, 0, 0], xeogl.math.identityMat4());
                        return xeogl.math.mulMat4(rotate, translate, xeogl.math.identityMat4());
                    })(),
                    pickable: true,
                    collidable: true,
                    clippable: false
                }), DONT_INHERIT_GROUP_STATE),

                yGreenShaft: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: geometries.axis,
                    material: materials.green,
                    highlight: true,
                    highlightMaterial: materials.highlightGreen,
                    position: [0, -radius / 2, 0],
                    pickable: false,
                    collidable: true,
                    clippable: false
                }), DONT_INHERIT_GROUP_STATE),

                zBlueArrow: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: geometries.arrowHead,
                    material: materials.blue,
                    highlight: true,
                    highlightMaterial: materials.highlightBlue,
                    matrix: (function () {
                        var translate = xeogl.math.translateMat4c(0, radius + .1, 0, xeogl.math.identityMat4());
                        var rotate = xeogl.math.rotationMat4v(-90 * xeogl.math.DEGTORAD, [0.8, 0, 0], xeogl.math.identityMat4());
                        return xeogl.math.mulMat4(rotate, translate, xeogl.math.identityMat4());
                    })(),
                    pickable: true,
                    collidable: true,
                    clippable: false
                }), DONT_INHERIT_GROUP_STATE),

                zBlueShaft: gumballGroup.addChild(new xeogl.Mesh(this, {
                    geometry: geometries.axis,
                    material: materials.blue,
                    highlight: true,
                    highlightMaterial: materials.highlightBlue,
                    matrix: (function () {
                        var translate = xeogl.math.translateMat4c(0, radius / 2, 0, xeogl.math.identityMat4());
                        var rotate = xeogl.math.rotationMat4v(-90 * xeogl.math.DEGTORAD, [1, 0, 0], xeogl.math.identityMat4());
                        return xeogl.math.mulMat4(rotate, translate, xeogl.math.identityMat4());
                    })(),
                    clippable: false,
                    pickable: false,
                    collidable: true
                }), DONT_INHERIT_GROUP_STATE)
            };

            this._hoops = {

                xHoop: gumballGroup.addChild(new xeogl.Mesh(this, { // Red hoop about Y-axis
                    geometry: geometries.hoop,
                    material: materials.transparentRed,
                    highlight: true,
                    highlightMaterial: materials.highlightRed,
                    matrix: (function () {
                        var rotate2 = xeogl.math.rotationMat4v(90 * xeogl.math.DEGTORAD, [0, 1, 0], xeogl.math.identityMat4());
                        var rotate1 = xeogl.math.rotationMat4v(270 * xeogl.math.DEGTORAD, [1, 0, 0], xeogl.math.identityMat4());
                        return xeogl.math.mulMat4(rotate1, rotate2, xeogl.math.identityMat4());
                    })(),
                    pickable: false,
                    collidable: true,
                    clippable: false,
                    visible: false
                }), DONT_INHERIT_GROUP_STATE),

                yHoop: gumballGroup.addChild(new xeogl.Mesh(this, { // Green hoop about Y-axis
                    geometry: geometries.hoop,
                    material: materials.transparentGreen,
                    highlight: true,
                    highlightMaterial: materials.highlightGreen,
                    rotation: [-90, 0, 0],
                    pickable: false,
                    collidable: true,
                    clippable: false,
                    visible: false
                }), DONT_INHERIT_GROUP_STATE),

                zHoop: gumballGroup.addChild(new xeogl.Mesh(this, { // Blue hoop about Z-axis
                    geometry: geometries.hoop,
                    material: materials.transparentBlue,
                    highlight: true,
                    highlightMaterial: materials.highlightBlue,
                    matrix: (function () {
                        var rotate2 = xeogl.math.rotationMat4v(90 * xeogl.math.DEGTORAD, [1, 0, 0], xeogl.math.identityMat4());
                        var rotate1 = xeogl.math.rotationMat4v(90 * xeogl.math.DEGTORAD, [1, 0, 0], xeogl.math.identityMat4());
                        return xeogl.math.mulMat4(rotate2, rotate1, xeogl.math.identityMat4());
                    })(),
                    pickable: false,
                    collidable: true,
                    clippable: false,
                    visible: false
                }), DONT_INHERIT_GROUP_STATE)
            };
        },

        _initEvents: function () {

            var self = this;
            var scene = this.scene;
            var math = xeogl.math;
            var canvas = this.scene.canvas.canvas;
            var over = false;

            const DRAG_ACTIONS = {
                none: -1,
                xPan: 0,
                yPan: 1,
                zPan: 2,
                xRotate: 3,
                yRotate: 4,
                zRotate: 5
            };

            var nextDragAction = null; // As we hover over an arrow or hoop, self is the action we would do if we then dragged it.
            var dragAction = null; // Action we're doing while we drag an arrow or hoop.

            var lastMouse = math.vec2();

            var xLocalAxis = math.vec3([1, 0, 0]);
            var yLocalAxis = math.vec3([0, 1, 0]);
            var zLocalAxis = math.vec3([0, 0, 1]);

            canvas.oncontextmenu = function (e) {
                e.preventDefault();
            };

            var getClickCoordsWithinElement = (function () {
                var coords = new Float32Array(2);
                return function (event) {
                    if (!event) {
                        event = window.event;
                        coords[0] = event.x;
                        coords[a] = event.y;
                    } else {
                        var element = event.target;
                        var totalOffsetLeft = 0;
                        var totalOffsetTop = 0;

                        while (element.offsetParent) {
                            totalOffsetLeft += element.offsetLeft;
                            totalOffsetTop += element.offsetTop;
                            element = element.offsetParent;
                        }
                        coords[0] = event.pageX - totalOffsetLeft;
                        coords[1] = event.pageY - totalOffsetTop;
                    }
                    return coords;
                };
            })();

            var localToWorldVec = (function () {
                var math = xeogl.math;
                var mat = math.mat4();
                return function (localVec, worldVec) {
                    math.quaternionToMat4(self._gumballGroup.quaternion, mat);
                    math.transformVec3(mat, localVec, worldVec);
                    math.normalizeVec3(worldVec);
                    return worldVec;
                };
            })();

            var pan = (function() {
                var p1 = math.vec3();
                var p2 = math.vec3();
                var worldAxis = math.vec4();

                return function (localAxis, fromMouse, toMouse) {
                    localToWorldVec(localAxis, worldAxis);

                    var planeNormal = getTranslationPlane(worldAxis, fromMouse, toMouse);

                    getMouseVectorOnPlane(fromMouse, planeNormal, p1);
                    getMouseVectorOnPlane(toMouse, planeNormal, p2);

                    math.subVec3(p2, p1);

                    var dot = math.dotVec3(p2, worldAxis);

                    self._clipPos[0] += worldAxis[0] * dot;
                    self._clipPos[1] += worldAxis[1] * dot;
                    self._clipPos[2] += worldAxis[2] * dot;

                    self._gumballGroup.position = self._clipPos;
                    if (self._attached.clip) {
                        self._attached.clip.pos = self._clipPos;
                    }
                }
            })();

            var getTranslationPlane = (function() {
                var planeNormal = math.vec3();
                return function(worldAxis) {
                    // find a best fit to find intersections with
                    var absX = Math.abs(worldAxis.x);
                    if (absX > Math.abs(worldAxis.y) && absX > Math.abs(worldAxis.z))
                        math.cross3Vec3(worldAxis, [0, 1, 0], planeNormal);
                    else
                        math.cross3Vec3(worldAxis, [1, 0, 0], planeNormal);

                    math.cross3Vec3(planeNormal, worldAxis, planeNormal);

                    math.normalizeVec3(planeNormal);
                    return planeNormal;
                }
            })();

            var rotate = (function() {
                var p1 = math.vec4();
                var p2 = math.vec4();
                var c = math.vec4();
                var worldAxis = math.vec4();

                return function (localAxis, fromMouse, toMouse) {
                    localToWorldVec(localAxis, worldAxis);

                    var dot;
                    var hasData = getMouseVectorOnPlane(fromMouse, worldAxis, p1);
                    hasData = hasData && getMouseVectorOnPlane(toMouse, worldAxis, p2);

                    if (!hasData) {
                        // find intersections with view plane and project down to origin
                        var planeNormal = getTranslationPlane(worldAxis, fromMouse, toMouse);

                        // the "1" makes sure the plane moves closer to the camera a bit, so the angles become workable
                        getMouseVectorOnPlane(fromMouse, planeNormal, p1, 1);
                        getMouseVectorOnPlane(toMouse, planeNormal, p2, 1);
                        dot = math.dotVec3(p1, worldAxis);
                        p1[0] -= dot * worldAxis[0];
                        p1[1] -= dot * worldAxis[1];
                        p1[2] -= dot * worldAxis[2];

                        dot = math.dotVec3(p2, worldAxis);
                        p2[0] -= dot * worldAxis[0];
                        p2[1] -= dot * worldAxis[1];
                        p2[2] -= dot * worldAxis[2];
                    }

                    math.normalizeVec3(p1);
                    math.normalizeVec3(p2);

                    dot = math.dotVec3(p1, p2);
                    // rounding errors can cause the dot to exceed its allowed range
                    dot = math.clamp(dot, -1.0, 1.0);
                    var incDegrees = Math.acos(dot) * math.RADTODEG;

                    // console.log(incDegrees);
                    math.cross3Vec3(p1, p2, c);
                    // test orientation of cross with actual axis
                    if (math.dotVec3(c, worldAxis) < 0.0)
                        incDegrees = -incDegrees;

                    self._gumballGroup.rotate(localAxis, incDegrees);
                    rotateClip();
                }})();

            // this returns the vector that points from the gumball origin to where the mouse ray intersects the plane
            var getMouseVectorOnPlane = (function() {
                var dir = math.vec4([0, 0, 0, 1]);
                var matrix = math.mat4();

                return function(mouse, axis, dest, offset) {
                    offset = offset || 0;
                    dir[0] = mouse[0] / canvas.width * 2.0 - 1.0;
                    dir[1] = -(mouse[1] / canvas.height * 2.0 - 1.0);
                    dir[2] = 0.0;
                    dir[3] = 1.0;

                    // unproject ndc to view coords
                    math.mulMat4(camera.projMatrix, camera.viewMatrix, matrix);
                    math.inverseMat4(matrix);
                    math.transformVec4(matrix, dir, dir);

                    // this is now "a" point on the ray in world space
                    math.mulVec4Scalar(dir, 1.0 / dir[3]);

                    // the direction
                    var rayO = camera.eye;
                    math.subVec4(dir, rayO, dir);

                    // the plane origin:
                    var origin = clip.pos;

                    var d = -math.dotVec3(origin, axis) - offset;
                    var dot = math.dotVec3(axis, dir);

                    console.log(Math.abs(dot));
                    if (Math.abs(dot) > 0.005) {
                        var t = -(math.dotVec3(axis, rayO) + d) / dot;
                        math.mulVec3Scalar(dir, t, dest);
                        math.addVec3(dest, rayO);
                        math.subVec3(dest, origin, dest)
                        return true;
                    }

                    return false;
                }
            })();

            var rotateClip = (function () {
                var math = xeogl.math;
                var dir = math.vec3();
                var mat = math.mat4();

                return function () {
                    if (self._attached.clip) {
                        math.quaternionToMat4(self._gumballGroup.quaternion, mat);  // << ---
                        math.transformVec3(mat, [0, 0, 1], dir);
                        self._attached.clip.dir = dir;
                    }
                };
            })();

            var pick = (function () {

                var lastHighlightedMesh;
                var lastShownMesh;

                return function pick(canvasPos) {

                    var hit = scene.pick({
                        canvasPos: canvasPos
                    });

                    if (lastHighlightedMesh) {
                        lastHighlightedMesh.highlight = false;
                    }

                    if (lastShownMesh) {
                        lastShownMesh.visible = false;
                    }

                    if (hit) {

                        var id = hit.mesh.id;

                        var highlightMesh;
                        var shownMesh;

                        switch (id) {
                            case self._display.xRedArrow.id:
                                highlightMesh = self._display.xRedArrow;
                                nextDragAction = DRAG_ACTIONS.xPan;
                                // localToWorldVec(xLocalAxis, panWorldVec);
                                // worldToCanvasVec(panWorldVec, panCanvasVec);
                                break;

                            case self._display.yGreenArrow.id:
                                highlightMesh = self._display.yGreenArrow;
                                nextDragAction = DRAG_ACTIONS.yPan;
                                // localToWorldVec(yLocalAxis, panWorldVec);
                                // worldToCanvasVec(panWorldVec, panCanvasVec);
                                break;

                            case self._display.zBlueArrow.id:
                                highlightMesh = self._display.zBlueArrow;
                                nextDragAction = DRAG_ACTIONS.zPan;
                                // localToWorldVec(zLocalAxis, panWorldVec);
                                // worldToCanvasVec(panWorldVec, panCanvasVec);
                                break;

                            case self._display.xRedCurvePickable.id:
                                highlightMesh = self._display.xRedCurve;
                                shownMesh = self._hoops.xHoop;
                                nextDragAction = DRAG_ACTIONS.xRotate;
                                break;

                            case self._display.yGreenCurvePickable.id:
                                highlightMesh = self._display.yGreenCurve;
                                shownMesh = self._hoops.yHoop;
                                nextDragAction = DRAG_ACTIONS.yRotate;
                                break;

                            case self._display.zBlueCurvePickable.id:
                                highlightMesh = self._display.zBlueCurve;
                                shownMesh = self._hoops.zHoop;
                                nextDragAction = DRAG_ACTIONS.zRotate;
                                break;

                            default:
                                nextDragAction = DRAG_ACTIONS.none;
                                return; // Not clicked an arrow or hoop
                        }

                        if (highlightMesh) {
                            highlightMesh.highlight = true;
                        }

                        if (shownMesh) {
                            shownMesh.visible = true;
                        }

                        lastHighlightedMesh = highlightMesh;
                        lastShownMesh = shownMesh;

                    } else {

                        lastHighlightedMesh = null;
                        lastShownMesh = null;
                        nextDragAction = DRAG_ACTIONS.none;
                    }
                };
            })();

            (function () {

                var down = false;

                var mouseDownLeft;
                var mouseDownMiddle;
                var mouseDownRight;

                canvas.addEventListener("mousemove", function (e) {

                    if (!self._active) {
                        return;
                    }

                    if (!over) {
                        return;
                    }

                    var coords = getClickCoordsWithinElement(e);

                    if (!down) {
                        pick(coords);
                        return;
                    }

                    var x = coords[0];
                    var y = coords[1];

                    updateControls(coords, lastMouse);

                    lastMouse[0] = x;
                    lastMouse[1] = y;
                });

                canvas.addEventListener("mousedown", function (e) {
                    if (!self._active) {
                        return;
                    }
                    if (!over) {
                        return;
                    }
                    switch (e.which) {

                        case 1: // Left button

                            mouseDownLeft = true;
                            down = true;
                            var coords = getClickCoordsWithinElement(e);

                            dragAction = nextDragAction;

                            lastMouse[0] = coords[0];
                            lastMouse[1] = coords[1];

                            break;

                        default:
                            break;
                    }
                });

                canvas.addEventListener("mouseup", function (e) {
                    if (!self._active) {
                        return;
                    }
                    switch (e.which) {
                        case 1: // Left button
                            mouseDownLeft = false;
                            break;
                        case 2: // Middle/both buttons
                            mouseDownMiddle = false;
                            break;
                        case 3: // Right button
                            mouseDownRight = false;
                            break;
                        default:
                            break;
                    }
                    down = false;
                });

                canvas.addEventListener("mouseenter", function () {
                    if (!self._active) {
                        return;
                    }
                    over = true;
                });

                canvas.addEventListener("mouseleave", function () {
                    if (!self._active) {
                        return;
                    }
                    over = false;
                });

                canvas.addEventListener("wheel", function (e) {
                    if (!self._active) {
                        return;
                    }
                    var delta = Math.max(-1, Math.min(1, -e.deltaY * 40));
                    if (delta === 0) {
                        return;
                    }
                    e.preventDefault();
                });

                function updateControls(mouse, oldMouse) {

                    if (dragAction === DRAG_ACTIONS.none) {
                        return;
                    }

                    switch (dragAction) {
                        case DRAG_ACTIONS.xPan:
                            // defined by projections on axis
                            pan(xLocalAxis, oldMouse, mouse);
                            break;
                        case DRAG_ACTIONS.yPan:
                            pan(yLocalAxis, oldMouse, mouse);
                            break;
                        case DRAG_ACTIONS.zPan:
                            pan(zLocalAxis, oldMouse, mouse);
                            break;
                        case DRAG_ACTIONS.xRotate:
                            rotate(xLocalAxis, oldMouse, mouse);
                            break;
                        case DRAG_ACTIONS.yRotate:
                            rotate(yLocalAxis, oldMouse, mouse);
                            break;
                        case DRAG_ACTIONS.zRotate:
                            rotate(zLocalAxis, oldMouse, mouse);
                            break;
                    }
                }

            })();
        },

        _destroy: function () {
            if (this._onSceneAABB) {
                this.scene.off(this._onSceneAABB);
            }
        }
    });
})();