Browse Source

feat(usePBRTexture): add component for PBR texture loading

- Implement `UsePBRTexture` component for declarative PBR texture loading
- Add new component to `src/composables/usePBRTexture/component.vue`
- Update documentation with component usage example
- Enhance playground with new PBR texture component example
- Expose component in composables index for easy importing
- Support scoped slot for flexible texture rendering
alvarosabu 6 tháng trước cách đây
mục cha
commit
8258f809f2

+ 76 - 27
docs/composables/use-pbr-texture.md

@@ -23,11 +23,16 @@ The composable can be used in two ways: with or without `await`.
 import { usePBRTexture } from '@tresjs/core'
 
 // Wait for all textures to fully load
-const { data: textures } = await usePBRTexture({
-  map: 'textures/wood/albedo.jpg',
-  normalMap: 'textures/wood/normal.jpg',
-  roughnessMap: 'textures/wood/roughness.jpg'
-})
+const { data: textures } = await usePBRTexture(
+  // Texture paths
+  {
+    map: 'textures/wood/albedo.jpg',
+    normalMap: 'textures/wood/normal.jpg',
+    roughnessMap: 'textures/wood/roughness.jpg'
+  },
+  // Optional loading manager
+  loadingManager
+)
 
 // All textures are fully loaded and ready to use
 console.log(textures.value.map) // Texture object with loaded image
@@ -40,11 +45,14 @@ import { usePBRTexture } from '@tresjs/core'
 import { watch } from 'vue'
 
 // Textures will start loading immediately
-const { data: textures, isLoading } = usePBRTexture({
-  map: 'textures/wood/albedo.jpg',
-  normalMap: 'textures/wood/normal.jpg',
-  roughnessMap: 'textures/wood/roughness.jpg'
-})
+const { data: textures, isLoading } = usePBRTexture(
+  // Texture paths
+  {
+    map: 'textures/wood/albedo.jpg',
+    normalMap: 'textures/wood/normal.jpg',
+    roughnessMap: 'textures/wood/roughness.jpg'
+  }
+)
 
 // Watch for loading completion
 watch(isLoading, (loading) => {
@@ -62,11 +70,14 @@ The textures can be directly bound to a TresMeshStandardMaterial. When using wit
 <script setup>
 import { usePBRTexture } from '@tresjs/core'
 
-const { data: textures, isLoading } = usePBRTexture({
-  map: 'textures/wood/albedo.jpg',
-  normalMap: 'textures/wood/normal.jpg',
-  roughnessMap: 'textures/wood/roughness.jpg'
-})
+const { data: textures, isLoading } = usePBRTexture(
+  // Texture paths
+  {
+    map: 'textures/wood/albedo.jpg',
+    normalMap: 'textures/wood/normal.jpg',
+    roughnessMap: 'textures/wood/roughness.jpg'
+  }
+)
 </script>
 
 <template>
@@ -83,11 +94,14 @@ const { data: textures, isLoading } = usePBRTexture({
 
 ```vue
 <script setup>
-const { data: textures, isLoading, error } = usePBRTexture({
-  map: 'textures/metal/albedo.jpg',
-  normalMap: 'textures/metal/normal.jpg',
-  roughnessMap: 'textures/metal/roughness.jpg'
-})
+const { data: textures, isLoading, error } = usePBRTexture(
+  // Texture paths
+  {
+    map: 'textures/metal/albedo.jpg',
+    normalMap: 'textures/metal/normal.jpg',
+    roughnessMap: 'textures/metal/roughness.jpg'
+  }
+)
 </script>
 
 <template>
@@ -118,11 +132,14 @@ When using with Suspense, you must use the await pattern:
 ```vue
 <!-- PBRMaterial.vue -->
 <script setup>
-const { data: textures } = await usePBRTexture({
-  map: 'textures/metal/albedo.jpg',
-  normalMap: 'textures/metal/normal.jpg',
-  // ...other textures
-})
+const { data: textures } = await usePBRTexture(
+  // Texture paths
+  {
+    map: 'textures/metal/albedo.jpg',
+    normalMap: 'textures/metal/normal.jpg',
+    roughnessMap: 'textures/metal/roughness.jpg'
+  }
+)
 </script>
 
 <template>
@@ -134,7 +151,9 @@ const { data: textures } = await usePBRTexture({
 
 ### Options
 
-The `usePBRTexture` composable accepts an options object with the following properties:
+The `usePBRTexture` composable accepts the following parameters:
+
+#### Paths Object
 
 | Property | Type | Description |
 | --- | --- | --- |
@@ -146,7 +165,11 @@ The `usePBRTexture` composable accepts an options object with the following prop
 | `displacementMap` | `string` | Path to the height/displacement map texture |
 | `emissiveMap` | `string` | Path to the emissive map texture |
 
-All properties are optional, allowing you to load only the textures you need.
+#### Loading Manager
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| `manager` | `LoadingManager` | Optional THREE.js LoadingManager for tracking load progress |
 
 ### Returns
 
@@ -182,3 +205,29 @@ The `data` object contains the following properties:
 - Compatible with Vue's Suspense feature for loading states
 - When using without await, textures will start loading immediately but might not be fully loaded
 - Always check `isLoading` when using without await to ensure textures are ready
+
+## Component Usage
+
+```vue
+<script setup>
+const paths = {
+  map: 'textures/wood/albedo.jpg',
+  normalMap: 'textures/wood/normal.jpg',
+  roughnessMap: 'textures/wood/roughness.jpg'
+}
+</script>
+
+<template>
+  <PBRTexture
+    :paths="paths"
+    @loaded="onTexturesLoaded"
+    @error="onError"
+  />
+</template>
+
+### Props
+
+| Name | Type | Description |
+| --- | --- | --- |
+| `paths` | `PBRTexturePaths` | Object containing paths to PBR textures |
+| `manager` | `LoadingManager` | Optional THREE.js LoadingManager |

+ 26 - 0
playground/vue/src/pages/composables/usePBRTexture/ObjectUsePBRTextureComponent.vue

@@ -0,0 +1,26 @@
+<script setup lang="ts">
+import { UsePBRTexture } from '@tresjs/core'
+import { Html } from '@tresjs/cientos'
+
+const pbr = {
+  map: 'https://raw.githubusercontent.com/Tresjs/assets/main/textures/black-rock/Rock035_2K_Color.jpg',
+  normalMap: 'https://raw.githubusercontent.com/Tresjs/assets/main/textures/black-rock/Rock035_2K_NormalDX.jpg',
+  roughnessMap: 'https://raw.githubusercontent.com/Tresjs/assets/main/textures/black-rock/Rock035_2K_Roughness.jpg',
+  aoMap: 'https://raw.githubusercontent.com/Tresjs/assets/main/textures/black-rock/Rock035_2K_AmbientOcclusion.jpg',
+  displacementMap: 'https://raw.githubusercontent.com/Tresjs/assets/main/textures/black-rock/Rock035_2K_Displacement.jpg',
+}
+</script>
+
+<template>
+  <UsePBRTexture v-slot="{ data: texture }" :paths="pbr">
+    <TresMesh :position="[-3, 1, 0]">
+      <Html transform position-y="1.5">
+        <span class="text-xs bg-white p-2 rounded-md">
+          Use pbr texture component
+        </span>
+      </Html>
+      <TresSphereGeometry :args="[1, 32, 32]" />
+      <TresMeshStandardMaterial v-bind="texture" />
+    </TresMesh>
+  </UsePBRTexture>
+</template>

+ 4 - 0
playground/vue/src/pages/composables/usePBRTexture/index.vue

@@ -5,6 +5,7 @@ import { BasicShadowMap, NoToneMapping, SRGBColorSpace } from 'three'
 import { OrbitControls } from '@tresjs/cientos'
 import ObjectSyncPBRTexture from './ObjectSyncPBRTexture.vue'
 import ObjectAsyncPBRTexture from './ObjectAsyncPBRTexture.vue'
+import ObjectUsePBRTextureComponent from './ObjectUsePBRTextureComponent.vue'
 
 const gl = {
   clearColor: '#82DBC5',
@@ -26,5 +27,8 @@ const gl = {
     <Suspense>
       <ObjectAsyncPBRTexture />
     </Suspense>
+    <Suspense>
+      <ObjectUsePBRTextureComponent />
+    </Suspense>
   </TresCanvas>
 </template>

+ 3 - 1
src/composables/index.ts

@@ -1,5 +1,6 @@
 import UseLoader from './useLoader/component.vue'
 import UseTexture from './useTexture/component.vue'
+import UsePBRTexture from './usePBRTexture/component.vue'
 
 export * from './useCamera/'
 export * from './useLoader'
@@ -14,4 +15,5 @@ export * from './useTexture'
 export * from './useTresContextProvider'
 export * from './useTresEventManager'
 export { onTresReady } from './useTresReady'
-export { UseLoader, UseTexture }
+
+export { UseLoader, UsePBRTexture, UseTexture }

+ 36 - 0
src/composables/usePBRTexture/component.vue

@@ -0,0 +1,36 @@
+<script setup lang="ts">
+import { reactive } from 'vue'
+import type { LoadingManager } from 'three'
+import { type PBRTexturePaths, type PBRTextureResult, usePBRTexture } from '.'
+
+const props = defineProps<{
+  /**
+   * PBR texture options containing paths to textures
+   */
+  paths: PBRTexturePaths
+  /**
+   * Optional THREE.js LoadingManager
+   */
+  manager?: LoadingManager
+}>()
+
+const emit = defineEmits<{
+  (e: 'loaded'): void
+  (e: 'error', error: Error): void
+}>()
+
+const textureData = reactive<PBRTextureResult>(usePBRTexture(props.paths, props.manager))
+
+// Handle loading state
+textureData.promise
+  .then(() => emit('loaded'))
+  .catch(err => emit('error', err))
+</script>
+
+<template>
+  <slot
+    :data="textureData.data"
+    :is-loading="textureData.isLoading"
+    :error="textureData.error"
+  ></slot>
+</template>

+ 10 - 6
src/composables/usePBRTexture/index.ts

@@ -1,9 +1,9 @@
 import type { Ref, ShallowRef } from 'vue'
 import { shallowRef } from 'vue'
-import type { Texture } from 'three'
+import type { LoadingManager, Texture } from 'three'
 import { useTexture } from '../useTexture'
 
-export interface PBRTextureOptions {
+export interface PBRTexturePaths {
   /**
    * Base color or albedo texture path
    */
@@ -80,9 +80,13 @@ export interface PBRTextureResult {
  * <TresMeshStandardMaterial v-bind="textures.value" />
  * ```
  *
- * @param options - Object containing paths to PBR textures
+ * @param paths - Object containing paths to PBR textures
+ * @param manager - Optional THREE.js LoadingManager for tracking load progress
  */
-export function usePBRTexture(options: PBRTextureOptions): PBRTextureResult & Promise<PBRTextureResult> {
+export function usePBRTexture(
+  paths: PBRTexturePaths,
+  manager?: LoadingManager,
+): PBRTextureResult & Promise<PBRTextureResult> {
   const data: ShallowRef<PBRTextures> = shallowRef({
     map: null,
     normalMap: null,
@@ -96,12 +100,12 @@ export function usePBRTexture(options: PBRTextureOptions): PBRTextureResult & Pr
   const error = shallowRef<Error | null>(null)
 
   // Filter out undefined paths and create a map of texture types
-  const textureEntries = Object.entries(options).filter(([_, path]) => path !== undefined)
+  const textureEntries = Object.entries(paths).filter(([_, path]) => path !== undefined)
 
   // Load all textures concurrently using useTexture
   const loadPromises = textureEntries.map(async ([type, path]) => {
     try {
-      const { data: texture } = useTexture(path)
+      const { data: texture } = useTexture(path, manager)
       // Update the textures ref when each texture loads
       data.value[type as keyof PBRTextures] = texture.value
     }

+ 4 - 8
src/composables/useTexture/component.vue

@@ -3,7 +3,7 @@ import { reactive } from 'vue'
 import type { LoadingManager, Texture } from 'three'
 import { useTexture, type UseTextureReturn } from './index'
 
-interface Props {
+const props = defineProps<{
   /**
    * Path or array of paths to texture(s)
    */
@@ -12,15 +12,11 @@ interface Props {
    * Optional THREE.js LoadingManager
    */
   manager?: LoadingManager
-}
-
-interface Emits {
+}>()
+const emit = defineEmits<{
   (e: 'loaded'): void
   (e: 'error', error: Error): void
-}
-
-const props = defineProps<Props>()
-const emit = defineEmits<Emits>()
+}>()
 
 // Type guard to handle the union type
 const textureData = Array.isArray(props.path)