Skip to content

Instantly share code, notes, and snippets.

@Tahul
Last active August 10, 2021 06:29
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Tahul/33f32487ad160a833f368a8ea29a4bfc to your computer and use it in GitHub Desktop.
Save Tahul/33f32487ad160a833f368a8ea29a4bfc to your computer and use it in GitHub Desktop.
[ThreeJS Typescript Simple GLTF/GLB Scene] A simple GLTF/GLB scene loader using TypeScript. #threejs, #gltf, #glb, #loader, #typescript
import {
PerspectiveCamera,
DirectionalLight,
HemisphereLight,
Scene,
WebGLRenderer,
Color,
Clock,
} from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
const clock = new Clock()
class Loop {
public camera
public scene
public renderer
public updatables: OrbitControls[]
constructor(
camera: THREE.Camera,
scene: THREE.Scene,
renderer: THREE.WebGLRenderer
) {
this.camera = camera
this.scene = scene
this.renderer = renderer
this.updatables = []
}
start() {
this.renderer.setAnimationLoop(() => {
// tell every animated object to tick forward one frame
this.tick()
// render a frame
this.renderer.render(this.scene, this.camera)
})
}
stop() {
this.renderer.setAnimationLoop(null)
}
tick() {
// only call the getDelta function once per frame!
// const delta = clock.getDelta()
// console.log(
// `The last frame rendered in ${delta * 1000} milliseconds`,
// );
for (const object of this.updatables) object.update()
}
}
let camera: PerspectiveCamera
let controls: OrbitControls
let renderer: WebGLRenderer
let scene: Scene
let loop: Loop
class World {
constructor(container: Element) {
camera = createCamera()
renderer = createRenderer()
scene = createScene()
loop = new Loop(camera, scene, renderer)
container.append(renderer.domElement)
controls = createControls(camera, renderer.domElement)
const { ambientLight, mainLight } = createLights()
loop.updatables.push(controls)
scene.add(ambientLight, mainLight)
const resizer = new Resizer(container, camera, renderer)
}
async init() {
const { character } = await loadModels()
// move the target to the center of the front bird
controls.target.copy(character)
scene.add(character)
}
render() {
renderer.render(scene, camera)
}
start() {
loop.start()
}
stop() {
loop.stop()
}
}
const createRenderer = (): WebGLRenderer => {
const renderer = new WebGLRenderer({ antialias: true })
renderer.physicallyCorrectLights = true
return renderer
}
const createCamera = (): PerspectiveCamera => {
const camera = new PerspectiveCamera(35, 1, 0.1, 100)
camera.position.set(-1.5, 1.5, 6.5)
return camera
}
type DefaultLights = {
ambientLight: HemisphereLight
mainLight: DirectionalLight
}
const createLights = (): DefaultLights => {
const ambientLight = new HemisphereLight('white', 'darkslategrey', 5)
const mainLight = new DirectionalLight('white', 4)
mainLight.position.set(10, 10, 10)
return { ambientLight, mainLight }
}
const createScene = (): Scene => {
const scene = new Scene()
scene.background = new Color('transparent')
return scene
}
const loadModels = async () => {
const loader = new GLTFLoader()
const modelData = await loader.loadAsync(
'~/assets/models/character/scene.gltf'
)
const character = setupModel(modelData)
character.position.set(0, 0, 2.5)
return {
character,
}
}
const setupModel = (data: any) => {
const model = data.scene.children[0]
return model
}
const createControls = (camera: THREE.Camera, canvas: any): OrbitControls => {
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true
return controls
}
const setSize = (
container: Element,
camera: PerspectiveCamera,
renderer: WebGLRenderer
) => {
camera.aspect = container.clientWidth / container.clientHeight
camera.updateProjectionMatrix()
renderer.setSize(container.clientWidth, container.clientHeight)
renderer.setPixelRatio(window.devicePixelRatio)
}
class Resizer {
constructor(
container: Element,
camera: PerspectiveCamera,
renderer: WebGLRenderer
) {
// set initial size
setSize(container, camera, renderer)
window.addEventListener('resize', () => {
// set the size again if a resize occurs
setSize(container, camera, renderer)
// perform any custom actions
this.onResize()
})
}
onResize() {}
}
const createWorld = async (container: Element | undefined) => {
if (!container) throw new Error('No container found')
// create a new world
const world = new World(container)
// complete async tasks
await world.init()
// start the animation loop
world.start()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment