three.js 缓动算法.easing(渐入相机动画)

news2024/11/17 17:46:36

效果:淡入,靠近物体

代码:

<template>
  <div>
    <el-container>
      <el-main>
        <div class="box-card-left">
          <div id="threejs" style="border: 1px solid red"></div>
          <div class="box-right">
            <el-button type="primary" @click="lookFor('设备A')"
              >设备A</el-button
            >
            <el-button type="primary" @click="lookFor('设备B')"
              >设备B</el-button
            >
            <el-button type="primary" @click="lookAll">整体</el-button>
            <el-button type="primary" @click="saveImg">保存图片</el-button>
          </div>
        </div>
      </el-main>
    </el-container>
  </div>
</template>
<script>
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import TWEEN from "@tweenjs/tween.js";

export default {
  data() {
    return {
      scene: null,
      camera: null,
      renderer: null,
      mesh: null,
      geometry: null,
      group: null,
      material: null,
      clock: null,
      mixer: null,
    };
  },
  created() {},
  mounted() {
    this.name = this.$route.query.name;
    this.init();
    // 监听点击事件
    this.addClickEventListener();
  },
  methods: {
    goBack() {
      this.$router.go(-1);
    },
    init() {
      // 创建场景对象
      this.scene = new this.$three.Scene();
      this.group = new this.$three.Group();
      this.createMesh({
        x: 50,
        y: 50,
        z: 50,
        name: "设备A",
      });
      this.createMesh({
        x: -50,
        y: 50,
        z: 50,
        name: "设备B",
      });

      this.scene.add(this.group);
      const axesHelper = new this.$three.AxesHelper(150);
      this.scene.add(axesHelper);
      // 创建环境光对象
      const ambientLight = new this.$three.AmbientLight(0xffffff);
      this.scene.add(ambientLight);
      // 创建相机对象
      this.camera = new this.$three.PerspectiveCamera();
      this.camera.position.set(300, 300, 300);
      this.camera.lookAt(0, 0, 0);
      // 创建渲染器对象
      this.renderer = new this.$three.WebGLRenderer({
        preserveDrawingBuffer: true, // 把画布内容保存为图片时,需要设置为true
      });
      this.renderer.setSize(1000, 800);
      this.renderer.render(this.scene, this.camera);
      window.document
        .getElementById("threejs")
        .append(this.renderer.domElement);

      // 创建相机空间轨道控制器对象
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.addEventListener("change", () => {
        this.renderer.render(this.scene, this.camera);
        console.log(
          " this.camera.position",
          this.camera.position.x,
          this.camera.position.y,
          this.camera.position.z
        );
      });
    },
    // 创建网格模型的方法
    createMesh(obj) {
      // 创建立方缓冲几何体对象
      const geometry = new this.$three.BoxGeometry(obj.x, obj.y, obj.z);
      // 创建材质对象
      const material = new this.$three.MeshLambertMaterial({
        color: this.randomColor(),
        transparent: true,//开启透明计算
        opacity: 0.0//完全透明
      });
      const mesh = new this.$three.Mesh(geometry, material);
      mesh.position.set(obj.x, obj.y, obj.z);
      mesh.name = obj.name;
      if (this.group) {
        this.group.add(mesh);
      }
    },
    lookFor(name) {
      if (this.scene && this.scene.getObjectByName(name)) {
        // 通过 getObjectByName() 方法获取name为设备A的模型
        const equipment_A = this.scene.getObjectByName(name);
        // 创建Vector3类型的位置对象
        const position = new this.$three.Vector3();
        // 获取设置A的世界坐标并赋值到position对象中
        equipment_A.getWorldPosition(position);
        // 向量x,y,z坐标值在position的基础上增加50,
        const position_scalar = position.clone().addScalar(100);
        // 创建TWEEN对象并调用Tween方法
        new TWEEN.Tween({
          x: this.camera.position.x,
          y: this.camera.position.y,
          z: this.camera.position.z,
          px: this.controls.target.x,
          py: this.controls.target.y,
          pz: this.controls.target.z,
          opacity: equipment_A.material.opacity//完全透明
        })
          .to(
            {
              x: position_scalar.x,
              y: position_scalar.y,
              z: position_scalar.z,
              px: equipment_A.position.x,
              py: equipment_A.position.y,
              pz: equipment_A.position.z,
              opacity: 1.0
            },
            3000
          )
          .onUpdate((obj) => {
            // 设置相机位置
            this.camera.position.set(obj.x, obj.y, obj.z);
            // 设置控制器指向
            this.controls.target.set(obj.px, obj.py, obj.pz);
            // 更新控制器
            this.controls.update();
            equipment_A.material.opacity = obj.opacity;
          })
          .onComplete(obj => {
            equipment_A.material.transparent = false;
          })
          .start()
          .easing(TWEEN.Easing.Sinusoidal.InOut); //使用二次缓动函数;
        /**
            easing()语法格式
         * // easing函数:缓动算法(运动效果)
            // easing类型:定义缓动算法起作用地方
            tween.easing(TWEEN.Easing.easing函数.easing类型); 
            // easing类型  In , Out , InOut

            Linear:默认效果可以不设置,可以理解为没有加速过程直接进入匀速状态,或者说没有减速过程,直接刹车

            Quadratic:二次方的缓动(t^2)

            Cubic:三次方的缓动(t^3)

            Quartic:四次方的缓动(t^4)

            Quintic:五次方的缓动(t^5)

            Sinusoidal:正弦曲线的缓动(sin(t))

            Exponential:指数曲线的缓动(2^t)启动非常慢,后面快

            Circular:圆形曲线的缓动(sqrt(1-t^2))会有弹性衰减往复运动感

            Elastic:指数衰减的正弦曲线缓动;TWEEN.Easing.Elastic.inout 会有弹性衰减往复运动感

            Back:超过范围的三次方缓动((s+1)*t^3 – s*t^2)会有弹性衰减往复运动感

            Bounce:指数衰减的反弹缓动。会有弹性衰减往复运动感
         *  */

        this.loop();
      }
    },
    loop() {
      this.renderer.render(this.scene, this.camera);
      TWEEN.update();
      window.requestAnimationFrame(this.loop);
    },
    lookAll() {
      /**
       * 查看整体的思路:
       * 用包围盒 Box3, 将场景中所有的模型包裹起来,计算出
       * (box3.min.x + box.max.x) / 2 = centerX
       * (box.min.y + box.max.y) / 2 = centerY
       * (box.min.z + box.max.z) / 2 = centerZ
       * , 计算出 centerX, centerY, centerZ  整体的中心坐标,
       *  为了显示包围盒的边界,可以使用Box3Helper辅助对象;
       * 相机的位置position要从当前位置定位到
       *
       *  */
      // 创建包围盒对象
      const box3 = new this.$three.Box3();
      // 设置包围盒中的对象
      const groupBox = box3.expandByObject(this.group);
      console.log(groupBox);
      const box3Helper = new this.$three.Box3Helper(box3, 0xffffff);
      this.scene.add(box3Helper);
      let max_x = groupBox.max.x;
      let max_y = groupBox.max.y;
      let max_z = groupBox.max.z;
      let min_x = groupBox.min.x;
      let min_y = groupBox.min.y;
      let min_z = groupBox.min.z;
      let center_x = (max_x + min_x) / 2;
      let center_y = (max_y + min_y) / 2;
      let center_z = (max_z + min_z) / 2;
      //
      let increment_x =
        Math.abs(max_x) > Math.abs(min_x) ? Math.abs(max_x) : Math.abs(min_y);
      let increment_y =
        Math.abs(max_y) > Math.abs(min_y) ? Math.abs(max_y) : Math.abs(min_y);
      let increment_z =
        Math.abs(max_z) > Math.abs(min_z) ? Math.abs(max_z) : Math.abs(min_z);

      new TWEEN.Tween({
        x: this.camera.position.x,
        y: this.camera.position.y,
        z: this.camera.position.z,
        px: this.controls.target.x,
        py: this.controls.target.y,
        pz: this.controls.target.z,
      })
        .to(
          {
            x: center_x + increment_x * 2,
            y: center_y + increment_y * 2,
            z: center_z + increment_z * 2,
            px: center_x,
            py: center_y,
            pz: center_z,
          },
          1200
        )
        .onUpdate((obj) => {
          this.camera.position.set(obj.x, obj.y, obj.z);
          this.controls.target.set(obj.px, obj.py, obj.pz);
          this.controls.update();
        })
        .start();
      this.loop();
    },
    saveImg() {
      const link = document.createElement("a");
      const canvas = this.renderer.domElement;
      link.href = canvas.toDataURL("image/png");
      link.download = "threejs.png";
      link.click();
    },
    randomColor() {
      const numbers = Array.from({ length: 255 }, (_, i) => i);
      const color = [...numbers];
      // 要生成min-max之间的随机数,公式为:Math.random()*(max-min+1)+min
      let i = Math.floor(Math.random() * (color.length - 0 + 1) + 0);
      let j = Math.floor(Math.random() * (color.length - 0 + 1) + 0);
      let k = Math.floor(Math.random() * (color.length - 0 + 1) + 0);
      return new this.$three.Color("rgb(" + i + ", " + j + ", " + k + ")");
    },
    // 在canvas画布上添加监听点击的事件
    addClickEventListener() {
      // 获取id 是 threejs 的元素;
      const dom = window.document.getElementById("threejs");
      const canvasWidth = dom.clientWidth; // 获取元素的宽度
      const canvasHeight = dom.clientHeight; // 获取元素的高度
      dom.addEventListener("click", (e) => {
        const x = e.offsetX; // 获取鼠标当前点击的点距离dom元素左上角原点  在x轴方向上的距离
        const y = e.offsetY; // 获取鼠标当前点击的点距离dom元素左上角原点  在y轴方向上的距离
        console.log(x, y);
        // 由于canvas画布上的坐标值与普通2d页面的坐标值是不一样的;
        // 在canvas画布上的坐标轴是以画布的中心点为原点,左右x轴,值 -1 ~ 1,,上下y轴,值-1 ~ 1;
        // 坐标需要进行坐标转换
        const pos_x = (x / canvasWidth) * 2 - 1; // 转换后的x坐标
        const pos_y = -(y / canvasHeight) * 2 + 1; // 转换后的y坐标
        // 创建射线投射器对象(可以在初始化方法中创建,每次点击时创建有些浪费资源)
        const rayCaster = new this.$three.Raycaster();
        // 计算射线(在点击位置创建一条射线,用来拾取模型对象)
        rayCaster.setFromCamera(
          new this.$three.Vector2(pos_x, pos_y),
          this.camera
        );
        const mesh_list = [];
        // traverse 是threejs中的递归遍历方法;找出group中的mesh
        this.group.traverse((obj) => {
          if (obj.isMesh) {
            mesh_list.push(obj);
          }
        });
        // 射线交叉计算(计算出与自身射线相交的网格模型)
        const intersects = rayCaster.intersectObjects(mesh_list);
        if (intersects && intersects.length > 0) {
          console.log(intersects[0]);
          this.lookFor(intersects[0].object.name);
        }
      });
    },
  },
};
</script>
//
<style lang="less" scoped>
.box-card-left {
  display: flex;
  align-items: flex-start;
  flex-direction: row;

  width: 100%;

  .box-right {
    img {
      width: 500px;
      user-select: none;
    }
  }
}
</style>

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

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

相关文章

go实现判断20000数据范围内哪些是素数(只能被1和它本身整除的数),采用多协程和管道实现

实现一个并发程序&#xff0c;用于寻找 20000 以内的所有素数。使用了 Goroutines 和 Channels 来分发和处理任务&#xff0c;并通过 WaitGroup&#xff08;实现为 exitChan&#xff09;来同步 Goroutines 的退出。 一.GO代码 package mainimport ("fmt""time…

docker安装confluence全套流程

docker安装confluence全套流程 1.安装mysql2.创建mysql容器3.拉取confluence7容器4.拷贝mysql驱动到confluence中:1195ef11d768为容器id(需要修改为你自己的)5.创建confluence容器6.激活confluence到数据连接可能需报数据库编码和环境隔离错误 1.安装mysql docker pull mysql/…

保姆版Vps安装灯塔(ARL)

因为灯塔的默认端口为5003&#xff0c;所以我们在安装之前就在防火墙里把我们的5003端口打开 打开端口步骤如下&#xff1a; 1.我们打开控制面板&#xff0c;在控制面板里点击 系统和安全 。如下图&#xff1a; 2.接着点击 Windows Defender防火墙,如下图&#xff1a; 3.再…

好用的流程图工具

分享工作中常用的装逼工具 目前市面上的流程图或者思维导图工具挺多的&#xff0c;但是有的会限制使用数量或者收费&#xff0c;典型的有processon、Xmind&#xff0c;推荐今天Mermaid(官网)。 快速上手 中文教程&#xff1a;Mermaid 初学者用户指南 | Mermaid 中文网。我们选择…

Spring | Spring中的Bean--下

Spring中的Bean: 4.Bean的生命周期5.Bean的配装配式 ( 添加Bean到IOC容器的方式 依赖注入的方式 )5.1 基于XML的配置5.2 基于Annotation (注解) 的装配 (更常用&#xff09;5.3 自动装配 4.Bean的生命周期 Spring容器可以管理 singleton作用域的Bean的生命周期&#xff0c;在此…

51单片机_电压采集器电压表

实物演示效果&#xff1a; https://www.bilibili.com/video/BV1My4y1F7xY/?vd_source6ff7cd03af95cd504b60511ef9373a1d 一、基本功能 利用51单片机作为主控芯片&#xff0c;3段式电压采集。模拟量经A/D&#xff08;ADC0809&#xff09;模数转换芯片&#xff0c;把模拟量转换…

【计算机网络】(1)OSI七层模型、协议、交换技术、路由器技术

文章目录 计算机网络功能与分类计算机网络的定义计算机网络的功能计算机网络的指标计算机网络的性能指标计算机网络的非性能指标 计算机网络的分布范围以及拓扑结构划分图计算机网络分类总线型拓扑星型拓扑环形图拓扑树型拓扑分布式拓扑 通信技术信道物理信道逻辑信道 发信机OS…

微信小程序(六)tabBar的使用

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1. 标签栏文字的内容以及默认与选中颜色 2. 标签栏图标的默认样式与选中样式 3. 标签选项路径页面 4.标签栏背景颜色 &#x1f43c;&#xff08;文末补充&#xff09;设置标签栏后为什么navigator标签无法跳转页…

ros2仿真学习04 -turtlebot3实现cartographer算法建图演示

安装看这里 https://blog.csdn.net/hai411741962/article/details/135619608?spm1001.2014.3001.5502 虚拟机配置&#xff1a; 内存16g cpu 4 核 磁盘40G,20G 不够 启动仿真 ros2 launch turtlebot3_gazebo turtlebot3_world.launch.py启动成功如下 启动建图 重新开一个…

springboot 集成短信发送功能(人工智能编写)

要在Spring Boot中集成短信发送功能&#xff0c;你可以使用第三方的短信服务提供商的API来实现。以下是一个基本的示例代码&#xff0c;我是通过chatGPT4.0一键生成代码。 1. 添加依赖&#xff1a;在pom.xml文件中添加相应的短信服务提供商的SDK依赖&#xff0c;例如阿里云的a…

枚举类型缝缝补补

✅作者简介&#xff1a;大家好&#xff0c;我是橘橙黄又青&#xff0c;一个想要与大家共同进步的男人&#x1f609;&#x1f609; &#x1f34e;个人主页&#xff1a;橘橙黄又青-CSDN博客 1.关键字enum的定义 enum是C语言中的一个关键字&#xff0c;enum叫枚举数据类型&#…

DSX-5000线缆认证测试仪-之原厂校准

众所周知&#xff0c;DTX-1800校准是校准本身的设备精度&#xff0c;还原原厂精度事宜。 135 375 00200 但是DSX-5000或者DSX-8000校准的不是平台本身&#xff0c;而是测试模块。也就是后面备注了型号的模块。所以每次告诉客户&#xff0c;不用把全部设备发过来&#xff…

备忘录怎么分享到微信 备忘录分享到微信的方法

在忙碌的工作和生活中&#xff0c;我时常发现自己需要记录一些重要的信息。那些一闪而过的灵感&#xff0c;或是同事突如其来的建议&#xff0c;都需要我迅速捕捉并妥善保存。这时&#xff0c;一个好用的备忘录就显得尤为重要。 然而&#xff0c;记录只是第一步。更多的时候&a…

Labview实现用户界面切换的几种方式---通过VI间相互调用

在做用户界面时我们的程序往往面对的对象是程序使用者&#xff0c;复杂程序如果放在同一个页面中&#xff0c;往往会导致程序冗长卡顿&#xff0c;此时通过多个VI之间的切换就可以实现多个界面之间的转换&#xff0c;也会显得程序更加的高大上。 本文所有程序均可下载&#xff…

30分钟带你深入优化安卓Bitmap大图

30分钟带你源码深入了解Bitmap以及优化安卓大图 一、前言二、Bitmap入门1. 如何创建Bitmap?2. Bitmap的堆内存分布在哪里3. 图片文件越大&#xff0c;Bitmap堆内存会越大吗&#xff1f;4. 如何管理Bitmap的内存&#xff1f;5. 实战修改Bitmap的堆内存&#xff0c;改变图片的图…

关于c++的三大特性 --- 多态(底层原理)

目录 多态的原理 虚函数表 底层 打印虚表 多继承的虚函数表 多态的原理 虚函数表 建议看下面的内容之前&#xff0c;先看一下c特性之多态 这里我们先来看一个笔试题&#xff1a;请问 sizeof(Base&#xff09;是多少&#xff1f; class Base { public:virtual void Func…

每周一算法:数独游戏

题目链接 数独游戏 题目描述 数独是根据 9 9 9 \times 9 99 盘面上的已知数字&#xff0c;推理出所有剩余空格的数字&#xff0c;并满足每一行、每一列、每一个粗线宫内的数字均含 1 − 9 1 - 9 1−9 &#xff0c;不重复。每一道合格的数独谜题都有且仅有唯一答案&#x…

vue3前端开发,生命周期函数的基础练习

vue3前端开发,生命周期函数的基础练习&#xff01; 下面先给大家看一个图片&#xff0c;帮助大家了解&#xff0c;vue3的生命周期函数&#xff0c;和旧版本vue2的生命周期函数&#xff0c;有什么变化。 如图所示&#xff0c;vue3里面&#xff0c;把前面2个函数&#xff0c;混在…

视频美颜SDK与人工智能的结合:技术突破与挑战

本篇文章&#xff0c;小编将与大家共同探讨美颜SDK与人工智能结合背后的技术原理、创新应用以及面临的挑战。 一、技术原理&#xff1a;人工智能在美颜中的应用 视频美颜SDK通过整合深度学习和计算机视觉技术&#xff0c;能够更准确地识别人脸特征、肤色、表情等信息&#xff…

CAN数据记录仪解决汽车电子与工程机械冬测难点

CAN数据记录仪在汽车电子与工程机械冬测中扮演着重要的角色。在寒冷的冬季&#xff0c;汽车可能会因为环境温度过低而出现各种问题&#xff0c;例如电池电量不足、发动机启动困难等。为了确保汽车在冬季的正常运行&#xff0c;需要对汽车进行电子冬测。 CAN数据记录仪在冬测中发…