feat(export): texture references — stamp sources at load, reference mode in GLB export (#532)
Bake v2 exporter side: library/preset, legacy scene-material, and item-GLB textures get a validated pascalTextureRef stamped at load (origin-checked, env-derived). Material-ish sources resolve to 'library-material' (storage bucket) or 'app-material' (static catalog on the assets CDN) by URL shape. exportSceneToGlb grows a textures: 'embed' | 'reference' option — reference mode swaps stamped textures for 1x1 canvas-backed placeholders (skipping the WebGPU blit) and writes the ref to both texture and image extras via a GLTFExporter writeTexture plugin. Download GLB stays embed; the bake page passes reference. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
cb6fadbc28
commit
0fba611a05
@@ -29,6 +29,7 @@ import {
|
||||
type RenderShading,
|
||||
resolveCdnUrl,
|
||||
resolveMaterialRef,
|
||||
stampPascalTextureRef,
|
||||
useItemLightPool,
|
||||
useNodeEvents,
|
||||
useViewer,
|
||||
@@ -38,7 +39,7 @@ import { Clone } from '@react-three/drei/core/Clone'
|
||||
import { useFrame, useLoader, useThree } from '@react-three/fiber'
|
||||
import { Suspense, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { AnimationAction, Group, Material, Mesh, Object3D } from 'three'
|
||||
import { MathUtils } from 'three'
|
||||
import { MathUtils, Texture } from 'three'
|
||||
import { MeshoptDecoder } from 'three/examples/jsm/libs/meshopt_decoder.module.js'
|
||||
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
|
||||
import type { GLTF } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
||||
@@ -225,11 +226,85 @@ type LoadedItemGltf = GLTF & {
|
||||
nodes: Record<string, Object3D>
|
||||
}
|
||||
|
||||
const ITEM_TEXTURE_SLOTS = [
|
||||
'map',
|
||||
'normalMap',
|
||||
'roughnessMap',
|
||||
'metalnessMap',
|
||||
'emissiveMap',
|
||||
'aoMap',
|
||||
'alphaMap',
|
||||
'lightMap',
|
||||
'bumpMap',
|
||||
'displacementMap',
|
||||
'clearcoatMap',
|
||||
'clearcoatNormalMap',
|
||||
'clearcoatRoughnessMap',
|
||||
'iridescenceMap',
|
||||
'iridescenceThicknessMap',
|
||||
'transmissionMap',
|
||||
'thicknessMap',
|
||||
'specularIntensityMap',
|
||||
'specularColorMap',
|
||||
'sheenRoughnessMap',
|
||||
'sheenColorMap',
|
||||
'anisotropyMap',
|
||||
] as const
|
||||
|
||||
function getItemTextureImageIndex(gltf: LoadedItemGltf, texture: Texture): number | null {
|
||||
const association = gltf.parser?.associations.get(texture)
|
||||
const textureIndex = association?.textures
|
||||
if (!Number.isInteger(textureIndex)) return null
|
||||
|
||||
const textureDef = gltf.parser.json.textures?.[textureIndex as number]
|
||||
const imageIndex =
|
||||
textureDef?.extensions?.KHR_texture_basisu?.source ??
|
||||
textureDef?.extensions?.EXT_texture_webp?.source ??
|
||||
textureDef?.extensions?.EXT_texture_avif?.source ??
|
||||
textureDef?.source
|
||||
return Number.isInteger(imageIndex) && imageIndex >= 0 ? imageIndex : null
|
||||
}
|
||||
|
||||
function stampItemTextureReferences(gltf: LoadedItemGltf, src: string) {
|
||||
if (!gltf.parser?.associations) return
|
||||
|
||||
const stamped = new Set<Texture>()
|
||||
gltf.scene.traverse((object) => {
|
||||
const mesh = object as Mesh
|
||||
if (!mesh.isMesh) return
|
||||
const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material]
|
||||
for (const material of materials) {
|
||||
const textureMaterial = material as Material & Record<string, unknown>
|
||||
for (const slot of ITEM_TEXTURE_SLOTS) {
|
||||
const texture = textureMaterial[slot]
|
||||
if (!(texture instanceof Texture)) continue
|
||||
if (stamped.has(texture)) continue
|
||||
const imageIndex = getItemTextureImageIndex(gltf, texture)
|
||||
if (imageIndex === null) continue
|
||||
if (
|
||||
stampPascalTextureRef(texture, {
|
||||
kind: 'item-glb',
|
||||
src,
|
||||
slot,
|
||||
imageIndex,
|
||||
})
|
||||
) {
|
||||
stamped.add(texture)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const useItemGltf = (url: string): LoadedItemGltf => {
|
||||
const renderer = useThree((state) => state.gl)
|
||||
return useLoader(ItemGLTFLoader, url, (loader) =>
|
||||
const gltf = useLoader(ItemGLTFLoader, url, (loader) =>
|
||||
configureItemModelLoader(loader, renderer),
|
||||
) as LoadedItemGltf
|
||||
return useMemo(() => {
|
||||
stampItemTextureReferences(gltf, url)
|
||||
return gltf
|
||||
}, [gltf, url])
|
||||
}
|
||||
|
||||
type DeferredUnavailableCleanup = {
|
||||
|
||||
Reference in New Issue
Block a user