主要内容: 使用 threejs 创建 20x20 的网格,鼠标移动时,方块跟随移动,点击时在网格任意位置放置方块,按 shift 时,删除当前位置方块。
流程如下:
- 创建网格
- 创建一个与网格同样尺寸的平面
- 创建一个方块 mesh_1 与网格同样的尺寸
- 一个与网格同样的方块 geometry_2 , 不加入 scene 中
- 三个事件:
- 鼠标移动事件,随着鼠标移动,更改 mesh_1 位置,并重新渲染
- 鼠标点击事件,在交点位置,创建新 mesh, 若是相交对象不为 平面,则删除当前对象
- keydown, keyup, 更改是否删除的状态
详细代码如下:
import * as THREE from './build/three.module' import { stat } from 'fs'; var camera, scene, renderer; var moveMesh, staticGeo,staticMat, plane; var objects = []; var raycaster, mouse; var isShiftDown = false; function init() { camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000); camera.position.set(500, 800, 1300); camera.lookAt(0, 0, 0); scene = new THREE.Scene(); scene.background = new THREE.Color(0xf0f0f0); // lights var light = new THREE.AmbientLight(0x606060); scene.add(light); // grids var grid = new THREE.GridHelper(1000, 20); scene.add(grid); // plane, 辅助碰撞检测 var planeGeo = new THREE.PlaneBufferGeometry(1000, 1000); var planMat = new THREE.MeshBasicMaterial({color: 0xffff00, visible : true}); plane = new THREE.Mesh(planeGeo, planMat); plane.rotateX(-Math.PI /2); scene.add(plane); objects.add(plane); // 射线 raycaster = new THREE.Raycaster(); mouse = new THREE.Vector2(); // moveCube; var moveGeo = new THREE.BoxBufferGeometry(50, 50, 50); var moveMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000, opacity: 0.5, transparent: true }); moveMesh = new THREE.Mesh(moveGeo, moveMaterial); scene.add(moveMesh); // static cube staticGeo = new THREE.BoxBufferGeometry(50, 50, 50); staticMat = new THREE.MeshLambertMaterial({ color: 0xfeb74c, map: new THREE.TextureLoader().load('textures/square-outline-textured.png') }); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); document.body.addEventListener('mousemove', onDocuementMouseMove, false); document.body.addEventListener('mousedown', onDocumentMouseDown, false); document.body.addEventListener('keydown', onDocuementKeyDown, false); document.body.addEventListener('keyup', onDocuementKeyUp, false); window.addEventListener('resize', onWindowResize, false); } function onDocumentMouseDown(event) { event.preventDefault(); // 鼠标位置归一化 mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; // 通过摄像机与鼠标更新射线 raycaster.setFromCamera(mouse, camera); var intersects = raycaster.intersectObjects(objects); if(intersects.length > 0) { var intersect = intersects[0]; if (isShiftDown) { if(intersect.object !== plane) { scene.remove(intersect.object); objects.splice(objects.indexOf(intersect.object), 1); } } else { var staticMesh = new THREE.Mesh(staticGeo, staticMat); staticMesh.position.copy(intersect.point).add(intersect.face.normal); staticMesh.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25); scene.add(staticMesh); objects.push(staticMesh); } } } function onDocuementMouseMove(event) { event.preventDefault(); // 鼠标位置归一化 mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; // 通过摄像机与鼠标更新射线 raycaster.setFromCamera(mouse, camera); var intersects = raycaster.intersectObjects(objects); if(intersects.length > 0) { intersect = intersects[0]; // 移动位置到目标点 moveMesh.position.copy(intersect.point).add(intersect.face.normal); // 计算具体方格 moveMesh.position.divideScalar(50).floor().multiplyScalar(50).addScalar(25); } render(); } function onDocuementKeyDown(event) { switch (event.keyCode) { case 16: isShiftDown = true; break; } } function onDocuementKeyUp(event) { switch (event.keyCode) { case 16: isShiftDown = false; break; } } function onWindowResize() { camera.aspect = window.innerWidth/ window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function render() { renderer.render(scene, camera); }