! Сегодня

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

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

Танцуй так, как будто на тебя никто не смотрит. Пой, как будто тебя никто не слышит. Люби так, как будто тебя никогда не предавали, и живи так, как будто земля — это рай.

6-декабря-2023, 21:37   18   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 и Технологии