Last active
August 10, 2021 06:29
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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