主要內容: 使用 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);
}
