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

}

 

posted @ 2022-06-08 15:41  名字不好起啊  阅读(904)  评论(0编辑  收藏  举报