寫在前面
之前有一位讀者@就是很愛你呀提出了一個問題:
我覺得非常有意思,囿於當時項目上的其他事情沒有來得及研究,只是匆匆給出了幾個官方類似的演示例子。最近總算空閒下來了就開始着手研究這個問題了,這裏也同樣感謝@就是很愛你呀 給了我深入探索的方向。
效果演示
原諒我主要從事JAVA後臺,前臺我實在是個萌新,無法真正完成這位讀者所要求的功能,不過大部分來看我覺得這個demo還是有參考的價值的。演示如下:
1.首先鼠標單擊物體選中,高亮物體,側邊欄上顯示物體的名稱,這個就是我們需要移動的物體。
2.點擊左側“進入編輯模式”按鈕,進入編輯模式,地面變成虛線框,鼠標所處的位置會顯示一個透明的藍色立方體,並會隨鼠標的移動而移動。
3.在想要移動到的位置單擊鼠標,貨物1就移動到了鼠標點擊的位置,再點擊“退出編輯模式”即可恢復原先的畫面,而且選中的物體已經移動好啦!
如何實現
一開始我也是照着讀者的思路,想要通過拖拽的方式移動。我知道官網上有一個鼠標拖動的例子,其實非常簡單,基本上只要引用DragControls.js
這個官網給出的JS就可以實現。但是以這種方式實現的移動是全方位的移動,你可以用你的鼠標將物體移動到任意位置。如下圖所示,這對於我們來說明顯是不好用的。
於是我結合了官網上的另一個例子:可以實現在鼠標點擊的位置放置一個方塊。
於是乎,在我之前寫的3D倉庫代碼基礎上,我完成了所需的功能,雖然感覺還是很low(狗頭)
HTML代碼
一脈繼承之前的3D倉庫,看過之前1-5章的朋友們應該會很輕鬆:
<!DOCTYPE html>
<html>
<head includeDefault="true">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<title>位置移動</title>
<style>
body {
margin: 0;
overflow: hidden;
}
#label {
position: absolute;
padding: 10px;
background: rgba(255, 255, 255, 0.6);
line-height: 1;
border-radius: 5px;
}
</style>
<script src="./ThreeJs/js/three.js"></script>
<script src="./ThreeJs/js/stats.min.js"></script>
<script src="./ThreeJs/js/DragControls.js"></script>
<script src="./ThreeJs/js/OrbitControls.js"></script>
<script src="./ThreeJs/js/dat.gui.min.js"></script>
<script src="./ThreeJs/js/EffectComposer.js"></script>
<script src="./ThreeJs/js/RenderPass.js"></script>
<script src="./ThreeJs/js/OutlinePass.js"></script>
<script src="./ThreeJs/js/FXAAShader.js"></script>
<script src="./ThreeJs/js/CopyShader.js"></script>
<script src="./ThreeJs/js/ShaderPass.js"></script>
<script src="./ThreeJs/js/ThreeBSP.js"></script>
<script src="./ThreeJs/ThreeJs_Drag.js"></script>
<script src="./ThreeJs/ThreeJs_Composer.js"></script>
<script src="./ThreeJs/Modules.js"></script>
<script src="./ThreeJs/js/jquery-1.11.0.min.js"></script>
</head>
<body>
<div id="label"></div>
<div id="container"></div>
<script>
var stats = initStats();
var camera, scene, renderer, controls, composer, transformControls, options;
var mouse, raycaster;
var rollOverMesh, rollOverMaterial;
var cubeGeo, cubeMaterial;
var objects = [];
var floor, gridHelper;
var selectobject = [];
init();
animate();
// 初始化場景
function initScene() {
scene = new THREE.Scene();
scene.background = new THREE.Color( 0xf0f0f0 );
scene.fog = new THREE.Fog(scene.background, 3000, 5000);
}
// 初始化相機
function initCamera() {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(0, 800, 1500);
camera.lookAt(new THREE.Vector3(0, 0, 0));
}
// 初始化燈光
function initLight() {
var directionalLight = new THREE.DirectionalLight(0xffffff, 0.3); //模擬遠處類似太陽的光源
directionalLight.color.setHSL(0.1, 1, 0.95);
directionalLight.position.set(0, 200, 0).normalize();
scene.add(directionalLight);
var ambient = new THREE.AmbientLight(0xffffff, 1); //AmbientLight,影響整個場景的光源
ambient.position.set(0, 0, 0);
scene.add(ambient);
}
// 初始化性能插件
function initStats() {
var stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
return stats;
}
// 初始化渲染器
function initRenderer() {
renderer = new THREE.WebGLRenderer({
antialias: true
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x4682B4, 1.0);
document.body.appendChild(renderer.domElement);
}
// 初始化軌跡球控件
function initControls() {
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.5;
// 視角最小距離
controls.minDistance = 100;
// 視角最遠距離
controls.maxDistance = 5000;
// 最大角度
controls.maxPolarAngle = Math.PI / 2.2;
}
//創建地板
function createFloor() {
var loader = new THREE.TextureLoader();
loader.load("./ThreeJs/images/floor.jpg", function(texture) {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
texture.repeat.set(10, 10);
var floorGeometry = new THREE.BoxGeometry(2000, 2000, 1);
var floorMaterial = new THREE.MeshBasicMaterial({
map: texture
});
floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2;
scene.add(floor);
objects.push( floor );
});
}
// 初始化GUI
function initGui() {
options = new function () {
this.selectObj ='';
this.startMove = function() {
scene.remove(floor);
scene.add( gridHelper );
scene.add( rollOverMesh );
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
};
this.endMove = function() {
scene.remove(gridHelper);
scene.remove(rollOverMesh);
scene.add( floor );
document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
document.removeEventListener( 'mousedown', onDocumentMouseDown, false );
};
};
var gui = new dat.GUI();
gui.domElement.style = 'position:absolute;top:50px;left:0px;height:600px';
gui.add(options, 'selectObj').name("選中的物體:").listen();
gui.add(options, 'startMove').name("進入編輯模式").listen();
gui.add(options, 'endMove').name("退出編輯模式:").listen();
}
function init() {
initScene();
initCamera();
initLight();
initStats();
initRenderer();
initControls();
createFloor();
initGui();
// roll-over helpers
var rollOverGeo = new THREE.BoxBufferGeometry( 50, 50, 50 );
rollOverMaterial = new THREE.MeshBasicMaterial( { color: 0x00BFFF, opacity: 0.5, transparent: true } );
rollOverMesh = new THREE.Mesh( rollOverGeo, rollOverMaterial );
gridHelper = new THREE.GridHelper(2000,40);
// cubes
cubeGeo = new THREE.BoxBufferGeometry( 50, 50, 50 );
cubeMaterial = new THREE.MeshLambertMaterial( { color: 0xfeb74c, map: new THREE.TextureLoader().load( './ThreeJs/images/box.png' ) } );
var voxel = new THREE.Mesh( cubeGeo, cubeMaterial );
voxel.position.set(25,25,25);
voxel.name = "貨物$1";
scene.add( voxel );
objects.push( voxel );
var voxel2 = voxel.clone();
voxel2.position.set(225,25,25);
voxel2.name = "貨物$2";
scene.add( voxel2 );
objects.push( voxel2 );
var voxel3 = voxel.clone();
voxel3.position.set(-225,25,25);
voxel3.name = "貨物$3";
scene.add( voxel3 );
objects.push( voxel3 );
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
window.addEventListener( 'resize', onWindowResize, false );
//添加選中時的蒙版
composer = new THREE.ThreeJs_Composer(renderer, scene, camera, options, selectobject);
//添加拖動效果
// transformControls = new THREE.ThreeJs_Drag(camera, renderer.domElement, scene, controls);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function onDocumentMouseMove( event ) {
event.preventDefault();
mouse.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 ) {
var intersect = intersects[ 0 ];
rollOverMesh.position.copy( intersect.point ).add( intersect.face.normal );
rollOverMesh.position.divideScalar( 50 ).floor().multiplyScalar( 50 ).addScalar( 25 );
}
renderer.render(scene, camera);
}
function onDocumentMouseDown( event ) {
event.preventDefault();
mouse.set( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1 );
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( objects );
if ( intersects.length > 0 ) {
var intersect = intersects[ 0 ];
selectobject[0].position.copy( intersect.point ).add( intersect.face.normal );
selectobject[0].position.divideScalar( 50 ).floor().multiplyScalar( 50 ).addScalar( 25 );
renderer.render(scene, camera);
}
}
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
composer.render();
update();
}
// 更新控件
function update() {
stats.update();
controls.update();
}
</script>
</body>
</html>
其他我導入的一些JS在我之前的幾章裏已經出現過了,我就不再贅述了。
代碼原理很簡單,在點擊“進入編輯模式”後,添加系統監聽事件:
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
然後再把地板抹除,添加一個gridHelper 以顯示虛線框。
scene.remove(floor);
scene.add( gridHelper );
其中mousemove
的作用是在鼠標移動的時候顯示一個半透明的藍色方塊。mousedown
的作用是將選中的物體的位置移動到鼠標點擊的位置。
點擊“退出編輯模式”後再把之前移除的地板,添加的監聽和gridHelper 恢復成原始的樣子就可以了。
結束語
我跟廣大學習ThreeJs的初學者一樣,仍帶着懵懂的心去探索這片新大陸,CSDN上的許多前輩都給了我很多關鍵的靈感和技術方法,如果大家有興趣,也可以互相交流成長,歡迎大家指導諮詢。PS:大家有興趣可以點進去我的頭像,陸陸續續也寫了十來篇了。
鏈接:使用ThreeJs從零開始構建3D智能倉庫——第一章: 點我跳轉.
鏈接:使用ThreeJs從零開始構建3D智能倉庫——第二章: 點我跳轉.
鏈接:使用ThreeJs從零開始構建3D智能倉庫——第三章: 點我跳轉.
鏈接:使用ThreeJs從零開始構建3D智能倉庫——第四章: 點我跳轉.
鏈接:使用ThreeJs從零開始構建3D智能倉庫——第五章: 點我跳轉.