ThreeJs技术交流(1)——以限定位置的方式移动物体

ThreeJs技术交流(1)——以限定位置的方式移动物体

写在前面

之前有一位读者@就是很爱你呀提出了一个问题:
在这里插入图片描述
我觉得非常有意思,囿于当时项目上的其他事情没有来得及研究,只是匆匆给出了几个官方类似的演示例子。最近总算空闲下来了就开始着手研究这个问题了,这里也同样感谢@就是很爱你呀 给了我深入探索的方向。

效果演示

原谅我主要从事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智能仓库——第五章: 点我跳转.

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值