Skip to content

Commit

Permalink
fix(types): useLoader generics (#781)
Browse files Browse the repository at this point in the history
* fix(types): improve useLoader generics

* docs: jsdocs
  • Loading branch information
alvarosabu authored Jul 17, 2024
1 parent 05b3009 commit b51d679
Show file tree
Hide file tree
Showing 12 changed files with 204 additions and 21 deletions.
14 changes: 14 additions & 0 deletions playground/src/composables/useFBX.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { FBXLoader } from 'three-stdlib'
import { useLoader } from '@tresjs/core'
import type { Object3D } from 'three'

/**
* Loads an FBX file and returns a THREE.Object3D.
*
* @export
* @param {(string | string[])} path
* @return {*} {Promise<Object3D>}
*/
export async function useFBX(path: string | string[]): Promise<Object3D> {
return (await useLoader(FBXLoader, path)) as unknown as Object3D
}
82 changes: 82 additions & 0 deletions playground/src/composables/useGLTF.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { type TresLoader, type TresObject3D, useLoader } from '@tresjs/core'
import type { AnimationClip, Material, Scene } from 'three'
import { DRACOLoader, GLTFLoader } from 'three-stdlib'
import type { GLTF } from 'three-stdlib'

export interface GLTFLoaderOptions {
/**
* Whether to use Draco compression.
*
* @type {boolean}
* @memberof GLTFLoaderOptions
*/
draco?: boolean
/**
* The path to the Draco decoder.
*
* @type {string}
* @memberof GLTFLoaderOptions
*/
decoderPath?: string
}

export interface GLTFResult {
animations: Array<AnimationClip>
nodes: Record<string, TresObject3D>
materials: Record<string, Material>
scene: Scene
}

let dracoLoader: DRACOLoader | null = null

export interface TresGLTFLoader extends TresLoader<GLTF> {
setDRACOLoader?: (dracoLoader: DRACOLoader) => void
}

/**
* Sets the extensions for the GLTFLoader.
*
* @param {GLTFLoaderOptions} options
* @param {(loader: TresGLTFLoader) => void} [extendLoader]
* @return {*}
*/
function setExtensions(options: GLTFLoaderOptions, extendLoader?: (loader: TresGLTFLoader) => void) {
return (loader: TresGLTFLoader) => {
if (extendLoader) {
extendLoader(loader)
}
if (options.draco) {
if (!dracoLoader) {
dracoLoader = new DRACOLoader()
}
dracoLoader.setDecoderPath(options.decoderPath || 'https://www.gstatic.com/draco/versioned/decoders/1.4.3/')
if (loader.setDRACOLoader) {
loader.setDRACOLoader(dracoLoader)
}
}
}
}

/**
* Loads a GLTF file and returns a THREE.Object3D.
*
* @export
* @param {(string | string[])} path
* @param {GLTFLoaderOptions} [options]
*
*
* @param {(loader: GLTFLoader) => void} [extendLoader]
* @return {*} {Promise<GLTFResult>}
*/
export async function useGLTF<T extends string | string[]>(
path: T,
options: GLTFLoaderOptions = {
draco: false,
},
extendLoader?: (loader: TresGLTFLoader) => void,
): Promise<T extends string[] ? GLTFResult[] : GLTFResult> {
const gltfModel = (await useLoader<GLTF>(GLTFLoader, path, setExtensions(options, extendLoader))) as unknown as GLTFResult
dracoLoader?.dispose()
dracoLoader = null
return gltfModel as T extends string[] ? GLTFResult[] : GLTFResult
}
2 changes: 2 additions & 0 deletions playground/src/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
cameraRoutes,
eventsRoutes,
issuesRoutes,
loaderRoutes,
miscRoutes,
modelsRoutes,
} from '../router/routes'
Expand All @@ -14,6 +15,7 @@ const sections = [
{ icon: '🤓', title: 'Advanced', routes: advancedRoutes },
{ icon: '📣', title: 'Events', routes: eventsRoutes },
{ icon: '📷', title: 'Camera', routes: cameraRoutes },
{ icon: '🛜', title: 'Loaders', routes: loaderRoutes },
{ icon: '🐇', title: 'Models', routes: modelsRoutes },
{ icon: '🤪', title: 'Misc', routes: miscRoutes },
{ icon: '🔬', title: 'Issues', routes: issuesRoutes },
Expand Down
14 changes: 14 additions & 0 deletions playground/src/pages/loaders/fbx-loader/TheExperience.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts">
import { OrbitControls } from '@tresjs/cientos'
import TheModel from './TheModel.vue'
</script>

<template>
<TresPerspectiveCamera :position="[3, 3, 3]" />
<OrbitControls />
<TresGridHelper />
<TresAmbientLight :intensity="1" />
<Suspense>
<TheModel />
</Suspense>
</template>
10 changes: 10 additions & 0 deletions playground/src/pages/loaders/fbx-loader/TheModel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script setup lang="ts">
import { useFBX } from '../../../composables/useFBX'
const scene = await useFBX('https://raw.githubusercontent.com/Tresjs/assets/main/models/fbx/low-poly-truck/Jeep_done.fbx')
scene.scale.set(0.01, 0.01, 0.01)
</script>

<template>
<primitive :object="scene" />
</template>
11 changes: 11 additions & 0 deletions playground/src/pages/loaders/fbx-loader/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core'
import TheExperience from './TheExperience.vue'
</script>

<template>
<TresCanvas clear-color="#C0ffee">
<TheExperience />
</TresCanvas>
</template>
14 changes: 14 additions & 0 deletions playground/src/pages/loaders/gltf-loader/TheExperience.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts">
import { OrbitControls } from '@tresjs/cientos'
import TheModel from './TheModel.vue'
</script>

<template>
<TresPerspectiveCamera :position="[3, 3, 3]" />
<OrbitControls />
<TresGridHelper />
<TresAmbientLight :intensity="1" />
<Suspense>
<TheModel />
</Suspense>
</template>
10 changes: 10 additions & 0 deletions playground/src/pages/loaders/gltf-loader/TheModel.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<script setup lang="ts">
import { useGLTF } from '../../../composables/useGLTF'
const { nodes } = await useGLTF('https://raw.githubusercontent.com/Tresjs/assets/main/models/gltf/blender-cube.glb', { draco: true })
const model = nodes.Cube
</script>

<template>
<primitive :object="model" />
</template>
11 changes: 11 additions & 0 deletions playground/src/pages/loaders/gltf-loader/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script setup lang="ts">
import { TresCanvas } from '@tresjs/core'
import TheExperience from './TheExperience.vue'
</script>

<template>
<TresCanvas clear-color="#C0ffee">
<TheExperience />
</TresCanvas>
</template>
3 changes: 3 additions & 0 deletions playground/src/router/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { basicRoutes } from './basic'
import { advancedRoutes } from './advanced'
import { miscRoutes } from './misc'
import { issuesRoutes } from './issues'
import { loaderRoutes } from './loaders'

const allRoutes = [
...basicRoutes,
Expand All @@ -14,6 +15,7 @@ const allRoutes = [
...modelsRoutes,
...miscRoutes,
...issuesRoutes,
...loaderRoutes,
]

export {
Expand All @@ -25,4 +27,5 @@ export {
miscRoutes,
issuesRoutes,
allRoutes,
loaderRoutes,
}
12 changes: 12 additions & 0 deletions playground/src/router/routes/loaders.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const loaderRoutes = [
{
path: '/loaders/gltf',
name: 'GLTF Loader',
component: () => import('../../pages/loaders/gltf-loader/index.vue'),
},
{
path: '/loaders/fbx',
name: 'FBX Loader',
component: () => import('../../pages/loaders/fbx-loader/index.vue'),
},
]
42 changes: 21 additions & 21 deletions src/composables/useLoader/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import type { Loader, LoadingManager, Object3D } from 'three'
import { isArray } from '@alvarosabu/utils'
import type { Loader, Object3D } from 'three'
import { useLogger } from '../useLogger'
import type { TresObject } from '../../types'
import { useLogger } from '..'

export interface TresLoader<T> extends Loader {
load: (
url: string,
onLoad?: (result: T) => void,
onLoad: (result: T) => void,
onProgress?: (event: ProgressEvent) => void,
onError?: (event: ErrorEvent) => void,
) => unknown
onError?: (event: ErrorEvent) => void
) => void
loadAsync: (url: string, onProgress?: (event: ProgressEvent) => void) => Promise<T>
}

export type LoaderProto<T> = new (...args: any) => TresLoader<T extends unknown ? any : T>
export type LoaderProto<T> = new (manager?: LoadingManager) => TresLoader<T>

export type LoaderReturnType<T, L extends LoaderProto<T>> = T extends unknown
? Awaited<ReturnType<InstanceType<L>['loadAsync']>>
: T
Expand Down Expand Up @@ -55,28 +57,27 @@ export type Extensions<T extends { prototype: LoaderProto<any> }> = (loader: T['
* ```
*
* @export
* @template T
* @template U
* @param {T} Loader
* @param {U} url
* @param {Extensions<T>} [extensions]
* @template LoaderProto<T>
* @template string | string[],
* @param {LoaderProto<T>} Loader
* @param {string | string[],} url
* @param {Extensions<TresLoader<T>>} [extensions]
* @param {(event: ProgressEvent<EventTarget>) => void} [onProgress]
* @param {(proto: TresLoader<T>) => void} [cb]
* @return {*}
*/
export async function useLoader<T extends LoaderProto<T>, U extends string | string[]>(
Loader: T,
url: U,
extensions?: Extensions<T>,
export async function useLoader<T>(
Loader: LoaderProto<T>,
url: string | string[],
extensions?: (loader: TresLoader<T>) => void,
onProgress?: (event: ProgressEvent<EventTarget>) => void,
cb?: (proto: TresLoader<T>) => void,
) {
): Promise<T | T[]> {
const { logError } = useLogger()
const proto = new Loader()
if (cb) {
cb(proto)
}

if (extensions) {
extensions(proto)
}
Expand All @@ -88,7 +89,8 @@ export async function useLoader<T extends LoaderProto<T>, U extends string | str
new Promise((resolve, reject) => {
proto.load(
path,
(data) => {
(result: T) => {
const data = result as unknown as TresObject
if (data.scene) {
Object.assign(data, trasverseObjects(data.scene))
}
Expand All @@ -100,7 +102,5 @@ export async function useLoader<T extends LoaderProto<T>, U extends string | str
}),
)

return (isArray(url) ? await Promise.all(results) : await results[0]) as U extends any[]
? LoaderReturnType<T, T>[]
: LoaderReturnType<T, T>
return (isArray(url) ? await Promise.all(results) : await results[0]) as T | T[]
}

0 comments on commit b51d679

Please sign in to comment.