feat(paint-slots): read pascal_material glTF extras as curated slot default
Insert the plan's middle resolution step for item slots: a curated default baked into the GLB as a per-material 'pascal_material' extra (three's GLTFLoader copies material extras to userData). Resolution per slot is now: node.slots[slotId] override -> pascal_material curated default -> authored GLB material. Bare ids resolve as library:<id>; unresolved refs fall through. Runtime reads only the GLB (no DB). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e0179c480a
commit
d52cbe6757
@@ -8,6 +8,9 @@ import {
|
|||||||
type Interactive,
|
type Interactive,
|
||||||
type ItemNode,
|
type ItemNode,
|
||||||
isSlotMaterialName,
|
isSlotMaterialName,
|
||||||
|
LIBRARY_MATERIAL_REF_PREFIX,
|
||||||
|
SCENE_MATERIAL_REF_PREFIX,
|
||||||
|
toLibraryMaterialRef,
|
||||||
type LightEffect,
|
type LightEffect,
|
||||||
useInteractive,
|
useInteractive,
|
||||||
useLiveNodeOverrides,
|
useLiveNodeOverrides,
|
||||||
@@ -50,12 +53,14 @@ type MutableMaterial = Material & {
|
|||||||
type CapturedSingleItemMaterialData = {
|
type CapturedSingleItemMaterialData = {
|
||||||
captured: true
|
captured: true
|
||||||
authoredMaterials: Material
|
authoredMaterials: Material
|
||||||
|
curatedRefs: string | undefined
|
||||||
slotIds: string | null
|
slotIds: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
type CapturedMultiItemMaterialData = {
|
type CapturedMultiItemMaterialData = {
|
||||||
captured: true
|
captured: true
|
||||||
authoredMaterials: Material[]
|
authoredMaterials: Material[]
|
||||||
|
curatedRefs: (string | undefined)[]
|
||||||
slotIds: (string | null)[]
|
slotIds: (string | null)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,6 +76,15 @@ type SceneMaterials = ReturnType<typeof useScene.getState>['materials']
|
|||||||
const getAuthoredSlotId = (material: Material): string | null =>
|
const getAuthoredSlotId = (material: Material): string | null =>
|
||||||
isSlotMaterialName(material.name) ? deriveSlotId(material.name) : null
|
isSlotMaterialName(material.name) ? deriveSlotId(material.name) : null
|
||||||
|
|
||||||
|
function curatedRefFromMaterial(material: Material): string | undefined {
|
||||||
|
const raw = (material.userData as { pascal_material?: unknown }).pascal_material
|
||||||
|
if (typeof raw !== 'string' || raw.length === 0) return undefined
|
||||||
|
if (raw.startsWith(LIBRARY_MATERIAL_REF_PREFIX) || raw.startsWith(SCENE_MATERIAL_REF_PREFIX)) {
|
||||||
|
return raw
|
||||||
|
}
|
||||||
|
return toLibraryMaterialRef(raw)
|
||||||
|
}
|
||||||
|
|
||||||
const captureItemMeshMaterials = (mesh: Mesh): CapturedItemMaterialData => {
|
const captureItemMeshMaterials = (mesh: Mesh): CapturedItemMaterialData => {
|
||||||
const userData = mesh.userData as ItemMeshUserData
|
const userData = mesh.userData as ItemMeshUserData
|
||||||
const captured = userData.pascalItemMaterialCapture
|
const captured = userData.pascalItemMaterialCapture
|
||||||
@@ -82,9 +96,11 @@ const captureItemMeshMaterials = (mesh: Mesh): CapturedItemMaterialData => {
|
|||||||
if (Array.isArray(mesh.material)) {
|
if (Array.isArray(mesh.material)) {
|
||||||
const authoredMaterials = mesh.material.slice()
|
const authoredMaterials = mesh.material.slice()
|
||||||
const slotIds = authoredMaterials.map(getAuthoredSlotId)
|
const slotIds = authoredMaterials.map(getAuthoredSlotId)
|
||||||
|
const curatedRefs = authoredMaterials.map(curatedRefFromMaterial)
|
||||||
const next: CapturedItemMaterialData = {
|
const next: CapturedItemMaterialData = {
|
||||||
captured: true,
|
captured: true,
|
||||||
authoredMaterials,
|
authoredMaterials,
|
||||||
|
curatedRefs,
|
||||||
slotIds,
|
slotIds,
|
||||||
}
|
}
|
||||||
userData.pascalItemMaterialCapture = next
|
userData.pascalItemMaterialCapture = next
|
||||||
@@ -93,9 +109,11 @@ const captureItemMeshMaterials = (mesh: Mesh): CapturedItemMaterialData => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const slotId = getAuthoredSlotId(mesh.material)
|
const slotId = getAuthoredSlotId(mesh.material)
|
||||||
|
const curatedRef = curatedRefFromMaterial(mesh.material)
|
||||||
const next: CapturedItemMaterialData = {
|
const next: CapturedItemMaterialData = {
|
||||||
captured: true,
|
captured: true,
|
||||||
authoredMaterials: mesh.material,
|
authoredMaterials: mesh.material,
|
||||||
|
curatedRefs: curatedRef,
|
||||||
slotIds: slotId,
|
slotIds: slotId,
|
||||||
}
|
}
|
||||||
userData.pascalItemMaterialCapture = next
|
userData.pascalItemMaterialCapture = next
|
||||||
@@ -134,6 +152,7 @@ const clampGeometryGroups = (mesh: Mesh, matCount: number): void => {
|
|||||||
const resolveItemMaterial = (
|
const resolveItemMaterial = (
|
||||||
authoredMaterial: Material,
|
authoredMaterial: Material,
|
||||||
slotId: string | null,
|
slotId: string | null,
|
||||||
|
curatedRef: string | undefined,
|
||||||
{
|
{
|
||||||
colorPreset,
|
colorPreset,
|
||||||
isAuthored,
|
isAuthored,
|
||||||
@@ -155,6 +174,8 @@ const resolveItemMaterial = (
|
|||||||
if (slotId != null) {
|
if (slotId != null) {
|
||||||
const override = resolveMaterialRef(nodeSlots?.[slotId], sceneMaterials, shading)
|
const override = resolveMaterialRef(nodeSlots?.[slotId], sceneMaterials, shading)
|
||||||
if (override) return override
|
if (override) return override
|
||||||
|
const curated = resolveMaterialRef(curatedRef, sceneMaterials, shading)
|
||||||
|
if (curated) return curated
|
||||||
return authoredMaterial
|
return authoredMaterial
|
||||||
}
|
}
|
||||||
if (isAuthored) return authoredMaterial
|
if (isAuthored) return authoredMaterial
|
||||||
@@ -342,7 +363,12 @@ const ModelRenderer = ({ node }: { node: ItemNode }) => {
|
|||||||
|
|
||||||
if (isCapturedMaterialArray(captured)) {
|
if (isCapturedMaterialArray(captured)) {
|
||||||
const nextMaterials = captured.authoredMaterials.map((authoredMaterial, index) =>
|
const nextMaterials = captured.authoredMaterials.map((authoredMaterial, index) =>
|
||||||
resolveItemMaterial(authoredMaterial, captured.slotIds[index] ?? null, materialOptions),
|
resolveItemMaterial(
|
||||||
|
authoredMaterial,
|
||||||
|
captured.slotIds[index] ?? null,
|
||||||
|
captured.curatedRefs[index],
|
||||||
|
materialOptions,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
mesh.material = nextMaterials
|
mesh.material = nextMaterials
|
||||||
hasGlass = nextMaterials.some(isGlassMaterial)
|
hasGlass = nextMaterials.some(isGlassMaterial)
|
||||||
@@ -351,6 +377,7 @@ const ModelRenderer = ({ node }: { node: ItemNode }) => {
|
|||||||
const nextMaterial = resolveItemMaterial(
|
const nextMaterial = resolveItemMaterial(
|
||||||
captured.authoredMaterials,
|
captured.authoredMaterials,
|
||||||
captured.slotIds,
|
captured.slotIds,
|
||||||
|
captured.curatedRefs,
|
||||||
materialOptions,
|
materialOptions,
|
||||||
)
|
)
|
||||||
mesh.material = nextMaterial
|
mesh.material = nextMaterial
|
||||||
|
|||||||
Reference in New Issue
Block a user