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;
}
}