Three.js+TypeScript+Webpack学习记录(三)

news2024/12/23 14:23:42

使用环境参考

Node.js v16.19.1

正文

独立功能文件

我们不可能一直在 index.ts 中写代码,分离文件:

// init.ts
import * as THREE from 'three'

export const initScene = () => {
    const scene = new THREE.Scene()
    scene.background = new THREE.Color('white')
    const light = new THREE.AmbientLight('white', 1.3)
    scene.add(light)
    return scene
}

export const initCamera = () => {
    const camera = new THREE.PerspectiveCamera(55, window.innerWidth / window.innerHeight, 0.1, 1000)
    camera.position.set(0, 3, 5)
    camera.lookAt(new THREE.Vector3(0, 0, 0))
    return camera
}

export const initWebGLRenderer = () => {
    const renderer = new THREE.WebGLRenderer({ antialias: true })
    renderer.setSize(window.innerWidth, window.innerHeight)
    document.body.appendChild(renderer.domElement)
    return renderer
}

// load.ts
import * as THREE from 'three'
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'

export const loadGLTF = (url: string) => new Promise<THREE.Group>((resolve, reject) => {
    const loader = new GLTFLoader()
    loader.load(url, (gltf: GLTF) => {
        console.log(gltf)
        resolve(gltf.scene)
    })
})

// index.ts
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { initCamera, initScene, initWebGLRenderer } from './init'
import { loadGLTF } from './load'

class Game {
    scene: THREE.Scene
    camera: THREE.PerspectiveCamera
    renderer: THREE.WebGLRenderer
    orbitControls: OrbitControls

    constructor() {
        this.scene = initScene()
        this.camera = initCamera()
        this.scene.add(this.camera)
        this.renderer = initWebGLRenderer()
        this.orbitControls = this.addOrbitControls(this.camera, this.renderer)
        this.addModel()
        this.addResizeEventListener()
    }

    addOrbitControls(camera: THREE.Camera, renderer: THREE.WebGLRenderer) {
        const controls = new OrbitControls(camera, renderer.domElement)
        controls.autoRotate = true
        controls.enableDamping = true
        controls.update()
        return controls
    }

    async addModel() {
        const model = await loadGLTF('gltf/SheenChair.glb')
        this.scene.add(model)
    }

    addResizeEventListener() {
        window.addEventListener('resize', () => {
            this.camera.aspect = window.innerWidth / window.innerHeight
            this.camera.updateProjectionMatrix()
            this.renderer.setSize(window.innerWidth, window.innerHeight)
        })
    }

    startMainLoop() {
        // 等待一帧用于初始化
        Promise.resolve().then(() => {
            this.step()
        })
    }

    step() {
        requestAnimationFrame(this.step.bind(this))
        this.orbitControls && this.orbitControls.update()
        this.renderer.render(this.scene, this.camera)
    }
}

const game = new Game()
game.startMainLoop()

射线检测

鼠标点击物体是最常见的一个需求,对 dom 新增点击事件,然后计算相对于 canvas 的坐标比例,计算进 three 的坐标系(-1 ~ 1)。

import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { initCamera, initScene, initWebGLRenderer } from './init'
import { loadGLTF } from './load'

class Game {
    scene: THREE.Scene
    camera: THREE.PerspectiveCamera
    renderer: THREE.WebGLRenderer
    orbitControls: OrbitControls

    raycaster = new THREE.Raycaster()
    mouse = new THREE.Vector2()

    constructor() {
        this.scene = initScene()
        this.camera = initCamera()
        this.scene.add(this.camera)
        this.renderer = initWebGLRenderer()
        this.orbitControls = this.addOrbitControls(this.camera, this.renderer)
        this.addModel()
        this.addResizeEventListener()
        this.addClickEvent()
    }

    addClickEvent() {
        this.renderer.domElement.addEventListener('click', (ev) => {
            this.mouse.x = (ev.clientX / window.innerWidth) * 2 - 1
            this.mouse.y = -(ev.clientY / window.innerHeight) * 2 + 1
            this.raycaster.setFromCamera(this.mouse, this.camera)
            const intersects = this.raycaster.intersectObject(this.scene, true)
            console.log(intersects)
        })
    }

    addOrbitControls(camera: THREE.Camera, renderer: THREE.WebGLRenderer) {
        const controls = new OrbitControls(camera, renderer.domElement)
        controls.autoRotate = true
        controls.enableDamping = true
        controls.update()
        return controls
    }

    async addModel() {
        const model = await loadGLTF('gltf/SheenChair.glb')
        this.scene.add(model)
    }

    addResizeEventListener() {
        window.addEventListener('resize', () => {
            this.camera.aspect = window.innerWidth / window.innerHeight
            this.camera.updateProjectionMatrix()
            this.renderer.setSize(window.innerWidth, window.innerHeight)
        })
    }

    startMainLoop() {
        // 等待一帧用于初始化
        Promise.resolve().then(() => {
            this.step()
        })
    }

    step() {
        requestAnimationFrame(this.step.bind(this))
        this.orbitControls && this.orbitControls.update()
        this.renderer.render(this.scene, this.camera)
    }
}

const game = new Game()
game.startMainLoop()

动画轨道

鼠标点击某个模型,对这个模型进行动画处理。

新建一个 animation.ts 负责处理动画

// animation.ts
import * as THREE from 'three'

export class AnimationManager {
    mixers: THREE.AnimationMixer[] = []
    clock = new THREE.Clock()

    addOnePosAnima(obj: THREE.Object3D) {
        const positionTimes = [0, 1, 2]
        const positionArr = [
            0, 0, 0,
            0, 1, 0,
            0, 0, 0
        ]
        const track = new THREE.VectorKeyframeTrack(
            `${obj.name}.position`,
            positionTimes,
            positionArr,
            THREE.InterpolateSmooth
        )
        // 动画名称,持续时间,track
        const clip = new THREE.AnimationClip('clip1', 2, [track])
        const mixer = new THREE.AnimationMixer(obj)
        const action = mixer.clipAction(clip)
        action.play()
        this.mixers.push(mixer)
    }

    step() {
        const dt = this.clock.getDelta()
        this.mixers.forEach(m => m.update(dt))
    }
}

在 index.ts 中,点击到哪儿模型就为其添加一个动画。

import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { initCamera, initScene, initWebGLRenderer } from './init'
import { loadGLTF } from './load'
import { AnimationManager } from './animation'

class Game {
    scene: THREE.Scene
    camera: THREE.PerspectiveCamera
    renderer: THREE.WebGLRenderer
    orbitControls: OrbitControls

    raycaster = new THREE.Raycaster()
    mouse = new THREE.Vector2()

    animationManager = new AnimationManager()

    constructor() {
        this.scene = initScene()
        this.camera = initCamera()
        this.scene.add(this.camera)
        this.renderer = initWebGLRenderer()
        this.orbitControls = this.addOrbitControls(this.camera, this.renderer)
        this.addModel()
        this.addResizeEventListener()
        this.addClickEvent()
    }

    addClickEvent() {
        this.renderer.domElement.addEventListener('click', (ev) => {
            this.mouse.x = (ev.clientX / window.innerWidth) * 2 - 1
            this.mouse.y = -(ev.clientY / window.innerHeight) * 2 + 1
            this.raycaster.setFromCamera(this.mouse, this.camera)
            const intersects = this.raycaster.intersectObject(this.scene, true)
            console.log(intersects)
            if (intersects[0]) {
                this.animationManager.addOnePosAnima(intersects[0].object)
            }
        })
    }

    addOrbitControls(camera: THREE.Camera, renderer: THREE.WebGLRenderer) {
        const controls = new OrbitControls(camera, renderer.domElement)
        controls.autoRotate = true
        controls.enableDamping = true
        controls.update()
        return controls
    }

    async addModel() {
        const model = await loadGLTF('gltf/SheenChair.glb')
        this.scene.add(model)
    }

    addResizeEventListener() {
        window.addEventListener('resize', () => {
            this.camera.aspect = window.innerWidth / window.innerHeight
            this.camera.updateProjectionMatrix()
            this.renderer.setSize(window.innerWidth, window.innerHeight)
        })
    }

    startMainLoop() {
        // 等待一帧用于初始化
        Promise.resolve().then(() => {
            this.step()
        })
    }

    step() {
        requestAnimationFrame(this.step.bind(this))
        this.orbitControls && this.orbitControls.update()
        this.animationManager.step()
        this.renderer.render(this.scene, this.camera)
    }
}

const game = new Game()
game.startMainLoop()

要注意模型是由多个子 Mesh 组成,如果想动整体,查询 parent 或者提前用变量存好或者记录 name 进行查找。

更多文章与分享

Three 学习项目链接:https://github.com/KuoKuo666/threejs-study

个人网站:www.kuokuo666.com

2023!Day Day Up!

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

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

相关文章

【多线程】Java中是如何保证多线程间的数据共享的?

文章目录 前言一、Java的内存模型二、保证可见性的方式volatilesynchronizedlockfinal 三、volatile的底层实现总结 前言 在讨论这个问题之前&#xff0c;我们可以先瞅瞅Java的内存模型JMM&#xff0c;JMM可不要和JVM混为一谈。我们说的是内存模型JMM&#xff08;Java Memory …

JavaSE-06 [面向对象OOP + 封装]

JavaSE-06 [面向对象OOP 封装] 第一章 面向对象思想 1.1 面向过程和面向对象 面向过程&#xff1a; 面向过程就是分析出解决问题所需要的步骤&#xff0c;然后用函数把这些步骤一步一步实现&#xff0c;使用的时候一个一个依次调用就可以了面向对象&#xff1a; 面向对象是…

博弈论(NIM游戏——取石子)相关的题目

1.异或的性质 &#x1f3f3;️‍&#x1f308;&#x1f3f3;️‍&#x1f308;&#x1f3f3;️‍&#x1f308;&#x1f3f3;️‍&#x1f308;&#x1f3f3;️‍&#x1f308;&#x1f3f3;️‍&#x1f308; 2.nim游戏 &#xff08;基础&#xff09; 891. Nim游戏 - AcWin…

直播软件app开发:如何处理直播延迟问题?

随着直播技术的发展&#xff0c;直播软件app的开发变得越来越普遍。然而&#xff0c;直播延迟问题一直是直播软件app开发中的一个挑战。在本文中&#xff0c;我们将探讨如何解决直播延迟问题&#xff0c;提高用户体验。 直播延迟的原因 直播延迟的原因是多方面的。其中最主要…

【Mycat2】介绍、安装、部署、配置、测试与 Bugs

文章目录 一、MyCat 概览1.1 简介1.2 官网网址1.3 仓库地址1.4 Mycat1.x 与 Mycat2 功能对比1.5 下载1.5.1 先决条件1.5.2 Mycat2 安装包&#xff08;以下二选一&#xff09; 1.6 Mycat2权威指南1.7 原型库什么是兼容性 SQL&#xff1f;什么是 Prototype 服务器&#xff1f;原型…

shell第二次作业

一、编写脚本for1.sh使用for循环创建20账户&#xff0c;账户名前缀由用户从键盘输入&#xff0c;账户初始密码由用户输入。 1、创建脚本for1.sh [rootlocalhost ~]# vim for1.sh2、编辑脚本内容 3.运行 [rootlocalhost ~]# bash for1.sh 请用户输入账户名的前缀&#xff1a…

WPS AI 再次引爆办公软件行业,以后写文档可由AI代笔

国内外办公软件两大巨头聚齐&#xff0c;使用AI助力再次成标配。 2023年3月16日&#xff0c;微软发布了 Microsoft 365 Copilot&#xff0c;Microsoft 365 这个名字听起来比较陌生&#xff0c;它集成了Office 365 企业版、Windows 10 企业版以及企业移动性安全性&#xff0c;以…

基于Jira的持续交付流水线实践方式

点击上方蓝字⭐️关注“DevOps云学堂”&#xff0c;接收最新技术实践 今天是「DevOps云学堂」与你共同进步的第 19 天 DevOps 为什么很重要&#xff1f; 软件和 Internet 改变了我们身处的世界&#xff0c;同时也改变了购物、娱乐、银行等行业的运营方式。软件不再仅仅是为业务…

【unity项目实战】3DRPG游戏开发03—— 绘制low poly风格的场景

搭建场景 拖入一颗树,按住V键,表示顶点吸附,点击树的底部,他就会自动吸附到相邻的平面上了 如果你的摄像机位置不是很好,我们可以场景移动到合适的位置,可以点击摄像机,按下键盘ctrl+shift+F同步调整摄像机位置 绘制low poly风格的环境 安装Ploybrush插件,并导入样…

linux安装南大通用数据库 GBase 8s V8.8

linux安装南大通用数据库 GBase 8s V8.8 1、操作系统、数据库2、下载链接3、安装文档4、安装前准备4.1、以root用户创建 gbasedbt 组和用户4.2、创建 GBase 8s 数据库安装目录4.3、上传并解压安装包 5、安装5.1、执行安装程序5.2、回车继续 直到接受许可条款5.3、输入安装目录绝…

Windows安装使用Redis,redis基本使用教程,python连接调用redis

文章目录 下载&#xff1a;安装&#xff1a;redis安装成Windows服务&#xff1a;修改密码&#xff1a;客户端连接&#xff1a;基本使用&#xff1a;python里调用redis&#xff1a; 下载&#xff1a; https://github.com/microsoftarchive/redis/releases/tag/win-3.2.100 安装…

机器学习:基于多项式贝叶斯对蘑菇毒性分类预测分析

基于多项式贝叶斯对蘑菇毒性分类预测分析 作者&#xff1a;i阿极 作者简介&#xff1a;Python领域新星作者、多项比赛获奖者&#xff1a;博主个人首页 &#x1f60a;&#x1f60a;&#x1f60a;如果觉得文章不错或能帮助到你学习&#xff0c;可以点赞&#x1f44d;收藏&#x1…

银行数字化转型导师坚鹏:银行业务数字化创新工作坊

银行业务数字化创新工作坊 课程背景&#xff1a; 很多银行存在以下问题&#xff1a; 不清楚如何进行业务数字化创新&#xff1f; 不知道如何开展银行数字化营销工作&#xff1f; 不知道零售业务数字化创新成功案例&#xff1f; 学员收获&#xff1a; 学习原创银行BLM…

【音视频】国际双向对讲方案

语音对讲的的整体流程为&#xff1a; 先拉流播放设备的视频&#xff1b;使用WebRTC推送语音流到ZLM服务&#xff1b;使用SIP协议下发Broadcast指令给设备&#xff1b;接收到设备的OK指令后&#xff0c;请求ZLM的startSendRtp接口&#xff0c;TCP协议请求startSendRtpPassive接口…

springboot集成webmagic和selenium,并部署到linux(问题坑)

springboot集成webmagic和selenium&#xff0c;并部署到linux&#xff08;问题坑&#xff09; 首先参考两个源代码spring boot集成找不到org.openqa.selenium.remote.AbstractDriverOptions的类文件代理ip--更换一个网页同时更换一个代理ip代理ip网址部署linux谷歌浏览器下载谷…

Python冒泡排序的实现

时间复杂度&#xff1a; 最坏时间复杂度O(n^2) 最优时间复杂度O(n):表遍历一次发现没有任何可以交换的元素&#xff0c;排序结束&#xff0c;这是最理想的 稳定性&#xff1a;稳定&#xff0c;&#xff08;执行前后没有对数据没有变化&#xff0c;位置等&#xff09; 原理和方…

供需两端催化口腔医疗服务市场增长 未来将呈现线上化、智能化、品质化三大趋势

一、口腔医疗服务行业概述 口腔由唇、颊、舌、腭、涎腺、牙和颌骨等部分组成。口腔疾病种类繁多&#xff0c;伴随人全生命周期&#xff0c;常见疾病有龋病、牙周疾病、牙髓病、根尖周病、牙齿缺损、错颌畸形等&#xff0c;多数口腔疾病的发病率高&#xff0c;诊疗需求大。除此…

深度强化学习——策略学习(3)

本文的重点&#xff1a;使用一个神经网络来近似策略函数Π 我们使用一个神经网络来近似策略函数&#xff0c;这个神经网络叫做策略网络&#xff0c;他可以用来控制agent运动&#xff0c;想要训练这个神经网络&#xff0c;就要用到policy gradient算法 策略函数Π的输入是当前…

苹果离开中国制造影响有多大?后果不堪设想,可能是灭顶之灾

由于苹果力推印度制造&#xff0c;各方都对苹果与中国制造的关系高度关注&#xff0c;那么苹果真的离开中国制造的话&#xff0c;结果会如何呢&#xff1f;对双方来说&#xff0c;影响会非常大&#xff0c;超出想象。 一、中国制造对苹果的依赖 苹果对产业链拥有巨大的影响力&a…

Python基础快速入门

一、中文编码问题 带大家过一遍菜鸟学python的基础内容 二、Python基础语法 1、Python 中的标识符是区分大小写的。 以下划线开头的标识符是有特殊意义的。以单下划线开头 _foo 的代表不能直接访问的类属性&#xff0c;需通过类提供的接口进行访问&#xff0c;不能用 from x…