THREEJS 使用CatmullRomCurve3实现汽车模型沿着指定轨迹移动

效果预览

在这里插入图片描述

准备所需资源

在这里插入图片描述

搭建场景环境

 const container = document.querySelector("#box_bim");
 // 创建摄像机
 camera = new THREE.PerspectiveCamera(
   50,
   window.innerWidth / window.innerHeight,
   0.1,
   1000
 );
 // 
 camera.position.set(500, 500, 500);
 // 调整近裁减值
 camera.near = 0.1; // 根据需要调整这个值
 // 调整远裁减值
 camera.far = 100000; // 根据需要调整这个值
 
 // 更新相机投影矩阵(如果使用了透视投影)
 camera.updateProjectionMatrix();
 // 创建场景
 scene = new THREE.Scene();
 renderer = new THREE.WebGLRenderer({ antialias: true });
 renderer.setPixelRatio(window.devicePixelRatio);
 renderer.setSize(window.innerWidth, window.innerHeight);
 renderer.shadowMap.enabled = true
 container.appendChild(renderer.domElement);
 //辅助观察的坐标系
 const axesHelper = new THREE.AxesHelper(100);
 scene.add(axesHelper);
 const environment = new RoomEnvironment();
const pmremGenerator = new THREE.PMREMGenerator(renderer);

 scene.background = new THREE.Color(0x000000); //背景色
 scene.environment = pmremGenerator.fromScene(environment).texture;

 // 如果你使用的是 THREE.SpotLight(光束光源),可以通过以下方式设置光束的范围:
 const spotLight = new THREE.SpotLight(0xFFFFFF, 1, 100);
 spotLight.position.set(50, 50, 50);
 spotLight.angle = Math.PI / 4; // 光束的角度
 spotLight.penumbra = 0.05; // 光束边缘的模糊度
 spotLight.decay = 2; // 光强随距离的减少速度
 scene.add(spotLight);
 
 labelRenderer = new CSS2DRenderer();
 labelRenderer.setSize(window.innerWidth, window.innerHeight);
 labelRenderer.domElement.style.position = "absolute";
 labelRenderer.domElement.style.top = "0px";
 container.appendChild(labelRenderer.domElement);

 controls = new OrbitControls(camera, labelRenderer.domElement); //控制图层,添加相机控件
 controls.enableDamping = true;
 // controls.minDistance = 1;
 // controls.maxDistance = 10;
 controls.target.set(0, 0.5, 0);
 controls.autoRotate = false
 controls.autoRotateSpeed =0.5
 controls.update();

添加模型

使用的是gltf类型的模型

        // 实例化加载器g1tf
        const gltfLoader = new GLTFLoader();
        // 实例化加较器draco -- 为了解决大型模型经过压缩无法直接加载的问题
        const dracoloader = new DRACOLoader();
        dracoloader.setDecoderPath("./draco/");

        // 加载第一个模型 - 人
        gltfLoader.setDRACOLoader(dracoloader);
        gltfLoader.load(
            "./model/people.gltf",
            (gltf) =>{
                // 设置模型大小
                gltf.scene.scale.set(0.2, 0.2, 0.2);
                gltf.scene.position.set(200,0,0)//调整人位置,使得车模型在小区模型的地面上
                people = gltf.scene
                scene.add(gltf.scene);
            }
        )
        // 加载第二个模型 - 车辆
        gltfLoader.load(
            "./model/car.gltf",
            (gltf) =>{
                gltf.scene.scale.set(0.2,0.2,0.2);
                gltf.scene.position.set(100,0,0)//调整车辆位置,使得车模型在小区模型的地面上
                car =  gltf.scene
                scene.add(gltf.scene); 
            }
        )
        // 加载第三个模型 - 小区
        gltfLoader.load(
            "./model/district.gltf",
            (gltf) =>{
                gltf.scene.scale.set(0.08, 0.08, 0.08); // 小区模型太大,需要缩小
                gltf.scene.position.set(100,238,500)//调整车辆位置,使得车模型在小区模型的地面上
                gltf.scene.rotation.y  = -Math.PI / 13;//旋转模型,使得坐标轴与小区的房屋平行便于计算轨迹向量
                scene.add(gltf.scene);
            }
        )

增加运动轨迹

 // 创建轨迹 - 车辆的移动轨迹
        spline = new THREE.CatmullRomCurve3([

            new THREE.Vector3(100,0,0), 
            new THREE.Vector3(100,0,30), 
            new THREE.Vector3(100,0,60), 
            new THREE.Vector3(100,0,90), 
            new THREE.Vector3(100,0,120), 
            new THREE.Vector3(100,0,150), 
            new THREE.Vector3(100,0,180), 
            new THREE.Vector3(100,0,210), 
            new THREE.Vector3(100,0,240), 
            new THREE.Vector3(100,0,270), 
            new THREE.Vector3(100,0,300), 
            new THREE.Vector3(110,0,300), 
            new THREE.Vector3(130,0,300), 
            new THREE.Vector3(150,0,300), 
            new THREE.Vector3(170,0,300), 
            new THREE.Vector3(190,0,300), 
            new THREE.Vector3(210,0,300), 
            new THREE.Vector3(290,0,300), 
            new THREE.Vector3(500,0,300), 
            new THREE.Vector3(800,0,300), 
            new THREE.Vector3(900,0,300), 
            new THREE.Vector3(900,0,290), 
            new THREE.Vector3(900,0,280), 
            new THREE.Vector3(900,0,270), 
            new THREE.Vector3(900,0,260), 
            new THREE.Vector3(900,0,250), 
            new THREE.Vector3(900,0,170), 
            new THREE.Vector3(900,0,120), 
            new THREE.Vector3(900,0,90), 
            new THREE.Vector3(900,0,60), 
            new THREE.Vector3(900,0,30), 
            new THREE.Vector3(900,0,-30), 
            new THREE.Vector3(900,0,-60), 
            new THREE.Vector3(900,0,-90), 
            new THREE.Vector3(800,0,-90), 
            new THREE.Vector3(100,0,-90), 
                        ]);
        spline.curveType = 'catmullrom'
        spline.closed = true //设置是否闭环
        spline.tension = 0.5 //设置线的张力,0为无弧度折线
        // 为曲线添加材质在场景中显示出来,方便看到轨迹线
        const points = spline.getPoints(50) // 50等分获取曲线点
        const geometry = new THREE.BufferGeometry().setFromPoints(points);
        const material = new THREE.LineBasicMaterial({ color: 0x000000 });

        // Create the final object to add to the scene
        const curveObject = new THREE.Line(geometry, material);
        scene.add(curveObject) // 添加到场景中

使物体沿着轨迹移动

  // 创建渲染函数
    let progress = 0; // 物体运动时在运动路径的初始位置,范围0~1
    const velocity = 0.0008; // 影响运动速率的一个值,范围0~1,需要和渲染频率结合计算才能得到真正的速率
    // 物体沿线移动方法
    function moveOnCurve() {
        if (spline == null || car == null) {
            console.log("Loading")
        } else {
            if (progress <= 1 - velocity) {
                const point = spline.getPointAt(progress); //获取样条曲线指定点坐标
                const pointBox = spline.getPointAt(progress + velocity); //获取样条曲线指定点坐标

                if (point && pointBox) {
                    car.position.set(point.x, point.y, point.z);
                    var targetPos = pointBox   //目标位置点
                    var offsetAngle = 0 //目标移动时的朝向偏移

                    // //以下代码在多段路径时可重复执行
                    var mtx = new THREE.Matrix4()  //创建一个4维矩阵
                    // .lookAt ( eye : Vector3, target : Vector3, up : Vector3 ) : this,构造一个旋转矩阵,从eye 指向 target,由向量 up 定向。
                    mtx.lookAt(car.position, targetPos, car.up) //设置朝向
                    mtx.multiply(new THREE.Matrix4().makeRotationFromEuler(new THREE.Euler(0, offsetAngle, 0)))
                    var toRot = new THREE.Quaternion().setFromRotationMatrix(mtx)  //计算出需要进行旋转的四元数值
                    car.quaternion.slerp(toRot, 0.2)
                }

                progress += velocity;
            } else {
                progress = 0;
            }
        }

    };

参考链接

Three.js学习

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
对整threeJS体系进行全面剖析。整理出全面的教学大纲,涵盖内容面非常广。此教学版本为threeJS107版本。关于版本不建议大家使用低于90的版本学习。以下是课程目录1-ThreeJS概览(基本图形简介,什么是点线面如何绘制点线面,什么是材质,什么是几何体,什么是相机,什么是渲染器,什么是场景)2-相机和渲染器(详解相机类型,渲染器如何使用,针对不同场景怎么用,怎么调效果,怎么渲染,怎么输出画布,如何解决透明问题等等)3-创建平面几何(常见的几何体如何使用,如何使用简单的几何体绘制出自定义自己想要的几何体,关于几何体的性能剖析,如何解决性能,几何体的渲染原理)4-高级图形算法常见库(求直线的斜率  计算线段与圆的交点 计算线段的长度 判断折线是否在多边形内 等等)5-sprite精灵(怎么让一个图标永远朝向屏幕,精灵的属性,精灵材质原理等,广告提示框必用)6-骨骼游戏动画(什么是模型动画,常见游戏案例,如何让人头进行各种攻击动作)7-3d模型加载(常见模型格式,如何渲染不同格式,不同格式的特点,什么格式性能优越,模型渲染异常,贴图不显示等问题详解)8-高阶动态纹理(你所不知道的纹理用法,我说你不知道,你肯定不知道)9-漫游轨迹以及其动画路径(怎么绘制贝塞尔曲线,如何使用曲线上的路径,跟随路径移动的原理,相机如何运动,物体如何运动)10-着色器(什么是着色器。初识着色器基础,着色器材质怎么用,怎么使用着色器库)11-常见渲染以及透明度问题12-对象拾取以及拖拽(3d世界里面如何拖拽物体,拖拽的原理,mousemove mouseon等的事件效果)13-世界坐标以及组的问题(什么是相对坐标,什么是世界坐标,什么是当前坐标,怎么转化父子坐标系,组的优化,为什么用组,组的优势)14-指定对象旋转中心(什么是物体的几何体中心,如何改变中心,如何绕轴转动)15-层级对象渲染(多个场景一键切换,切换的优势,针对大项目的用法)16-拓展了解系列(不定期不断更新案例,各种酷炫效果bloom,halo等,以及各种3d图表,粒子案例等,不断构建你的3d实践能力)
将 Three.js 中的 3D 模型存储到 IndexedDB 中可以通过以下步骤实现: 1. 创建 IndexedDB 数据库:使用 IndexedDB API 创建一个数据库,并在其中创建一个存储对象来存储 3D 模型数据。 2. 将 3D 模型转换为二进制数据:使用 Three.js 提供的 GLTFExporter 将 3D 模型转换为二进制数据。 3. 存储二进制数据:将转换后的二进制数据存储到 IndexedDB 中的存储对象中。 4. 从 IndexedDB 中获取数据:使用 IndexedDB API 获取存储对象中的数据,并使用 Three.js 提供的 GLTFLoader 将二进制数据转换为 3D 模型。 下面是一个示例代码,用于将 Three.js 中的 3D 模型存储到 IndexedDB 中并从 IndexedDB 中获取数据: ```javascript // 创建 IndexedDB 数据库 const request = window.indexedDB.open('3d-models', 1); let db; request.onerror = (event) => { console.log('Database error: ' + event.target.errorCode); }; request.onsuccess = (event) => { db = event.target.result; }; request.onupgradeneeded = (event) => { const db = event.target.result; const objectStore = db.createObjectStore('models', { keyPath: 'id' }); }; // 将 3D 模型转换为二进制数据并存储到 IndexedDB 中 const exporter = new THREE.GLTFExporter(); exporter.parse(scene, (gltf) => { const binaryData = new Blob([gltf], { type: 'application/octet-stream' }); const transaction = db.transaction(['models'], 'readwrite'); const objectStore = transaction.objectStore('models'); const request = objectStore.put({ id: 'my-3d-model', data: binaryData }); request.onerror = (event) => { console.log('Error storing 3D model: ' + event.target.error); }; request.onsuccess = (event) => { console.log('3D model stored successfully'); }; }); // 从 IndexedDB 中获取数据并将二进制数据转换为 3D 模型 const loader = new THREE.GLTFLoader(); const transaction = db.transaction(['models'], 'readonly'); const objectStore = transaction.objectStore('models'); const request = objectStore.get('my-3d-model'); request.onerror = (event) => { console.log('Error retrieving 3D model: ' + event.target.error); }; request.onsuccess = (event) => { const binaryData = event.target.result.data; const reader = new FileReader(); reader.onload = (event) => { const gltfBlob = new Blob([event.target.result], { type: 'application/octet-stream' }); const fileReader = new FileReader(); fileReader.onload = (event) => { const gltf = JSON.parse(event.target.result); loader.parse(gltf, '', (object) => { scene.add(object.scene); }); }; fileReader.readAsText(gltfBlob); }; reader.readAsArrayBuffer(binaryData); }; ``` 需要注意的是,IndexedDB 的 API 使用起来比较复杂,需要注意处理异步操作和错误处理。同时,存储和读取大型的二进制数据可能会影响性能,因此需要根据具体情况进行优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值