three.js 物体、路径与移动
import * as THREE from 'three'; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import { DragControls } from "three/examples/jsm/controls/DragControls"; import { Pieces } from "../@share/pieces"; /** * 3d 动画测试,路径与移动 * https://threejs.org/docs/index.html#api/zh/extras/curves/SplineCurve - 曲线 * https://threejs.org/docs/index.html#api/zh/extras/core/Curve - 核心 */ export class ThreePathAndMove { constructor(canvasId) { this.work(canvasId); } work(canvasId) { // 创建 3d 场景 const scene = new THREE.Scene(); scene.background = new THREE.Color(0x9e9e9e); // 渲染器 const renderer = this.addRenderer(); // 创建相机 const camera = this.makeCamera(); // .multiplyScalar() 矩阵的每个元素乘以参数。 camera.position.set(-20, 20, 80).multiplyScalar(3); // 朝向 camera.lookAt(0, 0, 0); // 控制相机 const controls = new OrbitControls(camera, renderer.domElement); controls.update(); // 初始化灯光 // 方向光 const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(0, 20, 0) scene.add(light); // 方向光 const light2 = new THREE.DirectionalLight(0xffffff, 1); light2.position.set(1, 2, 4); scene.add(light2); const len = 300; // 添加地面 this.addArea(scene, len); // 添加物体 const boat = this.makeObject(scene); // 绘制路径 const curve = this.addPath(scene, len / 2); const boatPosition = new THREE.Vector2() const boatTarget = new THREE.Vector2() function render(time) { time *= 0.0005; const boatTime = time * 0.05; curve.getPointAt(boatTime % 1, boatPosition); // 获取路径前一点坐标,用于头部向前 curve.getPointAt((boatTime + 0.01) % 1, boatTarget); // 位移 boat.position.set(boatPosition.x, 0, boatPosition.y); boat.lookAt(boatTarget.x, 1, boatTarget.y); // 加载渲染器 renderer.render(scene, camera) // 开始动画 requestAnimationFrame(render) } // 开始渲染 requestAnimationFrame(render); } /** * 创建相机公用方法 * */ makeCamera(fov = 40) { const aspect = 2 // the canvas default const zNear = 0.1 const zFar = 1000 return new THREE.PerspectiveCamera(fov, aspect, zNear, zFar) } /** * 添加平面 */ addArea(scene, len) { const groundGeometry = new THREE.PlaneGeometry(len, len); const groundMaterial = new THREE.MeshPhongMaterial({ color: 0x23ADE5 }); const groundMesh = new THREE.Mesh(groundGeometry, groundMaterial); groundMesh.rotation.x = Math.PI * -0.5; scene.add(groundMesh); } /** * 添加渲染器 */ addRenderer() { let renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); // 最后一步很重要,我们将renderer(渲染器)的dom元素(renderer.domElement)添加到我们的HTML文档中。这就是渲染器用来显示场景给我们看的<canvas>元素。 document.body.appendChild(renderer.domElement); return renderer; } /** * 添加船只 */ makeObject(scene) { const boat = new THREE.Object3D(); boat.position.y = -1; scene.add(boat); const bodyRadius = 2; const bodyLength = 10; // 舰体 const bodyGeometry = new THREE.CapsuleGeometry(bodyRadius, bodyLength, 4, 32); const bodyMaterial = new THREE.MeshPhongMaterial({ color: 0x6688aa }); const bodyMesh = new THREE.Mesh(bodyGeometry, bodyMaterial); bodyMesh.rotation.x = Math.PI * 0.5; boat.add(bodyMesh); const doorRadius = bodyRadius / 7 * 5; const doorHeight = 2; // 舰桥 const doorGeometry = new THREE.CylinderGeometry(doorRadius, doorRadius, doorHeight, 36); const doorMesh = new THREE.Mesh(doorGeometry, bodyMaterial); doorMesh.position.set(0, 2, -2); boat.add(doorMesh); const glassRadius = doorRadius / 7; const glassHeight = bodyLength / 2; // 潜望镜 const glassGeometry = new THREE.CylinderGeometry(glassRadius, glassRadius, glassHeight, 36); const glassMesh = new THREE.Mesh(glassGeometry, bodyMaterial); glassMesh.position.set(0, 2, -2.9); boat.add(glassMesh); const swingWidth = 5; const swingHeight = doorHeight / 20; // 舰桥翼 const swingGeometry = new THREE.BoxGeometry(swingWidth, swingHeight, 1, 36); const swingMesh = new THREE.Mesh(swingGeometry, bodyMaterial); swingMesh.position.set(0, 2.5, -2); boat.add(swingMesh); const swingTailHeight = doorHeight / 8; const swingTailWidth = 4; // 尾翼 const swingTail1Geometry = new THREE.BoxGeometry(swingTailWidth, swingTailHeight, 2, 36); const swingT1Mesh = new THREE.Mesh(swingTail1Geometry, bodyMaterial); swingT1Mesh.position.set(0, 0, -6); boat.add(swingT1Mesh); const swingTail2Geometry = new THREE.BoxGeometry(swingTailHeight, swingTailWidth, 2, 36); const swingT2Mesh = new THREE.Mesh(swingTail2Geometry, bodyMaterial); swingT2Mesh.position.set(0, 0, -6); boat.add(swingT2Mesh); return boat; } /** * 添加路径,平面 */ addPath(scene, num) { let max = num; let min = -num; let pointArr = []; // 随机点 for (let i = 0; i < 10; i++) { let point = Pieces.getRandomNumberByCount(2, max, 0, min); pointArr.push(new THREE.Vector2(point[0], point[1])); } // 封闭路径 pointArr.push(JSON.parse(JSON.stringify(pointArr[0]))); /** * 样条曲线(SplineCurve) * 从一系列的点中,创建一个平滑的二维样条曲线。内部使用Interpolations.CatmullRom来创建曲线。 * SplineCurve( points : Array ) * points – 定义曲线的Vector2点的数组。 * * 方法 * .getPoints ( divisions : Integer ) : Array * divisions -- 要将曲线划分为的分段数。默认是 5. * 使用getPoint(t)返回一组divisions+1的点 * * .getPointAt ( u : Float, optionalTarget : Vector ) : Vector * u - 根据弧长在曲线上的位置。必须在范围[0,1]内。 * optionalTarget — (可选) 如果需要, (可选) 如果需要, 结果将复制到此向量中,否则将创建一个新向量。 * 根据弧长返回曲线上给定位置的点。 * @type {SplineCurve} */ const curve = new THREE.SplineCurve(pointArr); const points = curve.getPoints(50); const geometry = new THREE.BufferGeometry().setFromPoints(points); const material = new THREE.LineBasicMaterial({ color: 0xffffff }); const splineObject = new THREE.Line(geometry, material); splineObject.rotation.x = Math.PI * 0.5; splineObject.position.y = 0.05; scene.add(splineObject); return curve; } }