Vue3+TypeScript项目实战——打造雨雪交加的智慧城市

个人简介

👀个人主页: 前端杂货铺
开源项目: rich-vue3 (基于 Vue3 + TS + Pinia + Element Plus + Spring全家桶 + MySQL)
🙋‍♂️学习方向: 主攻前端方向,正逐渐往全干发展
📃个人状态: 研发工程师,现效力于中国工业软件事业
🚀人生格言: 积跬步至千里,积小流成江海
🥇推荐学习:🍖开源 rich-vue3 🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js实战 🍒Three.js

🌕个人推广:每篇文章最下方都有加入方式,旨在交流学习&资源分享,快加入进来吧

内容参考链接
THREE.JS 专栏Three.js 入门

文章目录

    • 前言
    • 项目概况
      • 源码路径
      • 文件结构与职责
    • 写在最后

前言

大家好,这里是前端杂货铺。

这篇文章我们使用 Vue3+TypeScript+Three.js 等主流前端技术,打造 雨雪交加的智慧城市 项目。

three.js-雨雪交加的智慧城市

在这里插入图片描述

项目源码 => 请点击此处自行获取 [github] rich-vue3

如果此项目对你有些帮助,欢迎给个免费的 Star !!!(Thanks♪(・ω・)ノ)


项目概况

源码路径

该项目已被托管到 rich-vue3 中,具体源码在 rich-vue3 项目的 rich-vue3-webapp/src/views/city-three 路径。

文件结构与职责

下面是该项目涉及到文件的基本结构:

  • base/index.css:页面的基础样式
  • config/index.ts:存储项目中需要使用的颜色
  • effect/…: 各种效果及特效,包括 天空盒子、扩散半球、扩散圆、旋转四棱锥、飞线、文字、雷达、雨、路径运动、烟雾、雪、建筑物外围线条、透明墙等
  • enter/initCity.ts:初始化场景、 创建城市实例、监听浏览器变化、动画
  • enter/city.ts:城市类,加载城市模型、初始化各种效果、点击聚焦和滑动滑轮缩放
  • utils/index.ts:封装加载城市模型的方法
  • index.vue:基本 UI,初始化项目的入口

在这里插入图片描述

城市类代码如下,在 initEffect() 方法中会创建很多种效果。

import { loadFBX } from "../utils"
import * as THREE from "three"
import * as TWEEN from "@tweenjs/tween.js"
import { SurroundLine } from "@/views/city-three/effect/surroundLine"
import { Background } from "@/views/city-three/effect/background"
import { Radar } from "../effect/radar"
import { Wall } from "../effect/wall"
import { Circle } from "@/views/city-three/effect/circle"
import { Ball } from "@/views/city-three/effect/ball"
import { Cone } from "@/views/city-three/effect/cone"
import { Fly } from "@/views/city-three/effect/fly"
import { Road } from "@/views/city-three/effect/road"
import { Font } from "@/views/city-three/effect/font"
import { Snow } from "@/views/city-three/effect/snow"
import { Rain } from "@/views/city-three/effect/rain"
import { Smoke } from "@/views/city-three/effect/smoke";

export class City {
  private readonly scene: any
  private readonly camera: any
  private readonly controls: any
  private tweenPosition: any
  private tweenRotation: any
  private flag: boolean
  private readonly height: { value: number }
  private readonly time: { value: number }
  private readonly top: { value: number }
  private readonly effect: {
    snow: any
    rain: any
    smoke: any
  }
  constructor(scene: object, camera: object, controls: any) {
    this.scene = scene
    this.camera = camera
    this.controls = controls
    this.flag = false
    this.tweenPosition = null
    this.tweenRotation = null

    this.height = {
      value: 5
    }

    this.time = {
      value: 0
    }

    this.top = {
      value: 0
    }

    // 雪、雨、烟雾
    this.effect = {
      snow: null,
      rain: null,
      smoke: null
    }

    this.loadCity()
  }

  loadCity() {
    // 加载城市模型,并且渲染到画布
    loadFBX("model/beijing.fbx").then((object: any) => {
      object.traverse((child: any) => {
        if (child.isMesh) {
          new SurroundLine(this.scene, child, this.height, this.time)
        }
      })
      this.initEffect()
    })
  }

  // 初始化效果,各个功能点都放在了这里
  initEffect() {
    new Background(this.scene)

    new Radar(this.scene, this.time)

    new Wall(this.scene, this.time)

    new Circle(this.scene, this.time)

    new Ball(this.scene, this.time)

    new Cone(this.scene, this.top, this.height)

    new Fly(this.scene, this.time)

    new Road(this.scene, this.time)

    new Font(this.scene)

    this.effect.snow = new Snow(this.scene)

    this.effect.rain = new Rain(this.scene)

    this.effect.smoke = new Smoke(this.scene)

    // 点击选择
    this.addClick()

    this.addWheel()
  }

  addClick() {
    let flag = true
    document.onmousedown = () => {
      flag = true
      document.onmousemove = () => {
        flag = false
      }
    }
    document.onmouseup = (event) => {
      if (flag) {
        this.clickEvent(event)
      }
      document.onmousemove = null
    }
  }

  // 场景跟随鼠标坐标缩放
  addWheel() {
    const body: HTMLElement = document.body
    // @ts-ignore
    body.onmousewheel = (event: MouseEvent) => {
      // 鼠标当前的坐标
      const x = (event.clientX / window.innerWidth) * 2 - 1
      const y = -(event.clientY / window.innerHeight) * 2 + 1

      const value = 30

      const vector = new THREE.Vector3(x, y, 0.5)
      vector.unproject(this.camera)
      vector.sub(this.camera.position).normalize()

      // @ts-ignore
      if (event.wheelDelta > 0) {
        this.camera.position.x += vector.x * value
        this.camera.position.y += vector.y * value
        this.camera.position.z += vector.z * value

        this.controls.target.x += vector.x * value
        this.controls.target.y += vector.y * value
        this.controls.target.z += vector.z * value
      } else {
        this.camera.position.x -= vector.x * value
        this.camera.position.y -= vector.y * value
        this.camera.position.z -= vector.z * value

        this.controls.target.x -= vector.x * value
        this.controls.target.y -= vector.y * value
        this.controls.target.z -= vector.z * value
      }
    }
  }

  // 点击聚焦
  clickEvent(event: MouseEvent) {
    // 归一化坐标(将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1))
    const x = (event.clientX / window.innerWidth) * 2 - 1
    const y = -(event.clientY / window.innerHeight) * 2 + 1

    // 创建设备坐标(三维)
    const standardVector = new THREE.Vector3(x, y, 0.5)
    // 转化为世界坐标 (将此向量 (坐标) 从相机的标准化设备坐标 (NDC) 空间投影到世界空间)
    const worldVector = standardVector.unproject(this.camera)
    // 做序列化
    const ray = worldVector.sub(this.camera.position).normalize()

    // 实现点击选中
    // 创建一个射线发射器,用来发射一条射线
    const raycaster = new THREE.Raycaster(this.camera.position, ray)
    // 返回射线碰撞到的物体
    const intersects = raycaster.intersectObjects(this.scene.children, true)

    let point3d = null
    if (intersects.length) {
      point3d = intersects[0]
    }

    if (point3d) {
      const proportion = 3
      // 开始动画修改观察点
      const time = 1000

      this.tweenPosition = new TWEEN.Tween(this.camera.position)
        .to({ x: point3d.point.x * proportion, y: point3d.point.y * proportion, z: point3d.point.y * proportion }, time)
        .start()

      this.tweenRotation = new TWEEN.Tween(this.camera.rotation)
        .to({ x: this.camera.rotation.x, y: this.camera.rotation.y, z: this.camera.rotation.z }, time)
        .start()
    }
  }

  start(delta: number) {
    for (const key in this.effect) {
      // @ts-ignore
      this.effect[key] && this.effect[key].animation()
    }

    if (this.tweenPosition && this.tweenRotation) {
      this.tweenPosition.update()
      this.tweenRotation.update()
    }

    this.height.value += 0.4
    if (this.height.value > 160) {
      this.height.value = 5
    }

    this.time.value += delta

    if (this.top.value > 15 || this.top.value < 0) {
      this.flag = !this.flag
    }

    this.top.value += this.flag ? -0.8 : 0.8
  }
}

写在最后

由于本项目涉及到的代码较多,在本篇文章中就不一一讲解了,感兴趣的同学可以去下载项目源码自行学习,有问题的话可以评论区一起讨论交流~

好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!


参考资料:

  1. Three.js 官方文档
  2. WebGL+Three.js 入门与实战【作者:慕课网_yancy】

在这里插入图片描述


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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

leetcode 二分查找·系统掌握

题目&#xff1a; 题解&#xff1a; 在阶梯数达到某一值后已有的硬币数量就小于了阶梯可以装的硬币数量&#xff0c;根据题意可以使用~10~泛型查找出最后一个可以被填满的阶梯。对于这类型可以二分答案的题目关键在于二分答案的上下界&#xff0c;本题的下界就是1上界就是硬币…

内容安全复习 8 - 视觉内容伪造与检测

文章目录 研究背景内容伪造方法虚假人脸生成人脸替换属性编辑表情重演跨模态人脸编辑 伪造检测方法眨眼检测交互式人脸活体检测一些了解方法挑战 研究背景 图像内容篡改造成新闻报道的偏颇易导致社会和公共秩序的不安&#xff0c;对公共安全产生不良影响。 造成的影响&#x…

英伟达能保住全球市值第一的桂冠吗?

内容提要 《巴伦周刊》认为&#xff0c;英伟达市值的迅速上涨是该公司可能难以保持市值第一桂冠的关键原因。另一个担忧是&#xff0c;英伟达的崛起主要基于一项单一技术——为人工智能应用提供动力的芯片和平台。一些人担心&#xff0c;如果购买英伟达产品的公司无法从投资中…

Open MMLab 之 MMDetection框架

MMDetection框架入门教程&#xff08;完全版&#xff09;-CSDN博客 OpenMMLab MMDetection是商汤和港中文大学针对目标检测任务推出的一个开源项目&#xff0c;它基于Pytorch实现了大量的目标检测算法&#xff0c;把数据集构建、模型搭建、训练策略等过程都封装成了一个个模块…

域名防红程序网站源码-最新可用

网上泛滥的都是2.5的版本&#xff0c;这是2.7的版本&#xff01; 功能简介 解决QQ内报毒问题&#xff0c;直接跳浏览器操作&#xff0c;好像这个版本只能安卓QQ了&#xff0c;最新版的支持IOS QQ。 url.cn 大绿标功能&#xff01;此源码仅供测试使用&#xff01; 安装说明 …

如何获取特定 HIVE 库的元数据信息如其所有分区表和所有分区

如何获取特定 HIVE 库的元数据信息如其所有分区表和所有分区 1. 问题背景 有时我们需要获取特定 HIVE 库下所有分区表&#xff0c;或者所有分区表的所有分区&#xff0c;以便执行进一步的操作&#xff0c;比如通过 使用 HIVE 命令 MSCK REPAIR TABLE table_name sync partiti…

【C语言】关于字符串函数的使用及模拟实现(1)

一、字符串追加 1.1 库函数srecat的使用 1.2 库函数strncat的使用 1.3 模拟实现库函数 strcat 及 strncat 由上可知&#xff0c;字符串追加的原理是找到所添加字符串的 \0 位置&#xff0c;再对其进行添加。 代码1、 代码2、 二、字符串查找 2.1 库函数strstr的使用 使用…

Day28:回溯法 491.递增子序列 46.全排列 47.全排列 II 332.重新安排行程 51. N皇后 37. 解数独

491. 非递减子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xff0c;递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。 数组中可能含有重复元素&#xff0c;如出现两个整数相等&#xff0c;也可以视作递增序列的一种特殊情…

用含成员函数的类,分别输入和输出各对象中的时间(时:分:秒)

编写程序&#xff1a; 运行结果&#xff1a; 注意&#xff1a; &#xff08;1&#xff09;在主函数中调用两个成员函数时&#xff0c;应指明对象名(t1,t2)。表示调用的是哪一个对象的成员函数。t1.display()和t2.display()虽然都是调用同一个 display函数&#xff0c;但…

最长考拉兹序列

题目&#xff1a; 考虑如下定义在正整数集上的迭代规则&#xff1a; n n/2 (若n为偶数) n 3n1 &#xff08;若n为奇数&#xff09; 从13开始&#xff0c;可以迭代生成如下的序列&#xff1a; 13 40 20 10 5 16 8 4 2 1 可以看出这个序列&#xff08;从13…

pytest测试框架pytest-xdist插件并发执行测试用例

Pytest提供了丰富的插件来扩展其功能&#xff0c;本章介绍下插件pytest-xdist&#xff0c;主要是提供并行测试、分布式测试、循环测试等功能&#xff0c;可以加快测试速度。 pytest-xdist官方显示没有严格的python和pytest版本限制。 pytest-xdist安装 使用pip命令安装: pip…

高中数学:数列-等差数列、等比数列的和与通项公式的关系

一、等差数列 1、通项公式与求和公式 2、性质 性质1 求和公式比上n&#xff0c;依然是一个等差数列。 性质2 等差数列中&#xff0c;每相邻m项和&#xff0c;构成的数列&#xff0c;依然是等差数列&#xff0c;公差&#xff1a;m2d 二、等比数列 1、通项公式与求和公式 a…

「GPT源码探索」:从ChatPaper到学术论文GPT的二次开发实践

前言 本文的前两个部分最早是属于此旧文的《学术论文GPT的源码解读与微调&#xff1a;从ChatPaper到七月论文审稿GPT第1版》&#xff0c;但为了每一篇文章各自的内容更好的呈现&#xff0c;于是我今天做了以下三个改动 原来属于mamba第五部分的「Mamba近似工作之线性Transfor…

数据库原理与安全复习笔记

1 概念 产生与发展&#xff1a;人工管理阶段 → \to → 文件系统阶段 → \to → 数据库系统阶段。 数据库系统特点&#xff1a;数据的管理者&#xff08;DBMS&#xff09;&#xff1b;数据结构化&#xff1b;数据共享性高&#xff0c;冗余度低&#xff0c;易于扩充&#xff…

自动控制原理出射角计算

背景&#xff1a;突然发现自己出射角不会算 被减数是零点到极点的角度&#xff0c;减数是极点到极点的角度

毕业设计——可视化实验仿真平台

该程序用于毕业设计&#xff0c;架构为前后端分离技术&#xff0c;涉及技术包括vue3&#xff0c;SpringBoot&#xff0c;spring-secrity&#xff0c;Redis&#xff0c;需要者进群769119544进行相关咨询。 程序分为三个角色&#xff1a;学生、老师、管理员。使用了spring-secrit…

Vision Pro的3D跟踪能力:B端应用的工作流、使用教程和经验总结

Vision Pro的最新3D跟踪能力为工业、文博、营销等多个B端领域带来了革命性的交互体验。本文将详细介绍这一功能的工作流、使用教程,并结合实际经验进行总结。 第一部分:工作流详解 一、对象扫描 使用Reality Composer iPhone应用程序对目标对象进行3D扫描,如吉他或雕塑,…

从关键新闻和最新技术看AI行业发展(2024.6.3-6.16第二十五期) |【WeThinkIn老实人报】

写在前面 【WeThinkIn老实人报】旨在整理&挖掘AI行业的关键新闻和最新技术&#xff0c;同时Rocky会对这些关键信息进行解读&#xff0c;力求让读者们能从容跟随AI科技潮流。也欢迎大家提出宝贵的优化建议&#xff0c;一起交流学习&#x1f4aa; 欢迎大家关注Rocky的公众号&…

一键智能整理TXT文档,高效删除连续行,轻松提升工作效率与数据管理效能

信息爆炸的时代&#xff0c;TXT文档作为我们日常工作中不可或缺的一部分&#xff0c;承载着大量的数据和信息。然而&#xff0c;随着文档内容的不断增加&#xff0c;连续重复的行数也逐渐增多&#xff0c;这不仅影响了文档的整洁度&#xff0c;还大大降低了我们处理数据的效率。…

idea添加文档注释

一、easy javadoc插件 在settings的plugins中下载easy javadoc插件。 安装完成后重启idea&#xff0c;再次打开settings界面。会出现easyDoc相关配置。 二、设置模版以及使用 类描述模版参考设置&#xff1a; /** * 类描述 -> * * Author: ywz * Date: $Date$ */ 方法描述…