Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <script src="https://cdn.rawgit.com/mrdoob/stats.js/r17/build/stats.min.js"></script>
<script src="https://cdn.rawgit.com/eligrey/FileSaver.js/master/dist/FileSaver.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.min.js"></script> 
<!--<script src="https://threejs.org/examples/js/controls/OrbitControls.js"></script>-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.5.1/dat.gui.min.js"></script>
<!-- <script src="https://raw.githubusercontent.com/pholmq/tsnova-resources/master/xRingGeometry.js"></script>
</script> <script type="text/javascript" src="//cdn.rawgit.com/bubblin/The-Solar-System/master/js/shared/xRingGeometry.js"></script> -->
<!-- https://raw.githubusercontent.com/pholmq/tsnova-resources/master/xRingGeometry.js
</script>  -->
<!-- <script type="text/javascript" src="//raw.githubusercontent.com/pholmq/tsnova-resources/master/xRingGeometry.js"></script>
 -->
<!-- <script src="https://raw.githubusercontent.com/pholmq/tsnova-resources/master/xRingGeometry.js"></script> -->

<div id="info">
  The TYCHOSIUM<br> 
  <a href="https://www.tychos.space" target="_blank">www.tychos.space</a>
</div>

<script data-name="orbitControls.js">
    /**
     * @author qiao / https://github.com/qiao
     * @author mrdoob / http://mrdoob.com
     * @author alteredq / http://alteredqualia.com/
     * @author WestLangley / http://github.com/WestLangley
     * @author erich666 / http://erichaines.com
     */

    // This set of controls performs orbiting, dollying (zooming), and panning.
    // Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
    //
    //    Orbit - left mouse / touch: one finger move
    //    Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
    //    Pan - right mouse, or arrow keys / touch: three finger swipe

    THREE.OrbitControls = function(object, domElement) {

        this.object = object;

        this.domElement = (domElement !== undefined) ? domElement : document;

        // Set to false to disable this control
        this.enabled = true;

        // "target" sets the location of focus, where the object orbits around
        this.target = new THREE.Vector3();

        // How far you can dolly in and out ( PerspectiveCamera only )
        this.minDistance = 0;
        this.maxDistance = Infinity;

        // How far you can zoom in and out ( OrthographicCamera only )
        this.minZoom = 0;
        this.maxZoom = Infinity;

        // How far you can orbit vertically, upper and lower limits.
        // Range is 0 to Math.PI radians.
        this.minPolarAngle = 0; // radians
        this.maxPolarAngle = Math.PI; // radians

        // How far you can orbit horizontally, upper and lower limits.
        // If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
        this.minAzimuthAngle = -Infinity; // radians
        this.maxAzimuthAngle = Infinity; // radians

        // Set to true to enable damping (inertia)
        // If damping is enabled, you must call controls.update() in your animation loop
        this.enableDamping = false;
        this.dampingFactor = 0.25;

        // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
        // Set to false to disable zooming
        this.enableZoom = true;
        this.zoomSpeed = 1.0;

        // Set to false to disable rotating
        this.enableRotate = true;
        this.rotateSpeed = 1.0;

        // Set to false to disable panning
        this.enablePan = true;
        this.keyPanSpeed = 7.0; // pixels moved per arrow key push

        // Set to true to automatically rotate around the target
        // If auto-rotate is enabled, you must call controls.update() in your animation loop
        this.autoRotate = false;
        this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60

        // Set to false to disable use of the keys
        this.enableKeys = true;

        // The four arrow keys
        this.keys = {
            LEFT: 37,
            UP: 38,
            RIGHT: 39,
            BOTTOM: 40
        };

        // Mouse buttons
        this.mouseButtons = {
            ORBIT: THREE.MOUSE.LEFT,
            ZOOM: THREE.MOUSE.MIDDLE,
            PAN: THREE.MOUSE.RIGHT
        };

        // for reset
        this.target0 = this.target.clone();
        this.position0 = this.object.position.clone();
        this.zoom0 = this.object.zoom;

        //
        // public methods
        //

        this.getPolarAngle = function() {

            return spherical.phi;

        };

        this.getAzimuthalAngle = function() {

            return spherical.theta;

        };

        this.saveState = function() {

            scope.target0.copy(scope.target);
            scope.position0.copy(scope.object.position);
            scope.zoom0 = scope.object.zoom;

        };

        this.reset = function() {

            scope.target.copy(scope.target0);
            scope.object.position.copy(scope.position0);
            scope.object.zoom = scope.zoom0;

            scope.object.updateProjectionMatrix();
            scope.dispatchEvent(changeEvent);

            scope.update();

            state = STATE.NONE;

        };

        // this method is exposed, but perhaps it would be better if we can make it private...
        this.update = function() {

            var offset = new THREE.Vector3();

            // so camera.up is the orbit axis
            var quat = new THREE.Quaternion().setFromUnitVectors(object.up, new THREE.Vector3(0, 1, 0));
            var quatInverse = quat.clone().inverse();

            var lastPosition = new THREE.Vector3();
            var lastQuaternion = new THREE.Quaternion();

            return function update() {

                var position = scope.object.position;

                offset.copy(position).sub(scope.target);

                // rotate offset to "y-axis-is-up" space
                offset.applyQuaternion(quat);

                // angle from z-axis around y-axis
                spherical.setFromVector3(offset);

                if (scope.autoRotate && state === STATE.NONE) {

                    rotateLeft(getAutoRotationAngle());

                }

                spherical.theta += sphericalDelta.theta;
                spherical.phi += sphericalDelta.phi;

                // restrict theta to be between desired limits
                spherical.theta = Math.max(scope.minAzimuthAngle, Math.min(scope.maxAzimuthAngle, spherical.theta));

                // restrict phi to be between desired limits
                spherical.phi = Math.max(scope.minPolarAngle, Math.min(scope.maxPolarAngle, spherical.phi));

                spherical.makeSafe();


                spherical.radius *= scale;

                // restrict radius to be between desired limits
                spherical.radius = Math.max(scope.minDistance, Math.min(scope.maxDistance, spherical.radius));

                // move target to panned location
                scope.target.add(panOffset);

                offset.setFromSpherical(spherical);

                // rotate offset back to "camera-up-vector-is-up" space
                offset.applyQuaternion(quatInverse);

                position.copy(scope.target).add(offset);

                scope.object.lookAt(scope.target);

                if (scope.enableDamping === true) {

                    sphericalDelta.theta *= (1 - scope.dampingFactor);
                    sphericalDelta.phi *= (1 - scope.dampingFactor);

                } else {

                    sphericalDelta.set(0, 0, 0);

                }

                scale = 1;
                panOffset.set(0, 0, 0);

                // update condition is:
                // min(camera displacement, camera rotation in radians)^2 > EPS
                // using small-angle approximation cos(x/2) = 1 - x^2 / 8

                if (zoomChanged ||
                    lastPosition.distanceToSquared(scope.object.position) > EPS ||
                    8 * (1 - lastQuaternion.dot(scope.object.quaternion)) > EPS) {

                    scope.dispatchEvent(changeEvent);

                    lastPosition.copy(scope.object.position);
                    lastQuaternion.copy(scope.object.quaternion);
                    zoomChanged = false;

                    return true;

                }

                return false;

            };

        }();

        this.dispose = function() {

            scope.domElement.removeEventListener('contextmenu', onContextMenu, false);
            scope.domElement.removeEventListener('mousedown', onMouseDown, false);
            scope.domElement.removeEventListener('wheel', onMouseWheel, false);

            scope.domElement.removeEventListener('touchstart', onTouchStart, false);
            scope.domElement.removeEventListener('touchend', onTouchEnd, false);
            scope.domElement.removeEventListener('touchmove', onTouchMove, false);

            document.removeEventListener('mousemove', onMouseMove, false);
            document.removeEventListener('mouseup', onMouseUp, false);

            window.removeEventListener('keydown', onKeyDown, false);

            //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?

        };

        //
        // internals
        //

        var scope = this;

        var changeEvent = {
            type: 'change'
        };
        var startEvent = {
            type: 'start'
        };
        var endEvent = {
            type: 'end'
        };

        var STATE = {
            NONE: -1,
            ROTATE: 0,
            DOLLY: 1,
            PAN: 2,
            TOUCH_ROTATE: 3,
            TOUCH_DOLLY: 4,
            TOUCH_PAN: 5
        };

        var state = STATE.NONE;

        var EPS = 0.000001;

        // current position in spherical coordinates
        var spherical = new THREE.Spherical();
        var sphericalDelta = new THREE.Spherical();

        var scale = 1;
        var panOffset = new THREE.Vector3();
        var zoomChanged = false;

        var rotateStart = new THREE.Vector2();
        var rotateEnd = new THREE.Vector2();
        var rotateDelta = new THREE.Vector2();

        var panStart = new THREE.Vector2();
        var panEnd = new THREE.Vector2();
        var panDelta = new THREE.Vector2();

        var dollyStart = new THREE.Vector2();
        var dollyEnd = new THREE.Vector2();
        var dollyDelta = new THREE.Vector2();

        function getAutoRotationAngle() {

            return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;

        }

        function getZoomScale() {

            return Math.pow(0.95, scope.zoomSpeed);

        }

        function rotateLeft(angle) {

            sphericalDelta.theta -= angle;

        }

        function rotateUp(angle) {

            sphericalDelta.phi -= angle;

        }

        var panLeft = function() {

            var v = new THREE.Vector3();

            return function panLeft(distance, objectMatrix) {

                v.setFromMatrixColumn(objectMatrix, 0); // get X column of objectMatrix
                v.multiplyScalar(-distance);

                panOffset.add(v);

            };

        }();

        var panUp = function() {

            var v = new THREE.Vector3();

            return function panUp(distance, objectMatrix) {

                v.setFromMatrixColumn(objectMatrix, 1); // get Y column of objectMatrix
                v.multiplyScalar(distance);

                panOffset.add(v);

            };

        }();

        // deltaX and deltaY are in pixels; right and down are positive
        var pan = function() {

            var offset = new THREE.Vector3();

            return function pan(deltaX, deltaY) {

                var element = scope.domElement === document ? scope.domElement.body : scope.domElement;

                if (scope.object instanceof THREE.PerspectiveCamera) {

                    // perspective
                    var position = scope.object.position;
                    offset.copy(position).sub(scope.target);
                    var targetDistance = offset.length();

                    // half of the fov is center to top of screen
                    targetDistance *= Math.tan((scope.object.fov / 2) * Math.PI / 180.0);

                    // we actually don't use screenWidth, since perspective camera is fixed to screen height
                    panLeft(2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix);
                    panUp(2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix);

                } else if (scope.object instanceof THREE.OrthographicCamera) {

                    // orthographic
                    panLeft(deltaX * (scope.object.right - scope.object.left) / scope.object.zoom / element.clientWidth, scope.object.matrix);
                    panUp(deltaY * (scope.object.top - scope.object.bottom) / scope.object.zoom / element.clientHeight, scope.object.matrix);

                } else {

                    // camera neither orthographic nor perspective
                    console.warn('WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.');
                    scope.enablePan = false;

                }

            };

        }();

        function dollyIn(dollyScale) {

            if (scope.object instanceof THREE.PerspectiveCamera) {

                scale /= dollyScale;

            } else if (scope.object instanceof THREE.OrthographicCamera) {

                scope.object.zoom = Math.max(scope.minZoom, Math.min(scope.maxZoom, scope.object.zoom * dollyScale));
                scope.object.updateProjectionMatrix();
                zoomChanged = true;

            } else {

                console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.');
                scope.enableZoom = false;

            }

        }

        function dollyOut(dollyScale) {

            if (scope.object instanceof THREE.PerspectiveCamera) {

                scale *= dollyScale;

            } else if (scope.object instanceof THREE.OrthographicCamera) {

                scope.object.zoom = Math.max(scope.minZoom, Math.min(scope.maxZoom, scope.object.zoom / dollyScale));
                scope.object.updateProjectionMatrix();
                zoomChanged = true;

            } else {

                console.warn('WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.');
                scope.enableZoom = false;

            }

        }

        //
        // event callbacks - update the object state
        //

        function handleMouseDownRotate(event) {

            //console.log( 'handleMouseDownRotate' );

            rotateStart.set(event.clientX, event.clientY);

        }

        function handleMouseDownDolly(event) {

            //console.log( 'handleMouseDownDolly' );

            dollyStart.set(event.clientX, event.clientY);

        }

        function handleMouseDownPan(event) {

            //console.log( 'handleMouseDownPan' );

            panStart.set(event.clientX, event.clientY);

        }

        function handleMouseMoveRotate(event) {

            //console.log( 'handleMouseMoveRotate' );

            rotateEnd.set(event.clientX, event.clientY);
            rotateDelta.subVectors(rotateEnd, rotateStart);

            var element = scope.domElement === document ? scope.domElement.body : scope.domElement;

            // rotating across whole screen goes 360 degrees around
            rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed);

            // rotating up and down along whole screen attempts to go 360, but limited to 180
            rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed);

            rotateStart.copy(rotateEnd);

            scope.update();

        }

        function handleMouseMoveDolly(event) {

            //console.log( 'handleMouseMoveDolly' );

            dollyEnd.set(event.clientX, event.clientY);

            dollyDelta.subVectors(dollyEnd, dollyStart);

            if (dollyDelta.y > 0) {

                dollyIn(getZoomScale());

            } else if (dollyDelta.y < 0) {

                dollyOut(getZoomScale());

            }

            dollyStart.copy(dollyEnd);

            scope.update();

        }

        function handleMouseMovePan(event) {

            //console.log( 'handleMouseMovePan' );

            panEnd.set(event.clientX, event.clientY);

            panDelta.subVectors(panEnd, panStart);

            pan(panDelta.x, panDelta.y);

            panStart.copy(panEnd);

            scope.update();

        }

        function handleMouseUp(event) {

            // console.log( 'handleMouseUp' );

        }

        function handleMouseWheel(event) {

            // console.log( 'handleMouseWheel' );

            if (event.deltaY < 0) {

                dollyOut(getZoomScale());

            } else if (event.deltaY > 0) {

                dollyIn(getZoomScale());

            }

            scope.update();

        }

        function handleKeyDown(event) {

            //console.log( 'handleKeyDown' );

            switch (event.keyCode) {

                case scope.keys.UP:
                    pan(0, scope.keyPanSpeed);
                    scope.update();
                    break;

                case scope.keys.BOTTOM:
                    pan(0, -scope.keyPanSpeed);
                    scope.update();
                    break;

                case scope.keys.LEFT:
                    pan(scope.keyPanSpeed, 0);
                    scope.update();
                    break;

                case scope.keys.RIGHT:
                    pan(-scope.keyPanSpeed, 0);
                    scope.update();
                    break;

            }

        }

        function handleTouchStartRotate(event) {

            //console.log( 'handleTouchStartRotate' );

            rotateStart.set(event.touches[0].pageX, event.touches[0].pageY);

        }

        function handleTouchStartDolly(event) {

            //console.log( 'handleTouchStartDolly' );

            var dx = event.touches[0].pageX - event.touches[1].pageX;
            var dy = event.touches[0].pageY - event.touches[1].pageY;

            var distance = Math.sqrt(dx * dx + dy * dy);

            dollyStart.set(0, distance);

        }

        function handleTouchStartPan(event) {

            //console.log( 'handleTouchStartPan' );

            panStart.set(event.touches[0].pageX, event.touches[0].pageY);

        }

        function handleTouchMoveRotate(event) {

            //console.log( 'handleTouchMoveRotate' );

            rotateEnd.set(event.touches[0].pageX, event.touches[0].pageY);
            rotateDelta.subVectors(rotateEnd, rotateStart);

            var element = scope.domElement === document ? scope.domElement.body : scope.domElement;

            // rotating across whole screen goes 360 degrees around
            rotateLeft(2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed);

            // rotating up and down along whole screen attempts to go 360, but limited to 180
            rotateUp(2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed);

            rotateStart.copy(rotateEnd);

            scope.update();

        }

        function handleTouchMoveDolly(event) {

            //console.log( 'handleTouchMoveDolly' );

            var dx = event.touches[0].pageX - event.touches[1].pageX;
            var dy = event.touches[0].pageY - event.touches[1].pageY;

            var distance = Math.sqrt(dx * dx + dy * dy);

            dollyEnd.set(0, distance);

            dollyDelta.subVectors(dollyEnd, dollyStart);

            if (dollyDelta.y > 0) {

                dollyOut(getZoomScale());

            } else if (dollyDelta.y < 0) {

                dollyIn(getZoomScale());

            }

            dollyStart.copy(dollyEnd);

            scope.update();

        }

        function handleTouchMovePan(event) {

            //console.log( 'handleTouchMovePan' );

            panEnd.set(event.touches[0].pageX, event.touches[0].pageY);

            panDelta.subVectors(panEnd, panStart);

            pan(panDelta.x, panDelta.y);

            panStart.copy(panEnd);

            scope.update();

        }

        function handleTouchEnd(event) {

            //console.log( 'handleTouchEnd' );

        }

        //
        // event handlers - FSM: listen for events and reset state
        //

        function onMouseDown(event) {

            if (scope.enabled === false) return;

            event.preventDefault();

            switch (event.button) {

                case scope.mouseButtons.ORBIT:

                    if (scope.enableRotate === false) return;

                    handleMouseDownRotate(event);

                    state = STATE.ROTATE;

                    break;

                case scope.mouseButtons.ZOOM:

                    if (scope.enableZoom === false) return;

                    handleMouseDownDolly(event);

                    state = STATE.DOLLY;

                    break;

                case scope.mouseButtons.PAN:

                    if (scope.enablePan === false) return;

                    handleMouseDownPan(event);

                    state = STATE.PAN;

                    break;

            }

            if (state !== STATE.NONE) {

                document.addEventListener('mousemove', onMouseMove, false);
                document.addEventListener('mouseup', onMouseUp, false);

                scope.dispatchEvent(startEvent);

            }

        }

        function onMouseMove(event) {

            if (scope.enabled === false) return;

            event.preventDefault();

            switch (state) {

                case STATE.ROTATE:

                    if (scope.enableRotate === false) return;

                    handleMouseMoveRotate(event);

                    break;

                case STATE.DOLLY:

                    if (scope.enableZoom === false) return;

                    handleMouseMoveDolly(event);

                    break;

                case STATE.PAN:

                    if (scope.enablePan === false) return;

                    handleMouseMovePan(event);

                    break;

            }

        }

        function onMouseUp(event) {

            if (scope.enabled === false) return;

            handleMouseUp(event);

            document.removeEventListener('mousemove', onMouseMove, false);
            document.removeEventListener('mouseup', onMouseUp, false);

            scope.dispatchEvent(endEvent);

            state = STATE.NONE;

        }

        function onMouseWheel(event) {

            if (scope.enabled === false || scope.enableZoom === false || (state !== STATE.NONE && state !== STATE.ROTATE)) return;

            event.preventDefault();
            event.stopPropagation();

            handleMouseWheel(event);

            scope.dispatchEvent(startEvent); // not sure why these are here...
            scope.dispatchEvent(endEvent);

        }

        function onKeyDown(event) {

            if (scope.enabled === false || scope.enableKeys === false || scope.enablePan === false) return;

            handleKeyDown(event);

        }

        function onTouchStart(event) {

            if (scope.enabled === false) return;

            switch (event.touches.length) {

                case 1: // one-fingered touch: rotate

                    if (scope.enableRotate === false) return;

                    handleTouchStartRotate(event);

                    state = STATE.TOUCH_ROTATE;

                    break;

                case 2: // two-fingered touch: dolly

                    if (scope.enableZoom === false) return;

                    handleTouchStartDolly(event);

                    state = STATE.TOUCH_DOLLY;

                    break;

                case 3: // three-fingered touch: pan

                    if (scope.enablePan === false) return;

                    handleTouchStartPan(event);

                    state = STATE.TOUCH_PAN;

                    break;

                default:

                    state = STATE.NONE;

            }

            if (state !== STATE.NONE) {

                scope.dispatchEvent(startEvent);

            }

        }

        function onTouchMove(event) {

            if (scope.enabled === false) return;

            event.preventDefault();
            event.stopPropagation();

            switch (event.touches.length) {

                case 1: // one-fingered touch: rotate

                    if (scope.enableRotate === false) return;
                    if (state !== STATE.TOUCH_ROTATE) return; // is this needed?...

                    handleTouchMoveRotate(event);

                    break;

                case 2: // two-fingered touch: dolly

                    if (scope.enableZoom === false) return;
                    if (state !== STATE.TOUCH_DOLLY) return; // is this needed?...

                    handleTouchMoveDolly(event);

                    break;

                case 3: // three-fingered touch: pan

                    if (scope.enablePan === false) return;
                    if (state !== STATE.TOUCH_PAN) return; // is this needed?...

                    handleTouchMovePan(event);

                    break;

                default:

                    state = STATE.NONE;

            }

        }

        function onTouchEnd(event) {

            if (scope.enabled === false) return;

            handleTouchEnd(event);

            scope.dispatchEvent(endEvent);

            state = STATE.NONE;

        }

        function onContextMenu(event) {

            if (scope.enabled === false) return;

            event.preventDefault();

        }

        //

        scope.domElement.addEventListener('contextmenu', onContextMenu, false);

        scope.domElement.addEventListener('mousedown', onMouseDown, false);
        scope.domElement.addEventListener('wheel', onMouseWheel, false);

        scope.domElement.addEventListener('touchstart', onTouchStart, false);
        scope.domElement.addEventListener('touchend', onTouchEnd, false);
        scope.domElement.addEventListener('touchmove', onTouchMove, false);

        window.addEventListener('keydown', onKeyDown, false);

        // force an update at start

        this.update();

    };

    THREE.OrbitControls.prototype = Object.create(THREE.EventDispatcher.prototype);
    THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;

    Object.defineProperties(THREE.OrbitControls.prototype, {

        center: {

            get: function() {

                console.warn('THREE.OrbitControls: .center has been renamed to .target');
                return this.target;

            }

        },

        // backward compatibility

        noZoom: {

            get: function() {

                console.warn('THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.');
                return !this.enableZoom;

            },

            set: function(value) {

                console.warn('THREE.OrbitControls: .noZoom has been deprecated. Use .enableZoom instead.');
                this.enableZoom = !value;

            }

        },

        noRotate: {

            get: function() {

                console.warn('THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.');
                return !this.enableRotate;

            },

            set: function(value) {

                console.warn('THREE.OrbitControls: .noRotate has been deprecated. Use .enableRotate instead.');
                this.enableRotate = !value;

            }

        },

        noPan: {

            get: function() {

                console.warn('THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.');
                return !this.enablePan;

            },

            set: function(value) {

                console.warn('THREE.OrbitControls: .noPan has been deprecated. Use .enablePan instead.');
                this.enablePan = !value;

            }

        },

        noKeys: {

            get: function() {

                console.warn('THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.');
                return !this.enableKeys;

            },

            set: function(value) {

                console.warn('THREE.OrbitControls: .noKeys has been deprecated. Use .enableKeys instead.');
                this.enableKeys = !value;

            }

        },

        staticMoving: {

            get: function() {

                console.warn('THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.');
                return !this.enableDamping;

            },

            set: function(value) {

                console.warn('THREE.OrbitControls: .staticMoving has been deprecated. Use .enableDamping instead.');
                this.enableDamping = !value;

            }

        },

        dynamicDampingFactor: {

            get: function() {

                console.warn('THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.');
                return this.dampingFactor;

            },

            set: function(value) {

                console.warn('THREE.OrbitControls: .dynamicDampingFactor has been renamed. Use .dampingFactor instead.');
                this.dampingFactor = value;

            }

        }

    });

</script>

              
            
!

CSS

              
                main { position: absolute; top: 100px; left: 100px; }
body {
  margin: 0;
  padding: 0;
  background-color: black;
}

canvas {
  display: block;
}

/*Make dat gui panel unselectable so it doesent get selected during mouse drag*/
#gui {
  
  -moz-user-select: none;
  -webkit-user-select: none;
  -ms-user-select: none;
  -o-user-select: none;
  user-select: none;
}
a {color: white;
}
a:link {
  text-decoration: none;
}
#info {
  font-family: 'Cambria';
  font-style: italic;
  font-weight: bold;
  position: absolute;
  z-index: 10;
  width: 100%;
  bottom: 92%;
  left: -43%;
  right: 0;
  /* margin: auto; */
  text-align: center;
  /* text-transform: uppercase; */
  color: #f2f5f0;
  font-size: 1.2em;
  opacity: 0.7;
}

/* #gui { */
/*   margin-right:1000px !important; */
/*   position: absolute; top: 20px; left: 20px;} */
/* #gui { position: absolute;        top: 1em;        left: 1em;        min-width: 100px;     overflow: auto;     min-height: 1000px; }
 */
              
            
!

JS

              
                /*Copyright 2018 Simon Shack, Patrik Holmqvist
The TYCHOSIUM is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.*/

//*******************************************************************************
//CAUTION CHANGE ONLY defaultSettings!!!! 
//THESE VALUES WILL OVERRIDE THOSE IN THE PLANETS.
//*******************************************************************************
const defaultSettings = 
[
{
  "name": "Earth",
  "size": 4,
  "startPos": 0,
  "speed": -0.0002479160869310127,
  "rotationSpeed": 2301.1694948647196,
  "tilt": -23.439062,
  "tiltb": 0.26,
  "orbitRadius": 37.8453,
  "orbitCentera": 0,
  "orbitCenterb": 0,
  "orbitCenterc": 0,
  "orbitTilta": 0,
  "orbitTiltb": 0
},
{
  "name": "Moon deferent A",
  "size": 0.6,
  "startPos": 226.4,
  "speed": 0.71015440177343,
  "tilt": 0,
  "orbitRadius": 0.0279352315075,
  "orbitCentera": 0,
  "orbitCenterb": 0,
  "orbitCenterc": 0,
  "orbitTilta": -0.2,
  "orbitTiltb": 0.5
},
{
  "name": "Moon deferent B",
  "size": 0.6,
  "startPos": -1.8,
  "speed": 0,
  "tilt": 0,
  "orbitRadius": 0,
  "orbitCentera": -0.38,
  "orbitCenterb": 0.22,
  "orbitCenterc": 0,
  "orbitTilta": 2.3,
  "orbitTiltb": 2.6
},
{
  "name": "Moon",
  "size": 1,
  "startPos": 261.2,
  "speed": 83.28521007,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 10,
  "orbitCentera": 0.8,
  "orbitCenterb": -0.81,
  "orbitCenterc": -0.07,
  "orbitTilta": -1.8,
  "orbitTiltb": -2.6
},
{
  "name": "Sun deferent",
  "size": 2,
  "startPos": 0,
  "speed": 0,
  "tilt": 0,
  "orbitRadius": 0,
  "orbitCentera": 1.4,
  "orbitCenterb": -0.6,
  "orbitCenterc": 0,
  "orbitTilta": 0.1,
  "orbitTiltb": 0
},
{
  "name": "Sun",
  "size": 7,
  "startPos": 0,
  "speed": 6.283185307179586,
  "rotationSpeed": 83.995,
  "tilt": 0,
  "orbitRadius": 100,
  "orbitCentera": 1.2,
  "orbitCenterb": -0.1,
  "orbitCenterc": 0,
  "orbitTilta": 0.1,
  "orbitTiltb": 0
},
{
  "name": "Mercury def A",
  "size": 0.7,
  "startPos": 0,
  "speed": 6.283185307179586,
  "tilt": 0,
  "orbitRadius": 100,
  "orbitCentera": -6.9,
  "orbitCenterb": -3.2,
  "orbitCenterc": 0,
  "orbitTilta": 0,
  "orbitTiltb": 0
},
{
  "name": "Mercury def B",
  "size": 0.7,
  "startPos": 33,
  "speed": -6.283185307179586,
  "tilt": 0,
  "orbitRadius": 0,
  "orbitCentera": 0,
  "orbitCenterb": 0,
  "orbitCenterc": 0,
  "orbitTilta": -1.3,
  "orbitTiltb": 0.5
},
{
  "name": "Mercury",
  "size": 1.53,
  "startPos": -180.8,
  "speed": 26.08763042,
  "tilt": 0,
  "orbitRadius": 38.710225,
  "orbitCentera": 0.6,
  "orbitCenterb": 3,
  "orbitCenterc": -0.1,
  "orbitTilta": 3,
  "orbitTiltb": 0.5
},
{
  "name": "Venus deferent A",
  "size": 2,
  "startPos": 0.2,
  "speed": 6.283185307179586,
  "tilt": 0,
  "orbitRadius": 100,
  "orbitCentera": 0.4,
  "orbitCenterb": 0,
  "orbitCenterc": 0,
  "orbitTilta": 0,
  "orbitTiltb": 0.15
},
{
  "name": "Venus deferent B",
  "size": 2,
  "startPos": 19,
  "speed": -6.283185307179586,
  "tilt": 0,
  "orbitRadius": 0,
  "orbitCentera": 0.3,
  "orbitCenterb": 0.6,
  "orbitCenterc": 0,
  "orbitTilta": 0,
  "orbitTiltb": 0.1
},
{
  "name": "Venus",
  "size": 3.8,
  "startPos": -26.05,
  "speed": 10.21331385,
  "tilt": 0,
  "orbitRadius": 72.327789,
  "orbitCentera": 0.65,
  "orbitCenterb": -0.6,
  "orbitCenterc": 0,
  "orbitTilta": 3.4,
  "orbitTiltb": -0.1
},
{
  "name": "Mars E deferent",
  "size": 2,
  "startPos": 0.45,
  "speed": 6.283185307179586,
  "tilt": 0,
  "orbitRadius": 100,
  "orbitCentera": 12,
  "orbitCenterb": -20.5,
  "orbitCenterc": -0.5,
  "orbitTilta": -1.45,
  "orbitTiltb": 0.5
},
{
  "name": "Mars S deferent",
  "size": 2,
  "startPos": -99.85,
  "speed": 0.3982,
  "tilt": 0,
  "orbitRadius": 7.44385,
  "orbitCentera": -0.2,
  "orbitCenterb": -0.7,
  "orbitCenterc": 0,
  "orbitTilta": -0.1,
  "orbitTiltb": -0.325
},
{
  "name": "Mars",
  "size": 2.13,
  "startPos": 104.2,
  "speed": -3.3405902,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 152.677,
  "orbitCentera": 0.2,
  "orbitCenterb": -1.5,
  "orbitCenterc": 0,
  "orbitTilta": -0.4,
  "orbitTiltb": -2.2
},
{
  "name": "Phobos",
  "size": 0.5,
  "startPos": 122,
  "speed": 6986.5,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 5,
  "orbitCentera": 0,
  "orbitCenterb": 0,
  "orbitCenterc": 0,
  "orbitTilta": 0,
  "orbitTiltb": 0
},
{
  "name": "Deimos",
  "size": 0.5,
  "startPos": 0,
  "speed": 1802,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 10,
  "orbitCentera": 0,
  "orbitCenterb": 0,
  "orbitCenterc": 0,
  "orbitTilta": 0,
  "orbitTiltb": 0
},
{
  "name": "Jupiter deferent",
  "size": 1,
  "startPos": 11.5,
  "speed": -6.283185307179586,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 15,
  "orbitCentera": 11,
  "orbitCenterb": -6,
  "orbitCenterc": 0,
  "orbitTilta": 0.18,
  "orbitTiltb": -0.5
},
{
  "name": "Jupiter",
  "size": 7.5,
  "startPos": 29.8,
  "speed": 0.529899,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 520.4,
  "orbitCentera": -46,
  "orbitCenterb": -39,
  "orbitCenterc": 0,
  "orbitTilta": 0.6,
  "orbitTiltb": -0.45
},
{
  "name": "Saturn deferent",
  "size": 1,
  "startPos": 175.2,
  "speed": -6.283185307179586,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 20,
  "orbitCentera": -22,
  "orbitCenterb": 11,
  "orbitCenterc": 0.5,
  "orbitTilta": 0.45,
  "orbitTiltb": -0.4
},
{
  "name": "Saturn",
  "size": 6.5,
  "startPos": 219.1,
  "speed": 0.213549,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 958.2,
  "orbitCentera": 68,
  "orbitCenterb": 20,
  "orbitCenterc": 0,
  "orbitTilta": -1.7,
  "orbitTiltb": 0.7
},
{
  "name": "Uranus deferent",
  "size": 1,
  "startPos": 118,
  "speed": -6.283185307179586,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 20,
  "orbitCentera": 0,
  "orbitCenterb": 0,
  "orbitCenterc": 0,
  "orbitTilta": 0,
  "orbitTiltb": 0
},
{
  "name": "Uranus",
  "size": 7.5,
  "startPos": 375,
  "speed": 0.075033,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 1920.13568,
  "orbitCentera": 66,
  "orbitCenterb": -33,
  "orbitCenterc": 0,
  "orbitTilta": -0.6,
  "orbitTiltb": -0.6
},
{
  "name": "Neptune deferent",
  "size": 1,
  "startPos": 175.2,
  "speed": -6.283185307179586,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 20,
  "orbitCentera": 0,
  "orbitCenterb": 0,
  "orbitCenterc": 0,
  "orbitTilta": 0,
  "orbitTiltb": 0
},
{
  "name": "Neptune",
  "size": 7.5,
  "startPos": 329.3,
  "speed": 0.038373,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 3004.72,
  "orbitCentera": 0,
  "orbitCenterb": 20,
  "orbitCenterc": 0,
  "orbitTilta": -1.5,
  "orbitTiltb": 1.15
},
{
  "name": "Halleys deferent",
  "size": 1,
  "startPos": 179,
  "speed": -6.283185307179586,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 20,
  "orbitCentera": -5,
  "orbitCenterb": 10,
  "orbitCenterc": 11,
  "orbitTilta": 0,
  "orbitTiltb": 0
},
{
  "name": "Halleys",
  "size": 2,
  "startPos": 76.33,
  "speed": -0.08301,
  "rotationSpeed": 0,
  "tilt": 0,
  "orbitRadius": 1674.5,
  "orbitCentera": -1540,
  "orbitCenterb": -233.5,
  "orbitCenterc": -507,
  "orbitTilta": 6.4,
  "orbitTiltb": 18.55
},
{
  "name": "Eros deferent A",
  "size": 2,
  "startPos": 0,
  "speed": 6.283185307179586,
  "tilt": 0,
  "orbitRadius": 100,
  "orbitCentera": -40,
  "orbitCenterb": 31.5,
  "orbitCenterc": -0.5,
  "orbitTilta": -7.3,
  "orbitTiltb": 3.6
},
{
  "name": "Eros deferent B",
  "size": 2,
  "startPos": 0,
  "speed": -7.291563307179587,
  "tilt": 0,
  "orbitRadius": 0,
  "orbitCentera": -16,
  "orbitCenterb": -4.5,
  "orbitCenterc": 0,
  "orbitTilta": 0,
  "orbitTiltb": 0
},
{
  "name": "Eros",
  "size": 0.7,
  "startPos": 171.8,
  "speed": 4.57668492,
  "tilt": 0,
  "orbitRadius": 145.79,
  "orbitCentera": 5.2,
  "orbitCenterb": -6,
  "orbitCenterc": 0,
  "orbitTilta": 0,
  "orbitTiltb": 0
}
//   ,
// {
//   "name": "Mans path",
//   "size": 0.1,
//   "startPos": 0,
//   "speed": 0,
//   "rotationSpeed": 0,
//   "tilt": 0,
//   "orbitRadius": 0,
//   "orbitCentera": 0,
//   "orbitCenterb": 0,
//   "orbitCenterc": 0,
//   "orbitTilta": 0,
//   "orbitTiltb": 0
// }
]
//DEFINE TIME CONSTANTS
const yearLength = 365.2425
const earthRotations = 366.2425


const sDay = 1/yearLength;
const sYear = sDay*365
const sMonth = sDay*30;
const sWeek = sDay*7;
const sHour = sDay/24;
const sMinute = sHour/60;
const sSecond = sMinute/60;

//*************************************************************
//DEFINE PLANETS (Stars, Moons and deferents conunt as planets)
//*************************************************************
var earth = {
  name: "Earth",
  size: 4,   
  color: 0x578B7C,
  sphereSegments: 320,
  startPos: 0,    
  speed: -Math.PI*2/25344,
  rotationSpeed: Math.PI*2*earthRotations,
  tilt: -23.439062,
  tiltb: 0.26,
  orbitRadius: 37.8453,
  orbitCentera: 0,
  orbitCenterb: 0,
  orbitCenterc: 0,
  
  orbitTilta: 0,
  orbitTiltb: 0,  

  textureUrl: 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/EarthDay.jpg',
  visible: true,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,
  
  traceLength : sYear * 18,
  traceStep : sDay,
  traceOn: false,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};
var moonDef = {
  name: "Moon deferent A",
  size: 0.6 ,
  color: 0x8b8b8b,
  startPos: 227.35,
  speed: 0.71018840177343,
  tilt: 0,
  orbitRadius:0.0279352315075,
  orbitCentera: -0.2,
  orbitCenterb: 0.1,
  orbitCenterc: 0,
  orbitTilta: 0.1,
  orbitTiltb: 0.2,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: false,
  isDeferent: true,
}; 

var moonDefB = {
  name: "Moon deferent B",
  size: 0.6 ,
  color: 0x8b8b8b,
  startPos: -3,
  speed:0,
  tilt: 0,
  orbitRadius: 0,
  orbitCentera: 0.1,
  orbitCenterb: 0.2,
  orbitCenterc: -0.03,
  orbitTilta: 2.3,
  orbitTiltb: 2.6,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: false,
  isDeferent: true,
}; 


var moon = {
  name: "Moon",
  size: 1,
  color: 0x8b8b8b,
  startPos: 260.8,
  speed: 83.28517,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 10,
  orbitCentera: 0.4,
  orbitCenterb: -0.9,
  orbitCenterc: 0,
  orbitTilta: -1.8,
  orbitTiltb: -2.6,

  textureUrl: 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/Moon.jpg',
  visible: true,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,
  
  traceLength : sYear * 18,
  traceStep : sDay,
  traceOn: false,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};

var sunDef = {
  name: "Sun deferent",
  size: 2 ,
  color: 0xFEAA0D,
  startPos: 0,
  speed:0,
  tilt: 0,
  orbitRadius: 0,
  orbitCentera: 1.4,
  orbitCenterb: 0,
  orbitCenterc: 0,
  orbitTilta: 0,
  orbitTiltb: 0,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: false,
  isDeferent: true,
};

var sun = {
  name: "Sun",
  size: 5,    
  //color: 0xFEAA0D,
  color: 0xFFFF00,
  startPos: 0.1,    
  speed: Math.PI*2,
  rotationSpeed: 83.995,
  tilt: 0,
  orbitRadius: 100,
  orbitCentera: 1.6,
  orbitCenterb: -0.79,
  orbitCenterc: 0,
  orbitTilta: 0.25,
  orbitTiltb: 0,  
  textureUrl: 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/Sun.jpg',
  textureTransparency: 9,
  visible: true,
  emissive: true,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,

  traceLength : sYear * 25000,
  traceStep : sYear*10,
  traceOn: false,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};

var mercuryDef = {
  name: "Mercury def A",
  size: 0.7,
  color: 0x868485,
  startPos: 0,
  speed: Math.PI*2,
  tilt: 0,
  orbitRadius: 100,
  orbitCentera: -9,
  orbitCenterb: 1.1,
  orbitCenterc: -0.1,
  orbitTilta: 0.6,
  orbitTiltb: 0,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: false,
  isDeferent: true,
};

var mercuryDefB = {
  name: "Mercury def B",
  size: 0.7,
  color: 0x868485,
  startPos: 17.3,
  speed: -6.283185307179586,
  tilt: 0,
  orbitRadius: 0,
  orbitCentera: 0,
  orbitCenterb: 0.4,
  orbitCenterc: 0,
  orbitTilta: -1.5,
  orbitTiltb: 0.6,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: false,
  isDeferent: true,
};

var mercury = {
  name: "Mercury",
  size: 1.4,
  color: 0x868485,
  startPos:-164.7,
  speed: 26.087623,
  tilt: 0,
  orbitRadius: 38.710225,
  orbitCentera:1.2,
  orbitCenterb: -1.2,
  orbitCenterc: 0,
  orbitTilta: 4,
  orbitTiltb: 1.3, 

  textureUrl: 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/Mercury.jpg',
  visible: true,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,

  traceLength : sYear * 14,
  traceStep : sDay,
  traceOn: false,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};


var venusDef = {
  name: "Venus deferent A",
  size: 2,
  color: 0xA57C1B,
  startPos: 0,
  speed: Math.PI*2,
  tilt: 0,
  orbitRadius: 100,
  orbitCentera: 1.66,
  orbitCenterb: -0.15,
  orbitCenterc: 0,
  orbitTilta: 0,
  orbitTiltb: -0.15,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,
  isDeferent: true,
};

var venusDefB = {
  name: "Venus deferent B",
  size: 2,
  color: 0xA57C1B,
  startPos: 13,
  speed: -6.283185307179586,
  tilt: 0,
  orbitRadius: 0,
  orbitCentera: 0,
  orbitCenterb: -0.15,
  orbitCenterc: 0.2,
  orbitTilta: 0,
  orbitTiltb: 0.3,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,
  isDeferent: true,
};

var venus = {
  name: "Venus",
  size: 3.9,
  color: 0xA57C1B,
  startPos:-20.7,
  speed: 10.2133116,
  //10.213454 - twoPI
  // speed: 10.213454,
  //speed: 41197.22326*twoPI/TGY,
  tilt: 0,
  orbitRadius: 72.327789,
  orbitCentera: 0,
  orbitCenterb: -0.15,
  orbitCenterc: 0.15,
  orbitTilta: 3.4,
  orbitTiltb: 0,
  traceLength : sYear *16,
  traceStep : sWeek,

  textureUrl: 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/VenusAtmosphere.jpg',
  visible: true,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,

  traceOn: false,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};

var marsDef = {
  name: "Mars E deferent",
  size: 2,
  color: 0x008000,
  startPos: 0.45,
  speed: 6.283185307179586,
  tilt: 0,
  orbitRadius: 100 ,
  orbitCentera: 12,
  orbitCenterb: -20.5,
  orbitCenterc: -0.5,
  orbitTilta: -1.45,
  orbitTiltb: 0.5,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  isDeferent: true,
}; 

var marsSunDef = {
  name: "Mars S deferent",
  size: 2,
  color: 0xFEAA0D,
  startPos: -99.7,
  speed: 0.398150316,
  tilt: 0,
  orbitRadius: 7.44385 ,
  orbitCentera: -0.2,
  orbitCenterb: -0.7,
  orbitCenterc: 0,
  orbitTilta: -0.1,
  orbitTiltb: -0.325,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  isDeferent: true,
}; 

var mars = {
  name: "Mars",
  size: 2.12,
  color: 0xFF0000,
  startPos: 104,
  //speed: 3.34,
  speed: -3.3406209,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 152.677,
  orbitCentera: 0.2,
  orbitCenterb: -1.5,
  orbitCenterc: 0,
  orbitTilta:-0.4,
  orbitTiltb: -2.2,
  traceLength : sYear * 44,
  traceStep : sWeek, 

  textureUrl: 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/Mars.jpg',
  visible: true,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,

  traceOn: true,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};

var phobos = {
  name: "Phobos",
  size: 0.5,   
  color: 0x8b8b8b,
  startPos: 122,    
  speed: 6986.5,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 5,
  orbitCentera: 0,
  orbitCenterb: 0,
  orbitCenterc: 0,
  orbitTilta: 0,
  orbitTiltb: 0,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
};

var deimos = {
  name: "Deimos",
  size: 0.5,   
  color: 0x8b8b8b,
  startPos: 0,    
  speed: 1802,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 10,
  orbitCentera: 0,
  orbitCenterb: 0,
  orbitCenterc: 0,
  orbitTilta: 0,
  orbitTiltb: 0,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
};

var jupiterDef = {
  name: "Jupiter deferent",
  size: 1,   
  color: 0xCDC2B2,
  startPos: 5,    
  speed:-6.283185307179586,
  rotationSpeed: 0,
  tilt: 0,

  orbitRadius: 15,
  orbitCentera: 2,
  orbitCenterb: 0,
  orbitCenterc: 0,
  orbitTilta: -0.25,
  orbitTiltb: 0.4,
  
  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  isDeferent: true,
};


var jupiter = {
  name: "Jupiter",
  size: 7.5,   
  color: 0xCDC2B2,
  startPos: 36.5,    
  speed: 0.529908,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 520.4,
  orbitCentera: -25,
  orbitCenterb: -45,
  orbitCenterc: 0,
  orbitTilta: 0.9,
  orbitTiltb: -0.7,
  traceLength : sYear * 18,
  traceStep : sWeek,
  
  textureUrl: 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/Jupiter.jpg',
  visible: true,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,

  traceOn: false,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};
var saturnusDef = {
  name: "Saturn deferent",
  size: 1,   
  color: 0xA79662,
  startPos: 160.5,    
  speed: -6.283185307179586,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 20,
  orbitCentera: 17,
  orbitCenterb: 15,
  orbitCenterc: 0,
  orbitTilta: 0.45,
  orbitTiltb: -0.4,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  isDeferent: true,
};

var saturnus = {
  name: "Saturn",
  size: 6.5,   
  color: 0xA79662,
  startPos: 232.85,    
  speed: 0.213524,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 958.2,
  orbitCentera: 50,
  orbitCenterb: 35,
  orbitCenterc: 0,
  orbitTilta: -1.8,
  orbitTiltb: 0.3,
  traceLength : sYear * 45,
  traceStep : sWeek,

  textureUrl: 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/Saturn.jpg',
  ringUrl: 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/saturn-rings.png',
  ringSize: 10,
  visible: true,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,

  traceOn: false,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};

var uranusDef = {
  name: "Uranus deferent",
  size: 1,   
  color: 0xD2F9FA,
  startPos: 108.5,    
  speed: -6.283185307179586,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 20
  ,
  orbitCentera: -11,
  orbitCenterb: 11,
  orbitCenterc: 0,
  orbitTilta: 0.7,
  orbitTiltb: 0,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  isDeferent: true,
};


var uranus = {
  name: "Uranus",
  size: 7.5,   
  //color: 0xCDC2B2,
  color: 0xD2F9FA,
  //2B383A
  startPos: 384.6,    
  speed:0.0747998,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 1920.13568,
  orbitCentera: 60,
  orbitCenterb: -22,
  orbitCenterc: 0,
  orbitTilta: -0.5,
  orbitTiltb: -0.45,
  traceLength : sYear * 18,
  traceStep : sWeek,
  
  textureUrl: 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/Uranus.jpg',
  visible: true,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,
  traceOn: false,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};

var neptuneDef = {
  name: "Neptune deferent",
  size: 1,   
  color: 0x5E93F1,
  startPos: 175.2,    
  speed: -6.283185307179586,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 20
  ,
  orbitCentera: 0,
  orbitCenterb: 0,
  orbitCenterc: 0,
  orbitTilta: 0,
  orbitTiltb: 0,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  isDeferent: true,
};


var neptune = {
  name: "Neptune",
  size: 7.5,   
  color: 0x5E93F1,
  startPos: 329.3,    
  speed:0.0380799,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 3004.72,
  orbitCentera: 0,
  orbitCenterb: 20,
  orbitCenterc: 0,
  orbitTilta: -1.5,
  orbitTiltb: 1.15,
  traceLength : sYear * 18,
  traceStep : sWeek,
  
  textureUrl: 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/Neptune.jpg',
  visible: true,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,

  traceOn: false,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};

var halleysDef = {
  name: "Halleys deferent",
  size: 1,   
  color: 0xA57C1B,
  startPos: 192,    
  speed: -6.283185307179586,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 20
  ,
  orbitCentera: -7,
  orbitCenterb: 3,
  orbitCenterc: 12,
  orbitTilta: 0,
  orbitTiltb: 0,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  isDeferent: true,
};


var halleys = {
  name: "Halleys",
  size: 2,   
  color: 0x00FF00,
  planetColor: 0xFFFFFF,
  startPos: 75.25,    
  speed:-0.08301,
  rotationSpeed: 0,
  tilt: 0,
  orbitRadius: 1674.5,
  orbitCentera: -1524,
  orbitCenterb: -230,
  orbitCenterc: -509,
  orbitTilta: 7,
  orbitTiltb: 18.41,
  traceLength : sYear * 90,
  traceStep : sWeek,
  
  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,

  traceOn: false,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};

var erosDef = {
  name: "Eros deferent A",
  size: 2,
  color: 0xA57C1B,
  startPos: 0,
  speed: Math.PI*2,
  tilt: 0,
  orbitRadius: 100,
  orbitCentera: 1.66,
  orbitCenterb: -0.15,
  orbitCenterc: 0,
  orbitTilta: 0,
  orbitTiltb: -0.15,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,
  isDeferent: true,
};

var erosDefB = {
  name: "Eros deferent B",
  size: 2,
  color: 0xA57C1B,
  startPos: 13,
  speed: -6.283185307179586,
  tilt: 0,
  orbitRadius: 0,
  orbitCentera: 0,
  orbitCenterb: -0.15,
  orbitCenterc: 0.2,
  orbitTilta: 0,
  orbitTiltb: 0.3,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,
  isDeferent: true,
};

var eros = {
  name: "Eros",
  size: 3.9,
  color: 0xA57C1B,
  startPos:-20.7,
  speed: 10.2133116,
  //10.213454 - twoPI
  // speed: 10.213454,
  //speed: 41197.22326*twoPI/TGY,
  tilt: 0,
  orbitRadius: 72.327789,
  orbitCentera: 0,
  orbitCenterb: -0.15,
  orbitCenterc: 0.15,
  orbitTilta: 3.4,
  orbitTiltb: 0,
  traceLength : sYear *16,
  traceStep : sWeek,

  visible: false,
  containerObj:"",
  orbitObj:"",
  planetObj:"",
  pivotObj:"",
  axisHelper: true,

  traceOn: false,
  traceLine: false,
  traceStartPos : 0,
  traceCurrPos : 0,
  traceArrIndex : 0,
};


// var mansPath = {
//   name: "Mans path",
//   size: 0.1,   
//   color: 0x00FF00,
//   planetColor: 0xFFFFFF,
//   startPos: 0,    
//   speed: 0,
//   rotationSpeed: 0,
//   tilt: 0,
//   orbitRadius: 0,
//   orbitCentera: 0,
//   orbitCenterb: 0,
//   orbitCenterc: 0,
//   orbitTilta: 0,
//   orbitTiltb: 0,
//   traceLength : sYear * 90,
//   traceStep : sMonth,
  
//   visible: false,
//   containerObj:"",
//   orbitObj:"",
//   planetObj:"",
//   pivotObj:"",
//   axisHelper: true,

//   traceOn: false,
//   traceLine: false,
//   traceStartPos : 0,
//   traceCurrPos : 0,
//   traceArrIndex : 0,
//   isDeferent: true,
// };



//*************************************************************
//GLOBAL and GUI SETTINGS
//*************************************************************
var o = {
  'ambientLight' : 2,
  'sunLight' : 2,
  'background' : 0x000000,
  'Run' : false,
  'traceBtn' : false,
  '1 second equals' : sWeek,
  'speedFact' : sWeek,
  'speed' : 1,
  'reverse' : false,
  'Step forward' : function() {
    if (o.speedFact === sYear) {
      o.pos = dateToDays(addYears(o.Date, 1))*sDay + timeToPos(o.Time)
    } else if (o.speedFact === sYear*10 ) {
      o.pos = dateToDays(addYears(o.Date, 10))*sDay + timeToPos(o.Time)
    } else if (o.speedFact === sYear*100 ) {
      o.pos = dateToDays(addYears(o.Date, 100))*sDay + timeToPos(o.Time)
    } else if (o.speedFact === sYear*1000 ) {
      o.pos = dateToDays(addYears(o.Date, 1000))*sDay + timeToPos(o.Time)
    } else {
      o.pos += o.speedFact
    }
  },

  'Step backward' : function() {
    if (o.speedFact === sYear) {
      o.pos = dateToDays(addYears(o.Date, -1))*sDay + timeToPos(o.Time)
    } else if (o.speedFact === sYear*10 ) {
      o.pos = dateToDays(addYears(o.Date, -10))*sDay + timeToPos(o.Time)
    } else if (o.speedFact === sYear*100 ) {
      o.pos = dateToDays(addYears(o.Date, -100))*sDay + timeToPos(o.Time)
    } else if (o.speedFact === sYear*1000 ) {
      o.pos = dateToDays(addYears(o.Date, -1000))*sDay + timeToPos(o.Time)
    } else {
      o.pos -= o.speedFact
    }
  },
  'Reset' : function() {o.pos = 0; controls.reset()},
  'Today' : function() {
              const newPos = sDay * dateToDays(new Intl.DateTimeFormat("sv-SE", {year: "numeric", month: "2-digit", day: "2-digit"}).format(Date.now()))
              o.pos = newPos; controls.reset()
  },
  'Save settings' : function() {
    let subset, fText = "[";
    planets.forEach(obj => {
      subset = (({ name, size, startPos, speed, rotationSpeed, 
                  tilt, tiltb, orbitRadius, orbitCentera, orbitCenterb,
                  orbitCenterc, orbitTilta, orbitTiltb
                 }) => ({ name, size, startPos, speed, rotationSpeed, 
                         tilt, tiltb, orbitRadius, orbitCentera, orbitCenterb,
                         orbitCenterc, orbitTilta, orbitTiltb
                        }))(obj);
      fText +=  "\n" + JSON.stringify(subset, null, 2) + ","
      // obj.ra = "";
      // obj.dec = "";
      // obj.dist = "";      
    });
    fText = fText.substring(0, fText.length - 1);
    fText +="]";
    const blob = new Blob([fText], {type: "text/plain;charset=utf-8"});
    const d = new Date().toUTCString(); 

    const fileName = "TS Settings " + d + ".txt"
    saveAs(blob, fileName);
  },
  'Load settings' : function() {
    let input = document.createElement('input');
    input.type = 'file';
    input.onchange = e => { 
      let file = e.target.files[0]; 
      var reader = new FileReader();
      reader.readAsText(file,'UTF-8');

      // here we tell the reader what to do when it's done reading...
      reader.onload = readerEvent => {
        let content = readerEvent.target.result; // this is the content!
        console.log(content)
        let jsonObj = JSON.parse(content);
        planets.forEach(obj => {
          let newVals = jsonObj.find(obj2 => {
            return obj.name === obj2.name
          });
          Object.assign(obj, newVals);
          updatePlanet(obj)
          initTrace(obj)
        });
        gui.remove()
        setupGUI()
      }
    }
    input.click();
  }, 
  pos : 0,
  'Position' : "", // Dat.GUI var for pos
  'Date' : "",
  'cSphereSize' : 1,
  'zodiacSize'  : 1,
  'starDistanceScaleFact' : 1.5,
  'starDistance' : 5000,
  'starSize' : 1,
  'starNamesVisible' : false,
  'Axis helpers' : false,
  'Shadows' : false,
  'Orbits' : true,
  'Time' : "00:00:00",
  'Zoom' : 0,
  'worldCamX' : '0',
  'worldCamY' : '0',
  'worldCamZ' : '0',
  'worldCamDist' : '0',
  'worldCamDistKm' : '0',
  'worldCamRa' : '0',
  'worldCamDec' : '0',
  
  'Day' : "",
  'julianDay' : "",  
  'Line trace' : true,
  'Earth camera' : false,
  'Camera Lat': 0,
  'Camera Long': 0,
  'Polar line': false,
  'polarLineLength': 1,
  'Camera helper' : false,
  'Performance' : false,
  'camX' : 0,
  'camY' : 0,
  'camZ' : 0,
  'Size' : 1,
  'traceSize' : 1,
//  traceStartPos : 0,
  traceLength : sYear * 18,
  traceStep : sDay,
  traceCurrPos : 0,
  traceArrIndex : 0,
  Lines : true,

  "moonElongation":0.01,
  "mercuryElongation":0.01,
  "venusElongation":0.01,
  "marsElongation":0.01,
  "jupiterElongation":0.01,
  "saturnElongation":0.01,
  
  infotext: true,
  "Target" : "",
  lookAtObj : {},
}

const planets = [earth, moonDef, moonDefB, moon, sunDef, sun, mercuryDef, mercuryDefB, mercury, venusDef, venusDefB, venus, marsDef, marsSunDef, mars, phobos, deimos, jupiterDef, jupiter, saturnusDef, saturnus, uranusDef, uranus, neptuneDef, neptune, halleysDef, halleys,
erosDef, erosDefB, eros]

const tracePlanets = [moon, sun, mars, venus, mercury, jupiter, saturnus, uranus, neptune, halleys, eros]
//*************************************************************
//LOAD DEFAULT SETTINGS
//*************************************************************
let jsonObj = defaultSettings;
planets.forEach(obj => {
  let newVals = jsonObj.find(obj2 => {
    return obj.name === obj2.name
  });
  Object.assign(obj, newVals);  
  // updatePlanet(obj)
  // initTrace(obj)
});






const scene = new THREE.Scene();

//scene.background = new THREE.Color( 0xffffff );
scene.background = new THREE.Color( o.background );


const renderer = new THREE.WebGLRenderer({antialias: true});
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap; // default THREE.PCFShadowMap

document.body.appendChild(renderer.domElement);

// INIT XRING GEOMETRY AND CROSS ORIGIN TEXTURE LOADING
initXRingGeometry();
THREE.ImageUtils.crossOrigin = '';


//*************************************************************
//CREATE AND CONFIGURE PLANETS
//*************************************************************
createPlanet(earth);
createPlanet(moonDef);
createPlanet(moonDefB);
createPlanet(moon);
moon.planetObj.rotation.y = Math.PI //quick fix so that the Moon texture is turned towards Earth
createPlanet(sunDef);
createPlanet(sun);
createPlanet(venusDef);
createPlanet(venusDefB);
createPlanet(venus);
createPlanet(mercuryDef);
createPlanet(mercuryDefB);
createPlanet(mercury);
createPlanet(marsDef);
createPlanet(marsSunDef);
createPlanet(mars);
createPlanet(phobos);
createPlanet(deimos);
createPlanet(jupiterDef);
createPlanet(jupiter);
createPlanet(saturnusDef);
createPlanet(saturnus);
createPlanet(uranusDef);
createPlanet(uranus);
createPlanet(neptuneDef);
createPlanet(neptune);
createPlanet(halleysDef);
createPlanet(halleys);
createPlanet(erosDef);
createPlanet(erosDefB);
createPlanet(eros);
// createPlanet(mansPath);

earth.pivotObj.add(sunDef.containerObj);
sunDef.pivotObj.add(sun.containerObj);

earth.pivotObj.add(moonDef.containerObj);
moonDef.pivotObj.add(moonDefB.containerObj);
moonDefB.pivotObj.add(moon.containerObj);

earth.pivotObj.add(venusDef.containerObj);
venusDef.pivotObj.add(venusDefB.containerObj);
venusDefB.pivotObj.add(venus.containerObj);

earth.pivotObj.add(mercuryDef.containerObj);
mercuryDef.pivotObj.add(mercuryDefB.containerObj);
mercuryDefB.pivotObj.add(mercury.containerObj);

earth.pivotObj.add(marsDef.containerObj);
marsDef.pivotObj.add(marsSunDef.containerObj);
marsSunDef.pivotObj.add(mars.containerObj);

mars.pivotObj.add(phobos.containerObj);
mars.pivotObj.add(deimos.containerObj);

sun.pivotObj.add(jupiter.containerObj);
sun.pivotObj.add(saturnus.containerObj);

sun.pivotObj.add(jupiterDef.containerObj);
jupiterDef.pivotObj.add(jupiter.containerObj)
sun.pivotObj.add(saturnusDef.containerObj);
saturnusDef.pivotObj.add(saturnus.containerObj);

sun.pivotObj.add(uranusDef.containerObj);
uranusDef.pivotObj.add(uranus.containerObj);

sun.pivotObj.add(neptuneDef.containerObj);
neptuneDef.pivotObj.add(neptune.containerObj);

sun.pivotObj.add(halleysDef.containerObj);
halleysDef.pivotObj.add(halleys.containerObj);

earth.pivotObj.add(erosDef.containerObj);
erosDef.pivotObj.add(erosDefB.containerObj);
erosDefB.pivotObj.add(eros.containerObj);


earth.containerObj.rotation.y = Math.PI/2;
//END CREATE AND CONFIGURE PLANETS




//*************************************************************
//CREATE VALUE HOLDERS FOR Right Ascension, Declination and Distance
//*************************************************************
  planets.forEach(obj => {
    obj.ra = "";
    obj.dec = "";
    obj.dist = "";      
    obj.distKm = "";      
})

function createEarthPolarLine() {
  const material = new THREE.LineBasicMaterial({
    color: 0xffffff
  });
  const geometry = new THREE.Geometry();
  geometry.vertices.push(
    new THREE.Vector3(0,-100,0),
    new THREE.Vector3(0,100,0)
  );
  const line = new THREE.Line( geometry, material );
  line.visible = o['Polar line']
  return line
}
const polarLine = createEarthPolarLine();
earth.rotationAxis.add(polarLine);
//*************************************************************
//CREATTE BARYCENTER, CELESTIAL SPHERE AND ECLIPTIC GRID
//*************************************************************
const bGeometry = new THREE.SphereGeometry( 1, 32, 16 );
const bMaterial = new THREE.MeshBasicMaterial( { color: 0x333333 } );
const barycenter = new THREE.Mesh( new THREE.SphereGeometry( 1, 32, 16 ), new THREE.MeshBasicMaterial( { color: 0x333333 } ) );
scene.add(barycenter);
const celestialSphere = createCelestialSphere(o.starDistance)
earth.rotationAxis.add(celestialSphere);
celestialSphere.visible = false;
const csLookAtObj = new THREE.Object3D();
celestialSphere.add(csLookAtObj)

const zodiac = new THREE.PolarGridHelper( radius = 250, radials = 24, circles = 1, divisions = 64, color1 = 0x000000, color2 = 0x555555 );
const zCanvas = getCircularText("      GEMINI             TAURUS             ARIES             PISCES          AQUARIUS       CAPRICORN     SAGITTARIUS      SCORPIO             LIBRA              VIRGO                LEO               CANCER ", 800, 0, "right", false, true, "Arial", "18pt", 2);
const zTexture = new THREE.CanvasTexture(zCanvas);
const zLabelGeometry = new THREE.RingGeometry( 235, 250, 32 );
//const zLabelGeometry = new THREE.PlaneBufferGeometry(500, 500);
const zLabelMaterial = new THREE.MeshBasicMaterial({
    map: zTexture,
    side: THREE.DoubleSide,
    transparent: true,
    opacity: 1,
});
const zLabel = new THREE.Mesh(zLabelGeometry, zLabelMaterial);
zodiac.add(zLabel);
zLabel.rotation.x = -Math.PI/2
// scene.add( zodiac );
earth.pivotObj.add(zodiac);
zodiac.position.y = -3; //Set it slightly below the Barycentre to avoid planet/zodiacring interference
// zodiac.rotation.y = Math.PI/6;
zodiac.visible = false;

const plane = new THREE.GridHelper(o.starDistance*2, 30, 0x008800, 0x000088);
earth.pivotObj.add(plane);
plane.visible = false

//*************************************************************
//CREATE MILKYWAY SKYDOME
//*************************************************************
// const skyGeo = new THREE.SphereGeometry(100000, 25, 25);

// const skyTexture = new THREE.TextureLoader().load("https://raw.githubusercontent.com/pholmq/tsnova-resources/master/milkyway.jpg");


// const skyMaterial = new THREE.MeshBasicMaterial({ 
//         map: skyTexture,
// });
// const sky = new THREE.Mesh(skyGeo, skyMaterial);
// sky.material.side = THREE.BackSide;
// scene.add(sky);
//*************************************************************
//CREATE BACKGOUND STARFIELD AND PLOT NAKED EYE VISIBLE STARS
//*************************************************************
// createStarfield()
scene.updateMatrixWorld() 
const starsContainer = new THREE.Object3D();
starsContainer.applyMatrix( earth.rotationAxis.matrixWorld )
scene.add(starsContainer)
starsContainer.visible = false
// scene.updateMatrixWorld()

function createLabel(message) {
  const fontSize = 30;
  let canvas = document.createElement('canvas');
  let context = canvas.getContext('2d');
  canvas.width = 256;
  canvas.height = 128;
  context.font = fontSize + "px Arial";
  context.strokeStyle = 'black';
  context.lineWidth = 8;
  context.strokeText(message, 0, fontSize);
  context.fillStyle = 'LightGrey';
  context.fillText( message, 0, fontSize);
  let texture = new THREE.Texture(canvas) 
  texture.needsUpdate = true;
  let spriteMaterial = new THREE.SpriteMaterial( { map: texture, depthTest: false} );
  let sprite = new THREE.Sprite( spriteMaterial );
  return sprite;  
}

const bsc5url = 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/bsc5-short.json'
/*
https://github.com/brettonw/YaleBrightStarCatalog
Fields in the Short BSC5 file (empty fields are omitted):
Field	Description
HR	Harvard Revised Number = Bright Star Number
F	Flamsteed number, to be taken with the constellation name
B	Bayer designation as greek letter with superscript sequence (if multi), to be taken with the constellation name
N	The common name of the star (drawn from IAU designations and notes)
C	The traditional 3-letter abbreviation for the constellation name
RA	Right Ascension (00h 00m 00.0s), equinox J2000, epoch 2000.0
Dec	Declination (+/-00° 00′ 00″), equinox J2000, epoch 2000.0
K	An approximate color temperature of the star, computed from B-V or the SpectralCls
V	Visual magnitude
*/
fetch(bsc5url)
  .then(response => {
  return response.json();
})
  .then(bscStars => {

  bscStars.forEach(obj => {
    if (obj.N !== undefined) {    

      const starPos = new THREE.Object3D();
      starPos.rotation.z = decToRad(obj.Dec)
      starPos.rotation.y = raToRad(obj.RA) - Math.PI/2
      let starsize;
      if (obj.V < 1) {
        starsize = 12;
      } else if(obj.V > 1 && obj.V < 3) {
        starsize = 6;
      } else if(obj.V > 3 && obj.V < 5) {
        starsize = 3;
      } else {
        starsize = 1;
      }
      const star = new THREE.Mesh(
        new THREE.SphereBufferGeometry(starsize, 20, 20),

        new THREE.MeshBasicMaterial({color: colorTemperature2rgb(obj.K)})
      );
      star.position.x = o.starDistance;
      const nameTag = createLabel(obj.N);
      nameTag.visible = o.starNamesVisible;
      nameTag.position.copy(star.position)
      starPos.add(star)
      starPos.add(nameTag)
      starsContainer.add(starPos);
    }
  });
});


const constContainer = new THREE.Object3D();
scene.updateMatrixWorld()
constContainer.applyMatrix( earth.rotationAxis.matrixWorld )
scene.add(constContainer)
constContainer.visible = false;

const constellationsUrl = 'https://raw.githubusercontent.com/pholmq/tsnova-resources/master/constellations.json'
//create a blue LineBasicMaterial
const material = new THREE.LineBasicMaterial( { color: 0x0000ff } );
fetch(constellationsUrl)
  .then(response => {
  return response.json();
})
  .then(constData => {
    const cRA = constData.rightAscension
    const cDec = constData.declination
    const cAst = constData.asterismIndices
    
    let points = [];
    
    for (let i = 0; i < constData.asterismIndices.length; i++) {

      let starIndex = constData.asterismIndices[i];
      if (starIndex != -1) {

        // Compute star position.
        let ra = constData.rightAscension[starIndex];
        let dec = constData.declination[starIndex];

        let x = o.starDistance * Math.cos(dec) * Math.sin(ra);
        let y = o.starDistance * Math.sin(dec);
        let z = o.starDistance * Math.cos(dec) * Math.cos(ra);

        // points.push(new THREE.Vector3(-x, y, z));
        points.push(new THREE.Vector3(x, y, z));
      }
      else {

        // Create lines.
        const geometry = new THREE.BufferGeometry().setFromPoints( points );
        const line = new THREE.Line( geometry, material );
        constContainer.add(line);
        // Clear points array.
        points = [];
      }
    }
  });



//*************************************************************
//SETUP CAMERAS and CONTROLS
//*************************************************************
const camera = new THREE.PerspectiveCamera(15, window.innerWidth/window.innerHeight, 0.1, 10000000);
//earth.pivotObj.add(camera);
camera.position.set(0, 2500, 0);

const controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableKeys = false

//EARTH CAMERA
const camPivotX = new THREE.Object3D();
const camPivotY = new THREE.Object3D();

earth.planetObj.add(camPivotY);
camPivotY.add(camPivotX);

const cameraMount = new THREE.Object3D();
camPivotX.add(cameraMount);


//const planetCamera = new THREE.PerspectiveCamera( 1000, 1600/800, 0.0001, 2000 );
const planetCamera = new THREE.PerspectiveCamera(90, window.innerWidth/window.innerHeight, 0.001, 10000000);

// const left = -1;
// const right = 1;
// const top2 = 1;
// const bottom = -1;
// const near = 5;
// const far = 50;
//const planetCamera = new THREE.OrthographicCamera(-100, 100, 100, -100, 0.000001, 10000000);
//planetCamera.zoom = 0.2;

let SCREEN_WIDTH = window.innerWidth;
let SCREEN_HEIGHT = window.innerHeight;
let aspect = SCREEN_WIDTH / SCREEN_HEIGHT;

const frustumSize = 10;
		// cameraPerspective = new THREE.PerspectiveCamera( 50, 0.5 * aspect, 150, 1000 );

// const planetCamera = new THREE.OrthographicCamera( 0.5 * frustumSize * aspect / - 2, 0.5 * frustumSize * aspect / 2, frustumSize / 2, frustumSize / - 2, 1, 2000 );


cameraMount.add(planetCamera)

//Add the fake planet mansPath so we can trace "A mans yearly path"
// cameraMount.add(mansPath.containerObj)


cameraMount.position.z = -earth.size - 0.002

const cameraHelper = new THREE.CameraHelper( planetCamera );
scene.add( cameraHelper );
const axisHelper = new THREE.AxesHelper(10)
planetCamera.add(axisHelper)
planetCamera.rotateX(-1)

function updatePlanetCamera() {
     planetCamera.updateProjectionMatrix()
     cameraHelper.update()
}

// o['Camera helper'] = true


function trackSun() {
    camPivotX.rotation.x = o['Camera Lat'] + Math.PI/2
    camPivotY.rotation.y = o['Camera Long'] + Math.PI/2

}


window.addEventListener('keydown', function(event) {
  //event.preventDefault();
  let rotSpeed = 0.1;
  switch (event.key) {
    case "ArrowLeft" :
    case "a" :
      //planetCamera.rotateY( -rotSpeed );
      cameraMount.rotateZ( -rotSpeed );
      break;
    case "ArrowRight" :
    case "d" :
      //planetCamera.rotateY( -rotSpeed );
      cameraMount.rotateZ( rotSpeed );
      break;
    case "ArrowUp" :
    case "w" :
      planetCamera.rotateX( rotSpeed );
      break;
    case "ArrowDown":
    case "s" :
      planetCamera.rotateX( -rotSpeed );
      break;
    // case "q" :
    //   planetCamera.rotateZ( rotSpeed );
    //   break;
    // case "e" :
    //   planetCamera.rotateZ( -rotSpeed );
    //  break;
  }
});



function showHideCameraHelper () {
  axisHelper.visible = o['Camera helper']
  cameraHelper.visible = o['Camera helper']
}


//*************************************************************
//SETUP LIGHT
//*************************************************************

const ambientLight = new THREE.AmbientLight( 0x404040, o.ambientLight ); // soft white light
// const ambientLight = new THREE.AmbientLight(0xffffff, 0.3);
scene.add(ambientLight);

const light = new THREE.PointLight( 0xffffff, o.sunLight, 0 );
light.castShadow = true;
// light.position.set( 50, 50, 50 );
sun.pivotObj.add( light );

const lightMount = new THREE.Object3D();
sun.pivotObj.add( lightMount );

const lightOnSun = new THREE.SpotLight(0xffffff, 10);
lightMount.add( lightOnSun );
lightOnSun.position.y = 15
lightOnSun.angle = 0.5
lightOnSun.distance = 15
lightOnSun.target = lightMount
// const helper = new THREE.SpotLightHelper(lightOnSun);
// scene.add(helper);

const lightOnSun2 = new THREE.SpotLight(0xffffff, 10);
lightMount.add( lightOnSun2 );
lightOnSun2.position.y = -15
lightOnSun2.angle = 0.5
lightOnSun2.distance = 15
lightOnSun2.target = lightMount

const lightOnSun3 = new THREE.SpotLight(0xffffff, 10);
lightMount.add( lightOnSun3 );
lightOnSun3.position.z = 15
lightOnSun3.angle = 0.5
lightOnSun3.distance = 15
lightOnSun3.target = lightMount

const lightOnSun4 = new THREE.SpotLight(0xffffff, 10);
lightMount.add( lightOnSun4 );
lightOnSun4.position.z = -15
lightOnSun4.angle = 0.5
lightOnSun4.distance = 15
lightOnSun4.target = lightMount

const lightOnSun5 = new THREE.SpotLight(0xffffff, 10);
lightMount.add( lightOnSun5 );
lightOnSun5.position.x = 16
lightOnSun5.angle = 0.5
lightOnSun5.distance = 15
lightOnSun5.target = lightMount

const lightOnSun6 = new THREE.SpotLight(0xffffff, 10);
lightMount.add( lightOnSun6 );
lightOnSun6.position.x = -15
lightOnSun6.angle = 0.5
lightOnSun6.distance = 15
lightOnSun6.target = lightMount

moon.planetObj.castShadow = true;
earth.planetObj.receiveShadow = true;
light.shadow.camera.far = 50000
light.shadow.mapSize.width = 2560;  // 2560 4096 512 default 
light.shadow.mapSize.height = 2560; // 512 default
light.shadow.radius = 2;
// const axesHelper = new THREE.AxesHelper( 20 );
// scene.add(axesHelper);

//*************************************************************
//CREATE SETTINGGS AND SETUP GUI
//*************************************************************
setupGUI()
function setupGUI() {
  // console.log(gui)
  // if (gui) {
  //     gui.destroy(); 
  // } else {
  // }
  //var gui = new dat.GUI( { autoPlace: false } );
  var gui = new dat.GUI();
  gui.domElement.id = 'gui';
  gui.add(o, 'Date').listen().onFinishChange(() => {
    if (isValidDate(o.Date)) {
      o.pos = sDay * dateToDays(o.Date) + timeToPos(o.Time);
    }
  });
  
  gui.add(o, 'Time').name('Time (UTC)').listen().onFinishChange(function() {
    if (isValidTime(o.Time)) {
      o.pos = sDay * dateToDays(o.Date) + timeToPos(o.Time);
    } 
  });
  
  gui.add(o, 'julianDay').name('Julian day').listen().onFinishChange(() => {
    if (isNumeric(o.julianDay)) {
      o.Day = o.julianDay - 2451717;
      o.pos = sDay * o.Day + timeToPos(o.Time);
    }
  });

  let ctrlFolder = gui.addFolder('Controls')
  ctrlFolder.add(o, 'Run').listen();
  ctrlFolder.add(o, 'traceBtn').name('Trace').onFinishChange(()=>{
    tracePlanets.forEach(obj => {
      initTrace(obj);
    });
  });
  
  //  tracePlanets.forEach(obj => {
  //   folderT.add(obj, 'traceOn').name(obj.name).onFinishChange(()=>{initTrace(obj)})
  // });
  
  ctrlFolder.add(o, '1 second equals', 
                 { '1 second': sSecond, 
                  '1 minute': sMinute, 
                  '1 hour': sHour, 
                  '1 day': sDay, 
                  '1 week': sWeek, 
                  '1 month': sMonth,  
                  '1 year': sYear, 
                  '10 years': sYear*10,
                  '100 years': sYear*100,
                  '1000 years': sYear*1000,
                 }).onFinishChange(function() {
    o.speedFact = Number(o['1 second equals']);
  });
  ctrlFolder.add(o, 'speed', -5, 5).step(0.5).name('Speed multiplier');
  // ctrlFolder.add(o, 'reverse').name('Reverse direction').onFinishChange(() => {
  //   if (o.reverse) {o.speed = -1} else {o.speed = 1}
  // });
  ctrlFolder.add(o, 'Step forward' );
  ctrlFolder.add(o, 'Step backward' );
  ctrlFolder.add(o, 'Reset' );
  ctrlFolder.add(o, 'Today' );
  let planList = {}
  planets.forEach(obj => {
    if (!obj.isDeferent) {
      planList[obj.name] = obj.name
    }
  });

  ctrlFolder.add(o, 'Target', {'Barycenter': "", ...planList}).name('Look at').onFinishChange(()=>{
    // console.log(o.Target)
    o.lookAtObj = planets.find(obj => {
      return obj.name === o.Target
    })
    if (o.Target === "") {o.lookAtObj = {}}    
  });
  
  ctrlFolder.open()

  let folderCamera = gui.addFolder('Camera')  
  // folderCamera.add(o, 'worldCamX').name('X').listen().onFinishChange(function() {
  //   if (isNumeric(o.worldCamX)) { camera.position.x = o.worldCamX }
  // });
  // folderCamera.add(o, 'worldCamY').name('Y').listen().onFinishChange(function() {
  //   if (isNumeric(o.worldCamY)) { camera.position.y = o.worldCamY }
  // });
  // folderCamera.add(o, 'worldCamZ').name('Z').listen().onFinishChange(function() {
  //   if (isNumeric(o.worldCamZ)) { camera.position.z = o.worldCamZ }
  // });

  folderCamera.add(o, 'worldCamRa').name('RA').listen()
  folderCamera.add(o, 'worldCamDec').name('Dec').listen()
  // folderCamera.add(o, 'worldCamDistKm').name('Kilometers').listen()
  folderCamera.add(o, 'worldCamDist').name('AU distance').listen()

  
let folderCam = folderCamera.addFolder('Earth cam (experimental) Move w arrows')
folderCam.add(o, 'Earth camera')
  //folderCam.add(planetCamera, 'fov', 1, 180).onChange(updatePlanetCamera);
  o['Camera Lat'] = 0.01
  o['Camera Long'] = 0.01
  folderCam.add(o, 'Camera Lat', 0.00, Math.PI).listen()
  folderCam.add(o, 'Camera Long', 0.00, Math.PI*2).listen()
  o['Camera Lat'] = 0.67
  o['Camera Long'] = 0
  folderCam.add(o, 'Camera helper').onFinishChange(() => {
    showHideCameraHelper()
  });  
  
  let folderT = gui.addFolder('Trace')
  folderT.add(o, 'traceSize', 0.1, 2).name('Dot size').onChange(()=>{changeTraceScale()})

  folderT.add(o, 'Lines').onFinishChange(()=>{
    tracePlanets.forEach(obj => {
      setTraceMaterial(obj)
    });
  });

  tracePlanets.forEach(obj => {
    folderT.add(obj, 'traceOn').name(obj.name).onFinishChange(()=>{initTrace(obj)})
  });
  // folderT.open();

  
  let posFolder = gui.addFolder('Positions')
  let posPlFolder
  tracePlanets.forEach(obj => {
    posPlFolder = posFolder.addFolder(obj.name)
    posPlFolder.add(obj, 'ra').listen().name('RA')
    posPlFolder.add(obj, 'dec').listen().name('Dec')
    posPlFolder.add(obj, 'distKm').listen().name('Kilometers')
    posPlFolder.add(obj, 'dist').listen().name('AU Distance')
    posPlFolder.open()
  })

  
  

  let folderElongations=gui.addFolder("Elongations");
  folderElongations.add(o,"moonElongation").min(0.0).max(180.0).listen().name("Moon")
  folderElongations.add(o,"mercuryElongation").min(0.0).max(180.0).listen().name("Mercury")
  folderElongations.add(o,"venusElongation").min(0.0).max(180.0).listen().name("Venus")
  folderElongations.add(o,"marsElongation").min(0.0).max(180.0).listen().name("Mars")
  folderElongations.add(o,"jupiterElongation").min(0.0).max(180.0).listen().name("Jupiter")
  folderElongations.add(o,"saturnElongation").min(0.0).max(180.0).listen().name("Saturn")  
  
  function changeSphereScale() {
      celestialSphere.scale.set(o.cSphereSize, o.cSphereSize, o.cSphereSize);
    
  }
  function changeZodiacScale() {
      zodiac.scale.set(o.zodiacSize, o.zodiacSize, o.zodiacSize);
    
  }
  
  let folderO = gui.addFolder('Stars & helper objects')
  folderO.add(zodiac, 'visible').name('Zodiac');
  //folderO.add(zodiac, 'scale.y', 0.1, 200).step(0.1).name('Zodiac size');
  //folderO.add(zodiac, 'renderOrder', 0, 200);
  folderO.add(o, 'zodiacSize', 0.1, 10).step(0.1).name('Zodiac size').onChange(()=>{changeZodiacScale()})
  // folderO.add(zodiac.position, 'y', -10, 10).step(0.1).name('Zodiac position');
  
  folderO.add(o, 'Polar line').onFinishChange(()=>{
    polarLine.visible = o['Polar line']
  });
  folderO.add(o, 'polarLineLength', 0.1, 50).name('Line length').onChange(()=>{
      // polarLine.scale.x = o.polarLineLength
      polarLine.scale.y = o.polarLineLength
  });

  
  // folderO.add(o, 'Axis helpers' ).onFinishChange(()=>{
  //     showHideAxisHelpers();
  //   });
  
  folderO.add(starsContainer, 'visible' ).name('Stars');
  folderO.add(o, 'starNamesVisible').name('Star names').onChange(()=>{
    starsContainer.children.forEach(
      function(starPos) {
        const nameTag = starPos.children[1];
        nameTag.visible = o.starNamesVisible;
      });
    
  });
  folderO.add(constContainer, 'visible').name('Constellations')
  
  folderO.add(o, 'starDistanceScaleFact', 0.1, 2).step(0.1).name('Star distance').onChange(()=>{
    starsContainer.children.forEach(
      function(starPos) {
        const star = starPos.children[0];
        star.position.x = o.starDistance * o.starDistanceScaleFact;
        const nameTag = starPos.children[1];
        nameTag.position.x = o.starDistance * o.starDistanceScaleFact;
      });
    celestialSphere.scale.set(o.starDistanceScaleFact, o.starDistanceScaleFact, o.starDistanceScaleFact);
    plane.scale.set(o.starDistanceScaleFact, o.starDistanceScaleFact, o.starDistanceScaleFact);
    constContainer.scale.set(o.starDistanceScaleFact, o.starDistanceScaleFact, o.starDistanceScaleFact);

  });
  
  folderO.add(o, 'starSize', 0.1, 5).step(0.1).name('Star sizes').onChange(()=>{
    starsContainer.children.forEach(
      function(starPos) {
        const star = starPos.children[0];
        star.scale.x = o.starSize
        star.scale.y = o.starSize
        star.scale.z = o.starSize
      });
  });
 

        // const star = starPos.children[0];
//     const nametag = star.children[0];
//     const scale = scaleVec.subVectors(star.getWorldPosition(starPosVec), camera.position).length() / scaleFactor;
//     nametag.scale.set(scale, scale, 1);
//   });})
  
//   folderO.add(o, 'Stars' ).onFinishChange(()=>{
//     showHideStars();
//   });
  
//   folderO.add(o, 'Star distance', 0.1, 20).onChange(() => {
//     setStarDistance();
//   });
 folderO.add(celestialSphere, 'visible').name('Celestial sphere')
 folderO.add(plane, 'visible').name('Ecliptic grid')

  // folderO.add(o, 'cSphereSize', 0.1, 50).step(0.1).name('Sphere size').onChange(()=>{changeSphereScale()})
  // folderO.add(barycenter, 'visible').name('Barycenter')
  
  // folderO.add(o, infotext ).onFinishChange(()=>{
  //   showHideInfoText();
  // });
  // folderO.add(o, 'Performance').onFinishChange(() => {
  //   if (o.Performance) {stats.dom.style.visibility = 'visible'} 
  //   else {stats.dom.style.visibility = 'hidden'}
  // });

  function changePlanetScale(){
    planets.forEach(obj => {
      obj.planetObj.scale.x = o.Size
      obj.planetObj.scale.y = o.Size
      obj.planetObj.scale.z = o.Size
    });  
  }
  
  let folderPlanets = gui.addFolder('Planets');
  // folderPlanets.open();
  folderPlanets.add(o, 'Orbits' ).onFinishChange(()=>{
    showHideOrbits();
  });

  folderPlanets.add(o, 'Size', 0.4, 1.4).onChange(()=>{changePlanetScale()})
  planets.forEach(obj => {
    if (!obj.isDeferent) {
      folderPlanets.add(obj, 'visible').name(obj.name).onFinishChange(()=>{
        showHideObject(obj);
      });
    }
  })
  function changeTraceScale(){
    tracePlanets.forEach(obj => {
      if (obj.traceLine) {
        obj.traceLine.material.size = obj.size*10 * o.traceSize
      }
    });  
  }
  
  let sFolder = gui.addFolder('Settings')
  const lightFolder = sFolder.addFolder("Light and color")
  //lightFolder.open();
  lightFolder.add(o, 'ambientLight', 0.1, 4).name('Amb. light').onChange(()=>{
    ambientLight.intensity = o.ambientLight})
  lightFolder.add(o, 'sunLight', 0.1, 4).name('Sunlight').onChange(()=>{
    light.intensity = o.sunLight})
  lightFolder.addColor(o, 'background').onChange(()=> {
    scene.background = new THREE.Color(o.background)})

  lightFolder.add(o, 'Shadows' )
  let folderDef = sFolder.addFolder('Deferents show/hide');
  planets.forEach(obj => {
    if (obj.isDeferent) {
      folderDef.add(obj, 'visible').name(obj.name).onFinishChange(()=>{
        showHideObject(obj);
      });
    }
  })

  sFolder.add(o, 'Save settings' )
  sFolder.add(o, 'Load settings' )



  let oFolder 
  planets.forEach(obj => {
    oFolder = sFolder.addFolder(obj.name)
    addSetting(obj, 'startPos', 'Start Position', oFolder)
    addSetting(obj, 'speed', 'Speed', oFolder)
    addSetting(obj, 'orbitCentera', 'Orbit center A', oFolder)
    addSetting(obj, 'orbitCenterb', 'Orbit center B', oFolder)
    addSetting(obj, 'orbitCenterc', 'Orbit center C', oFolder)
    addSetting(obj, 'orbitTilta', 'Orbit tilt A', oFolder)
    addSetting(obj, 'orbitTiltb', 'Orbit tilt B', oFolder)
    if (obj.settingsVisible) {
      oFolder.open()
    }
  })

  function addSetting(obj, attrib, name, folder) {
    obj[name] = obj[attrib].toString()
    folder.add(obj, name).listen().onFinishChange(()=>{
      if (isNumeric(obj[name])) {
        obj[attrib] = Number(obj[name])
        obj[name] = obj[attrib].toString()
        updatePlanet(obj)
        initTrace(obj)
      } else {
        obj[name] = obj[attrib].toString()
      }
    })
  }

}  


const stats = new Stats()
document.body.appendChild( stats.dom )
if (!o.Perfomance) stats.dom.style.visibility = 'hidden';

// stats.dom.container.style.visibility = 'hidden';
const clock = new THREE.Clock(); 

window.addEventListener('resize', onWindowResize, false);
onWindowResize();


// window.addEventListener('resize', onWindowResize, false);
// onWindowResize();

// var orbit;
var pause = true;


planets.forEach(obj => {
  showHideObject(obj)
});
showHideAxisHelpers();
// showHideStars();
showHideCameraHelper();
showHideInfoText();

o.pos = 0
let currPos 
let tlapsed = 0;
renderer.render(scene, camera);//renderer needs to be called otherwise the position are wrong
const centerPosVec = new THREE.Vector3();
const starPosVec  = new THREE.Vector3();
const scaleVec = new THREE.Vector3();
//*************************************************************
//THE RENDER LOOP
//*************************************************************
function render() {
  requestAnimationFrame(render);
  stats.update();
//   if (o.Target !== "") {
  
//   camera.copy(fakeCamera)

  if (Object.keys(o.lookAtObj).length !== 0) {
    //controls.target = earth.planetObj.getWorldPosition(new THREE.Vector3())
    
    controls.target = o.lookAtObj.pivotObj.getWorldPosition(centerPosVec)
    // controls.target = o.lookAtObj.planetObj.getWorldPosition(new THREE.Vector3())
    controls.update();
  } 
  let delta = clock.getDelta();
  tlapsed += delta
  if (tlapsed > 0.05) {
    tlapsed = 0;
    o['Position'] = o.pos
    o.Day = posToDays(o.pos);
    o.julianDay = o.Day + 2451717;
    o.Date = daysToDate(o.Day)
    o.Time = posToTime(o.pos)
    o.worldCamX = Math.round(camera.position.x)
    o.worldCamY = Math.round(camera.position.y)
    o.worldCamZ = Math.round(camera.position.z)
    renderer.shadowMap.enabled = o['Shadows'];


  }
  if (o.Run) {
    o.pos += Number(o.speedFact) * o.speed * delta;
  }
  //tracePlanet(mars, o.pos);  
  // lineTrace(o.pos);
  trace(o.pos);
  moveModel(o.pos);
  updateElongations();
  updatePositions();
  trackSun();
  if (o['Earth camera']) {
    renderer.render(scene, planetCamera);

  } else {
    renderer.render(scene, camera);

  }
  
  
  starsContainer.children.forEach(function(starPos) {

    const scaleFactor = 30;
    const star = starPos.children[0];
    const nametag = starPos.children[1];
    // const scale = scaleVec.subVectors(nametag.getWorldPosition(starPosVec), camera.position).length() / scaleFactor;
    const scale = scaleVec.subVectors(star.getWorldPosition(starPosVec), camera.position).length() / scaleFactor;
    nametag.scale.set(scale, scale, 1);
    // NO EFFECT
    // nametag.position.copy(star.position) 

  });
  
  

  
}
render();
//*************************************************************
//END RENDER LOOP
//*************************************************************

function updatePositions() {
  scene.updateMatrixWorld() //No effect(?)


  const csPos = new THREE.Vector3();
  celestialSphere.getWorldPosition(csPos);

  const sphericalPos = new THREE.Spherical();

  tracePlanets.forEach(obj => {    
    const planetPos = new THREE.Vector3();
    const lookAtDir = new THREE.Vector3(0,0,1);
    obj.planetObj.getWorldPosition(planetPos)
    csLookAtObj.lookAt(planetPos)
    lookAtDir.applyQuaternion(csLookAtObj.quaternion);
    lookAtDir.setLength(csPos.distanceTo(planetPos))
    sphericalPos.setFromVector3(lookAtDir)
    obj.ra = radToRa(sphericalPos.theta)
    obj.dec = radToDec(sphericalPos.phi)
    
    if (obj.name === "Moon") {
      obj.distKm = (sphericalPos.radius/100 * 149597871/39.2078).toFixed(2)
      obj.dist = (sphericalPos.radius/100/39.2078).toFixed(8)
    } else {
      obj.distKm = (sphericalPos.radius/100 * 149597871).toFixed(2)
      obj.dist = (sphericalPos.radius/100).toFixed(8)
    }
  });
  //Get camera pos
  const cameraPos = new THREE.Vector3();
  const lookAtDir = new THREE.Vector3(0,0,1);
  camera.getWorldPosition(cameraPos)
  csLookAtObj.lookAt(cameraPos)
  lookAtDir.applyQuaternion(csLookAtObj.quaternion);
  lookAtDir.setLength(csPos.distanceTo(cameraPos));
  sphericalPos.setFromVector3(lookAtDir);
  o.worldCamRa = radToRa(sphericalPos.theta);
  o.worldCamDec = radToDec(sphericalPos.phi);
  o.worldCamDistKm = (sphericalPos.radius/100 * 149597871/39.2078).toFixed(2);
  o.worldCamDist = (sphericalPos.radius/100).toFixed(8);
  


  
}

function drawSunLine(){
  const csPos = new THREE.Vector3();
  celestialSphere.getWorldPosition(csPos);
  const lookAtDir = new THREE.Vector3(0,0,1);
  const planetPos = new THREE.Vector3();

  const sphericalPos = new THREE.Spherical();
  
  sun.planetObj.getWorldPosition(planetPos)
  csLookAtObj.lookAt(planetPos)
  lookAtDir.applyQuaternion(csLookAtObj.quaternion);
  lookAtDir.setLength(csPos.distanceTo(planetPos))
  
  
  sphericalPos.setFromVector3(lookAtDir)

  
  var material = new THREE.LineBasicMaterial({
    color: 0x0000ff
  });

  var geometry = new THREE.Geometry();
  geometry.vertices.push(
    new THREE.Vector3(0,0,0),
    new THREE.Vector3().setFromSpherical(sphericalPos)
  );

  var line = new THREE.Line( geometry, material );
  celestialSphere.add( line );
  
  console.log(sphericalPos.theta)
  
  sphericalPos.theta = sun.ra 
  sphericalPos.phi =  sun.dec
  sphericalPos.radius = sun.dist

  
  var material2 = new THREE.LineBasicMaterial({
    color: 0xff0000
  });

  var geometry2 = new THREE.Geometry();
  geometry2.vertices.push(
    new THREE.Vector3(0,0,0),
    new THREE.Vector3().setFromSpherical(sphericalPos)
  );

  var line2 = new THREE.Line( geometry2, material2 );
  celestialSphere.add( line2 );
  
  
  
}


function trace(pos) {
    tracePlanets.forEach(obj => {
      tracePlanet(obj, pos)
    });        
  // tracePlanet(mars, pos)
}

  function initTrace(obj) {
    obj.traceStartPos = obj.traceCurrPos = o.pos; 
    obj.traceArrIndex = 0;
  }

function setTraceMaterial(obj) {
  let lineMaterial 
  let lineGeometry
  if (!obj.traceLine) {
    lineMaterial = new THREE.PointsMaterial( { 
      color: obj.color,                                          
      size: obj.size*10,
      //sizeAttenuation: false,
      transparent: true,
      opacity: 0.7,
      alphaTest : 0.5,
      map: new THREE.TextureLoader().load( "https://raw.githubusercontent.com/pholmq/tsnova-resources/master/disc.png" ),
      
    } )
            
    lineGeometry = new THREE.Geometry()
    obj.traceLine = new THREE.Points( lineGeometry, lineMaterial )
    obj.traceLine.sortParticles = true;
    obj.traceLine.geometry.vertices.length = (obj.traceLength/obj.traceStep).toFixed()
  } else {
    scene.remove(obj.traceLine)
    lineMaterial = obj.traceLine.material 
    lineGeometry = obj.traceLine.geometry

  }  
  if (o.Lines) {
    obj.traceLine = new THREE.Line( lineGeometry, lineMaterial )    
  } else {
    obj.traceLine = new THREE.Points( lineGeometry, lineMaterial )    
    
  }
  scene.add(obj.traceLine)
}


function tracePlanet(obj, pos) {
  let update = false;
  if (!obj.traceOn || !o.traceBtn) { obj.traceLine.visible = false; return;}
  if (pos < obj.traceStartPos) {initTrace(obj); update = true}
  if (pos < obj.traceCurrPos) {obj.traceCurrPos = obj.traceStartPos; obj.traceArrIndex = 0; update = true}
  if (obj.traceCurrPos + obj.traceStep > pos && !update) return;

  let firstRun = false
  if (obj.traceArrIndex === 0) firstRun = true;
  
  if (!obj.traceLine) {
    setTraceMaterial(obj)
  }    
  let nextPos = obj.traceCurrPos
  let vertArray = obj.traceLine.geometry.vertices
  while(nextPos < pos) {
    moveModel(nextPos);
    earth.containerObj.updateMatrixWorld();
    let epos = new THREE.Vector3(); 
    obj.planetObj.getWorldPosition(epos); //NEEDS to be a new vector every time! (declared inside the loop)
    if (obj.traceArrIndex < vertArray.length) {
      vertArray[obj.traceArrIndex] = epos;    
      obj.traceArrIndex ++
    } else {
      for(let i = 0; i < vertArray.length-1; i++) {
        vertArray[i] = vertArray[i+1]
      }
      vertArray[vertArray.length-1] = epos;          
    }
    nextPos += obj.traceStep
  }
  if (firstRun) {
    //We need to pad the vertices array 
    let index = obj.traceArrIndex;
    while (index < obj.traceLine.geometry.vertices.length) {
      obj.traceLine.geometry.vertices[index ++] = 0;    
    }    
  }  
  obj.traceLine.geometry.verticesNeedUpdate = true;
  obj.traceCurrPos = nextPos - obj.traceStep;
  obj.traceLine.visible = true;
}

//To get the Zodiac ring to not rotate while following Earth,
//set its rotation to the negative speed of Earths PVP orbit
var zodiacRotationSpeed = 0.0002479160869310127
function moveModel(pos){
  planets.forEach(obj => {
    obj.orbitObj.rotation.y = obj.speed * pos - obj.startPos * (Math.PI/180)
    if (obj.rotationSpeed) {
      obj.planetObj.rotation.y = obj.rotationSpeed * pos 
    }
  })
 zodiac.rotation.y = -Math.PI/3 + zodiacRotationSpeed * pos
}
// Math.PI/6 + 
function onWindowResize() {
  if (o['Earth camera']) {
    planetCamera.aspect = window.innerWidth / window.innerHeight;
    planetCamera.updateProjectionMatrix();
  } else {
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

  }
  renderer.setSize(window.innerWidth, window.innerHeight);
}

function addPolarGridHelper(inplanet) {
  var polarGridHelper = new THREE.PolarGridHelper( 10, 16, 8, 64, 0x0000ff, 0x808080 );
  inplanet.add(polarGridHelper);
}

function posToDays(pos) {
  pos += sHour*12 //Set the clock to tweleve for pos 0
	return Math.floor(pos/sDay)
}

function posToTime(pos) {
  pos += sHour*12 //Set the clock to tweleve for pos 0
	let days = pos/sDay - Math.floor(pos/sDay)
  let hours = Math.floor(days*24);
  let minutes = Math.floor((days*24 - hours) * 60);
  let seconds = Math.round(((days*24 - hours) * 60 - minutes) * 60);

  if (seconds === 60) {
    seconds = 0;
    minutes += 1;
  }

  if (minutes === 60) {
    minutes = 0;
    hours += 1;
  }
  
	let hh = ("0" + hours).slice(-2);
  let mm = ("0" + minutes).slice(-2);
  let ss = ("0" + seconds).slice(-2);
  

  return hh + ":" + mm +":" + ss
}

function timeToPos(value) {
  let aTime = value.split(":");
  let pos = aTime[0] * sHour + aTime[1] * sMinute + aTime[2] * sSecond
  return pos-= sHour*12 //Set the clock to tweleve for pos 0
}

//console.log(raToRadians("00:00:60"))
function raToRadians(rightAscension) {
  const time = rightAscension.split(":");
  const deg = (Number(time[0]) + time[1]/60 + time[2]/3600)*15;
  //console.log(deg)
  return deg * (Math.PI/180);
}

function radiansToRa(radians) {
  const raDec = radians * 12/Math.PI
  const hours = Math.floor(raDec);
  const minutes = raDec % 1 
}


//console.log(decToRadians("360:00:00"))
function decToRadians(declination) {
  const time = declination.split(":");
  const deg = Number(time[0]) + time[1]/60 + time[2]/3600;
  //console.log(deg)
  return deg * (Math.PI/180);
}

function raToRadOld(ra) {
  const hours = ra.split('h')[0];
  const minutes = ra.split('h')[1].split('m')[0]
  const seconds = ra.split('h')[1].split('m')[1].split('s')[0]
  return hours*Math.PI/12 + minutes*(Math.PI/(12*60)) + seconds*(Math.PI/(12*3600))
}

   // ra: "02h31m49.09s", //02h 31m 49.09s "02h 31m 48.7s"
   //  dec: "89d15m50.8s", //89° 15′ 50.8 "+89° 15′ 51″"

function raToRad(ra) {
  ra.replace(/\s+/g, '');
  const hours = ra.split('h')[0];
  const minutes = ra.split('h')[1].split('m')[0]
  const seconds = ra.split('h')[1].split('m')[1].split('s')[0]
  return hours*Math.PI/12 + minutes*(Math.PI/(12*60)) + seconds*(Math.PI/(12*3600))
}

function decToRadOld(dec) {
  const degrees = dec.split('d')[0];
  const minutes = dec.split('d')[1].split('m')[0]
  const seconds = dec.split('d')[1].split('m')[1].split('s')[0]
  return degrees*Math.PI/180 + minutes*(Math.PI/(180*60)) + seconds*(Math.PI/(180*3600))
}

function decToRad(dec) {
  dec.replace(/\s+/g, '');
  const degrees = dec.split('°')[0];
  const minutes = dec.split('°')[1].split('′')[0]
  const seconds = dec.split('°')[1].split('′')[1].split('″')[0]
  return degrees*Math.PI/180 + minutes*(Math.PI/(180*60)) + seconds*(Math.PI/(180*3600))
}

function radToRa(rad){
  if ( rad < 0 ) {rad = rad + Math.PI*2}
  const raDec = rad * 12/Math.PI
  const hours = Math.floor(raDec);
  const minutesSeconds = (raDec - hours) * 60
  const minutes = Math.floor((raDec - hours) * 60)
  const seconds = (minutesSeconds - minutes) * 60
  return leadZero(hours) + "h" + leadZero(minutes) + "m" + leadZero(seconds.toFixed(0)) + "s"
  }

function radToDec(rad){
  if (rad <= 0) {
    rad = rad + Math.PI/2
  } else {
    rad = Math.PI/2 - rad
  }
  var degDec = rad * 180/Math.PI
  var degreesSign=""

  if(degDec<0)
  {
    degDec*=-1.0
    degreesSign="-"
  }

  const degrees = Math.floor(degDec);
  const minutesSeconds = (degDec - degrees) * 60
  const minutes = Math.floor((degDec - degrees) * 60)
  const seconds = (minutesSeconds - minutes) * 60
  return leadZero(degreesSign+degrees, true) + "\xB0" + leadZero(minutes) + "\'" + leadZero(seconds.toFixed(0)) + "\""
}

function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

function leadZero(n, plus){
    let sign;
    n < 0 ? sign = "-": sign = "";
    if (sign === "" && plus) {
      sign = "+"
    }
    n = Math.abs(n);
    return n > 9 ? sign + n: sign + "0" + n;
}


function isValidTime(value) {
  //check input
  let aTime = value.split(":");
  if (aTime.length > 3) {
    //Only hh:mm:ss
    return false;
  }
  //Check with regex that we only have numbers and a valid time
  if (!/^\d+$/.test(aTime[0]) || aTime[0].length != 2 ) return false;
  if (aTime[0] > 24) return false;
  if (!/^\d+$/.test(aTime[1]) || aTime[1].length != 2 ) return false;
  if (aTime[1] > 59) return false;
  if (!/^\d+$/.test(aTime[2]) || aTime[2].length != 2 ) return false;
  if (aTime[2] > 59) return false;

  return true;
}

function isValidDate(value) {
  //check input
  let aDate = value.split("-");
  if (aDate.length > 3) {
    //Assume we have a minus sign first
    aDate.shift();
  }
  if (aDate.length > 3) {
  	//Only year-month-day allowed
    return false;
  }
  //Check with regex that we only have numbers and a (probably) valid date
  if (!/^\d+$/.test(aDate[0]) || aDate[0].length > 20 ) {
    return false;
  }
  if (!/^\d+$/.test(aDate[1]) || aDate[1].length != 2 ) {
    return false;
  }
  if (aDate[1] > 12 || aDate[1] < 1) {
    return false;  
  }
  if (!/^\d+$/.test(aDate[2]) || aDate[2].length != 2 ) {
    return false;
  }
  if (aDate[2] > 31 || aDate[2] < 1) {
    return false;  
  }
  // if (Number(aDate[0]) === 0) return false; //Year 0 is not allowed
  if (Number(aDate[0]) === 1582 && Number(aDate[1]) === 10 ) {
    if (aDate[2] > 4 && aDate[2] < 15) return false; //Day 5-14, oct 1582 are not dates
  } 
  
  return true;
}

//console.log(dateToDays("2000-06-20"))

function dateToDays(sDate) {
  //Calculates the number of days passed since 2000-06-21 for a date. Positive or negative
  //Taken from https://alcor.concordia.ca/~gpkatch/gdate-algorithm.html
  let aDate = sDate.split("-");
  let y,m,d;
  if (aDate.length > 3) {
    //We had a minus sign first (a BC date)
    y = -Number(aDate[1]); m = Number(aDate[2]); d = Number(aDate[3]);

  } else {
    y = Number(aDate[0]); m = Number(aDate[1]); d = Number(aDate[2]);
  };
  
  if (y < 1582) return julianDateToDays(sDate);
  if (y === 1582 && m < 10) return julianDateToDays(sDate);
  if (y === 1582 && m === 10 && d < 15)  return julianDateToDays(sDate);
  
  m = (m + 9) % 12;
  y = y - Math.floor(m/10);
  return 365*y + Math.floor(y/4) - Math.floor(y/100) + Math.floor(y/400) + Math.floor((m*306 + 5)/10) + ( d - 1 ) - 730597;
};

function julianDateToDays(sDate) {
  //Calculates the number of days passed since 2000-06-21 for a date. Positive or negative
  //Taken from https://alcor.concordia.ca/~gpkatch/gdate-algorithm.html
  let aDate = sDate.split("-");
  let y,m,d, jd;
  if (aDate.length > 3) {
    //We had a minus sign first (a BC date)
    y = -Number(aDate[1]); m = Number(aDate[2]); d = Number(aDate[3]);

  } else {
    y = Number(aDate[0]); m = Number(aDate[1]); d = Number(aDate[2]);
  };
  
  // if (y < 0 ) y += 1;
  //if (y === -1) y -= 1;
  
  if (m < 3) {
  	m += 12;
    y -= 1;
  }
  //Math.trunc(x) 
  jd =  Math.trunc(365.25*(y + 4716)) + Math.trunc(30.6001*(m + 1)) + d - 1524
  
  return jd - 2451717;
};

function addYears(sDate, year) {
  let aDate = sDate.split("-");
  let y, date;
  if (aDate.length > 3) {
    //We had a minus sign first = a BC date
    y = -Number(aDate[1])
    date = (y + year) + "-" + aDate[2] + "-" + aDate[3];
  } else {
    y = Number(aDate[0])
    date = (y + year) + "-" + aDate[1] + "-" + aDate[2];
  };
  return date
}

//console.log(daysToDate(0))

function daysToDate(g) {
  if (g < -152556) return julianCalDayToDate(g); //Julian dates earlier than 1582-10-15
  g += 730597;
  let y = Math.floor((10000*g + 14780)/3652425);
  let ddd = g - (365*y + Math.floor(y/4) - Math.floor(y/100) + Math.floor(y/400));
  if (ddd < 0) {
    y = y - 1
    ddd = g - (365*y + Math.floor(y/4) - Math.floor(y/100) + Math.floor(y/400));
  };
  let mi = Math.floor((100*ddd + 52)/3060);
  let mm = (mi + 2)%12 + 1
  y = y + Math.floor((mi + 2)/12);
  let dd = ddd - Math.floor((mi*306 + 5)/10) + 1
  
  mm = ("0" + mm).slice(-2);
  dd = ("0" + dd).slice(-2);
  
  return y + "-" + mm + "-" + dd;
};

function julianCalDayToDate(g) {
  var jDay = g + 2451717 //+ 10;
  var z = Math.floor(jDay - 1721116.5);
  var r = jDay - 1721116.5 - z;
  var year = Math.floor((z - 0.25) / 365.25);
  var c = z - Math.floor(365.25 * year);
  var month = Math.trunc((5 * c + 456) / 153);
  var day = c - Math.trunc((153 * month - 457) / 5) + r - 0.5;
  if (month > 12) {
  	year = year + 1;
    month = month -12;
  }
  month = ("0" + month).slice(-2);
  day = ("0" + day).slice(-2);
  // if (year <= 0) year -= 1;
  return year + "-" + month + "-" + day
};

//Returns the angle from the sun to targetPlanet as viewed from earth using the cosine rule.
function getElongationFromSun(targetPlanet)
{
  var sunPosition=new THREE.Vector3();
  var earthPosition=new THREE.Vector3();
  var targetPlanetPosition=new THREE.Vector3();
  sun.planetObj.getWorldPosition(sunPosition);
  earth.planetObj.getWorldPosition(earthPosition);
  targetPlanet.planetObj.getWorldPosition(targetPlanetPosition);

  var earthSunDistance=earthPosition.distanceTo(sunPosition);
  var earthTargetPlanetDistance=earthPosition.distanceTo(targetPlanetPosition);
  var sunTargetPlanetDistance=sunPosition.distanceTo(targetPlanetPosition);
	
  var numerator=(Math.pow(earthSunDistance,2)+Math.pow(earthTargetPlanetDistance,2))-Math.pow(sunTargetPlanetDistance,2);
  var denominator=2.0*earthSunDistance*earthTargetPlanetDistance;
  elongationRadians=Math.acos(numerator/denominator);
  return (180.0*elongationRadians)/Math.PI;
};

function updateElongations()
{
  o["moonElongation"]=getElongationFromSun(moon);
  o["mercuryElongation"]=getElongationFromSun(mercury);
  o["venusElongation"]=getElongationFromSun(venus);
  o["marsElongation"]=getElongationFromSun(mars);
  o["jupiterElongation"]=getElongationFromSun(jupiter);
  o["saturnElongation"]=getElongationFromSun(saturnus);
};


//CREATE PLANETS

function createPlanet (pd) { //pd = Planet Data
  var orbitContainer = new THREE.Object3D();
  orbitContainer.rotation.x = pd.orbitTilta * (Math.PI/180);
  orbitContainer.rotation.z = pd.orbitTiltb * (Math.PI/180);
  orbitContainer.position.x = pd.orbitCentera;
  orbitContainer.position.z = pd.orbitCenterb;
  orbitContainer.position.y = pd.orbitCenterc;
  
  var orbit = new THREE.Object3D();
  var geometry = new THREE.CircleGeometry(pd.orbitRadius, 100);
  geometry.vertices.shift();
  
  var line = new THREE.LineLoop( geometry, new THREE.LineBasicMaterial({color: pd.color, transparent: true, opacity : 0.4} ));
  line.rotation.x = Math.PI/2;
  orbit.add(line);

  var planetMesh
  if (pd.emissive) {
    planetMesh = new THREE.MeshPhongMaterial({color: pd.color, emissive: pd.color, emissiveIntensity: 2});
  } else {
    if (pd.planetColor) { //Halleys
      planetMesh = new THREE.MeshPhongMaterial({color: pd.planetColor, emissive: pd.planetColor, emissiveIntensity: 2});
    } else {
      planetMesh = new THREE.MeshPhongMaterial({color: pd.color});
    }
  }
  
  if (pd.textureUrl) {
    const texture = new THREE.TextureLoader().load(pd.textureUrl)
    if (pd.textureTransparency) {
      planetMesh = new THREE.MeshPhongMaterial({ map: texture, bumpScale: 0.05, specular: new THREE.Color('#190909'), transparent: true, opacity: pd.textureTransparency, });

    } else {
      planetMesh = new THREE.MeshPhongMaterial({ map: texture, bumpScale: 0.05, specular: new THREE.Color('#190909') });
    }

  }
  if (pd.sphereSegments) {
    var planet = new THREE.Mesh(
    new THREE.SphereBufferGeometry(pd.size, pd.sphereSegments, pd.sphereSegments), planetMesh);  
  } else {
    var planet = new THREE.Mesh(
    new THREE.SphereBufferGeometry(pd.size, 32, 32), planetMesh);
  }

  var pivot = new THREE.Object3D();
  pivot.position.set(pd.orbitRadius, 0.0, 0.0);
  orbit.add(pivot);

  var rotationAxis = new THREE.Object3D();
  rotationAxis.position.set(pd.orbitRadius, 0.0, 0.0);
  rotationAxis.rotation.z = pd.tilt * (Math.PI/180)
  if (pd.tiltb) {
    rotationAxis.rotation.x = pd.tiltb * (Math.PI/180)
  }

  if (pd.ringUrl) {
    var texloader = new THREE.TextureLoader();
    texloader.load(pd.ringUrl, function(tex) {
      const ring = createRings(pd.ringSize, 32, tex)
      rotationAxis.add(ring);
      pd.ringObj = ring;
    });
  };
  rotationAxis.add(planet);

  // const nameTag = createLabel(pd.name);
  // nameTag.position.copy(rotationAxis.position)
  // nameTag.scale.set(10,10,10)
  // rotationAxis.add(nameTag);

  
  
  orbit.add(rotationAxis);
  orbitContainer.add(orbit);

  if (pd.axisHelper) {
    pd.axisHelper = new THREE.AxesHelper(pd.size*3)
    planet.add(pd.axisHelper);
  }  
  pd.containerObj = orbitContainer;
  pd.orbitObj = orbit;
  pd.orbitLineObj = line;
  pd.planetObj = planet;
  pd.planetMesh = planetMesh;
  pd.pivotObj = pivot;
  pd.rotationAxis = rotationAxis;
  scene.add(orbitContainer);
}

function updatePlanet (pd) { 
  pd.containerObj.rotation.x = pd.orbitTilta * (Math.PI/180);
  pd.containerObj.rotation.z = pd.orbitTiltb * (Math.PI/180);
  pd.containerObj.position.x = pd.orbitCentera;
  pd.containerObj.position.z = pd.orbitCenterb;
  pd.containerObj.position.y = pd.orbitCenterc;
  pd.rotationAxis.rotation.z = pd.tilt * (Math.PI/180)
  if (pd.hasOwnProperty('tiltb')) {
    pd.rotationAxis.rotation.x = pd.tiltb * (Math.PI/180)
  }

}

function createCelestialSphere(radius) {
  const geometry1 = new THREE.SphereBufferGeometry( radius, 40, 40 );
  const material1 = new THREE.MeshNormalMaterial( { transparent: true, wireframe: false, opacity: 0 , depthWrite: false} );
  const mesh1 = new THREE.Mesh( geometry1, material1 );
  const edgesGeometry = new THREE.EdgesGeometry( geometry1 );
  const wireframe = new THREE.LineSegments( edgesGeometry, new THREE.LineBasicMaterial( { color: 0x666666, transparent: true, opacity: 0.3 } ) );
  wireframe.add(new THREE.PolarGridHelper( radius, 4, 1, 60, 0x0000ff, 0x0000ff ));

  mesh1.add( wireframe );
  mesh1.wireFrameObj = wireframe;
  return mesh1;
}

  function showHideObject(obj) {
    obj.orbitLineObj.visible = obj.visible;
    obj.planetMesh.visible = obj.visible;
    if (obj.axisHelper) {
      if (obj.visible) {
        obj.axisHelper.visible = o['Axis helpers']
      } else {
        obj.axisHelper.visible = obj.visible;                       
      }
    }  
    if (obj.ringObj) {
    obj.ringObj.visible = obj.visible;
  }
}

function showHideAxisHelpers() {
  planets.forEach(obj => {
    if (obj.axisHelper) {
      obj.axisHelper.visible = o['Axis helpers'];
    }  
  });
}

function showHideOrbits() {
  planets.forEach(obj => {
    if (obj.orbitLineObj && !obj.isDeferent) {
       if (obj.visible) {
        obj.orbitLineObj.visible = o['Orbits'];
       }
    }  
  });
}



// function showHideStars() {
//   stars.forEach(obj => {
//     obj.starObj.visible = o.Stars
//   })
// };

function showHideInfoText() {
    var x = document.getElementById("info");
  if(o.infotext) {
    x.style.display = "block";    
  } else {
    x.style.display = "none";    
  }
  // if (x.style.display === "none") {
  //   x.style.display = "block";
  // } else {
  //   x.style.display = "none";
  // }
};



function randomPointInSphere( radius ) {
  const v = new THREE.Vector3();

  const x = THREE.Math.randFloat( -1, 1 );
  const y = THREE.Math.randFloat( -1, 1 );
  const z = THREE.Math.randFloat( -1, 1 );
  const normalizationFactor = 1 / Math.sqrt( x * x + y * y + z * z );

  v.x = x * normalizationFactor * radius;
  v.y = y * normalizationFactor * radius;
  v.z = z * normalizationFactor * radius;

  return v;
}

function createStarfield() {  
  const geometry = new THREE.BufferGeometry();
  var positions = [];

  for (var i = 0; i < 100000; i ++ ) {

    var vertex = randomPointInSphere( 1000000 );
    positions.push( vertex.x, vertex.y, vertex.z );

  }

  geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );

  material = new THREE.PointsMaterial( { color: 0x888888, size: 0.05, sizeAttenuation: false} );
  //material = new THREE.PointsMaterial( { color: 0xffffff, size: 0.01 } );
  particles = new THREE.Points(geometry, material);
  scene.add( particles );
}

function setStarDistance() {
  stars.forEach(obj => {
    obj.starObj.position.x = obj.dist * o['Star distance'];
  })

}



function createRings(radius, segments, texture) {
  return new THREE.Mesh(new THREE.XRingGeometry(1.2 * radius, 2 * radius, 2 * segments, 5, 0, Math.PI * 2), new THREE.MeshBasicMaterial({ map: texture, side: THREE.DoubleSide, transparent: true, opacity: 0.6 })); 
}

function initXRingGeometry() {
  /**
 * @author Kaleb Murphy
 * Modified uvs.push on line no. 42.
 */
  
 //This allows textures to be added to a disc in a way that makes planetary ring look nice
  THREE.XRingGeometry = function ( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) {

    THREE.Geometry.call( this );

    this.type = 'XRingGeometry';

    this.parameters = {
      innerRadius: innerRadius,
      outerRadius: outerRadius,
      thetaSegments: thetaSegments,
      phiSegments: phiSegments,
      thetaStart: thetaStart,
      thetaLength: thetaLength
    };

    innerRadius = innerRadius || 0;
    outerRadius = outerRadius || 50;

    thetaStart = thetaStart !== undefined ? thetaStart : 0;
    thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;

    thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8;
    phiSegments = phiSegments !== undefined ? Math.max( 1, phiSegments ) : 8;

    var i, o, uvs = [], radius = innerRadius, radiusStep = ( ( outerRadius - innerRadius ) / phiSegments );

    for ( i = 0; i < phiSegments + 1; i ++ ) { // concentric circles inside ring

      for ( o = 0; o < thetaSegments + 1; o ++ ) { // number of segments per circle

        var vertex = new THREE.Vector3();
        var segment = thetaStart + o / thetaSegments * thetaLength;
        vertex.x = radius * Math.cos( segment );
        vertex.z = radius * Math.sin( segment );

        this.vertices.push( vertex );
        // uvs.push( new THREE.Vector2( ( vertex.x / outerRadius + 1 ) / 2, ( vertex.y / outerRadius + 1 ) / 2 ) );
        uvs.push( new THREE.Vector2( o / thetaSegments, i / phiSegments ) );
      }

      radius += radiusStep;

    }

    var n = new THREE.Vector3( 1, 0, 0 );

    for ( i = 0; i < phiSegments; i ++ ) { // concentric circles inside ring

      var thetaSegment = i * (thetaSegments + 1);

      for ( o = 0; o < thetaSegments ; o ++ ) { // number of segments per circle

        var segment = o + thetaSegment;

        var v1 = segment;
        var v2 = segment + thetaSegments + 1;
        var v3 = segment + thetaSegments + 2;

        this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) );
        this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ]);

        v1 = segment;
        v2 = segment + thetaSegments + 2;
        v3 = segment + 1;

        this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) );
        this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ]);

      }
    }

    this.computeFaceNormals();

    this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );

  };

  THREE.XRingGeometry.prototype = Object.create( THREE.Geometry.prototype );
  THREE.XRingGeometry.prototype.constructor = THREE.XRingGeometry;

}

// function makeTextSprite( message, parameters )
//     {
//         if ( parameters === undefined ) parameters = {};
//         var fontface = parameters.hasOwnProperty("fontface") ? parameters["fontface"] : "Courier New";
//         var fontsize = parameters.hasOwnProperty("fontsize") ? parameters["fontsize"] : 18;
//         var borderThickness = parameters.hasOwnProperty("borderThickness") ? parameters["borderThickness"] : 4;
//         var borderColor = parameters.hasOwnProperty("borderColor") ?parameters["borderColor"] : { r:0, g:0, b:0, a:1.0 };
//         var backgroundColor = parameters.hasOwnProperty("backgroundColor") ?parameters["backgroundColor"] : { r:0, g:0, b:255, a:1.0 };
//         var textColor = parameters.hasOwnProperty("textColor") ?parameters["textColor"] : { r:0, g:0, b:0, a:1.0 };

//         var canvas = document.createElement('canvas');
//         var context = canvas.getContext('2d');
//         context.font = "Bold " + fontsize + "px " + fontface;
//         var metrics = context.measureText( message );
//         var textWidth = metrics.width;

//         context.fillStyle   = "rgba(" + backgroundColor.r + "," + backgroundColor.g + "," + backgroundColor.b + "," + backgroundColor.a + ")";
//         context.strokeStyle = "rgba(" + borderColor.r + "," + borderColor.g + "," + borderColor.b + "," + borderColor.a + ")";
//         context.fillStyle = "rgba("+textColor.r+", "+textColor.g+", "+textColor.b+", 1.0)";
//         context.fillText( message, borderThickness, fontsize + borderThickness);

//         var texture = new THREE.Texture(canvas) 
//         texture.needsUpdate = true;
//         var spriteMaterial = new THREE.SpriteMaterial( { map: texture} );
//         var sprite = new THREE.Sprite( spriteMaterial );
//         sprite.scale.set(0.5 * fontsize, 0.25 * fontsize, 0.75 * fontsize);
//         return sprite;  
//     }

function getCircularText(text, diameter, startAngle, align, textInside, inwardFacing, fName, fSize, kerning) {
    // text:         The text to be displayed in circular fashion
    // diameter:     The diameter of the circle around which the text will
    //               be displayed (inside or outside)
    // startAngle:   In degrees, Where the text will be shown. 0 degrees
    //               if the top of the circle
    // align:        Positions text to left right or center of startAngle
    // textInside:   true to show inside the diameter. False to show outside
    // inwardFacing: true for base of text facing inward. false for outward
    // fName:        name of font family. Make sure it is loaded
    // fSize:        size of font family. Don't forget to include units
    // kearning:     0 for normal gap between letters. positive or
    //               negative number to expand/compact gap in pixels
 //------------------------------------------------------------------------

    // declare and intialize canvas, reference, and useful variables
    align = align.toLowerCase();
    var mainCanvas = document.createElement('canvas');
    var ctxRef = mainCanvas.getContext('2d');
    var clockwise = align == "right" ? 1 : -1; // draw clockwise for aligned right. Else Anticlockwise
    startAngle = startAngle * (Math.PI / 180); // convert to radians

    // calculate height of the font. Many ways to do this
    // you can replace with your own!
    var div = document.createElement("div");
    div.innerHTML = text;
    div.style.position = 'absolute';
    div.style.top = '-10000px';
    div.style.left = '-10000px';
    div.style.fontFamily = fName;
    div.style.fontSize = fSize;
    document.body.appendChild(div);
    var textHeight = div.offsetHeight;
    document.body.removeChild(div);
    
    // in cases where we are drawing outside diameter,
    // expand diameter to handle it
    if (!textInside) diameter += textHeight * 2;

    mainCanvas.width = diameter;
    mainCanvas.height = diameter;
    // omit next line for transparent background
    //mainCanvas.style.backgroundColor = 'lightgray'; 
    ctxRef.fillStyle = 'grey';
    ctxRef.font = fSize + ' ' + fName;
    
    // Reverse letters for align Left inward, align right outward 
    // and align center inward.
    if (((["left", "center"].indexOf(align) > -1) && inwardFacing) || (align == "right" && !inwardFacing)) text = text.split("").reverse().join(""); 
    
    // Setup letters and positioning
    ctxRef.translate(diameter / 2, diameter / 2); // Move to center
    startAngle += (Math.PI * !inwardFacing); // Rotate 180 if outward
    ctxRef.textBaseline = 'middle'; // Ensure we draw in exact center
    ctxRef.textAlign = 'center'; // Ensure we draw in exact center

    // rotate 50% of total angle for center alignment
    if (align == "center") {
        for (var j = 0; j < text.length; j++) {
            var charWid = ctxRef.measureText(text[j]).width;
            startAngle += ((charWid + (j == text.length-1 ? 0 : kerning)) / (diameter / 2 - textHeight)) / 2 * -clockwise;
        }
    }

    // Phew... now rotate into final start position
    ctxRef.rotate(startAngle);

    // Now for the fun bit: draw, rotate, and repeat
    for (var j = 0; j < text.length; j++) {
        var charWid = ctxRef.measureText(text[j]).width; // half letter
        // rotate half letter
        ctxRef.rotate((charWid/2) / (diameter / 2 - textHeight) * clockwise); 
        // draw the character at "top" or "bottom" 
        // depending on inward or outward facing
        ctxRef.fillText(text[j], 0, (inwardFacing ? 1 : -1) * (0 - diameter / 2 + textHeight / 2));

        ctxRef.rotate((charWid/2 + kerning) / (diameter / 2 - textHeight) * clockwise); // rotate half letter
    }

    // Return it
    return (mainCanvas);
}

 function colorTemperature2rgb(kelvin) {

  var temperature = kelvin / 100.0;
  var red, green, blue;

  if (temperature < 66.0) {
    red = 255;
  } else {
    // a + b x + c Log[x] /.
    // {a -> 351.97690566805693`,
    // b -> 0.114206453784165`,
    // c -> -40.25366309332127
    //x -> (kelvin/100) - 55}
    red = temperature - 55.0;
    red = 351.97690566805693+ 0.114206453784165 * red - 40.25366309332127 * Math.log(red);
    if (red < 0) red = 0;
    if (red > 255) red = 255;
  }

  /* Calculate green */

  if (temperature < 66.0) {

    // a + b x + c Log[x] /.
    // {a -> -155.25485562709179`,
    // b -> -0.44596950469579133`,
    // c -> 104.49216199393888`,
    // x -> (kelvin/100) - 2}
    green = temperature - 2;
    green = -155.25485562709179 - 0.44596950469579133 * green + 104.49216199393888 * Math.log(green);
    if (green < 0) green = 0;
    if (green > 255) green = 255;

  } else {

    // a + b x + c Log[x] /.
    // {a -> 325.4494125711974`,
    // b -> 0.07943456536662342`,
    // c -> -28.0852963507957`,
    // x -> (kelvin/100) - 50}
    green = temperature - 50.0;
    green = 325.4494125711974 + 0.07943456536662342 * green - 28.0852963507957 * Math.log(green);
    if (green < 0) green = 0;
    if (green > 255) green = 255;

  }

  /* Calculate blue */

  if (temperature >= 66.0) {
    blue = 255;
  } else {

    if (temperature <= 20.0) {
      blue = 0;
    } else {

      // a + b x + c Log[x] /.
      // {a -> -254.76935184120902`,
      // b -> 0.8274096064007395`,
      // c -> 115.67994401066147`,
      // x -> kelvin/100 - 10}
      blue = temperature - 10;
      blue = -254.76935184120902 + 0.8274096064007395 * blue + 115.67994401066147 * Math.log(blue);
      if (blue < 0) blue = 0;
      if (blue > 255) blue = 255;
    }
  }
  //  return {red: Math.round(red), blue: Math.round(blue), green: Math.round(green)};

   //const white = new THREE.Color('rgb(255,255,255)');
  return 'rgb(' + Math.round(red) + ',' + Math.round(green) + ',' + Math.round(blue) + ')'; 
}

              
            
!
999px

Console