feat(paint-slots): concrete-plate finish + try defaults (wall=concrete, slab=wood plank 48, ceiling=soft white)

- material-library: add 'concrete-plate' KTX2 finish (512, fabric/leather-style
  pipeline) + editor-app texture mirror.
- viewer: shared resolveSlotDefaultMaterial(colour|library ref) so a kind's slot
  default can be a catalog finish, not just a flat colour.
- wall default -> library:concrete-plate (interior + exterior).
- slab default -> library:wood-woodplank48 (slab geometry resolves it via the
  shared helper).
- ceiling default -> soft white #f2eee6 (ceiling renders flat-tinted).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Wassim SAMAD
2026-06-17 08:25:48 -04:00
co-authored by Claude Opus 4.8
parent 967a905e3b
commit 737c4e9d1d
12 changed files with 74 additions and 11 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

+38
View File
@@ -3860,6 +3860,44 @@ export const MATERIAL_CATALOG: MaterialCatalogItem[] = [
}, },
}, },
}, },
{
id: 'concrete-plate',
label: 'Concrete Plate',
category: 'concrete',
surfaces: ['wall', 'floor'],
description: 'Smooth cast concrete plate',
previewThumbnailUrl: '/material/concrete/concrete_plate/concrete_plate_thumb.webp',
preset: {
maps: {
albedoMap: '/material/concrete/concrete_plate/concrete_plate_basecolor_512.ktx2',
normalMap: '/material/concrete/concrete_plate/concrete_plate_normal_512.ktx2',
roughnessMap: '/material/concrete/concrete_plate/concrete_plate_roughness_512.ktx2',
aoMap: '/material/concrete/concrete_plate/concrete_plate_ao_512.ktx2',
},
mapProperties: {
color: '#ffffff',
roughness: 0.8,
metalness: 0,
repeatX: 0.5,
repeatY: 0.5,
rotation: 0,
wrapS: 'Repeat',
wrapT: 'Repeat',
normalScaleX: 1,
normalScaleY: 1,
emissiveIntensity: 1,
displacementScale: 0,
transparent: false,
flipY: false,
bumpScale: 1,
emissiveColor: '#000000',
aoMapIntensity: 1,
side: 0,
opacity: 1,
lightMapIntensity: 1,
},
},
},
{ {
id: 'concrete-stucco', id: 'concrete-stucco',
label: 'White Stucco', label: 'White Stucco',
+2 -2
View File
@@ -52,8 +52,8 @@ export type WallSurfaceSide = 'interior' | 'exterior'
// May be a `#rrggbb` colour or a `library:<id>` ref. Textures-off still // May be a `#rrggbb` colour or a `library:<id>` ref. Textures-off still
// collapses to the themed wall role (the escape hatch). // collapses to the themed wall role (the escape hatch).
export const WALL_SLOT_DEFAULT: Record<WallSurfaceSide, string> = { export const WALL_SLOT_DEFAULT: Record<WallSurfaceSide, string> = {
interior: '#ffffff', interior: 'library:concrete-plate',
exterior: '#ffffff', exterior: 'library:concrete-plate',
} }
export type WallSurfaceMaterialSpec = { export type WallSurfaceMaterialSpec = {
+3 -2
View File
@@ -2,8 +2,9 @@ import type { SlotDeclaration } from '@pascal-app/core'
export type CeilingSlotId = 'surface' export type CeilingSlotId = 'surface'
// Visual parity with the retired DEFAULT_CEILING_MATERIAL (warm beige). // Soft white — the default underside colour for an unpainted ceiling. (A
export const CEILING_SLOT_DEFAULT_COLOR = '#f5f5dc' // ceiling renders flat-tinted, so this is a colour, not a `library:` finish.)
export const CEILING_SLOT_DEFAULT_COLOR = '#f2eee6'
/** A ceiling exposes a single paintable underside surface. */ /** A ceiling exposes a single paintable underside surface. */
export function ceilingSlots(): SlotDeclaration[] { export function ceilingSlots(): SlotDeclaration[] {
+5 -4
View File
@@ -8,9 +8,10 @@ import {
generateSlabGeometry, generateSlabGeometry,
type RenderShading, type RenderShading,
resolveMaterialRef, resolveMaterialRef,
resolveSlotDefaultMaterial,
} from '@pascal-app/viewer' } from '@pascal-app/viewer'
import { FrontSide, Group, type Material, Mesh, type Texture } from 'three' import { FrontSide, Group, type Material, Mesh, type Texture } from 'three'
import { SLAB_SLOT_DEFAULT_COLOR } from './slots' import { SLAB_SLOT_DEFAULT } from './slots'
/** /**
* Stage B builder for slab. Reuses `generateSlabGeometry` (pure * Stage B builder for slab. Reuses `generateSlabGeometry` (pure
@@ -62,8 +63,8 @@ function getSlabMaterial(
return getLegacySlabMaterial(node, shading) return getLegacySlabMaterial(node, shading)
} }
// Declared slot default (visual parity with the retired DEFAULT_SLAB_MATERIAL). // Declared slot default — a catalog `library:` finish or a flat colour.
return createDefaultMaterial(SLAB_SLOT_DEFAULT_COLOR, 0.8, shading) return resolveSlotDefaultMaterial(SLAB_SLOT_DEFAULT, shading, 0.8)
} }
function getLegacySlabMaterial(node: SlabNode, shading: RenderShading): Material { function getLegacySlabMaterial(node: SlabNode, shading: RenderShading): Material {
@@ -83,7 +84,7 @@ function getLegacySlabMaterial(node: SlabNode, shading: RenderShading): Material
? createDefaultMaterial('#ffffff', 0.5, shading) ? createDefaultMaterial('#ffffff', 0.5, shading)
: node.material : node.material
? createMaterial(node.material, shading).clone() ? createMaterial(node.material, shading).clone()
: createDefaultMaterial(SLAB_SLOT_DEFAULT_COLOR, 0.8, shading) : createDefaultMaterial('#e5e5e5', 0.8, shading)
if (preset) { if (preset) {
applyMaterialPresetToMaterials(material, preset) applyMaterialPresetToMaterials(material, preset)
+5 -3
View File
@@ -2,10 +2,12 @@ import type { SlotDeclaration } from '@pascal-app/core'
export type SlabSlotId = 'surface' export type SlabSlotId = 'surface'
// Visual parity with the retired DEFAULT_SLAB_MATERIAL (light grey). // Declared default appearance for an unpainted slab surface in colored mode —
export const SLAB_SLOT_DEFAULT_COLOR = '#e5e5e5' // a catalog `library:<id>` finish or a `#rrggbb` colour. Textures-off collapses
// to the themed floor role (the escape hatch).
export const SLAB_SLOT_DEFAULT = 'library:wood-woodplank48'
/** A slab exposes a single paintable floor surface. */ /** A slab exposes a single paintable floor surface. */
export function slabSlots(): SlotDeclaration[] { export function slabSlots(): SlotDeclaration[] {
return [{ slotId: 'surface', label: 'Surface', default: SLAB_SLOT_DEFAULT_COLOR }] return [{ slotId: 'surface', label: 'Surface', default: SLAB_SLOT_DEFAULT }]
} }
+1
View File
@@ -69,6 +69,7 @@ export {
PRESET_PALETTES, PRESET_PALETTES,
type RenderShading, type RenderShading,
resolveMaterialRef, resolveMaterialRef,
resolveSlotDefaultMaterial,
resolveSurfaceColor, resolveSurfaceColor,
WHITE_PALETTE, WHITE_PALETTE,
} from './lib/materials' } from './lib/materials'
+20
View File
@@ -516,6 +516,26 @@ export function resolveMaterialRef(
return createMaterial(sceneMaterial.material, shading) return createMaterial(sceneMaterial.material, shading)
} }
/**
* Resolve a node kind's declared slot default — either a catalog `library:<id>`
* finish or a flat `#rrggbb` colour — to a renderable material. Shared by the
* procedural kinds whose colored-mode unpainted appearance comes from a
* declarative default (slab, wall).
*/
export function resolveSlotDefaultMaterial(
slotDefault: string,
shading: RenderShading = 'rendered',
roughness = 0.9,
): THREE.Material {
if (parseMaterialRef(slotDefault)?.kind === 'library') {
return (
createMaterialFromPresetRef(slotDefault, shading) ??
createDefaultMaterial('#ffffff', roughness, shading)
)
}
return createDefaultMaterial(slotDefault, roughness, shading)
}
export function createDefaultMaterial( export function createDefaultMaterial(
color = '#ffffff', color = '#ffffff',
roughness = 0.9, roughness = 0.9,