Threejs用切线实现模型沿着轨道行驶

news2024/11/19 4:45:07

        这次讲一个经常遇到的使用场景,让模型沿着轨迹运动,这个场景需要解决两个问题,第一是让模型沿着轨迹运动,第二是在沿着轨迹运动的同时,要保持模型的头部也时刻保持前方,而不是单纯的只是更新模型位置。

        还是先创建一个场景,添加相机,灯光,渲染器等,然后需要创建一个轨迹,这里用CatmullRomCurve3创建一个3维曲线,这个的好处是等会可以将此曲线拆解成多个同等份的点,因为我们需要不断更新模型在此曲线的位置,实际上就是不停的切换此曲线上连接的多个点,来实现位置的不断更新。

        首先根据四个点创建曲线,并将曲线分解为多个点,再用这些点绘制成一条曲线并加入到场景中,方便后面观察模型的运动轨迹。

 this.cameraCurve = new THREE.CatmullRomCurve3(
          [
            new THREE.Vector3(-300, 40, 200),
            new THREE.Vector3(300, 40, 200),
            new THREE.Vector3(300, 40, -200),
            new THREE.Vector3(-300, 40, -200),
          ],
          true
      );
      //参考路径上取1000个点,每个点上添加蓝色小球
      const pathPoints = this.cameraCurve.getPoints(this.pathIndex);
      //绘制一条路径参考线与上面的线重合,方便查看小车的行动轨迹
      const geometry = new THREE.BufferGeometry().setFromPoints(pathPoints);
      const material = new THREE.LineBasicMaterial({ color: '#000000', linewidth: 1, });//设置线条的颜色和宽度
      const curveObject = new THREE.Line(geometry, material);
      scene.add(curveObject);

此时场景中就出现一条曲线,作为模型运动的轨迹,

接着,需要在场景中添加一个模型,我这添加一个agv车,更方便观察车的车头方向,因为是外部模型需要加载GLTFLoader,缩放到适合的大小,并将车的位置放在曲线的第一个点位置,防止在运动前突然闪现到开始运动的点开始运动。

 //在场景中加载一个agv小车,并将agv小车放在曲线的第一个点上
      const loader = new GLTFLoader()
      loader.load("/static/model/agv.gltf", (gltf) => {
        this.agv = gltf.scene;
        this.agv.position.set(pathPoints[0].x, pathPoints[0].y, pathPoints[0].z)   // 模型位置
        this.agv.scale.set(0.1,0.1,0.1)
        scene.add(this.agv)   // 加入场景
      })

曲线和车都加好了,需要开始设置动画了,也是最关键的部分,运动的部分比较简单,因为获取到了曲线的多个连续点,只需要不断地更新车的位置到每个点就好了,保持车头方向需要先获取车所在点向量的切线,位置向量和切线向量相加即为所需朝向的点向量。

 if (this.agv) {// 判断agv加载完成后,开始不断更新agv的位置
        const sphereCurveIndex = this.pathIndex / 1000; // //取相参考径上当前点的坐标,取值0~1
        const positionVec = this.cameraCurve.getPointAt(sphereCurveIndex);//获取曲线上位置的点,传值为0-1的小数表示整个线段的位置
        this.agv.position.set( positionVec.x, positionVec.y, positionVec.z);//设置新的agv位置
        const tangent = this.cameraCurve.getTangentAt(sphereCurveIndex); // 返回一个点t在曲线上位置向量的法线向量(getTangentAt是返回曲线上某个点的切线)
        const lookAtVec = tangent.add(positionVec);// 位置向量和切线向量相加即为所需朝向的点向量
        this.agv.lookAt(lookAtVec);//设置agv的模型朝向为切线的方向
      }

完整的代码如下:

<template>
  <div>
    <div id="container"></div>
  </div>
</template>

<script>
import * as THREE from 'three'
import {OrbitControls} from "three/addons/controls/OrbitControls";
import {GLTFLoader} from "three/addons/loaders/GLTFLoader";

let scene;
export default {
  name: "agv-single",
  data() {
    return{
      camera:null,
      cameraCurve:null,
      renderer:null,
      container:null,
      controls:null,
      pathIndex:1000,//小车的运动轨迹点索引
      agv:null
    }
  },
  methods:{
    initScene(){
      scene = new THREE.Scene();
    },
    initCamera(){
      this.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
      this.camera.position.set(500,500,500);
    },
    initLight(){
      //添加两个平行光
      const directionalLight1 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight1.position.set(-300,-300,600)
      scene.add(directionalLight1);
      const directionalLight2 = new THREE.DirectionalLight(0xffffff, 1.5);
      directionalLight2.position.set(600,200,600)
      scene.add(directionalLight2);
    },
    initRound(){
      //通过CatmullRomCurve3连接4个点绘制一条曲线,且闭合
      this.cameraCurve = new THREE.CatmullRomCurve3(
          [
            new THREE.Vector3(-300, 40, 200),
            new THREE.Vector3(300, 40, 200),
            new THREE.Vector3(300, 40, -200),
            new THREE.Vector3(-300, 40, -200),
          ],
          true
      );
      //参考路径上取1000个点,每个点上添加蓝色小球
      const pathPoints = this.cameraCurve.getPoints(this.pathIndex);
      //绘制一条路径参考线与上面的线重合,方便查看小车的行动轨迹
      const geometry = new THREE.BufferGeometry().setFromPoints(pathPoints);
      const material = new THREE.LineBasicMaterial({ color: '#000000', linewidth: 1, });//设置线条的颜色和宽度
      const curveObject = new THREE.Line(geometry, material);
      scene.add(curveObject);

      //在场景中加载一个agv小车,并将agv小车放在曲线的第一个点上
      const loader = new GLTFLoader()
      loader.load("/static/model/agv.gltf", (gltf) => {
        this.agv = gltf.scene;
        this.agv.position.set(pathPoints[0].x, pathPoints[0].y, pathPoints[0].z)   // 模型位置
        this.agv.scale.set(0.1,0.1,0.1)
        scene.add(this.agv)   // 加入场景
      })
    },
    initRenderer(){
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.container = document.getElementById("container")
      this.renderer.setSize(this.container.clientWidth, this.container.clientHeight);
      this.renderer.setClearColor('#AAAAAA', 1.0);
      this.container.appendChild(this.renderer.domElement);
    },
    initControl(){
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.enableDamping = true;
      this.controls.maxPolarAngle = Math.PI / 2.2;      // // 最大角度
    },
    initAnimate() {
      //参考路径的索引在1001~0中往复减少以实现小车循环行驶
      if (this.pathIndex === 0) {
        this.pathIndex = 1001;
      }
      this.pathIndex -= 1;
      if (this.agv) {// 判断agv加载完成后,开始不断更新agv的位置
        const sphereCurveIndex = this.pathIndex / 1000; // //取相参考径上当前点的坐标,取值0~1
        const positionVec = this.cameraCurve.getPointAt(sphereCurveIndex);//获取曲线上位置的点,传值为0-1的小数表示整个线段的位置
        this.agv.position.set( positionVec.x, positionVec.y, positionVec.z);//设置新的agv位置
        const tangent = this.cameraCurve.getTangentAt(sphereCurveIndex); // 返回一个点t在曲线上位置向量的法线向量(getTangentAt是返回曲线上某个点的切线)
        const lookAtVec = tangent.add(positionVec);// 位置向量和切线向量相加即为所需朝向的点向量
        this.agv.lookAt(lookAtVec);//设置agv的模型朝向为切线的方向
      }
      requestAnimationFrame(this.initAnimate);
      this.renderer.render(scene, this.camera);
    },
    initPage(){
      this.initScene();
      this.initCamera();
      this.initLight();
      this.initRenderer();
      this.initControl();
      this.initRound();
      this.initAnimate();
    }
  },
  mounted() {
    this.initPage()
  }
}
</script>

<style scoped>
#container{
  position: absolute;
  width:100%;
  height:100%;
  overflow: hidden;
}

</style>

效果如下:

模型沿着曲线运动

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1500908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Type-C接口PD协议统一:引领电子科技新纪元的优势解析

在电子科技日新月异的今天&#xff0c;充电接口的统一化已经成为了业界的一大趋势。其中&#xff0c;Type-C接口凭借其传输速度快、使用便捷等优点&#xff0c;迅速成为了市场上的主流选择。而PD&#xff08;Power Delivery&#xff09;协议的统一&#xff0c;更是为Type-C接口…

jenkins+selenium+python实现web自动化测试

jenkinsselenium可以做到对web自动化的持续集成。 Jenkins的基本操作&#xff1a; 一、新建视图及job 新建视图&#xff1a; 新建job&#xff1a; 可以选择构建一个自由风格的软件项目或者复制已有的item 二、准备工作&#xff1a; 安装Jenkins插件&#xff0c;SSH plugin …

零基础,学6个月嵌入式,能找到工作吗?

今天看到一个老铁问&#xff0c;他报了个班&#xff0c;学6个月&#xff0c;学完能找到工作吗&#xff1f; 我看了下他的学习内容&#xff0c;包含C语言、数据结构、系统编程、网络编程、STM32、RTOS、物联网通讯协议、Linux内核驱动&#xff0c;这是大纲&#xff0c;细节的课程…

混合输入矩阵乘法的性能优化

作者 | Manish Gupta OneFlow编译 翻译&#xff5c;宛子琳、杨婷 AI驱动的技术正逐渐融入人们日常生活的各个角落&#xff0c;有望提高人们获取知识的能力&#xff0c;并提升整体生产效率。语言大模型&#xff08;LLM&#xff09;正是这些应用的核心。LLM对内存的需求很高&…

Haproxy介绍、安装

Haproxy介绍、安装 文章目录 Haproxy介绍、安装1.Haproxy介绍1.1 企业版1.2 社区版1.3 版本对比1.4 HAProxy功能 2.HAProxy安装2.1 主机初始化2.1.1 设置网卡名和ip地址2.1.2 配置镜像源2.1.3 关闭防火墙2.1.4 禁用SELinux2.1.5 设置时区 2.2 包安装2.2.1 Ubuntu 安装2.2.2 Cen…

透视B站财报:从前景看“钱景”

3月7日晚&#xff0c;哔哩哔哩发布了2023年度及第四季度财报。 财报显示&#xff0c;哔哩哔哩2023年总营收225亿元&#xff0c;净亏损同比大幅收窄49%&#xff0c;其中第四季度总营收达63.5亿元。 在后续电话会议上&#xff0c;哔哩哔哩管理层对市场上重点关注的“B站2024年能…

python界面开发 - Menu (popupmenu) 右键菜单

文章目录 1. python图形界面开发1.1. Python图形界面开发——Tkinter1.2. Python图形界面开发——PyQt1.3. Python图形界面开发——wxPython1.4. Python图形界面开发—— PyGTK&#xff1a;基于GTK1.5. Python图形界面开发—— Kivy1.6. Python图形界面开发——可视化工具1.7. …

在外包公司干了3年,出来发现技术退步太明显...

先说情况&#xff0c;大专毕业&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了3年的功能测试&#xff…

LoadBalancer 客户端的负载均衡器+openFeign 请求转发

LoadBalancer Spring Cloud LoadBalancer是Spring Cloud中负责客户端负载均衡的模块&#xff0c;其主要原理是从nacos中获取服务列表通过选择合适的服务实例来实现负载均衡。 源码跟踪 可以看到这里的intercept()方法&#xff0c;拦截了用户的HttpRequest请求&#xff0c;然…

【Spring云原生系列】Spring RabbitMQ:异步处理机制的基础--消息队列 原理讲解+使用教程

&#x1f389;&#x1f389;欢迎光临&#xff0c;终于等到你啦&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;持续更新的专栏《Spring 狂野之旅&#xff1a;从入门到入魔》 &a…

超级简单的Docker安装(centos7)

文章目录 先安装所需要的工具包设置远程仓库安装启动docker查看版本 先安装所需要的工具包 yum install -y yum-utils #安装工具包&#xff0c;缺少这些依赖将无法完成&#xff1b;设置远程仓库 yum-config-manager --add-repo https://download.docker.com/linux/centos/doc…

48、兰州大学、青海师范:专门用于深度CNNs的天阶斗技-ELA Local Attention

本文由兰州大学信息科学与工程学院、青海省物联网重点实验室、青海师范大学于2024年3.2日发表于ArXiv。为了解决现有的注意力模型在有效利用空间信息方面存在的限制和困难&#xff0c;提出了一种高效的局部注意力ELA模型。该方法通过分析坐标注意力的局限性&#xff0c;作者识别…

迈向未来:内部审计的数字化转型的道与术

目录 前言 一、内部审计的发展阶段 二、内部审计的逻辑架构 三、内部审计数字化转型面临的问题 四、内部审计数字化转型的框架方法 五、内部审计的数字化转型能力体系 六、内部审计的数字化转型路径 七、内部审计的数字化系统平台 前言 内部审计是一种独立的、客观的确…

upload-labs通关记录

文章目录 前言 1.pass-012.pass-023.pass-034.pass-045.pass-056.pass-067.pass-078.pass-089.pass-0910.pass-1011.pass-1112.pass-1213.pass-1314.pass-1415.pass-1516.pass-1617.pass-1718.pass-1819.pass-19 前言 本篇文章记录upload-labs中&#xff0c;所有的通过技巧和各…

树莓派(Raspberry Pi)常见的各种引脚介绍

本文将为您详细讲解树莓派&#xff08;Raspberry Pi&#xff09;常见的各种引脚&#xff0c;以及它们的特点、区别和优势。树莓派是一款非常受欢迎的单板计算机&#xff0c;它拥有多个 GPIO&#xff08;通用输入输出&#xff09;引脚&#xff0c;这些引脚可以用于各种电子项目和…

网站建设大概需要多少钱?费用是多少?怎么样

1、网站建设详细报价单。 建立一个网站需要多少钱&#xff1f; 公司不同&#xff0c;地点不同&#xff0c;技术不同&#xff0c;需求不同&#xff0c;价格也不同。 1、不要尝试用几百块钱建立一个网站&#xff0c;2、几千块做一个普通的网站&#xff0c;3、几万块做一个更好的…

代码随想录第52天| ● 392.判断子序列 ● 115.不同的子序列

文章目录 ● 392.判断子序列思路代码&#xff1a; ● 115.不同的子序列思路&#xff1a;代码&#xff1a; ● 392.判断子序列 思路 递推顺序&#xff1a;从上到下 从左到右 代码&#xff1a; class Solution {public boolean isSubsequence(String s, String t) {int[][]dpn…

两两交换链表中的节点+力扣

题目 题目链接 . - 力扣&#xff08;LeetCode&#xff09; 题目描述 代码实现 class Solution { public:ListNode* swapPairs(ListNode* head) {if(head nullptr || head->next nullptr) return head;ListNode *tmpHead swapPairs(head->next->next);ListNode …

Vue | 基于 vue-admin-template 项目的跨域问题解决方法

目录 一、现存问题 二、解决方法 2.1 修改的第一个地方 2.2 修改的第二个地方 2.3 修改的第三个地方 自存 一、现存问题 报错截图如下&#xff1a; 二、解决方法 2.1 修改的第一个地方 在 .env.development 文件中&#xff1a; # base api # VUE_APP_BASE_API /d…

瑞芯微 | I2S-音频基础 -1

最近调试音频驱动&#xff0c;顺便整理学习了一下i2s、alsa相关知识&#xff0c;整理成了几篇文章&#xff0c;后续会陆续更新。 喜欢嵌入式、Li怒晓得老铁可以关注一口君账号。 1. 音频常用术语 名称含义ADC&#xff08;Analog to Digit Conversion&#xff09;模拟信号转换…