Use environment reference
Node.js v16.19.1
text
stand-alone function file
We can't keep writing code in index.ts, separate files:
// 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()
X-ray inspection
Mouse click on an object is the most common requirement. Add a click event to the dom, and then calculate the coordinate ratio relative to the canvas, and calculate it into the three coordinate system (-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 track
Click a model with the mouse to animate the model.
Create a new animation.ts responsible for handling animation
// 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))
}
}
In index.ts, an animation is added to the model wherever it is clicked.
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()
It should be noted that the model is composed of multiple sub-Mesh. If you want to move the whole, query the parent or save it in a variable in advance or record the name to search.
More articles and shares
Three study project link: https://github.com/KuoKuo666/threejs-study
Personal website: www.kuokuo666.com
2023!Day Day Up!