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

使用环境参考

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!

猜你喜欢

转载自blog.csdn.net/kuokuo666/article/details/130313907
今日推荐