import { z } from 'zod' import { AssetUrl } from './asset-url' export const MaterialPreset = z.enum([ 'white', 'brick', 'concrete', 'wood', 'glass', 'metal', 'plaster', 'tile', 'marble', 'custom', ]) export type MaterialPreset = z.infer export const MaterialProperties = z.object({ color: z.string().default('#ffffff'), roughness: z.number().min(0).max(1).default(0.5), metalness: z.number().min(0).max(1).default(0), opacity: z.number().min(0).max(1).default(1), transparent: z.boolean().default(false), side: z.enum(['front', 'back', 'double']).default('front'), }) export type MaterialProperties = z.infer export const MaterialSchema = z.object({ id: z.string().optional(), // Coerce unknown presets (legacy/AI-generated data) to 'custom' instead of throwing. preset: MaterialPreset.catch('custom').optional(), properties: MaterialProperties.optional(), texture: z .object({ url: AssetUrl, repeat: z.tuple([z.number(), z.number()]).optional(), scale: z.number().optional(), }) .optional(), }) export type MaterialSchema = z.infer export const MaterialTarget = z.enum([ 'wall', 'roof', 'roof-segment', 'stair', 'stair-segment', 'fence', 'column', 'slab', 'ceiling', 'door', 'window', 'shelf', 'chimney', 'skylight', 'dormer', 'box-vent', 'ridge-vent', 'turbine-vent', 'cupola', 'eyebrow-vent', 'gutter', ]) export type MaterialTarget = z.infer export const TextureWrapMode = z.enum(['Repeat', 'ClampToEdge', 'MirroredRepeat']) export type TextureWrapMode = z.infer export const MaterialMapsSchema = z.object({ albedoMap: AssetUrl.optional(), metalnessMap: AssetUrl.optional(), roughnessMap: AssetUrl.optional(), normalMap: AssetUrl.optional(), displacementMap: AssetUrl.optional(), aoMap: AssetUrl.optional(), emissiveMap: AssetUrl.optional(), bumpMap: AssetUrl.optional(), alphaMap: AssetUrl.optional(), lightMap: AssetUrl.optional(), }) export type MaterialMaps = z.infer export const MaterialMapPropertiesSchema = z.object({ color: z.string().default('#ffffff'), roughness: z.number().min(0).max(1).default(0.5), metalness: z.number().min(0).max(1).default(0), repeatX: z.number().default(1), repeatY: z.number().default(1), rotation: z.number().default(0), wrapS: TextureWrapMode.default('Repeat'), wrapT: TextureWrapMode.default('Repeat'), normalScaleX: z.number().default(1), normalScaleY: z.number().default(1), emissiveIntensity: z.number().default(1), displacementScale: z.number().default(0.02), transparent: z.boolean().default(false), flipY: z.boolean().default(true), bumpScale: z.number().default(1), emissiveColor: z.string().default('#000000'), aoMapIntensity: z.number().default(1), side: z.number().default(0), opacity: z.number().min(0).max(1).default(1), lightMapIntensity: z.number().default(1), }) export type MaterialMapProperties = z.infer export const MaterialPresetPayloadSchema = z.object({ maps: MaterialMapsSchema, mapProperties: MaterialMapPropertiesSchema, }) export type MaterialPresetPayload = z.infer export const DEFAULT_MATERIALS: Record = { white: { color: '#ffffff', roughness: 0.9, metalness: 0, opacity: 1, transparent: false, side: 'front', }, brick: { color: '#8b4513', roughness: 0.85, metalness: 0, opacity: 1, transparent: false, side: 'front', }, concrete: { color: '#808080', roughness: 0.8, metalness: 0, opacity: 1, transparent: false, side: 'front', }, wood: { color: '#deb887', roughness: 0.7, metalness: 0, opacity: 1, transparent: false, side: 'front', }, glass: { color: '#87ceeb', roughness: 0.1, metalness: 0.1, opacity: 0.3, transparent: true, side: 'double', }, metal: { color: '#c0c0c0', roughness: 0.3, metalness: 0.9, opacity: 1, transparent: false, side: 'front', }, plaster: { color: '#f5f5dc', roughness: 0.95, metalness: 0, opacity: 1, transparent: false, side: 'front', }, tile: { color: '#d3d3d3', roughness: 0.4, metalness: 0.1, opacity: 1, transparent: false, side: 'front', }, marble: { color: '#fafafa', roughness: 0.2, metalness: 0.1, opacity: 1, transparent: false, side: 'front', }, custom: { color: '#ffffff', roughness: 0.5, metalness: 0, opacity: 1, transparent: false, side: 'front', }, } export function resolveMaterial(material?: MaterialSchema): MaterialProperties { if (!material) { return DEFAULT_MATERIALS.white } if (material.preset && material.preset !== 'custom') { const presetProps = DEFAULT_MATERIALS[material.preset] return { ...presetProps, ...material.properties, } } return { ...DEFAULT_MATERIALS.custom, ...material.properties, } }