diff --git a/src/document/migrate-scene-document.ts b/src/document/migrate-scene-document.ts index 3fa6832e..bb9d0d97 100644 --- a/src/document/migrate-scene-document.ts +++ b/src/document/migrate-scene-document.ts @@ -1,5 +1,8 @@ import { + MATERIAL_TEXTURE_SLOTS, + createEmptyCustomMaterialTextureRefs, createStarterMaterialRegistry, + type CustomMaterialTextureRefs, type MaterialDef } from "../materials/starter-material-library"; import type { Vec3 } from "../core/vector"; @@ -263,6 +266,7 @@ import { SPLINE_PATH_SCENE_DOCUMENT_VERSION, WHITEBOX_BOX_LIGHT_VOLUME_SCENE_DOCUMENT_VERSION, CELESTIAL_BODY_OVERLAY_SCENE_DOCUMENT_VERSION, + CUSTOM_PBR_MATERIALS_SCENE_DOCUMENT_VERSION, SCENE_DOCUMENT_VERSION, STATIC_SIMPLE_MODEL_COLLIDERS_SCENE_DOCUMENT_VERSION, TRIGGER_ACTION_TARGET_FOUNDATION_SCENE_DOCUMENT_VERSION, diff --git a/src/document/scene-document.ts b/src/document/scene-document.ts index 2c511067..5b6d4a2a 100644 --- a/src/document/scene-document.ts +++ b/src/document/scene-document.ts @@ -36,7 +36,8 @@ import { type FoliagePrototypeRegistry } from "../foliage/foliage"; -export const SCENE_DOCUMENT_VERSION = 109 as const; +export const SCENE_DOCUMENT_VERSION = 110 as const; +export const CUSTOM_PBR_MATERIALS_SCENE_DOCUMENT_VERSION = 110 as const; export const SPLINE_CORRIDOR_JUNCTION_SHAPE_SCENE_DOCUMENT_VERSION = 109 as const; export const SPLINE_CORRIDOR_JUNCTION_EDGE_SCENE_DOCUMENT_VERSION = 108 as const; export const SPLINE_CORRIDOR_JUNCTIONS_SCENE_DOCUMENT_VERSION = 107 as const; diff --git a/src/materials/starter-material-library.ts b/src/materials/starter-material-library.ts index 58521072..a4bf13ef 100644 --- a/src/materials/starter-material-library.ts +++ b/src/materials/starter-material-library.ts @@ -564,35 +564,46 @@ function cloneMaterialSizeCm(sizeCm: MaterialSizeCm): MaterialSizeCm { }; } -export const STARTER_MATERIAL_LIBRARY: readonly MaterialDef[] = +export const STARTER_MATERIAL_LIBRARY: readonly StarterMaterialDef[] = STARTER_MATERIAL_CATALOG.map((entry) => ({ ...entry, + kind: "starter", sizeCm: cloneMaterialSizeCm(entry.sizeCm), tags: createMaterialTags(entry) })); -export function getStarterMaterialAssetDirectory(material: MaterialDef): string { +export function getStarterMaterialAssetDirectory( + material: StarterMaterialDef +): string { return `${STARTER_MATERIAL_ASSET_ROOT}/${material.assetFolder}`; } -export function getStarterMaterialPreviewUrl(material: MaterialDef): string { +export function getStarterMaterialPreviewUrl( + material: StarterMaterialDef +): string { return `${getStarterMaterialAssetDirectory(material)}/${material.previewImageName}`; } -export function getStarterMaterialBaseColorUrl(material: MaterialDef): string { +export function getStarterMaterialBaseColorUrl( + material: StarterMaterialDef +): string { return `${getStarterMaterialAssetDirectory(material)}/basecolor.webp`; } -export function getStarterMaterialNormalUrl(material: MaterialDef): string { +export function getStarterMaterialNormalUrl( + material: StarterMaterialDef +): string { return `${getStarterMaterialAssetDirectory(material)}/normal.webp`; } -export function getStarterMaterialRoughnessUrl(material: MaterialDef): string { +export function getStarterMaterialRoughnessUrl( + material: StarterMaterialDef +): string { return `${getStarterMaterialAssetDirectory(material)}/roughness.webp`; } export function getStarterMaterialMetallicUrl( - material: MaterialDef + material: StarterMaterialDef ): string | null { return material.workflow === "metallic-roughness" ? `${getStarterMaterialAssetDirectory(material)}/metallic.webp` @@ -600,14 +611,14 @@ export function getStarterMaterialMetallicUrl( } export function getStarterMaterialSpecularUrl( - material: MaterialDef + material: StarterMaterialDef ): string | null { return material.workflow === "specular-roughness" ? `${getStarterMaterialAssetDirectory(material)}/specular.webp` : null; } -export function getStarterMaterialTileSizeMeters(material: MaterialDef): { +export function getStarterMaterialTileSizeMeters(material: StarterMaterialDef): { x: number; y: number; } { @@ -617,7 +628,7 @@ export function getStarterMaterialTileSizeMeters(material: MaterialDef): { }; } -export function getStarterMaterialTextureRepeat(material: MaterialDef): { +export function getStarterMaterialTextureRepeat(material: StarterMaterialDef): { x: number; y: number; } { @@ -630,10 +641,18 @@ export function getStarterMaterialTextureRepeat(material: MaterialDef): { } export function cloneMaterialDef(material: MaterialDef): MaterialDef { + if (material.kind === "starter") { + return { + ...material, + sizeCm: cloneMaterialSizeCm(material.sizeCm), + tags: [...material.tags] + }; + } + return { ...material, - sizeCm: cloneMaterialSizeCm(material.sizeCm), - tags: [...material.tags] + tags: [...material.tags], + textures: cloneCustomMaterialTextureRefs(material.textures) }; } @@ -656,3 +675,68 @@ export function createStarterMaterialRegistry(): Record { ]) ); } + +export function createEmptyCustomMaterialTextureRefs(): CustomMaterialTextureRefs { + return { + albedo: null, + normal: null, + roughness: null, + metallic: null + }; +} + +export function cloneCustomMaterialTextureRefs( + textures: CustomMaterialTextureRefs +): CustomMaterialTextureRefs { + return { + albedo: + textures.albedo === null ? null : { assetId: textures.albedo.assetId }, + normal: + textures.normal === null ? null : { assetId: textures.normal.assetId }, + roughness: + textures.roughness === null + ? null + : { assetId: textures.roughness.assetId }, + metallic: + textures.metallic === null + ? null + : { assetId: textures.metallic.assetId } + }; +} + +export function createCustomMaterialDef( + overrides: Partial< + Pick< + CustomMaterialDef, + | "id" + | "name" + | "swatchColorHex" + | "tags" + | "albedoColorHex" + | "opacity" + | "roughness" + | "metallic" + | "normalStrength" + | "textures" + > + > = {} +): CustomMaterialDef { + const albedoColorHex = overrides.albedoColorHex ?? "#ffffff"; + + return { + id: overrides.id ?? createOpaqueId("material"), + kind: "custom", + name: overrides.name ?? "Custom Material", + swatchColorHex: overrides.swatchColorHex ?? albedoColorHex, + tags: overrides.tags ?? ["custom"], + albedoColorHex, + opacity: overrides.opacity ?? 1, + roughness: overrides.roughness ?? 0.8, + metallic: overrides.metallic ?? 0, + normalStrength: overrides.normalStrength ?? 1, + textures: + overrides.textures === undefined + ? createEmptyCustomMaterialTextureRefs() + : cloneCustomMaterialTextureRefs(overrides.textures) + }; +} diff --git a/src/materials/starter-material-textures.ts b/src/materials/starter-material-textures.ts index afd6dea6..742cb634 100644 --- a/src/materials/starter-material-textures.ts +++ b/src/materials/starter-material-textures.ts @@ -12,7 +12,7 @@ import { getStarterMaterialRoughnessUrl, getStarterMaterialSpecularUrl, getStarterMaterialTextureRepeat, - type MaterialDef + type StarterMaterialDef } from "./starter-material-library"; export interface StarterMaterialTextureSet { @@ -23,8 +23,11 @@ export interface StarterMaterialTextureSet { specular: Texture | null; } -export function createStarterMaterialSignature(material: MaterialDef): string { +export function createStarterMaterialSignature( + material: StarterMaterialDef +): string { return [ + material.kind, material.assetFolder, material.workflow, material.previewImageName, @@ -72,7 +75,7 @@ function createRepeatableTextureImage( function configureTexture( texture: Texture, - material: MaterialDef, + material: StarterMaterialDef, options: { colorSpace?: typeof SRGBColorSpace | null } ) { const repeat = getStarterMaterialTextureRepeat(material); @@ -85,7 +88,7 @@ function configureTexture( function loadMaterialTexture( loader: TextureLoader, url: string, - material: MaterialDef, + material: StarterMaterialDef, options: { colorSpace?: typeof SRGBColorSpace | null } ): Texture { const texture = loader.load(url, (loadedTexture) => { @@ -102,7 +105,7 @@ function loadMaterialTexture( } export function createStarterMaterialTextureSet( - material: MaterialDef, + material: StarterMaterialDef, loader: TextureLoader = new TextureLoader() ): StarterMaterialTextureSet { const metallicUrl = getStarterMaterialMetallicUrl(material);