import { Vector3, Color, MeshStandardMaterial, Object3D } from 'three'; /** * Procedural surface materials — the reason low-poly SCENA props can look * richer than a downloaded GLTF at a fraction of the bytes. * * A downloaded model ships baked albedo/normal/roughness textures (often * megabytes). Here the same detail — weathered stone, wood grain, mottled * plaster, thatch, tile — is generated in the shader from triplanar value * noise, so nothing is fetched and every prop is unique. The result is a * plain `MeshStandardMaterial` with an `onBeforeCompile` patch, so it keeps * three's full PBR lighting, shadows, fog, tone-mapping, and SCENA's * day/night emissive dimming — none of which a raw `ShaderMaterial` would. * * ```ts * const mat = createSurface('stone', { color: 0x8a8f98 }); * mesh.material = mat; // that's it * ``` * * Triplanar means no UVs are needed (a `BoxGeometry` has none worth using), * and because the noise is sampled in world space a wall built from several * abutting boxes reads as one continuous stone face with no visible seams. */ type SurfaceKind = 'plaster' | 'stone' | 'wood' | 'plank' | 'thatch' | 'tile' | 'metal' | 'dirt' | 'sand' | 'gravel' | 'mud' | 'grass' | 'sandstone' | 'granite' | 'slate' | 'bark' | 'leather' | 'canvas' | 'parchment' | 'terracotta' | 'bone' | 'rust' | 'bronze' | 'brass' | 'brick' | 'cobblestone' | 'ashlar' | 'floortile' | 'shingle' | 'snow' | 'moss' | 'lava' | 'crystal' | 'concrete' | 'paint' | 'marble' | 'terrazzo' | 'steel' | 'chrome' | 'paintedMetal' | 'corten' | 'teak' | 'porcelain' | 'glaze' | 'mosaic' | 'parquet' | 'patternedTile' | 'corrugatedIron' | 'asphalt' | 'diamondPlate' | 'galvanised' | 'copperPatina' | 'basalt' | 'velvet' | 'silk' | 'brushedMetal' | 'nacre' | 'ice' | 'gemstone'; interface SurfaceParams { /** * Natural base colour for this kind (hex int). Used when the caller passes * no `color`, so `createSurface('sand')` looks like sand out of the box. * A caller's `color` always wins. */ baseColor?: number; /** PBR base roughness. */ roughness: number; /** PBR metalness. */ metalness: number; /** Noise frequency in world units (higher = finer grain). */ scale: number; /** How strongly the fine noise lightens/darkens the albedo (0–1). */ albedoVar: number; /** Secondary colour blended into cavities, as a hex int. */ tint: number; /** How much of `tint` shows in cavities (0–1). */ tintAmount: number; /** Low-frequency cavity darkening — the baked-AO look (0–1). */ ao: number; /** Surface relief strength (normal perturbation from the noise). */ bump: number; /** Roughness variation added from the noise (0–1). */ roughVar: number; /** Anisotropic grain strength for wood-like surfaces (0 = none). */ grain: number; /** Grain ring frequency. */ grainScale: number; /** World axis the grain runs along. */ grainAxis: Vector3; /** flatShading default for this kind. */ flat: boolean; /** Tiling strength (0 off, 1 full). Turns on the brick/tile grid. */ tile?: number; /** Cell width in world metres (the length of a brick/tile). */ tileW?: number; /** Cell height in world metres (the course height). */ tileH?: number; /** Mortar-joint width in world metres. */ mortar?: number; /** Row offset: 0 = aligned grid, 1 = half-cell running bond. */ bond?: number; /** Cell profile: 0 = flat tiles, 1 = domed cobbles. */ round?: number; /** Per-cell brightness/roughness jitter (0–1), so no two read alike. */ tileJitter?: number; /** Mortar-joint colour, as a hex int. */ mortarColor?: number; /** Groove relief strength for the joints (normal perturbation). */ tileRelief?: number; /** Fraction of cells painted solid `tint` (mosaic accent chips, 0–1). */ tileTint?: number; /** 1 = shear alternate column bands ±45° — chevron/herringbone parquet. */ chevron?: number; /** Per-cell ring + dot motif painted in `tint` (patterned cement tiles). */ motif?: number; /** Cap strength (0 off, 1 full) — snow, moss, dust on the tops. */ cap?: number; /** Cap colour, as a hex int (white snow, green moss…). */ capColor?: number; /** How up-facing a face must be before the cap takes (0 everywhere … 1 only dead-level tops). */ capUp?: number; /** Softness of the cap edge (bigger = more gradual). */ capSharp?: number; /** Roughness inside the capped area (fresh snow reads matte/bright). */ capRough?: number; /** * Build a `MeshPhysicalMaterial` instead of a `MeshStandardMaterial`. * * Some surfaces are defined by a light response the standard model has no * term for at all — the retroreflective rim of velvet, the stretched * highlight on silk, the thin-film hue shift on a shell, light going * THROUGH ice. None of those can be faked with albedo and roughness. * * It costs: a physical material is a bigger shader, and `transmission` in * particular makes three render the scene a second time into a buffer. Use * it on OBJECTS, not on facades — which is exactly why `createGlass` fakes * its glass rather than transmitting it. */ physical?: boolean; /** Sheen strength — velvet, felt, wool. The rim IS the material. */ sheen?: number; /** Sheen colour, as a hex int. */ sheenColor?: number; /** Sheen roughness. */ sheenRough?: number; /** * Anisotropy strength: a stretched highlight instead of a round one. * Silk, brushed metal, carbon fibre. NOTE it follows the geometry's UVs, * so a mesh with no UV attribute will not show it. */ anisotropy?: number; /** Which way the grain runs, in radians. */ anisotropyRotation?: number; /** Thin-film iridescence — nacre, beetle shell, oil on water. */ iridescence?: number; /** Iridescent film IOR. */ iridescenceIOR?: number; /** Film thickness range in nanometres, [min, max]. */ iridescenceThickness?: [number, number]; /** Transmission: light passing THROUGH. Expensive — see `physical`. */ transmission?: number; /** How thick the volume is, for transmission. */ thickness?: number; /** Index of refraction (water 1.33, ice 1.31, glass 1.5, diamond 2.42). */ ior?: number; /** * Chromatic dispersion: how far apart the red and blue refractions fall. * * A real transparent solid has a DIFFERENT index of refraction for every * wavelength, which is why a cut stone throws colour and a window does not. * three refracts red, green and blue separately over * `ior ± (ior - 1) * 0.025 * dispersion`, so the spread grows with the IOR * as well — 0 is a plain glass, 10 on a diamond-grade IOR is a gemstone. * * Needs `transmission` and a non-zero `thickness`: with nothing passing * through the volume there is nothing to split. Costs a third transmission * sample per pixel. */ dispersion?: number; /** Colour light picks up on its way through, as a hex int. */ attenuationColor?: number; /** How far light travels before it is fully attenuated, in metres. */ attenuationDistance?: number; /** Ridge relief strength (0 off). Corrugated sheet, fluting, tread plate. */ ribs?: number; /** Ridges per world metre. */ ribScale?: number; /** * 1 = turn the ridges through 90° on the face. Ribs are laid out in the * FACE's own plane, not along a world axis: two world axes that differ in * space collapse onto the same direction once projected onto a wall, which * turned a crossed tread plate back into plain stripes. */ ribTurn?: number; /** 1 = a second set crossed over the first: the diamond studs of tread plate. */ ribCross?: number; /** * Chip contrast. The rest of the shader runs on smooth fbm, which reads as * mottling; asphalt is STONES IN TAR, and stones have edges. */ speck?: number; /** Chips per world metre. */ speckScale?: number; /** * Cell strength. Irregular polygons: the columnar jointing of basalt and * the zinc spangle on galvanised steel are the same function at two very * different scales. */ cells?: number; /** Cells per world metre. */ cellScale?: number; /** How dark and deep the seams between cells run (0–1). */ cellEdge?: number; /** Per-cell brightness jitter (0–1). */ cellJitter?: number; /** * 1 = lay the cells out in PLAN and extrude them vertically, whatever the * face is pointing at. That is what columnar jointing is — a crazy paving * seen from above, pulled up into columns — and projecting it per-face * instead gives blotches on a wall rather than columns in a cliff. */ cellPlan?: number; /** * How much crust has grown: verdigris on copper, a rust bloom, lichen. * It takes in the cavities and on the up-facing side, and it is a CRUST, * not a tint — wherever it takes hold the metalness goes with it, because * a mineral scab does not reflect like the metal underneath. */ crust?: number; /** Crust colour, as a hex int (verdigris green, rust orange…). */ crustColor?: number; /** Roughness inside the crust. */ crustRough?: number; /** * How wet the surface is: 0 dry, 1 running with water. * * This is a STATE, not a kind. Every one of the presets above can be * rained on, and water behaves the same way on all of them — it darkens * the albedo, collapses the roughness to a film, and, most of all, it * POOLS: at a light wetting only the hollows and the mortar joints are * dark and glossy, and the face only sheets over when it is properly * raining. */ wet?: number; /** * How well water clings to vertical faces: 0 = only the tops ever get * wet, 1 = a wall wets as fast as a floor. Default 0.55 — rain falls * down, so a sill soaks while the wall under it is merely damp. Sealed, * shedding surfaces (glass, glaze, chrome) want less; things that wick * (plaster, concrete, canvas) want more. */ wetCling?: number; /** Glow strength added straight to emissive radiance (lava, crystal, runes). */ glow?: number; /** Glow colour, as a hex int. */ glowColor?: number; /** How much of the surface glows: low = only the deep cracks, high = most of it. */ glowThreshold?: number; } declare const SURFACE_PRESETS: Record; interface SurfaceOptions extends Partial { /** Base colour (hex int or three Color). Defaults to a neutral grey. */ color?: number | Color; /** * Seed offset so two props with the same colour still weather * differently. Any number; shifts the noise field. */ seed?: number; } /** * Build a procedural surface material. Pass a preset name for the defaults, * plus any overrides (colour, roughness, bump, seed, …). */ declare function createSurface(kind: SurfaceKind, options?: SurfaceOptions): MeshStandardMaterial; /** * Screens — the lit rectangles that make a scene read as modern. * * A screen is not a texture here. It is drawn procedurally in the fragment * shader from the panel's own UVs, the same way `createSurface` draws stone * and brick: nothing is fetched, every panel is unique, and a phone and a * television share one material path. * * The content is deliberately **not text**. At the size a screen occupies on * a monitor — a phone is a few pixels across at conversational distance — * you need the *impression* of an interface, not readable content: rows, * tiles, a scrubbing bar, a route line. Real glyphs would cost thousands of * triangles for something illegible, and look worse on the rare occasion the * camera got close enough to read them. * * ```ts * const panel = createScreenPanel(0.6, 0.34, { mode: 'feed' }); * mesh.material = panel.material; * game.onUpdate((t) => panel.update(t.delta)); * ``` * * The content is written into **emissive radiance** rather than base colour, * so a screen lights up in a dark room and the day/night cycle cannot dim it * — a monitor at midnight is exactly as bright as one at noon, which is the * whole reason anyone notices screens at night. */ type ScreenMode = 'off' | 'standby' | 'home' | 'feed' | 'video' | 'map' | 'chart' | 'call' | 'keypad'; interface ScreenOptions { /** What the screen is showing. Default 'home'. */ mode?: ScreenMode; /** Varies icon colours, chart bars, the cut rhythm of video. Default 1. */ seed?: number; /** UI accent colour (the highlighted bar, the answer pill). */ accent?: number; /** Overall emissive gain. Default 1. Dim a phone, brighten a TV wall. */ brightness?: number; /** Rows scrolled per second in 'feed'. Default 0 (a still screen). */ scrollRate?: number; } /** * A lit panel. Structurally what ANIMA's `Viewable` wants (`surface`, * `width`, `height`) so a character can look at one, and what GAMA's * `DisplayTarget` wants (`setMode`) so a device can drive one — with no * library importing another. */ interface ScreenPanel { /** The lit face. Set by the prop that owns it; the gaze target. */ surface: Object3D; /** Panel size in metres. */ width: number; height: number; material: MeshStandardMaterial; readonly mode: ScreenMode; setMode(mode: ScreenMode): void; /** * What this screen is throwing into the room right now — colour and * strength, updated every frame. A light that copies these flickers in * time with the content, which is the difference between a television and * a blue lamp. */ readonly glow: { color: Color; intensity: number; }; update(dt: number): void; } interface ScreenPanelOptions extends ScreenOptions { /** Aspect override. Defaults to width / height. */ aspect?: number; } /** * Build a screen's material and its live state. Callers own the mesh — the * panel only needs to know how big it is so its UI lays out square. */ declare function createScreenPanel(width: number, height: number, options?: ScreenPanelOptions): ScreenPanel; /** * Pictures — the images that go on walls. * * This is `createScreenPanel`'s twin, and deliberately so: the same trick of * drawing content procedurally in the fragment shader from the panel's own * UVs, so a gallery of forty framed pictures fetches nothing, repeats * nothing, and costs one material path. * * The one structural difference matters more than everything else here. A * screen writes into **emissive radiance** — it is a light source, and the * day/night cycle must not dim it. A picture writes into **base colour**. It * has no light of its own; a painting in an unlit room is invisible, and one * that keeps glowing at midnight is the single loudest tell that a wall is * decorated with screenshots. So this patches `map_fragment`, not * `emissivemap_fragment`, and everything downstream — lighting, shadow, the * day cycle, fog — applies to it exactly as it does to plaster. * * ```ts * const picture = createPicture(0.6, 0.45, { style: 'landscape', seed: 7 }); * mesh.material = picture.material; * ``` * * Content is not representational in any detail. At the size a picture * occupies on screen — a framed photo is a couple of dozen pixels across * from the sofa — what reads is **value structure**: where the light is, * where the dark mass is, roughly what the subject's silhouette does. Paint * that correctly and the eye supplies a landscape. Paint individual leaves * and you get mush that costs more. */ type PictureStyle = /** Sky, horizon, receding hills. The default wall filler. */ 'landscape' /** A figure: shoulders, head, hair mass, warm and low-contrast. */ | 'portrait' /** Dark ground, a table edge, lit objects — the old-master still life. */ | 'stillLife' /** Soft bands of colour with blurred edges. */ | 'abstract' /** Hard-edged shapes in a limited palette. */ | 'geometric' /** Desaturated, vignetted, a bright window and a dark subject. */ | 'photo' /** Flat colour field, a heavy band, and blocks where the type goes. */ | 'poster' /** Mostly white with ruled lines of text and a stamp — a printed notice. */ | 'notice' /** * Not a picture at all: a pale gradient with a skewed bright patch where a * window would land. A real mirror is a second render pass per mirror, * which is an absurd price for set dressing, and a metal surface with no * environment map renders black. A painted reflection reads correctly at * any distance you would actually film a room from. */ | 'mirror'; declare const PICTURE_STYLES: PictureStyle[]; /** Everything `createPicture` can draw, including the non-painting styles. */ declare const ALL_PICTURE_STYLES: PictureStyle[]; interface PictureOptions { /** What the picture is of. Default 'landscape'. */ style?: PictureStyle; /** Drives every hue, layout and placement choice. Default 1. */ seed?: number; /** * Yellowing, darkened varnish and a heavier vignette (0–1). Default 0.25. * Push it up for a manor, down for a new-build. */ age?: number; /** Overall value. Default 1. Below 1 for a picture in a dim corner. */ brightness?: number; /** Aspect override. Defaults to width / height. */ aspect?: number; } interface Picture { material: MeshStandardMaterial; style: PictureStyle; width: number; height: number; } /** * Build a picture's material. The caller owns the mesh; the picture only * needs to know its own proportions so shapes inside it stay round. * * Whatever the style, the output stays inside a painterly value range — * nothing goes to white and nothing to black. A wall picture that clips at * either end stops looking like a made object and starts looking like a hole * in the wall. */ declare function createPicture(width: number, height: number, options?: PictureOptions): Picture; /** Pick a picture style from a seed, excluding 'mirror'. */ declare function pickPictureStyle(seed: number): PictureStyle; export { ALL_PICTURE_STYLES as A, PICTURE_STYLES as P, SURFACE_PRESETS as S, type Picture as a, type PictureOptions as b, type PictureStyle as c, type ScreenMode as d, type ScreenOptions as e, type ScreenPanel as f, type ScreenPanelOptions as g, type SurfaceKind as h, type SurfaceOptions as i, type SurfaceParams as j, createPicture as k, createScreenPanel as l, createSurface as m, pickPictureStyle as p };