three.js內部拖拽例子全詳解

allenjiao發表於2018-04-11
<!DOCTYPE html>
<html>
    <head>
        <title>three.js webgl - draggable cubes</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <style>
            body {
                font-family: Monospace;
                background-color: #f0f0f0;
                margin: 0px;
                overflow: hidden;
            }
        </style>
    </head>
    <body>
        <script src="script/three.js"></script>
        <script src="script/jquery-1.7.2.min.js"></script>
        <script src="script/libs/tween.min.js"></script>
        <script src="script/controls/TrackballControls.js"></script>
        <!-- <script src="script/renderers/CSS3DRenderer.js"></script> -->
        <script src="script/libs/stats.min.js"></script>
            
            
        <script>
            //container是指頁面放置這個webGL的一個層  stats是指
            var container, stats;
            //camera是指場景相機 controls是指控制器,比如一般的滑鼠視角控制等  scene是場景,就好像一個大的舞臺 
            //projector是可能指螢幕和場景轉換工具 renderer是指場景渲染,能把場景呈現到瀏覽器裡
            var camera, controls, scene, projector, renderer;
            //objects是指場景中的實體集合  plane是一個水平面網格,當選中一個物體時,可以通過這個水平面,看到和它在同一平面內的其他物理
            var objects = [], plane;
            //mouse,滑鼠所對應的二維向量  offset 是指三維偏移向量 INTERSECTED是指相交的物件 SELECTED選中的物件
            var mouse = new THREE.Vector2(),
            offset = new THREE.Vector3(),
            INTERSECTED, SELECTED;
                
            //初始化整個場景
            init();
            //開始每秒最大60幀的渲染
            animate();
    
            function init() {
                //建立一個放置webGL的層
                container = document.createElement( 'div' );
                //把上面的層放到body裡
                document.body.appendChild( container );
                //建立一個透視相機 可視角度70度 瀏覽器的全屏的寬高 水平視錐 最近1 最遠10000 
                camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
                //相機的位置z正半軸3000
                camera.position.z = 3000;
                //軌跡球控制 滑鼠左擊旋轉  右擊平移 滾輪遠近
                controls = new THREE.TrackballControls( camera );
                //旋轉速度
                controls.rotateSpeed = 1.0;
                //變焦速度
                controls.zoomSpeed = 1.2;
                //平移速度
                controls.panSpeed = 0.8;
                //是否不變焦
                controls.noZoom = false;
                //是否不平移
                controls.noPan = true;
                //可能是慣性 true沒有慣性
                controls.staticMoving = false;
                //動態阻尼係數 就是靈敏度
                controls.dynamicDampingFactor = 0.3;
                    
                //新建一個場景
                scene = new THREE.Scene();
    
                    
                //新建一個環境光 就是正常物體都能照到的光
                var ambientLight = new THREE.AmbientLight( Math.random() *0xffffff );
                //把環境光加到場景中
                scene.add( ambientLight );
                //再新建一個無線遠的平行光,就是像太陽光一樣的,
                var directionalLight = new THREE.DirectionalLight( Math.random() * 0xffffff );
                //把平行光放在y軸正方向上的無窮遠處
                directionalLight.position.set( 0, 1, 0 );
                //把平行光加到場景中
                scene.add( directionalLight );
                //再建一個點光源 顏色 強度 照射距離
                var pointLight = new THREE.PointLight( 0xff0000, 1, 500 );
                //設定點光源的位置
                pointLight.position.set(0,0,-200);
                //把點光源加入到場景中
                scene.add( pointLight );
                //定義一個球體
                var geometry = new THREE.SphereGeometry( 70, 32, 16 );
    
                for ( var i = 0; i < 50; i ++ ) {
                    //把這個球體加上網格材質,隨機生成一種顏色的網格物件
                    var object = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: Math.random() * 0xffffff } ) );
                        
                    //把材質顏色放到物件的環境顏色上,還沒有理解
                    object.material.ambient = object.material.color;
                        
                    //隨機放置一個位置
                    object.position.x = Math.random() * 3000 - 1500;
                    object.position.y = Math.random() * 1800 - 1000;
                    object.position.z = Math.random() * 2400 - 1200;
                        
                    //加到場景中去
                    scene.add( object );
                        
                    //放到物件陣列中
                    objects.push( object );
    
                }
                //建立一個長2000寬2000,8*8的網格物件並加上一種基本材質
                plane = new THREE.Mesh( new THREE.PlaneGeometry( 2000, 2000, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0x000000, opacity: 0.25, transparent: true, wireframe: true } ) );
                //網格物件是否可見
                plane.visible = true;
                //把網格物件加到場景中
                scene.add( plane );
                    
                //建立一個螢幕和場景轉換工具
                projector = new THREE.Projector();
                //建立一個抗鋸齒的webgl渲染器
                renderer = new THREE.WebGLRenderer( { antialias: true } );
                //是否排列物件 預設是true
                renderer.sortObjects = false;
                //設定渲染的範圍
                renderer.setSize( window.innerWidth, window.innerHeight );
                //是否啟用陰影地圖
                renderer.shadowMapEnabled = true;
                //設定陰影地圖是紋理陰影
                renderer.shadowMapType = THREE.PCFShadowMap;
                //把渲染成的所有頁面標籤加入到webgl層中
                container.appendChild( renderer.domElement );
                    
                //顯示了一個左上角的效能監視視窗
                stats = new Stats();
                stats.domElement.style.position = 'absolute';
                stats.domElement.style.top = '0px';
                container.appendChild( stats.domElement );
                    
                //加入滑鼠拖動物件的一系列監聽事件
                renderer.domElement.addEventListener( 'mousemove', onDocumentMouseMove, false );
                renderer.domElement.addEventListener( 'mousedown', onDocumentMouseDown, false );
                renderer.domElement.addEventListener( 'mouseup', onDocumentMouseUp, false );
    
                //加入視窗改變大小時的監聽事件
                window.addEventListener( 'resize', onWindowResize, false );
    
            }
            //視窗改變大小時的觸發事件
            function onWindowResize() {
                //改變相機的aspect屬性為當前視窗的寬高
                camera.aspect = window.innerWidth / window.innerHeight;
                //更新相機的投影矩陣
                camera.updateProjectionMatrix();
                //重新設定場景寬高
                renderer.setSize( window.innerWidth, window.innerHeight );
    
            }
                
            //當滑鼠移動時觸發的事件
            function onDocumentMouseMove( event ) {
                //阻止本來的預設事件,比如瀏覽器的預設右鍵事件是彈出瀏覽器的選項
                event.preventDefault();
                    
                //mouse.x是指 滑鼠的x到螢幕y軸的距離與螢幕寬的一半的比值 絕對值不超過1
                //mouse.y是指 滑鼠的y到螢幕x軸的距離與螢幕寬的一半的比值 絕對值不超過1
                //
                //下面的矩形是顯示器螢幕,三維空間座標系的佈局以及螢幕的二維座標系
                //
                // 滑鼠是從  二維座標系
                // 這個點 .-------------------------------------------|-->滑鼠x正半軸
                //  開始算|                   個 y     /              |
                //   x,y  |                    |     /                |
                //        |                    |   /                  |
                //        |          三維座標系| /                    |
                //        | -------------------/-------------------->x|
                //        |                  / |                      |
                //        |                /   |                      |
                //        |              /     |                      |
                //        |__________Z_匕______|______________________|
                //        |
                // 滑鼠y  \/
                // 正半軸
                mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
                mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
    
                //新建一個三維變換半單位向量 假設z方向就是0.5,這樣我左右移的時候,還會有前後移的效果
                var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
                    
                //螢幕和場景轉換工具根據照相機,把這個向量從螢幕轉化為場景中的向量
                projector.unprojectVector( vector, camera );
                
                //vector.sub( camera.position ).normalize()變換過後的向量vector減去相機的位置向量後標準化
                //新建一條從相機的位置到vector向量的一道光線
                var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
    
                //是否有東西被選中
                if ( SELECTED ) {
                    //有的話取到這條光線射到的物體所在水平面上所有相交元素的集合,所以這樣就可以限制每次拖動距離不能超出水平面panel
                    var intersects = raycaster.intersectObject( plane );
                    //這個滑鼠點中的點的位置減去偏移向量,新位置賦值給選中物體
                    if(intersects.length > 0){
                        SELECTED.position.copy( intersects[ 0 ].point.sub( offset ) );
                    }
                    return;
    
                }
    
                //否則的話,光線和所有物體相交,返回相交的物體
                var intersects = raycaster.intersectObjects( objects );
                //如果有物體相交了
                if ( intersects.length > 0 ) {
                    //並且相交物體不是上一個相交物體
                    if ( INTERSECTED != intersects[ 0 ].object ) {
                        //將這個物件放到INTERSECTED中
                        INTERSECTED = intersects[ 0 ].object;
                        //改變水平面的位置
                        plane.position.copy( INTERSECTED.position );
                        //並把水平面指向到相機的方向
                        plane.lookAt( camera.position );
    
                    }
                    //改變滑鼠的樣式
                    container.style.cursor = 'pointer';
    
                } else {
                    //改變滑鼠的樣式
                    container.style.cursor = 'auto';
    
                }
    
            }
            //當滑鼠按下時觸發的事件
            function onDocumentMouseDown( event ) {
                //阻止本來的預設事件,比如瀏覽器的預設右鍵事件是彈出瀏覽器的選項
                event.preventDefault();
    
                var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
                projector.unprojectVector( vector, camera );
    
                var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
    
                var intersects = raycaster.intersectObjects( objects );
    
                if ( intersects.length > 0 ) {
                    //不能改變視角了
                    controls.enabled = false;
                    //把選中的物件放到全域性變數SELECTED中
                    SELECTED = intersects[ 0 ].object;
                    //再和水平面相交
                    var intersects = raycaster.intersectObject( plane );
                        
                    //選中位置和水平面位置(物體中心)的偏移量
                    offset.copy( intersects[ 0 ].point ).sub( plane.position );
                    //改變滑鼠的樣式
                    container.style.cursor = 'move';
    
                }
    
            }
    
            function onDocumentMouseUp( event ) {
    
                event.preventDefault();
                //又能改變視角了
                controls.enabled = true;
                //如果有相交物體
                if ( INTERSECTED ) {
                    //把位置給水平面
                    plane.position.copy( INTERSECTED.position );
                    //選中物體置空
                    SELECTED = null;
    
                }
                //改變滑鼠的樣式
                container.style.cursor = 'auto';
    
            }
    
            //每秒最大60幀的渲染,一直在迴圈
            function animate() {
                //迴圈本身
                requestAnimationFrame( animate );
                //渲染場景
                render();
                //更新效能監視視窗
                stats.update();
    
            }
    
            function render() {
                //更新控制器
                controls.update();
                //渲染場景和相機
                renderer.render( scene, camera );
            }
        </script>
    </body>
</html>

相關文章