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

news2024/11/25 6:55:42

个人简介

👀个人主页: 前端杂货铺
开源项目: 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】

在这里插入图片描述


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

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

相关文章

C# SerialPort串口通讯

串口通信 在.NET平台下创建C#串口通信程序&#xff0c;.NET 2.0提供了串口通信的功能&#xff0c;其命名空间是System.IO.Ports。这个新的框架不但可以访问计算机上的串口&#xff0c;还可以和串口设备进行通信。 创建C#串口通信程序之命名空间 System.IO.Ports命名空间中最重…

【跟我学RISC-V】(三)openEuler特别篇

写在前面 这篇文章是跟我学RISC-V指令集的第三期&#xff0c;距离我上一次发文已经过去一个多月了&#xff0c;在这个月里我加入了oerv的实习项目组&#xff0c;并且还要准备期末考试&#xff0c;比较忙&#xff0c;所以更新频率不高&#xff0c;不过对于Linux kernel和RISC-V…

vue修改node_modules打补丁步骤和注意事项_node_modules 打补丁

1、vue-pdf问题解决及patch-package简介&#xff1a;https://www.jianshu.com/p/d1887e02f8d6 2、使用“黑魔法”优雅的修改第三方依赖包&#xff1a;https://zhuanlan.zhihu.com/p/412753695 3、使用patch-package定制node_modules中的依赖包&#xff1a;https://blog.csdn.…

macbook配置adb环境和用adb操作安卓手机

&#xff08;参考&#xff1a;ADB工具包的安装与使用_adb工具箱-CSDN博客&#xff09; 第一步&#xff1a;从Android开发者网站下载Android SDK&#xff08;软件开发工具包&#xff09;。下载地址为&#xff1a; 第二步&#xff1a;解压下载的SDK压缩文件到某个目录中。 进入解…

Python 数据可视化 多色散点图

Python 数据可视化 多色散点图 fig, ax plt.subplots() max_line max([max(merged_df[unif_ref_value]), max(merged_df[unif_rust_value])]) min_line min([max(merged_df[unif_ref_value]), max(merged_df[unif_rust_value])]) ax.plot([min_line, max_line], [min_line, …

基于JSP的教学质量评价系统

开头语&#xff1a; 你好&#xff0c;我是计算机学长猫哥。如果您对教学质量评价系统感兴趣或有相关需求&#xff0c;欢迎随时联系我。 开发语言&#xff1a; Java 数据库&#xff1a; MySQL 技术&#xff1a; JSP技术 Java语言 工具&#xff1a; MyEclipse、Tomcat服…

基于JSP的“塞纳河畔左岸”的咖啡馆管理系统

开头语&#xff1a; 塞纳河畔左岸的咖啡&#xff0c;我手一杯品尝的你美~ 哎哟&#xff0c;不错哦&#xff01;我们今天来介绍一下咖啡馆管理系统&#xff01; 你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果你对咖啡馆管理系统感兴趣或有相关需求&#xff0c;欢迎联…

基于 GoLang 编写的 IOT 物联网在线直播抓娃娃企业级项目

大家好&#xff0c;我是码农先森。 引言 前几年&#xff0c;娱乐物联网的热度很高。我当时所在的公司启动了一个将线下娃娃机的玩法&#xff0c;迁移到线上的项目&#xff0c;因此公司决定开发一个在线直播抓娃娃的项目。近来&#xff0c;娱乐物联网的领域已经进入资本的寒冬…

小白学-WEBGL

第一天&#xff1a; 1.canvas和webgl的区别 Canvas 和 WebGL 都是用于在网页上绘制图形的技术&#xff0c;它们通过浏览器提供的 API 使开发者能够创建丰富的视觉内容&#xff0c;但它们的工作原理和用途有所不同。 Canvas Canvas API 提供了一个通过 JavaScript 和 HTML <…

家政预约小程序14权限配置

目录 1 创建用户2 创建角色3 启用登录4 实现退出总结 我们现在小程序端的功能基本开发好了&#xff0c;小程序开发好之后需要给运营人员提供管理后台&#xff0c;要分配账号、配置权限&#xff0c;我们本篇就介绍一下权限如何分配。 1 创建用户 在微搭中&#xff0c;用户分为内…

基于Pico和MicroPython点亮ws2812彩色灯带

基于Pico和MicroPython点亮ws2812彩色灯带 文章目录 基于Pico和MicroPython点亮ws2812彩色灯带IntroductionPracticeConclusion Introduction 点亮发光的LED灯是简单有趣的实验&#xff0c;点亮多个ws2812小灯串联起来的灯带&#xff0c;可对多个彩色小灯进行编程&#xff0c;…

react笔记-03react-router篇

本文章是react的路由笔记 一、react路由&#xff08;v5版本&#xff09; 1. 什么是路由&#xff08;前端&#xff09;&#xff1f; 一个路由就算一个映射关系&#xff08;key: value)key为路径&#xff0c;value为组件 2. 前端路由的工作原理 根据浏览器历史记录&#xff…

Java | Leetcode Java题解之第167题两数之和II-输入有序数组

题目&#xff1a; 题解&#xff1a; class Solution {public int[] twoSum(int[] numbers, int target) {int low 0, high numbers.length - 1;while (low < high) {int sum numbers[low] numbers[high];if (sum target) {return new int[]{low 1, high 1};} else i…

C++ (week9):Git

文章目录 1.git介绍2.git安装3.git配置4.获取自己的SSH公钥5.新建仓库6.邀请开发者7.克隆远程仓库到本地8.在本地进行开发9.本地项目推送到远程仓库10.git的工作原理11.分支管理(1)合作开发的方式(2)分支管理(3)分支合并的原理、冲突管理 12.git 与 svn 的区别13.设置alias别名…

Ubuntu iso 镜像下载 步骤截图说明

Ubuntu镜像下载&#xff0c;在这个网址&#xff1a; Enterprise Open Source and Linux | Ubuntu 步骤如下图所示&#xff1a; 1、登入网址 2、点击Get Ubuntu 3、点击Download Ubuntu Desktop 后续点击Downloadload 24.04 LTS直接下载就行 如果需要下载其它版本&#xf…

0 知识的补充

目录 矢量运算 矢量加法 矢量减法 矢量点乘 矢量叉乘 矢量混合积 坐标系 直角坐标系 柱坐标系 球坐标系 ​​​​​​​ 矢量运算 矢量加法 矢量减法 矢量点乘 矢量叉乘 ​​​​​​​ 矢量混合积 坐标系 直角坐标系 柱坐标系 ​​​​​​​ 球坐标系

MATLAB-SSA-CNN-SVM,基于SSA麻雀优化算法优化卷积神经网络CNN结合支持向量机SVM数据分类(多特征输入多分类)

MATLAB-SSA-CNN-SVM,基于SSA麻雀优化算法优化卷积神经网络CNN结合支持向量机SVM数据分类(多特征输入多分类) 1.数据均为Excel数据&#xff0c;直接替换数据就可以运行程序。 2.所有程序都经过验证&#xff0c;保证程序可以运行。 3.具有良好的编程习惯&#xff0c;程序均包含…

【密码学】分组密码

文章目录 分组密码的模式分组密码与流密码模式明文分组与密文分组 ECB模式ECB定义ECB特点对ECB模式的攻击改变分组顺序攻击 CBC模式CBC定义初始化向量IVCBC特点对CBC模式的攻击对初始向量进行反转攻击填充提示攻击 CFB模式CFB定义对CFB模式的攻击重放攻击 OFB模式OFB定义CFB模式…

模板初阶【C++】

模板的作用 模板的主要作用是实现泛型编程&#xff0c;泛型编程即编写与类型无关的通用代码&#xff0c;是代码复用的一种手段 模板就是泛型编程的基础。 例 我们经常使用的交换函数就可以使用泛型编程来进行编写&#xff0c;这样可以大大减少重复的代码 一般编写方式 可以…

MyBatis拦截器(Interceptor)的理解与实践

文章目录 1. 什么是MyBatis拦截器&#xff1f;2. 拦截器的基本原理3. 编写自定义拦截器3.1 示例&#xff1a;实现SQL执行时间统计拦截器3.2 配置拦截器 4. 实战应用场景5. 总结 &#x1f389;欢迎来到SpringBoot框架学习专栏~ ☆* o(≧▽≦)o *☆嗨~我是IT陈寒&#x1f379;✨博…