! Сегодня

Главная » Web и Технологии » Танцующая новогодняя ёлочка для сайта

Танцующая новогодняя ёлочка для сайта

Не всегда просит прощения тот, кто виноват. Просит прощения тот, кто дорожит отношениями.

6-декабря-2023, 21:37   11   0

Танцующая новогодняя ёлочка для сайта

Интересное решение анимированной елочки для сайта. Елочку можно вращать, она в 3D  исполнении, так же по клику начинает играть музыка.

Можно украсить веб проект, может кому и пригодиться. Всех с наступающими праздниками!!!

HTML

<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <meta name="theme-color" content="#6f11f1">
  <meta name="application-name" content="Dancing Tree">
  <meta name="description" content="Threejs Audio Visualizer">
  <meta name="keywords" content="Threejs, jаvascript, WebGl, WebAudio, Ion Drimba Filho">
  <meta name="subject" content="Dancing Tree -Threejs Audio Visualizer">
  <meta name="copyright" content="Ion Drimba Filho">
  <meta name="robots" content="index,follow">
  <meta name="topic" content="">
  <meta name="summary" content="Dancing Tree">
  <meta name="author" content="Ion Drimba Filho">
  <meta name="url" content="https://iondrimba.github.io/threejs-audio-visualizer-tree/public/">
  <meta name="pagename" content="Dancing Tree">
  <meta name="category" content="">
  <meta name="coverage" content="Worldwide">
  <meta name="distribution" content="Global">
  <meta name="rating" content="General">
  <meta name="subtitle" content="Dancing Tree">
  <meta name="target" content="all">
  <meta http-equiv="cleartype" content="on">
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:site" content="Dancing Tree">
  <meta name="twitter:creator" content="Ion Drimba Filho">
  <meta name="twitter:title" content="Dancing Tree">
  <meta name="twitter:description" content="Audio Visualizer made with Threejs">
  <meta name="twitter:image:src" content="https://raw.githubusercontent.com/iondrimba/images/master/dancing.trhree.PNG">
  <meta property="og:url" content="https://iondrimba.github.io/threejs-audio-visualizer-tree/public/">
  <meta property="og:type" content="website">
  <meta property="og:title" content="Dancing Tree - Threejs Audio Visualizer">
  <meta property="og:image" content="https://raw.githubusercontent.com/iondrimba/images/master/dancing.trhree.PNG">
  <meta property="og:description" content="Threejs Audio Visualizer">
  <meta property="og:site_name" content="Dancing Tree">
  <meta property="article:author" content="https://iondrimbafilho.me/">
  <meta property="article:publisher" content="https://iondrimbafilho.me/">
  <meta itemprop="name" content="Dancing Tree">
  <meta itemprop="description" content="Threejs Audio Visualizer">
  <meta itemprop="image" content="https://raw.githubusercontent.com/iondrimba/images/master/dancing.trhree.PNG">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="mobile-web-app-capable" content="yes">
  <title>Dancing Tree</title>
  <link href="https://fonts.googleapis.com/css?family=Ropa+Sans" rel="stylesheet">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.20.3/TweenMax.min.js"></script>

</head>

<body>
  <button class="play-intro">
    <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="0 0 32 32" data-tags="play,media control">
      <g fill="#fff" transform="scale(0.03125 0.03125)">
        <path d="M192 0v1024l640-511.264-640-512.736z" />
      </g>
    </svg>
  </button>
  <div class="credits">
    <h1>Dancing Tree</h1>
    <div class="controls">
      <button class="play">
        <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="32" height="32" viewBox="0 0 25 32" data-tags="play,media control">
          <g fill="#fff" transform="scale(0.03125 0.03125)">
            <path d="M192 0v1024l640-511.264-640-512.736z" />
          </g>
        </svg>
      </button>
      <button class="pause">
        <svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32" data-tags="pause,media control">
          <g fill="#fff" transform="scale(0.03125 0.03125)">
            <path d="M352 0h-192c-17.696 0-32 14.336-32 32v960c0 17.696 14.304 32 32 32h192c17.696 0 32-14.304 32-32v-960c0-17.664-14.304-32-32-32zM864 0h-192c-17.696 0-32 14.336-32 32v960c0 17.696 14.304 32 32 32h192c17.696 0 32-14.304 32-32v-960c0-17.664-14.304-32-32-32z" />
          </g>
        </svg>
      </button>
    </div>
  </div>
  <div class="loader"></div>
  <audio id="audio" crossOrigin="anonymous"></audio>
  <footer>
    <h2>credits (models):</h2>
    <a href="https://poly.google.com/view/9NXf-SDxJny" target="_blank" rel="noopener noreferrer">Star</a> /
    <a href="https://poly.google.com/view/8eJfmMDsvso" target="_blank" rel="noopener noreferrer">Squared Christmas Gifts</a> /
    <a href="https://poly.google.com/view/8b5ywSuzte0" target="_blank" rel="noopener noreferrer">Xmas Gift</a> /
    <a href="https://poly.google.com/view/9JI_vKK0NfB" target="_blank" rel="noopener noreferrer">Cabin</a>
  </footer>
</body>

</html>


CSS

html, body {
  margin: 0;
  padding: 0;
  font-family: 'Ropa Sans', sans-serif;
  background-color: #6f11f1;
  color: #fff;
  box-sizing: border-box;
  overflow: hidden;
}

canvas { width: 100%; height: 100% }

h1, h2 {
  padding: 0;
  margin: 0;
  color: inherit;
}

.play-intro {
  position: absolute;
  top: 50%;
  left: 50%;
  display: none;
  transform: translate(-50%,-50%);
  height: 100px;
  width: 100px;
  cursor: pointer;

  svg {
    width: 100%;
  }
}

button {
  background-color: transparent;
  border: 0;
  cursor: pointer;
}


footer {
  position: absolute;
  bottom: 0;
  margin-left: 20px;
  margin-bottom: 20px;
}

.controls {
  position: absolute;
  right: 30px;
  top: 10px;
}

.play {
  display: none;
}

.pause {
  display: none;
}

.control-show  {
  display: block;
}

.loader {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  z-index: 0;
  transform: scale(0, 1);
  transform-origin: left;
  -webkit-transition: transform .3s cubic-bezier(0.23, 1, 0.32, 1);
  transition: transform .3s cubic-bezier(0.23, 1, 0.32, 1);
  background: #007adf;  /* fallback for old browsers */
  background-image: linear-gradient(to top, #007adf 0%, #00ecbc 100%);
}

a {
  color: inherit ;
  text-decoration: none;
}

.credits {
  margin: 20px;
  position: absolute;
  z-index: 1;
  bottom: 90px;
  width: 100%;
}

@media screen and (min-width: 768px) {
  .credits {
    margin-left: 20px;
    margin-top: 20px;
    position: absolute;
    z-index: 1;
    bottom: inherit;
    width: 100%;
  }
}


JS(Babel)

/**
 * @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
 * @author ScieCode / http://github.com/sciecode
 */

// 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 left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move

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.05;

    // 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.panSpeed = 1.0;
    this.screenSpacePanning = false; // if true, pan in screen-space
    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 = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN };

    // Touch fingers
    this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN };

    // 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() );

            }

            if ( scope.enableDamping ) {

                spherical.theta += sphericalDelta.theta * scope.dampingFactor;
                spherical.phi += sphericalDelta.phi * scope.dampingFactor;

            } else {

                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

            if ( scope.enableDamping === true ) {

                scope.target.addScaledVector( panOffset, scope.dampingFactor );

            } else {

                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 );

                panOffset.multiplyScalar( 1 - scope.dampingFactor );

            } else {

                sphericalDelta.set( 0, 0, 0 );

                panOffset.set( 0, 0, 0 );

            }

            scale = 1;

            // 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_PAN: 4,
        TOUCH_DOLLY_PAN: 5,
        TOUCH_DOLLY_ROTATE: 6
    };

    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 ) {

            if ( scope.screenSpacePanning === true ) {

                v.setFromMatrixColumn( objectMatrix, 1 );

            } else {

                v.setFromMatrixColumn( objectMatrix, 0 );
                v.crossVectors( scope.object.up, v );

            }

            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.isPerspectiveCamera ) {

                // 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 use only clientHeight here so aspect ratio does not distort speed
                panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
                panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );

            } else if ( scope.object.isOrthographicCamera ) {

                // 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.isPerspectiveCamera ) {

            scale /= dollyScale;

        } else if ( scope.object.isOrthographicCamera ) {

            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.isPerspectiveCamera ) {

            scale *= dollyScale;

        } else if ( scope.object.isOrthographicCamera ) {

            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 ).multiplyScalar( scope.rotateSpeed );

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

        rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height

        rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );

        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 ).multiplyScalar( scope.panSpeed );

        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' );

        var needsUpdate = false;

        switch ( event.keyCode ) {

            case scope.keys.UP:
                pan( 0, scope.keyPanSpeed );
                needsUpdate = true;
                break;

            case scope.keys.BOTTOM:
                pan( 0, - scope.keyPanSpeed );
                needsUpdate = true;
                break;

            case scope.keys.LEFT:
                pan( scope.keyPanSpeed, 0 );
                needsUpdate = true;
                break;

            case scope.keys.RIGHT:
                pan( - scope.keyPanSpeed, 0 );
                needsUpdate = true;
                break;

        }

        if ( needsUpdate ) {

            // prevent the browser from scrolling on cursor keys
            event.preventDefault();

            scope.update();

        }


    }

    function handleTouchStartRotate( event ) {

        //console.log( 'handleTouchStartRotate' );

        if ( event.touches.length == 1 ) {

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

        } else {

            var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
            var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );

            rotateStart.set( x, y );

        }

    }

    function handleTouchStartPan( event ) {

        //console.log( 'handleTouchStartPan' );

        if ( event.touches.length == 1 ) {

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

        } else {

            var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
            var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );

            panStart.set( x, y );

        }

    }

    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 handleTouchStartDollyPan( event ) {

        //console.log( 'handleTouchStartDollyPan' );

        if ( scope.enableZoom ) handleTouchStartDolly( event );

        if ( scope.enablePan ) handleTouchStartPan( event );

    }

    function handleTouchStartDollyRotate( event ) {

        //console.log( 'handleTouchStartDollyRotate' );

        if ( scope.enableZoom ) handleTouchStartDolly( event );

        if ( scope.enableRotate ) handleTouchStartRotate( event );

    }

    function handleTouchMoveRotate( event ) {

        //console.log( 'handleTouchMoveRotate' );

        if ( event.touches.length == 1 ) {

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

        } else {

            var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
            var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );

            rotateEnd.set( x, y );

        }

        rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );

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

        rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height

        rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );

        rotateStart.copy( rotateEnd );

    }

    function handleTouchMovePan( event ) {

        //console.log( 'handleTouchMoveRotate' );

        if ( event.touches.length == 1 ) {

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

        } else {

            var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
            var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );

            panEnd.set( x, y );

        }

        panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );

        pan( panDelta.x, panDelta.y );

        panStart.copy( panEnd );

    }

    function handleTouchMoveDolly( event ) {

        //console.log( 'handleTouchMoveRotate' );

        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.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );

        dollyIn( dollyDelta.y );

        dollyStart.copy( dollyEnd );

    }

    function handleTouchMoveDollyPan( event ) {

        //console.log( 'handleTouchMoveDollyPan' );

        if ( scope.enableZoom ) handleTouchMoveDolly( event );

        if ( scope.enablePan ) handleTouchMovePan( event );

    }

    function handleTouchMoveDollyRotate( event ) {

        //console.log( 'handleTouchMoveDollyPan' );

        if ( scope.enableZoom ) handleTouchMoveDolly( event );

        if ( scope.enableRotate ) handleTouchMoveRotate( event );

    }

    function handleTouchEnd( /*event*/ ) {

        //console.log( 'handleTouchEnd' );

    }

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

    function onmousedown( event ) {

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

        // Prevent the browser from scrolling.

        event.preventDefault();

        // Manually set the focus since calling preventDefault above
        // prevents the browser from setting it automatically.

        scope.domElement.focus ? scope.domElement.focus() : window.focus();

        switch ( event.button ) {

            case 0:

                switch ( scope.mouseButtons.LEFT ) {

                    case THREE.MOUSE.ROTATE:

                        if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

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

                            handleMouseDownPan( event );

                            state = STATE.PAN;

                        } else {

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

                            handleMouseDownRotate( event );

                            state = STATE.ROTATE;

                        }

                        break;

                    case THREE.MOUSE.PAN:

                        if ( event.ctrlKey || event.metaKey || event.shiftKey ) {

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

                            handleMouseDownRotate( event );

                            state = STATE.ROTATE;

                        } else {

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

                            handleMouseDownPan( event );

                            state = STATE.PAN;

                        }

                        break;

                    default:

                        state = STATE.NONE;

                }

                break;


            case 1:

                switch ( scope.mouseButtons.MIDDLE ) {

                    case THREE.MOUSE.DOLLY:

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

                        handleMouseDownDolly( event );

                        state = STATE.DOLLY;

                        break;


                    default:

                        state = STATE.NONE;

                }

                break;

            case 2:

                switch ( scope.mouseButtons.RIGHT ) {

                    case THREE.MOUSE.ROTATE:

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

                        handleMouseDownRotate( event );

                        state = STATE.ROTATE;

                        break;

                    case THREE.MOUSE.PAN:

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

                        handleMouseDownPan( event );

                        state = STATE.PAN;

                        break;

                    default:

                        state = STATE.NONE;

                }

                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();

        scope.dispatchEvent( startEvent );

        handleMouseWheel( event );

        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;

        event.preventDefault();

        switch ( event.touches.length ) {

            case 1:

                switch ( scope.touches.ONE ) {

                    case THREE.TOUCH.ROTATE:

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

                        handleTouchStartRotate( event );

                        state = STATE.TOUCH_ROTATE;

                        break;

                    case THREE.TOUCH.PAN:

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

                        handleTouchStartPan( event );

                        state = STATE.TOUCH_PAN;

                        break;

                    default:

                        state = STATE.NONE;

                }

                break;

            case 2:

                switch ( scope.touches.TWO ) {

                    case THREE.TOUCH.DOLLY_PAN:

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

                        handleTouchStartDollyPan( event );

                        state = STATE.TOUCH_DOLLY_PAN;

                        break;

                    case THREE.TOUCH.DOLLY_ROTATE:

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

                        handleTouchStartDollyRotate( event );

                        state = STATE.TOUCH_DOLLY_ROTATE;

                        break;

                    default:

                        state = STATE.NONE;

                }

                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 ( state ) {

            case STATE.TOUCH_ROTATE:

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

                handleTouchMoveRotate( event );

                scope.update();

                break;

            case STATE.TOUCH_PAN:

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

                handleTouchMovePan( event );

                scope.update();

                break;

            case STATE.TOUCH_DOLLY_PAN:

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

                handleTouchMoveDollyPan( event );

                scope.update();

                break;

            case STATE.TOUCH_DOLLY_ROTATE:

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

                handleTouchMoveDollyRotate( event );

                scope.update();

                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;

        }

    }

} );

// This set of controls performs orbiting, dollying (zooming), and panning.
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
// This is very similar to OrbitControls, another set of touch behavior
//
//    Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
//    Pan - left mouse, or arrow keys / touch: one-finger move

THREE.MapControls = function ( object, domElement ) {

    THREE.OrbitControls.call( this, object, domElement );

    this.mouseButtons.LEFT = THREE.MOUSE.PAN;
    this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;

    this.touches.ONE = THREE.TOUCH.PAN;
    this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;

};

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

/**
 * @author mrdoob / http://mrdoob.com/
 */

THREE.OBJLoader = ( function () {

    // o object_name | g group_name
    var object_pattern = /^[og]\s*(.+)?/;
    // mtllib file_reference
    var material_library_pattern = /^mtllib /;
    // usemtl material_name
    var material_use_pattern = /^usemtl /;

    function ParserState() {

        var state = {
            objects: [],
            object: {},

            vertices: [],
            normals: [],
            colors: [],
            uvs: [],

            materialLibraries: [],

            startObject: function ( name, fromDeclaration ) {

                // If the current object (initial from reset) is not from a g/o declaration in the parsed
                // file. We need to use it for the first parsed g/o to keep things in sync.
                if ( this.object && this.object.fromDeclaration === false ) {

                    this.object.name = name;
                    this.object.fromDeclaration = ( fromDeclaration !== false );
                    return;

                }

                var previousMaterial = ( this.object && typeof this.object.currentMaterial === 'function' ? this.object.currentMaterial() : undefined );

                if ( this.object && typeof this.object._finalize === 'function' ) {

                    this.object._finalize( true );

                }

                this.object = {
                    name: name || '',
                    fromDeclaration: ( fromDeclaration !== false ),

                    geometry: {
                        vertices: [],
                        normals: [],
                        colors: [],
                        uvs: []
                    },
                    materials: [],
                    smooth: true,

                    startMaterial: function ( name, libraries ) {

                        var previous = this._finalize( false );

                        // New usemtl declaration overwrites an inherited material, except if faces were declared
                        // after the material, then it must be preserved for proper MultiMaterial continuation.
                        if ( previous && ( previous.inherited || previous.groupCount <= 0 ) ) {

                            this.materials.splice( previous.index, 1 );

                        }

                        var material = {
                            index: this.materials.length,
                            name: name || '',
                            mtllib: ( Array.isArray( libraries ) && libraries.length > 0 ? libraries[ libraries.length - 1 ] : '' ),
                            smooth: ( previous !== undefined ? previous.smooth : this.smooth ),
                            groupStart: ( previous !== undefined ? previous.groupEnd : 0 ),
                            groupEnd: - 1,
                            groupCount: - 1,
                            inherited: false,

                            clone: function ( index ) {

                                var cloned = {
                                    index: ( typeof index === 'number' ? index : this.index ),
                                    name: this.name,
                                    mtllib: this.mtllib,
                                    smooth: this.smooth,
                                    groupStart: 0,
                                    groupEnd: - 1,
                                    groupCount: - 1,
                                    inherited: false
                                };
                                cloned.clone = this.clone.bind( cloned );
                                return cloned;

                            }
                        };

                        this.materials.push( material );

                        return material;

                    },

                    currentMaterial: function () {

                        if ( this.materials.length > 0 ) {

                            return this.materials[ this.materials.length - 1 ];

                        }

                        return undefined;

                    },

                    _finalize: function ( end ) {

                        var lastMultiMaterial = this.currentMaterial();
                        if ( lastMultiMaterial && lastMultiMaterial.groupEnd === - 1 ) {

                            lastMultiMaterial.groupEnd = this.geometry.vertices.length / 3;
                            lastMultiMaterial.groupCount = lastMultiMaterial.groupEnd - lastMultiMaterial.groupStart;
                            lastMultiMaterial.inherited = false;

                        }

                        // Ignore objects tail materials if no face declarations followed them before a new o/g started.
                        if ( end && this.materials.length > 1 ) {

                            for ( var mi = this.materials.length - 1; mi >= 0; mi -- ) {

                                if ( this.materials[ mi ].groupCount <= 0 ) {

                                    this.materials.splice( mi, 1 );

                                }

                            }

                        }

                        // Guarantee at least one empty material, this makes the creation later more straight forward.
                        if ( end && this.materials.length === 0 ) {

                            this.materials.push( {
                                name: '',
                                smooth: this.smooth
                            } );

                        }

                        return lastMultiMaterial;

                    }
                };

                // Inherit previous objects material.
                // Spec tells us that a declared material must be set to all objects until a new material is declared.
                // If a usemtl declaration is encountered while this new object is being parsed, it will
                // overwrite the inherited material. Exception being that there was already face declarations
                // to the inherited material, then it will be preserved for proper MultiMaterial continuation.

                if ( previousMaterial && previousMaterial.name && typeof previousMaterial.clone === 'function' ) {

                    var declared = previousMaterial.clone( 0 );
                    declared.inherited = true;
                    this.object.materials.push( declared );

                }

                this.objects.push( this.object );

            },

            finalize: function () {

                if ( this.object && typeof this.object._finalize === 'function' ) {

                    this.object._finalize( true );

                }

            },

            parseVertexIndex: function ( value, len ) {

                var index = parseInt( value, 10 );
                return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;

            },

            parseNormalIndex: function ( value, len ) {

                var index = parseInt( value, 10 );
                return ( index >= 0 ? index - 1 : index + len / 3 ) * 3;

            },

            parseUVIndex: function ( value, len ) {

                var index = parseInt( value, 10 );
                return ( index >= 0 ? index - 1 : index + len / 2 ) * 2;

            },

            addVertex: function ( a, b, c ) {

                var src = this.vertices;
                var dst = this.object.geometry.vertices;

                dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
                dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
                dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );

            },

            addVertexPoint: function ( a ) {

                var src = this.vertices;
                var dst = this.object.geometry.vertices;

                dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );

            },

            addVertexLine: function ( a ) {

                var src = this.vertices;
                var dst = this.object.geometry.vertices;

                dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );

            },

            addNormal: function ( a, b, c ) {

                var src = this.normals;
                var dst = this.object.geometry.normals;

                dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
                dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
                dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );

            },

            addColor: function ( a, b, c ) {

                var src = this.colors;
                var dst = this.object.geometry.colors;

                dst.push( src[ a + 0 ], src[ a + 1 ], src[ a + 2 ] );
                dst.push( src[ b + 0 ], src[ b + 1 ], src[ b + 2 ] );
                dst.push( src[ c + 0 ], src[ c + 1 ], src[ c + 2 ] );

            },

            addUV: function ( a, b, c ) {

                var src = this.uvs;
                var dst = this.object.geometry.uvs;

                dst.push( src[ a + 0 ], src[ a + 1 ] );
                dst.push( src[ b + 0 ], src[ b + 1 ] );
                dst.push( src[ c + 0 ], src[ c + 1 ] );

            },

            addUVLine: function ( a ) {

                var src = this.uvs;
                var dst = this.object.geometry.uvs;

                dst.push( src[ a + 0 ], src[ a + 1 ] );

            },

            addFace: function ( a, b, c, ua, ub, uc, na, nb, nc ) {

                var vLen = this.vertices.length;

                var ia = this.parseVertexIndex( a, vLen );
                var ib = this.parseVertexIndex( b, vLen );
                var ic = this.parseVertexIndex( c, vLen );

                this.addVertex( ia, ib, ic );

                if ( ua !== undefined && ua !== '' ) {

                    var uvLen = this.uvs.length;
                    ia = this.parseUVIndex( ua, uvLen );
                    ib = this.parseUVIndex( ub, uvLen );
                    ic = this.parseUVIndex( uc, uvLen );
                    this.addUV( ia, ib, ic );

                }

                if ( na !== undefined && na !== '' ) {

                    // Normals are many times the same. If so, skip function call and parseInt.
                    var nLen = this.normals.length;
                    ia = this.parseNormalIndex( na, nLen );

                    ib = na === nb ? ia : this.parseNormalIndex( nb, nLen );
                    ic = na === nc ? ia : this.parseNormalIndex( nc, nLen );

                    this.addNormal( ia, ib, ic );

                }

                if ( this.colors.length > 0 ) {

                    this.addColor( ia, ib, ic );

                }

            },

            addPointGeometry: function ( vertices ) {

                this.object.geometry.type = 'Points';

                var vLen = this.vertices.length;

                for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {

                    this.addVertexPoint( this.parseVertexIndex( vertices[ vi ], vLen ) );

                }

            },

            addLineGeometry: function ( vertices, uvs ) {

                this.object.geometry.type = 'Line';

                var vLen = this.vertices.length;
                var uvLen = this.uvs.length;

                for ( var vi = 0, l = vertices.length; vi < l; vi ++ ) {

                    this.addVertexLine( this.parseVertexIndex( vertices[ vi ], vLen ) );

                }

                for ( var uvi = 0, l = uvs.length; uvi < l; uvi ++ ) {

                    this.addUVLine( this.parseUVIndex( uvs[ uvi ], uvLen ) );

                }

            }

        };

        state.startObject( '', false );

        return state;

    }

    //

    function OBJLoader( manager ) {

        this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

        this.materials = null;

    }

    OBJLoader.prototype = {

        constructor: OBJLoader,

        load: function ( url, onload, onprogress, onerror ) {

            var scope = this;

            var loader = new THREE.FileLoader( scope.manager );
            loader.setPath( this.path );
            loader.load( url, function ( text ) {

                onload( scope.parse( text ) );

            }, onprogress, onerror );

        },

        setPath: function ( value ) {

            this.path = value;

            return this;

        },

        setMaterials: function ( materials ) {

            this.materials = materials;

            return this;

        },

        parse: function ( text ) {

            console.time( 'OBJLoader' );

            var state = new ParserState();

            if ( text.indexOf( '\r\n' ) !== - 1 ) {

                // This is faster than String.split with regex that splits on both
                text = text.replace( /\r\n/g, '\n' );

            }

            if ( text.indexOf( '\\\n' ) !== - 1 ) {

                // join lines separated by a line continuation character (\)
                text = text.replace( /\\\n/g, '' );

            }

            var lines = text.split( '\n' );
            var line = '', lineFirstChar = '';
            var lineLength = 0;
            var result = [];

            // Faster to just trim left side of the line. Use if available.
            var trimLeft = ( typeof ''.trimLeft === 'function' );

            for ( var i = 0, l = lines.length; i < l; i ++ ) {

                line = lines[ i ];

                line = trimLeft ? line.trimLeft() : line.trim();

                lineLength = line.length;

                if ( lineLength === 0 ) continue;

                lineFirstChar = line.charAt( 0 );

                // @todo invoke passed in handler if any
                if ( lineFirstChar === '#' ) continue;

                if ( lineFirstChar === 'v' ) {

                    var data = line.split( /\s+/ );

                    switch ( data[ 0 ] ) {

                        case 'v':
                            state.vertices.push(
                                parseFloat( data[ 1 ] ),
                                parseFloat( data[ 2 ] ),
                                parseFloat( data[ 3 ] )
                            );
                            if ( data.length >= 7 ) {

                                state.colors.push(
                                    parseFloat( data[ 4 ] ),
                                    parseFloat( data[ 5 ] ),
                                    parseFloat( data[ 6 ] )

                                );

                            }
                            break;
                        case 'vn':
                            state.normals.push(
                                parseFloat( data[ 1 ] ),
                                parseFloat( data[ 2 ] ),
                                parseFloat( data[ 3 ] )
                            );
                            break;
                        case 'vt':
                            state.uvs.push(
                                parseFloat( data[ 1 ] ),
                                parseFloat( data[ 2 ] )
                            );
                            break;

                    }

                } else if ( lineFirstChar === 'f' ) {

                    var lineData = line.substr( 1 ).trim();
                    var vertexData = lineData.split( /\s+/ );
                    var faceVertices = [];

                    // Parse the face vertex data into an easy to work with format

                    for ( var j = 0, jl = vertexData.length; j < jl; j ++ ) {

                        var vertex = vertexData[ j ];

                        if ( vertex.length > 0 ) {

                            var vertexParts = vertex.split( '/' );
                            faceVertices.push( vertexParts );

                        }

                    }

                    // Draw an edge between the first vertex and all subsequent vertices to form an n-gon

                    var v1 = faceVertices[ 0 ];

                    for ( var j = 1, jl = faceVertices.length - 1; j < jl; j ++ ) {

                        var v2 = faceVertices[ j ];
                        var v3 = faceVertices[ j + 1 ];

                        state.addFace(
                            v1[ 0 ], v2[ 0 ], v3[ 0 ],
                            v1[ 1 ], v2[ 1 ], v3[ 1 ],
                            v1[ 2 ], v2[ 2 ], v3[ 2 ]
                        );

                    }

                } else if ( lineFirstChar === 'l' ) {

                    var lineParts = line.substring( 1 ).trim().split( " " );
                    var lineVertices = [], lineUVs = [];

                    if ( line.indexOf( "/" ) === - 1 ) {

                        lineVertices = lineParts;

                    } else {

                        for ( var li = 0, llen = lineParts.length; li < llen; li ++ ) {

                            var parts = lineParts[ li ].split( "/" );

                            if ( parts[ 0 ] !== "" ) lineVertices.push( parts[ 0 ] );
                            if ( parts[ 1 ] !== "" ) lineUVs.push( parts[ 1 ] );

                        }

                    }
                    state.addLineGeometry( lineVertices, lineUVs );

                } else if ( lineFirstChar === 'p' ) {

                    var lineData = line.substr( 1 ).trim();
                    var pointData = lineData.split( " " );

                    state.addPointGeometry( pointData );

                } else if ( ( result = object_pattern.exec( line ) ) !== null ) {

                    // o object_name
                    // or
                    // g group_name

                    // WORKAROUND: https://bugs.chromium.org/p/v8/issues/detail?id=2869
                    // var name = result[ 0 ].substr( 1 ).trim();
                    var name = ( " " + result[ 0 ].substr( 1 ).trim() ).substr( 1 );

                    state.startObject( name );

                } else if ( material_use_pattern.test( line ) ) {

                    // material

                    state.object.startMaterial( line.substring( 7 ).trim(), state.materialLibraries );

                } else if ( material_library_pattern.test( line ) ) {

                    // mtl file

                    state.materialLibraries.push( line.substring( 7 ).trim() );

                } else if ( lineFirstChar === 's' ) {

                    result = line.split( ' ' );

                    // smooth shading

                    // @todo Handle files that have varying smooth values for a set of faces inside one geometry,
                    // but does not define a usemtl for each face set.
                    // This should be detected and a dummy material created (later MultiMaterial and geometry groups).
                    // This requires some care to not create extra material on each smooth value for "normal" obj files.
                    // where explicit usemtl defines geometry groups.
                    // Example asset: examples/models/obj/cerberus/Cerberus.obj

                    /*
                     * http://paulbourke.net/dataformats/obj/
                     * or
                     * http://www.cs.utah.edu/~boulos/cs3505/obj_spec.pdf
                     *
                     * From chapter "Grouping" Syntax explanation "s group_number":
                     * "group_number is the smoothing group number. To turn off smoothing groups, use a value of 0 or off.
                     * Polygonal elements use group numbers to put elements in different smoothing groups. For free-form
                     * surfaces, smoothing groups are either turned on or off; there is no difference between values greater
                     * than 0."
                     */
                    if ( result.length > 1 ) {

                        var value = result[ 1 ].trim().toLowerCase();
                        state.object.smooth = ( value !== '0' && value !== 'off' );

                    } else {

                        // ZBrush can produce "s" lines #11707
                        state.object.smooth = true;

                    }
                    var material = state.object.currentMaterial();
                    if ( material ) material.smooth = state.object.smooth;

                } else {

                    // Handle null terminated files without exception
                    if ( line === '\0' ) continue;

                    throw new Error( 'THREE.OBJLoader: Unexpected line: "' + line + '"' );

                }

            }

            state.finalize();

            var container = new THREE.Group();
            container.materialLibraries = [].concat( state.materialLibraries );

            for ( var i = 0, l = state.objects.length; i < l; i ++ ) {

                var object = state.objects[ i ];
                var geometry = object.geometry;
                var materials = object.materials;
                var isLine = ( geometry.type === 'Line' );
                var isPoints = ( geometry.type === 'Points' );
                var hasVertexColors = false;

                // Skip o/g line declarations that did not follow with any faces
                if ( geometry.vertices.length === 0 ) continue;

                var buffergeometry = new THREE.BufferGeometry();

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

                if ( geometry.normals.length > 0 ) {

                    buffergeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( geometry.normals, 3 ) );

                } else {

                    buffergeometry.computeVertexNormals();

                }

                if ( geometry.colors.length > 0 ) {

                    hasVertexColors = true;
                    buffergeometry.addAttribute( 'color', new THREE.Float32BufferAttribute( geometry.colors, 3 ) );

                }

                if ( geometry.uvs.length > 0 ) {

                    buffergeometry.addAttribute( 'uv', new THREE.Float32BufferAttribute( geometry.uvs, 2 ) );

                }

                // Create materials

                var createdMaterials = [];

                for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {

                    var sourceMaterial = materials[ mi ];
                    var material = undefined;

                    if ( this.materials !== null ) {

                        material = this.materials.create( sourceMaterial.name );

                        // mtl etc. loaders probably can't create line materials correctly, copy properties to a line material.
                        if ( isLine && material && ! ( material instanceof THREE.LineBasicMaterial ) ) {

                            var materialLine = new THREE.LineBasicMaterial();
                            THREE.Material.prototype.copy.call( materialLine, material );
                            materialLine.color.copy( material.color );
                            materialLine.lights = false;
                            material = materialLine;

                        } else if ( isPoints && material && ! ( material instanceof THREE.PointsMaterial ) ) {

                            var materialPoints = new THREE.PointsMaterial( { size: 10, sizeAttenuation: false } );
                            THREE.Material.prototype.copy.call( materialPoints, material );
                            materialPoints.color.copy( material.color );
                            materialPoints.map = material.map;
                            materialPoints.lights = false;
                            material = materialPoints;

                        }

                    }

                    if ( ! material ) {

                        if ( isLine ) {

                            material = new THREE.LineBasicMaterial();

                        } else if ( isPoints ) {

                            material = new THREE.PointsMaterial( { size: 1, sizeAttenuation: false } );

                        } else {

                            material = new THREE.MeshPhongMaterial();

                        }

                        material.name = sourceMaterial.name;

                    }

                    material.flatShading = sourceMaterial.smooth ? false : true;
                    material.vertexColors = hasVertexColors ? THREE.VertexColors : THREE.NoColors;

                    createdMaterials.push( material );

                }

                // Create mesh

                var mesh;

                if ( createdMaterials.length > 1 ) {

                    for ( var mi = 0, miLen = materials.length; mi < miLen; mi ++ ) {

                        var sourceMaterial = materials[ mi ];
                        buffergeometry.addGroup( sourceMaterial.groupStart, sourceMaterial.groupCount, mi );

                    }

                    if ( isLine ) {

                        mesh = new THREE.LineSegments( buffergeometry, createdMaterials );

                    } else if ( isPoints ) {

                        mesh = new THREE.Points( buffergeometry, createdMaterials );

                    } else {

                        mesh = new THREE.Mesh( buffergeometry, createdMaterials );

                    }

                } else {

                    if ( isLine ) {

                        mesh = new THREE.LineSegments( buffergeometry, createdMaterials[ 0 ] );

                    } else if ( isPoints ) {

                        mesh = new THREE.Points( buffergeometry, createdMaterials[ 0 ] );

                    } else {

                        mesh = new THREE.Mesh( buffergeometry, createdMaterials[ 0 ] );

                    }

                }

                mesh.name = object.name;

                container.add( mesh );

            }

            console.timeEnd( 'OBJLoader' );

            return container;

        }

    };

    return OBJLoader;

} )();

/**
 * Loads a Wavefront .mtl file specifying materials
 *
 * @author angelxuanchang
 */

THREE.MTLLoader = function ( manager ) {

    this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;

};

THREE.MTLLoader.prototype = {

    constructor: THREE.MTLLoader,

    crossOrigin: 'anonymous',

    /**
     * Loads and parses a MTL asset from a URL.
     *
     * @param {String} url - URL to the MTL file.
     * @param {Function} [onload] - Callback invoked with the loaded object.
     * @param {Function} [onprogress] - Callback for download progress.
     * @param {Function} [onerror] - Callback for download errors.
     *
     * @see setPath setResourcePath
     *
     * @note In order for relative texture references to resolve correctly
     * you must call setResourcePath() explicitly prior to load.
     */
    load: function ( url, onload, onprogress, onerror ) {

        var scope = this;

        var path = ( this.path === undefined ) ? THREE.LoaderUtils.extractUrlBase( url ) : this.path;

        var loader = new THREE.FileLoader( this.manager );
        loader.setPath( this.path );
        loader.load( url, function ( text ) {

            onload( scope.parse( text, path ) );

        }, onprogress, onerror );

    },

    /**
     * Set base path for resolving references.
     * If set this path will be prepended to each loaded and found reference.
     *
     * @see setResourcePath
     * @param {String} path
     * @return {THREE.MTLLoader}
     *
     * @example
     *     mtlLoader.setPath( 'assets/obj/' );
     *     mtlLoader.load( 'my.mtl', ... );
     */
    setPath: function ( path ) {

        this.path = path;
        return this;

    },

    /**
     * Set base path for additional resources like textures.
     *
     * @see setPath
     * @param {String} path
     * @return {THREE.MTLLoader}
     *
     * @example
     *     mtlLoader.setPath( 'assets/obj/' );
     *     mtlLoader.setResourcePath( 'assets/textures/' );
     *     mtlLoader.load( 'my.mtl', ... );
     */
    setResourcePath: function ( path ) {

        this.resourcePath = path;
        return this;

    },

    setTexturePath: function ( path ) {

        console.warn( 'THREE.MTLLoader: .setTexturePath() has been renamed to .setResourcePath().' );
        return this.setResourcePath( path );

    },

    setCrossOrigin: function ( value ) {

        this.crossOrigin = value;
        return this;

    },

    setMaterialOptions: function ( value ) {

        this.materialOptions = value;
        return this;

    },

    /**
     * Parses a MTL file.
     *
     * @param {String} text - Content of MTL file
     * @return {THREE.MTLLoader.MaterialCreator}
     *
     * @see setPath setResourcePath
     *
     * @note In order for relative texture references to resolve correctly
     * you must call setResourcePath() explicitly prior to parse.
     */
    parse: function ( text, path ) {

        var lines = text.split( '\n' );
        var info = {};
        var delimiter_pattern = /\s+/;
        var materialsInfo = {};

        for ( var i = 0; i < lines.length; i ++ ) {

            var line = lines[ i ];
            line = line.trim();

            if ( line.length === 0 || line.charAt( 0 ) === '#' ) {

                // Blank line or comment ignore
                continue;

            }

            var pos = line.indexOf( ' ' );

            var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
            key = key.toLowerCase();

            var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
            value = value.trim();

            if ( key === 'newmtl' ) {

                // New material

                info = { name: value };
                materialsInfo[ value ] = info;

            } else {

                if ( key === 'ka' || key === 'kd' || key === 'ks' || key ==='ke' ) {

                    var ss = value.split( delimiter_pattern, 3 );
                    info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];

                } else {

                    info[ key ] = value;

                }

            }

        }

        var materialCreator = new THREE.MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions );
        materialCreator.setCrossOrigin( this.crossOrigin );
        materialCreator.setManager( this.manager );
        materialCreator.setMaterials( materialsInfo );
        return materialCreator;

    }

};

/**
 * Create a new THREE-MTLLoader.MaterialCreator
 * @param baseUrl - Url relative to which textures are loaded
 * @param options - Set of options on how to construct the materials
 *                  side: Which side to apply the material
 *                        THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
 *                  wrap: What type of wrapping to apply for textures
 *                        THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
 *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
 *                                Default: false, assumed to be already normalized
 *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
 *                                  Default: false
 * @constructor
 */

THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) {

    this.baseUrl = baseUrl || '';
    this.options = options;
    this.materialsInfo = {};
    this.materials = {};
    this.materialsArray = [];
    this.nameLookup = {};

    this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide;
    this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping;

};

THREE.MTLLoader.MaterialCreator.prototype = {

    constructor: THREE.MTLLoader.MaterialCreator,

    crossOrigin: 'anonymous',

    setCrossOrigin: function ( value ) {

        this.crossOrigin = value;
        return this;

    },

    setManager: function ( value ) {

        this.manager = value;

    },

    setMaterials: function ( materialsInfo ) {

        this.materialsInfo = this.convert( materialsInfo );
        this.materials = {};
        this.materialsArray = [];
        this.nameLookup = {};

    },

    convert: function ( materialsInfo ) {

        if ( ! this.options ) return materialsInfo;

        var converted = {};

        for ( var mn in materialsInfo ) {

            // Convert materials info into normalized form based on options

            var mat = materialsInfo[ mn ];

            var covmat = {};

            converted[ mn ] = covmat;

            for ( var prop in mat ) {

                var save = true;
                var value = mat[ prop ];
                var lprop = prop.toLowerCase();

                switch ( lprop ) {

                    case 'kd':
                    case 'ka':
                    case 'ks':

                        // Diffuse color (color under white light) using RGB values

                        if ( this.options && this.options.normalizeRGB ) {

                            value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];

                        }

                        if ( this.options && this.options.ignoreZeroRGBs ) {

                            if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {

                                // ignore

                                save = false;

                            }

                        }

                        break;

                    default:

                        break;

                }

                if ( save ) {

                    covmat[ lprop ] = value;

                }

            }

        }

        return converted;

    },

    preload: function () {

        for ( var mn in this.materialsInfo ) {

            this.create( mn );

        }

    },

    getIndex: function ( materialName ) {

        return this.nameLookup[ materialName ];

    },

    getAsArray: function () {

        var index = 0;

        for ( var mn in this.materialsInfo ) {

            this.materialsArray[ index ] = this.create( mn );
            this.nameLookup[ mn ] = index;
            index ++;

        }

        return this.materialsArray;

    },

    create: function ( materialName ) {

        if ( this.materials[ materialName ] === undefined ) {

            this.createMaterial_( materialName );

        }

        return this.materials[ materialName ];

    },

    createMaterial_: function ( materialName ) {

        // Create material

        var scope = this;
        var mat = this.materialsInfo[ materialName ];
        var params = {

            name: materialName,
            side: this.side

        };

        function resolveURL( baseUrl, url ) {

            if ( typeof url !== 'string' || url === '' )
                return '';

            // Absolute URL
            if ( /^https?:\/\//i.test( url ) ) return url;

            return baseUrl + url;

        }

        function setMapForType( mapType, value ) {

            if ( params[ mapType ] ) return; // Keep the first encountered texture

            var texParams = scope.getTextureParams( value, params );
            var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );

            map.repeat.copy( texParams.scale );
            map.offset.copy( texParams.offset );

            map.wrapS = scope.wrap;
            map.wrapT = scope.wrap;

            params[ mapType ] = map;

        }

        for ( var prop in mat ) {

            var value = mat[ prop ];
            var n;

            if ( value === '' ) continue;

            switch ( prop.toLowerCase() ) {

                // Ns is material specular exponent

                case 'kd':

                    // Diffuse color (color under white light) using RGB values

                    params.color = new THREE.Color().fromArray( value );

                    break;

                case 'ks':

                    // Specular color (color when light is reflected from shiny surface) using RGB values
                    params.specular = new THREE.Color().fromArray( value );

                    break;

                case 'ke':

                    // Emissive using RGB values
                    params.emissive = new THREE.Color().fromArray( value );

                    break;

                case 'map_kd':

                    // Diffuse texture map

                    setMapForType( "map", value );

                    break;

                case 'map_ks':

                    // Specular map

                    setMapForType( "specularMap", value );

                    break;

                case 'map_ke':

                    // Emissive map

                    setMapForType( "emissiveMap", value );

                    break;

                case 'norm':

                    setMapForType( "normalMap", value );

                    break;

                case 'map_bump':
                case 'bump':

                    // Bump texture map

                    setMapForType( "bumpMap", value );

                    break;

                case 'map_d':

                    // Alpha map

                    setMapForType( "alphaMap", value );
                    params.transparent = true;

                    break;

                case 'ns':

                    // The specular exponent (defines the focus of the specular highlight)
                    // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.

                    params.shininess = parseFloat( value );

                    break;

                case 'd':
                    n = parseFloat( value );

                    if ( n < 1 ) {

                        params.opacity = n;
                        params.transparent = true;

                    }

                    break;

                case 'tr':
                    n = parseFloat( value );

                    if ( this.options && this.options.invertTrProperty ) n = 1 - n;

                    if ( n > 0 ) {

                        params.opacity = 1 - n;
                        params.transparent = true;

                    }

                    break;

                default:
                    break;

            }

        }

        this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
        return this.materials[ materialName ];

    },

    getTextureParams: function ( value, matParams ) {

        var texParams = {

            scale: new THREE.Vector2( 1, 1 ),
            offset: new THREE.Vector2( 0, 0 )

         };

        var items = value.split( /\s+/ );
        var pos;

        pos = items.indexOf( '-bm' );

        if ( pos >= 0 ) {

            matParams.bumpScale = parseFloat( items[ pos + 1 ] );
            items.splice( pos, 2 );

        }

        pos = items.indexOf( '-s' );

        if ( pos >= 0 ) {

            texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
            items.splice( pos, 4 ); // we expect 3 parameters here!

        }

        pos = items.indexOf( '-o' );

        if ( pos >= 0 ) {

            texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
            items.splice( pos, 4 ); // we expect 3 parameters here!

        }

        texParams.url = items.join( ' ' ).trim();
        return texParams;

    },

    loadTexture: function ( url, mapping, onload, onprogress, onerror ) {

        var texture;
        var loader = THREE.Loader.Handlers.get( url );
        var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;

        if ( loader === null ) {

            loader = new THREE.TextureLoader( manager );

        }

        if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
        texture = loader.load( url, onload, onprogress, onerror );

        if ( mapping !== undefined ) texture.mapping = mapping;

        return texture;

    }

};


const hexToRgbTreeJs = (hex) => {
  const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);

  return result
    ? {
        r: parseInt(result[1], 16) / 255,
        g: parseInt(result[2], 16) / 255,
        b: parseInt(result[3], 16) / 255
      }
    : null;
};

class Loader {
  constructor() {
    this.callback = null;
  }

  load(file) {
    const request = new XMLHttpRequest();

    request.open("GET", file, true);
    request.onprogress = (evt) => {
      let percent = Math.floor((evt.loaded / evt.total) * 100);

      this.callback(percent);
    };

    request.onload = () => {
      this.complete(file);
    };

    request.send();
  }

  progress(callback) {
    this.callback = callback;
  }

  complete() {}
}

class App {
  constructor() {
    this.loader = new Loader();
    this.loader.progress((percent) => {
      this.progress(percent);
    });

    this.playIntro = document.querySelector(".play-intro");
    this.loaderBar = document.querySelector(".loader");

    this.loader.load("https://iondrimbafilho.me/jingle_bells.mp3");
    this.loader.complete = this.complete.bind(this);

    this.count = 0;
    this.percent = 0;
    this.playing = false;
    this.bgColor = 0x6f11f1;
    this.objects = [];
    this.angle = 0;
  }

  progress(percent) {
    this.loaderBar.style.transform = "scale(" + percent / 100 + ", 1)";

    if (percent === 100) {
      setTimeout(() => {
        requestAnimationFrame(() => {
          this.playIntro.classList.add("control-show");
          this.loaderBar.classList.add("removeLoader");
          this.loaderBar.style.transform = "scale(1, 0)";
        });
      }, 300);
    }
  }

  complete(file) {
    setTimeout(() => {
      this.star = new THREE.Object3D();
      this.house = new THREE.Object3D();

      this.setupAudio();
      this.addSoundControls();
      this.createScene();
      this.createCamera();
      this.addAmbientLight();
      this.addSpotLight();

      this.addCameraControls();
      this.addFloor();

      this.rings = [];

      this.color = 0x1fc30e;

      this.createRingOfSpheres(20, 4, this.color, this.rings, -4);
      this.createRingOfSpheres(18, 3.5, this.color, this.rings, -2);
      this.createRingOfSpheres(16, 3, this.color, this.rings, 0);
      this.createRingOfSpheres(14, 2.5, this.color, this.rings, 2);
      this.createRingOfSpheres(10, 2, this.color, this.rings, 4);
      this.createRingOfSpheres(6, 1.5, this.color, this.rings, 6);
      this.createRingOfSpheres(4, 1.2, this.color, this.rings, 8);
      this.createRingOfSpheres(3, 0.8, this.color, this.rings, 10);
      this.createRingOfSpheres(2, 0.4, this.color, this.rings, 12);
      this.createRingOfSpheres(1, 0, this.color, this.rings, 14);

      this.animate();

      this.loadModels("house", (house) => {
        const scale = 7;
        this.house = house;
        house.scale.set(scale, scale, scale);
        house.position.set(15, -1, 5);

        console.log(this.house.children[0].material.color);

        this.house.children[0].material.color = hexToRgbTreeJs("#000000");
        this.house.children[1].material.color = hexToRgbTreeJs("#000000");

        this.scene.add(this.house);
      });

      this.loadModels("gift-group", (gifts) => {
        const scale = 3;
        gifts.scale.set(scale, scale, scale);
        gifts.position.set(-15, -3, 5);
        gifts.rotateY(180);

        this.scene.add(gifts);
      });

      this.loadModels("single-gift", (gift) => {
        const scale = 20;
        gift.scale.set(scale, scale, scale);
        gift.position.set(8, -3, 11);
        gift.rotateY(-45);

        this.scene.add(gift);
      });

      this.loadModels("star", (star) => {
        const scale = 1;
        this.star = star;
        star.scale.set(scale, scale, scale);
        star.position.set(0, 18, 0);
        this.scene.add(this.star);
      });

      this.playSound(file);
    }, 200);
  }

  addSoundControls() {
    this.btnPlay = document.querySelector(".play");
    this.btnPause = document.querySelector(".pause");

    this.btnPlay.addEventListener("click", () => {
      this.play();
    });

    this.btnPause.addEventListener("click", () => {
      this.pause();
    });
  }

  play() {
    this.audioCtx.resume();
    this.audioElement.play();
    this.btnPlay.classList.remove("control-show");
    this.btnPause.classList.add("control-show");
  }

  pause() {
    this.audioElement.pause();
    this.btnPause.classList.remove("control-show");
    this.btnPlay.classList.add("control-show");
  }

  createRingOfSpheres(count, radius, color, rings, posY) {
    const group = new THREE.Object3D();

    for (let index = 0; index < count; index++) {
      const l = 360 / count;
      const pos = this.radians(l * index);
      const obj = this.createObj(color);
      const distance = radius * 2;
      const sin = Math.sin(pos) * distance;
      const cos = Math.cos(pos) * distance;

      obj.position.set(sin, posY, cos);
      obj.originalPosition = {
        x: sin,
        y: posY,
        z: cos
      };

      this.objects.push(obj);

      group.add(obj);
    }

    this.rings.push(group);
    this.scene.add(group);
  }

  loadModels(name, callback) {
    const mtlLoader = new THREE.MTLLoader();
    const folder = "https://iondrimbafilho.me/models/";

    mtlLoader.setPath(folder);
    mtlLoader.load(`${name}.mtl`, (materials) => {
      materials.preload();

      const objLoader = new THREE.OBJLoader();

      objLoader.setMaterials(materials);
      objLoader.setPath(folder);

      objLoader.load(
        `${name}.obj`,
        (object) => {
          object.castShadow = true;

          callback(object);
        },
        onprogress,
        onerror
      );
    });

    const onprogress = (xhr) => {
      if (xhr.lengthComputable) {
        const percentComplete = (xhr.loaded / xhr.total) * 100;
      }
    };

    const onerror = (xhr) => {};
  }

  createScene() {
    this.scene = new THREE.Scene();
    this.scene.background = new THREE.Color(this.bgColor);

    this.renderer = new THREE.WebGLRenderer({ antialias: true });
    this.renderer.setSize(window.innerWidth, window.innerHeight);

    this.renderer.shadowMap.enabled = true;
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;

    document.body.appendChild(this.renderer.domElement);
  }

  createCamera() {
    this.camera = new THREE.PerspectiveCamera(
      70,
      window.innerWidth / window.innerHeight
    );
    this.camera.position.set(-5, 19, 32);

    this.scene.add(this.camera);
  }

  addCameraControls() {
    this.controls = new THREE.OrbitControls(
      this.camera,
      this.renderer.domElement
    );
    
            this.controls.enableDamping = true;
    this.controls.dampingFactor = 0.04;
    
    document.body.style.cursor = "-moz-grabg";
    document.body.style.cursor = "-webkit-grab";

    this.controls.addEventListener("start", () => {
      requestAnimationFrame(() => {
        document.body.style.cursor = "-moz-grabbing";
        document.body.style.cursor = "-webkit-grabbing";
      });
    });

    this.controls.addEventListener("end", () => {
      requestAnimationFrame(() => {
        document.body.style.cursor = "-moz-grab";
        document.body.style.cursor = "-webkit-grab";
      });
    });
  }

  addGrid() {
    const size = 25;
    const divisions = 25;
    const gridHelper = new THREE.GridHelper(size, divisions);

    gridHelper.position.set(0, -5, 0);
    gridHelper.material.opacity = 0.5;
    gridHelper.material.transparent = false;

    this.scene.add(gridHelper);
  }

  createObj(color) {
    const radius = 1;
    const widthSegments = 20;
    const heightSegments = 20;
    const geometry = new THREE.SphereGeometry(
      radius,
      widthSegments,
      heightSegments
    );
    const material = new THREE.MeshStandardMaterial({
      color: color,
      emissive: 0x0,
      metalness: 0.3,
      roughness: 0.1
    });

    const obj = new THREE.Mesh(geometry, material);

    obj.castShadow = true;
    obj.receiveShadow = true;
    obj.needsUpdate = true;

    return obj;
  }

  onresize() {
    const ww = window.innerWidth;
    const wh = window.innerHeight;

    this.camera.aspect = ww / wh;
    this.camera.updateProjectionMatrix();
    this.renderer.setSize(ww, wh);
  }

  addFloor() {
    const planeGeometry = new THREE.PlaneGeometry(2000, 2000);
    const planeMaterial = new THREE.ShadowMaterial({ opacity: 0.08 });
    const plane = new THREE.Mesh(planeGeometry, planeMaterial);

    planeGeometry.rotateX(-Math.PI / 2);

    plane.position.y = -5;
    plane.receiveShadow = true;

    this.scene.add(plane);
  }

  rotateObject(group, value) {
    group.rotation.y += value;
  }

  addSpotLight() {
    const spotLight = new THREE.SpotLight(0xffffff);

    spotLight.position.set(4, 30, 1);
    spotLight.castShadow = true;

    this.scene.add(spotLight);

    const spotLightHelper = new THREE.SpotLightHelper(spotLight);
  }

  addAmbientLight() {
    const light = new THREE.AmbientLight(0xffffff);

    this.scene.add(light);
  }

  animate() {
    this.controls.update();

    this.angle += 0.2;
    // this.camera.position.x = Math.cos(this.radians(this.angle)) * 35;
    // this.camera.position.z = Math.sin(this.radians(this.angle)) * 35;

    this.drawWave();

    this.renderer.render(this.scene, this.camera);

    requestAnimationFrame(this.animate.bind(this));
  }

  radians(degrees) {
    return (degrees * Math.PI) / 180;
  }

  drawWave() {
    if (this.playing) {
      this.analyser.getByteFrequencyData(this.frequencyData);

      for (let i = 0; i < this.rings.length; i++) {
        const p = this.frequencyData[i];
        const s = this.rings[i];
        const delta = p / 60;

        TweenMax.to(s.position, 0.1, { y: delta });

        TweenMax.to(this.star.position, 0.1, { y: delta + 16 });
      }

      for (let j = 0; j < this.objects.length; j++) {
        const p1 = this.frequencyData[j];
        const s1 = this.objects[j];
        let delta1 = p1 / 100;

        if (delta1 <= 0) {
          delta1 = 1;
        }

        if (delta1 > 1) {
          delta1 = 1;
        }

        TweenMax.to(s1.scale, 0.1, {
          x: delta1,
          y: delta1,
          z: delta1
        });

        TweenMax.to(s1.position, 0.1, {
          y: s1.originalPosition.y + p1 / 50
        });
      }
    }

    this.rotateObject(this.rings[0], 0.01);
    this.rotateObject(this.rings[1], -0.01);
    this.rotateObject(this.rings[2], 0.02);
    this.rotateObject(this.rings[3], -0.02);
    this.rotateObject(this.rings[4], 0.01);
    this.rotateObject(this.rings[5], -0.01);
    this.rotateObject(this.rings[6], 0.02);
    this.rotateObject(this.rings[7], -0.02);
    this.rotateObject(this.star, -0.04);
  }

  setupAudio() {
    this.audioElement = document.getElementById("audio");
    this.audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    this.analyser = this.audioCtx.createAnalyser();

    this.source = this.audioCtx.createMediaElementSource(this.audioElement);
    this.source.connect(this.analyser);
    this.source.connect(this.audioCtx.destination);

    this.bufferLength = this.analyser.frequencyBinCount;

    this.frequencyData = new Uint8Array(this.bufferLength);
    this.audioElement.volume = 1;

    this.audioElement.addEventListener("playing", () => {
      this.playing = true;
    });

    this.audioElement.addEventListener("pause", () => {
      this.playing = false;
    });

    this.audioElement.addEventListener("ended", () => {
      this.playing = false;
      this.btnPause.click();
    });
  }

  playSound(file) {
    setTimeout(() => {
      this.playIntro.addEventListener("click", (evt) => {
        evt.currentTarget.classList.remove("control-show");
        this.play();
      });

      this.audioElement.src = file;
    }, 300);
  }
}

window.app = new App();

window.addEventListener("resize", app.onresize.bind(app));

Поделиться

Tags

  • bowtiesmilelaughingblushsmileyrelaxedsmirk
    heart_eyeskissing_heartkissing_closed_eyesflushedrelievedsatisfiedgrin
    winkstuck_out_tongue_winking_eyestuck_out_tongue_closed_eyesgrinningkissingstuck_out_tonguesleeping
    worriedfrowninganguishedopen_mouthgrimacingconfusedhushed
    expressionlessunamusedsweat_smilesweatdisappointed_relievedwearypensive
    disappointedconfoundedfearfulcold_sweatperseverecrysob
    joyastonishedscreamtired_faceangryragetriumph
    sleepyyummasksunglassesdizzy_faceimpsmiling_imp
    neutral_faceno_mouthinnocent
Что приходит после зимы ?

Редакторы выбирают

Web и Технологии