@developerz.ai/ts23d 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/CLAUDE.md +1 -1
- package/README.md +10 -5
- package/dist/{chunk-a8fxakcj.js → chunk-6b05mnmx.js} +2 -2
- package/dist/{chunk-kgvbpqqw.js → chunk-7rtfjh9f.js} +6 -5
- package/dist/chunk-7rtfjh9f.js.map +10 -0
- package/dist/{chunk-1ypfs7ga.js → chunk-8j4c28jt.js} +790 -276
- package/dist/chunk-8j4c28jt.js.map +46 -0
- package/dist/{chunk-wtqhq529.js → chunk-d1fv6hrm.js} +6 -6
- package/dist/{chunk-wtqhq529.js.map → chunk-d1fv6hrm.js.map} +1 -1
- package/dist/{chunk-gcyp9b4d.js → chunk-dbq4qf52.js} +362 -215
- package/dist/chunk-dbq4qf52.js.map +25 -0
- package/dist/{chunk-xnt2sdhf.js → chunk-drna2rny.js} +2 -2
- package/dist/{chunk-ravbb06q.js → chunk-dxvypr7y.js} +2 -2
- package/dist/{chunk-11v28977.js → chunk-f6yme003.js} +85 -71
- package/dist/{chunk-11v28977.js.map → chunk-f6yme003.js.map} +6 -6
- package/dist/{chunk-4ddzsqtc.js → chunk-hg7pf34j.js} +4 -3
- package/dist/{chunk-4ddzsqtc.js.map → chunk-hg7pf34j.js.map} +5 -5
- package/dist/{chunk-w1zyhj40.js → chunk-jq4e17tj.js} +2 -2
- package/dist/{chunk-kvcep7ft.js → chunk-y63c6zcv.js} +967 -218
- package/dist/chunk-y63c6zcv.js.map +53 -0
- package/dist/cli/args.d.ts +7 -1
- package/dist/cli/bin.js +66 -25
- package/dist/cli/bin.js.map +6 -5
- package/dist/cli/manifest.d.ts +5 -1
- package/dist/cli/texture-cache.d.ts +4 -0
- package/dist/core/definition.d.ts +7 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/export/credits.d.ts +4 -0
- package/dist/export/glb-options.d.ts +48 -0
- package/dist/export/glb.d.ts +5 -35
- package/dist/export/hierarchy.d.ts +23 -0
- package/dist/export/index.d.ts +2 -1
- package/dist/export/index.js +21 -7
- package/dist/export/index.js.map +1 -1
- package/dist/export/lod-coverage.d.ts +12 -0
- package/dist/export/metadata.d.ts +2 -0
- package/dist/export/version.d.ts +2 -2
- package/dist/grammar/index.js +2 -2
- package/dist/index.js +63 -17
- package/dist/index.js.map +1 -1
- package/dist/kinds/head-armour.d.ts +3 -7
- package/dist/kinds/head-bust.d.ts +3 -2
- package/dist/kinds/head-face.d.ts +3 -1
- package/dist/kinds/head-garb.d.ts +4 -0
- package/dist/kinds/head-mesh.d.ts +4 -0
- package/dist/kinds/head-mouth.d.ts +4 -0
- package/dist/kinds/head-params.d.ts +9 -1
- package/dist/kinds/head-tone.d.ts +10 -0
- package/dist/kinds/head-torso.d.ts +35 -0
- package/dist/kinds/index.js +6 -6
- package/dist/material/cc0.d.ts +4 -0
- package/dist/material/define.d.ts +34 -0
- package/dist/material/generator.d.ts +1 -1
- package/dist/material/index.d.ts +8 -3
- package/dist/material/index.js +39 -7
- package/dist/material/index.js.map +1 -1
- package/dist/material/library.d.ts +7 -0
- package/dist/material/material-def.d.ts +30 -0
- package/dist/material/paint/canvas.d.ts +7 -0
- package/dist/material/paint/default-sheet.d.ts +3 -1
- package/dist/material/paint/fabric.d.ts +4 -0
- package/dist/material/paint/image-strip.d.ts +4 -0
- package/dist/material/paint/kit.d.ts +5 -0
- package/dist/material/paint/metal.d.ts +3 -0
- package/dist/material/paint/orm.d.ts +1 -1
- package/dist/material/paint/strip.d.ts +1 -1
- package/dist/material/paint/types.d.ts +32 -2
- package/dist/material/palette.d.ts +2 -0
- package/dist/material/slots.d.ts +5 -0
- package/dist/material/texture-source.d.ts +34 -0
- package/dist/material/trim-sheet.d.ts +20 -4
- package/dist/neural/index.js +2 -2
- package/dist/passes/index.js +2 -2
- package/dist/primitives/index.js +3 -3
- package/dist/qa/index.js +2 -2
- package/dist/render/beauty.d.ts +1 -1
- package/dist/render/index.js +3 -3
- package/dist/render/lighting.d.ts +7 -0
- package/dist/render/shade.d.ts +1 -1
- package/dist/testing/index.js +5 -5
- package/docs/README.md +4 -0
- package/docs/architecture/gltf-capabilities.md +38 -1
- package/docs/architecture/materials.md +151 -0
- package/docs/idea/10-sculpting.md +166 -0
- package/docs/idea/11-materials-authoring.md +132 -0
- package/docs/idea/12-blender-bridge.md +136 -0
- package/docs/idea/13-asset-pipeline-research.md +89 -0
- package/docs/plans/roadmap.md +12 -0
- package/package.json +2 -1
- package/recipes/boxcar/README.md +7 -0
- package/recipes/boxcar/boxcar.ts +118 -34
- package/recipes/goblin_head/README.md +27 -14
- package/recipes/goblin_head/goblin_head.ts +11 -9
- package/recipes/human_head/README.md +8 -4
- package/recipes/human_head/human_head.ts +4 -3
- package/recipes/material_swatches/material_swatches.ts +11 -5
- package/recipes/orc_head/README.md +32 -20
- package/recipes/orc_head/orc_head.ts +9 -7
- package/src/cli/args.ts +16 -3
- package/src/cli/manifest.ts +6 -0
- package/src/cli/run.ts +33 -9
- package/src/cli/texture-cache.ts +24 -0
- package/src/core/asset.ts +1 -0
- package/src/core/definition.ts +7 -0
- package/src/core/errors.ts +6 -1
- package/src/export/credits.ts +25 -0
- package/src/export/glb-options.ts +53 -0
- package/src/export/glb.ts +100 -113
- package/src/export/hierarchy.ts +122 -0
- package/src/export/index.ts +17 -1
- package/src/export/lod-coverage.ts +71 -0
- package/src/export/metadata.ts +3 -0
- package/src/export/version.ts +1 -1
- package/src/kinds/head-armour.ts +103 -73
- package/src/kinds/head-bust.ts +49 -86
- package/src/kinds/head-face.ts +94 -74
- package/src/kinds/head-garb.ts +181 -0
- package/src/kinds/head-hair.ts +74 -33
- package/src/kinds/head-mesh.ts +43 -5
- package/src/kinds/head-mouth.ts +111 -0
- package/src/kinds/head-params.ts +11 -2
- package/src/kinds/head-presets.ts +30 -27
- package/src/kinds/head-tone.ts +52 -0
- package/src/kinds/head-torso.ts +230 -0
- package/src/kinds/head.ts +8 -3
- package/src/material/cc0.ts +41 -0
- package/src/material/define.ts +130 -0
- package/src/material/generator.ts +15 -8
- package/src/material/index.ts +34 -3
- package/src/material/library.ts +196 -0
- package/src/material/material-def.ts +84 -0
- package/src/material/paint/canvas.ts +11 -1
- package/src/material/paint/compose.ts +1 -1
- package/src/material/paint/decode-png.ts +19 -13
- package/src/material/paint/default-sheet.ts +40 -28
- package/src/material/paint/fabric.ts +63 -0
- package/src/material/paint/image-strip.ts +88 -0
- package/src/material/paint/kit.ts +37 -0
- package/src/material/paint/masks.ts +3 -1
- package/src/material/paint/metal.ts +109 -0
- package/src/material/paint/orm.ts +14 -1
- package/src/material/paint/planks.ts +49 -4
- package/src/material/paint/strip.ts +35 -29
- package/src/material/paint/types.ts +45 -2
- package/src/material/paint/validate.ts +19 -3
- package/src/material/palette.ts +3 -0
- package/src/material/slots.ts +18 -0
- package/src/material/texture-source.ts +86 -0
- package/src/material/trim-sheet.ts +79 -13
- package/src/render/beauty.ts +2 -2
- package/src/render/lighting.ts +26 -0
- package/src/render/shade.ts +10 -5
- package/dist/chunk-1ypfs7ga.js.map +0 -42
- package/dist/chunk-gcyp9b4d.js.map +0 -22
- package/dist/chunk-kgvbpqqw.js.map +0 -10
- package/dist/chunk-kvcep7ft.js.map +0 -43
- /package/dist/{chunk-a8fxakcj.js.map → chunk-6b05mnmx.js.map} +0 -0
- /package/dist/{chunk-xnt2sdhf.js.map → chunk-drna2rny.js.map} +0 -0
- /package/dist/{chunk-ravbb06q.js.map → chunk-dxvypr7y.js.map} +0 -0
- /package/dist/{chunk-w1zyhj40.js.map → chunk-jq4e17tj.js.map} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Vec3 } from '../../core/index.js';
|
|
1
2
|
export interface Canvas {
|
|
2
3
|
readonly w: number;
|
|
3
4
|
readonly h: number;
|
|
@@ -12,8 +13,14 @@ export interface Canvas {
|
|
|
12
13
|
readonly metal: Float32Array;
|
|
13
14
|
/** Extra occlusion multiplied into the cavity AO (foliage darkens toward the twig). */
|
|
14
15
|
readonly occl: Float32Array;
|
|
16
|
+
/** 0 = `colours[0]`, 1 = `colours[1]` (latewood, rust, bare wood under chipped paint…). */
|
|
17
|
+
readonly mix: Float32Array;
|
|
18
|
+
/** Linear albedo pair for material strips; null = greyscale (vertex colour carries hue). */
|
|
19
|
+
colours: readonly [Vec3, Vec3] | null;
|
|
15
20
|
/** sRGB RGBA override (stamped images, coloured foliage); null when painted greyscale. */
|
|
16
21
|
rgba: Uint8Array | null;
|
|
22
|
+
/** Tangent-space normal override (image strips); null = derived from `height`. */
|
|
23
|
+
normalRgba: Uint8Array | null;
|
|
17
24
|
}
|
|
18
25
|
export declare function createCanvas(w: number, h: number, metresU: number, metresV: number, wrapU: boolean): Canvas;
|
|
19
26
|
/** Metres per pixel along x and y. */
|
|
@@ -1,2 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { StripId } from '../trim-sheet.js';
|
|
2
|
+
import type { SheetRecipe, StripRecipe } from './types.js';
|
|
3
|
+
export declare const HOUSE_STRIPS: Partial<Record<StripId, StripRecipe>>;
|
|
2
4
|
export declare const DEFAULT_SHEET: SheetRecipe;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type Canvas } from './canvas.js';
|
|
2
|
+
import type { PaintKit } from './types.js';
|
|
3
|
+
export declare function paintKit(c: Canvas, seed: number): PaintKit;
|
|
4
|
+
/** Throw T3D_MATERIAL_INVALID if any layer holds a non-finite value. */
|
|
5
|
+
export declare function checkFinite(c: Canvas, where: string): void;
|
|
@@ -4,5 +4,5 @@ export declare function cavityOcclusion(c: Canvas): Float32Array;
|
|
|
4
4
|
export declare const SRGB: Uint8Array<ArrayBuffer>;
|
|
5
5
|
/** RGBA8 ORM: R = occlusion, G = roughness, B = metalness, A = 255. */
|
|
6
6
|
export declare function packOrm(c: Canvas, ao: Float32Array): Uint8Array;
|
|
7
|
-
/** RGBA8 sRGB albedo: the stamped image if any, else grey tone ×
|
|
7
|
+
/** RGBA8 sRGB albedo: the stamped image if any, else (colour pair by mix or grey) × tone × cavity. */
|
|
8
8
|
export declare function packAlbedo(c: Canvas, ao: Float32Array): Uint8Array;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { Canvas } from './canvas.js';
|
|
2
2
|
import type { StripRecipe } from './types.js';
|
|
3
|
-
export declare function paintStrip(c: Canvas, r: StripRecipe, seed: number): void;
|
|
3
|
+
export declare function paintStrip(c: Canvas, r: StripRecipe, seed: number, where?: string): void;
|
|
@@ -1,14 +1,36 @@
|
|
|
1
|
+
import type { Vec3 } from '../../core/index.js';
|
|
2
|
+
import type { TextureSource } from '../texture-source.js';
|
|
1
3
|
import type { CellId, StripId } from '../trim-sheet.js';
|
|
4
|
+
import type { Canvas } from './canvas.js';
|
|
2
5
|
export interface PaintedSheet {
|
|
3
6
|
readonly size: number;
|
|
4
|
-
/** RGBA8, sRGB.
|
|
7
|
+
/** RGBA8, sRGB. Classic strips are greyscale (vertex colour carries hue); materials carry colour. */
|
|
5
8
|
readonly albedo: Uint8Array;
|
|
6
9
|
/** RGBA8, tangent-space (glTF / OpenGL, +Y toward decreasing V). */
|
|
7
10
|
readonly normal: Uint8Array;
|
|
8
11
|
/** R = occlusion, G = roughness, B = metalness. */
|
|
9
12
|
readonly orm: Uint8Array;
|
|
10
13
|
}
|
|
11
|
-
export type StripKind = 'bond' | 'planks' | 'plates' | 'noise' | 'flat' | 'slate' | 'concrete' | 'glass';
|
|
14
|
+
export type StripKind = 'bond' | 'planks' | 'plates' | 'noise' | 'flat' | 'slate' | 'concrete' | 'glass' | 'metal' | 'weave' | 'leather';
|
|
15
|
+
/**
|
|
16
|
+
* The surface a painter writes, one float per texel per layer, in real metres. Row y grows with
|
|
17
|
+
* world up (+V); strips wrap along x. Layers: `height` (metres, + is out), `tone` (linear
|
|
18
|
+
* brightness multiplier), `rough` and `metal` (0–1), `mix` (0 = albedo, 1 = albedo2), `occl`.
|
|
19
|
+
*/
|
|
20
|
+
export type MaterialSurface = Canvas;
|
|
21
|
+
/** Helpers handed to a custom painter: seeded, tileable noise and smoothstep, all in metres. */
|
|
22
|
+
export interface PaintKit {
|
|
23
|
+
readonly seed: number;
|
|
24
|
+
/** Tileable fBm in [−1, 1] with feature size `sizeU` × `sizeV` metres; `salt` varies it. */
|
|
25
|
+
noise(xm: number, ym: number, sizeU: number, sizeV: number, salt?: number, octaves?: number): number;
|
|
26
|
+
/** Integer hash in [0, 1). */
|
|
27
|
+
hash(a: number, b: number, salt?: number): number;
|
|
28
|
+
smooth(e0: number, e1: number, v: number): number;
|
|
29
|
+
/** Visit every texel: index into the layers and its centre in metres. */
|
|
30
|
+
each(fn: (i: number, xm: number, ym: number) => void): void;
|
|
31
|
+
}
|
|
32
|
+
/** A project painter: runs after the base `kind`, layering its own pattern over it. */
|
|
33
|
+
export type MaterialPainter = (s: MaterialSurface, kit: PaintKit) => void;
|
|
12
34
|
export interface StripRecipe {
|
|
13
35
|
readonly kind: StripKind;
|
|
14
36
|
/**
|
|
@@ -20,6 +42,14 @@ export interface StripRecipe {
|
|
|
20
42
|
readonly metalness?: number;
|
|
21
43
|
/** Brick/plank courses, rivet pitch, etc. — read per kind (see each painter's header). */
|
|
22
44
|
readonly params?: Readonly<Record<string, number>>;
|
|
45
|
+
/** Intrinsic linear albedo; omitted = white (greyscale strip, vertex colour carries hue). */
|
|
46
|
+
readonly albedo?: Vec3;
|
|
47
|
+
/** Second linear albedo the painter blends to through `mix` (latewood, rust, bare wood…). */
|
|
48
|
+
readonly albedo2?: Vec3;
|
|
49
|
+
/** Extra layer painted after `kind` (see `defineMaterial`). */
|
|
50
|
+
readonly paint?: MaterialPainter;
|
|
51
|
+
/** Paint from a cached CC0 texture set instead of `kind` (see texture-source.ts). */
|
|
52
|
+
readonly texture?: TextureSource;
|
|
23
53
|
}
|
|
24
54
|
/** A cell stamped from PNG bytes (8-bit grey/RGB/RGBA, non-interlaced), e.g. a kubeez.com decal. */
|
|
25
55
|
export interface CellImage {
|
|
@@ -2,6 +2,8 @@ import type { Vec3 } from '../core/index.js';
|
|
|
2
2
|
export type Palette = Readonly<Record<string, Vec3>>;
|
|
3
3
|
/** sRGB hex → linear RGB, the space glTF vertex colours are in. */
|
|
4
4
|
export declare function hex(value: string): Vec3;
|
|
5
|
+
/** Vertex colour for material strips that carry their own colour (oak, brass, rubber…). */
|
|
6
|
+
export declare const NATURAL: Vec3;
|
|
5
7
|
export declare function definePalette(entries: Readonly<Record<string, string>>): Palette;
|
|
6
8
|
/** Era/region palettes ship as data; recipes extend them with object spread. */
|
|
7
9
|
export declare const palettes: {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** Slot index among the custom strips, or −1. */
|
|
2
|
+
export declare const customIndex: (id: string) => number;
|
|
3
|
+
export declare const customIds: () => readonly string[];
|
|
4
|
+
export declare function pushCustom(id: string): number;
|
|
5
|
+
export declare function clearCustom(): void;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface TextureFile {
|
|
2
|
+
/** Download URL (https) or, for `source: 'file'`, a path relative to the working directory. */
|
|
3
|
+
readonly url: string;
|
|
4
|
+
/** Lower-case hex sha256 of the file's bytes: the cache key and the integrity check. */
|
|
5
|
+
readonly sha256: string;
|
|
6
|
+
}
|
|
7
|
+
export interface TextureSource {
|
|
8
|
+
readonly source: 'polyhaven' | 'ambientcg' | 'file';
|
|
9
|
+
/** The set's id at the source, e.g. 'wood_planks_grey'. */
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** Only public-domain sets are accepted, so a generated asset never inherits a licence duty. */
|
|
12
|
+
readonly license: 'CC0-1.0';
|
|
13
|
+
/** Human page crediting the set (goes into the GLB's XMP credits). */
|
|
14
|
+
readonly page: string;
|
|
15
|
+
readonly author?: string;
|
|
16
|
+
/** Real-world size the image covers, metres (U × V). Poly Haven lists it as `dimensions`. */
|
|
17
|
+
readonly metres: readonly [number, number];
|
|
18
|
+
readonly maps: {
|
|
19
|
+
/** sRGB base colour. */
|
|
20
|
+
readonly albedo: TextureFile;
|
|
21
|
+
/** Tangent-space normal, OpenGL convention (+Y up in the image; Poly Haven `nor_gl`). */
|
|
22
|
+
readonly normal?: TextureFile;
|
|
23
|
+
/** Packed AO / roughness / metalness (Poly Haven `arm`, glTF ORM order). */
|
|
24
|
+
readonly arm?: TextureFile;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/** PNG bytes by sha256. */
|
|
28
|
+
export type TextureCache = ReadonlyMap<string, Uint8Array>;
|
|
29
|
+
/** Hand verified texture bytes to the painter (e.g. from `loadTextureCache()` in scripts). */
|
|
30
|
+
export declare function provideTextures(cache: TextureCache): void;
|
|
31
|
+
export declare function textureBytes(file: TextureFile, where: string): Uint8Array;
|
|
32
|
+
export declare function validateTexture(id: string, t: TextureSource): void;
|
|
33
|
+
/** One credit line for the GLB's XMP and the docs. */
|
|
34
|
+
export declare const creditOf: (t: TextureSource) => string;
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/** The original eight strips: greyscale, tinted by vertex colour. */
|
|
2
|
+
export type ClassicStripId = 'plain' | 'brick' | 'stone' | 'slate' | 'iron' | 'wood' | 'glass' | 'concrete';
|
|
3
|
+
/** Material strips with measured colour: use a near-white vertex colour (`NATURAL`) for truth. */
|
|
4
|
+
export type MaterialStripId = 'oak' | 'pine' | 'wood_weathered' | 'wood_painted' | 'iron_rust' | 'iron_cast' | 'steel_painted' | 'steel_galvanised' | 'brass' | 'copper' | 'aluminium' | 'chrome' | 'rubber' | 'canvas' | 'leather' | 'plastic';
|
|
5
|
+
export type BuiltinStripId = ClassicStripId | MaterialStripId;
|
|
6
|
+
/** A project material's id, as returned by `defineMaterial` (a branded string). */
|
|
7
|
+
export type CustomStripId = string & {
|
|
8
|
+
readonly __ts23dMaterial: true;
|
|
9
|
+
};
|
|
10
|
+
export type StripId = BuiltinStripId | CustomStripId;
|
|
2
11
|
export type DecalCellId = 'window_sash' | 'door_panel' | 'clock' | 'smokebox' | 'sign' | 'porthole';
|
|
3
12
|
/** Cutout leaf cards: the anchor (twig base) is the cell's bottom-centre, growth toward +V. */
|
|
4
13
|
export type FoliageCellId = 'foliage_broadleaf' | 'foliage_maple' | 'foliage_maple_autumn' | 'foliage_birch' | 'foliage_acacia' | 'foliage_palm' | 'foliage_spruce' | 'foliage_spruce_snow' | 'foliage_willow' | 'foliage_reeds' | 'foliage_needles';
|
|
@@ -11,11 +20,18 @@ export interface UvRect {
|
|
|
11
20
|
/** Metres of world surface one full rect covers, so texel density is equal across assets. */
|
|
12
21
|
readonly metres: number;
|
|
13
22
|
}
|
|
14
|
-
|
|
23
|
+
/** Strip rows in the atlas: built-ins first, then `CUSTOM_STRIP_SLOTS` project materials. */
|
|
24
|
+
export declare const STRIP_ROWS = 28;
|
|
25
|
+
export declare const CUSTOM_STRIP_SLOTS: number;
|
|
26
|
+
export declare const TRIM_SHEET_SIZE = 2048;
|
|
27
|
+
/** Atlas row of a strip id, or −1 when it is neither built in nor defined. */
|
|
28
|
+
export declare function stripRow(id: string): number;
|
|
15
29
|
export declare function stripRect(id: StripId): UvRect;
|
|
16
|
-
/** Cells sit in the lower half (V ≥ 0.5), 8 per row: decals on row 0, foliage from row 1. */
|
|
17
30
|
export declare function cellRect(id: CellId): UvRect;
|
|
18
|
-
|
|
31
|
+
/** Every built-in strip, classic first. */
|
|
32
|
+
export declare const STRIPS: readonly BuiltinStripId[];
|
|
33
|
+
export declare const CLASSIC_STRIPS: readonly ClassicStripId[];
|
|
34
|
+
export declare const MATERIAL_STRIPS: readonly MaterialStripId[];
|
|
19
35
|
export declare const CELLS: readonly CellId[];
|
|
20
36
|
export declare const DECAL_CELLS: readonly DecalCellId[];
|
|
21
37
|
export declare const FOLIAGE_CELLS: readonly FoliageCellId[];
|
package/dist/neural/index.js
CHANGED
package/dist/passes/index.js
CHANGED
package/dist/primitives/index.js
CHANGED
|
@@ -35,9 +35,9 @@ import {
|
|
|
35
35
|
text2,
|
|
36
36
|
tiled2,
|
|
37
37
|
valueNoise3
|
|
38
|
-
} from "../chunk-
|
|
39
|
-
import"../chunk-
|
|
40
|
-
import"../chunk-
|
|
38
|
+
} from "../chunk-d1fv6hrm.js";
|
|
39
|
+
import"../chunk-y63c6zcv.js";
|
|
40
|
+
import"../chunk-hg7pf34j.js";
|
|
41
41
|
export {
|
|
42
42
|
arcLengths2 as arcLengths,
|
|
43
43
|
beam2 as beam,
|
package/dist/qa/index.js
CHANGED
|
@@ -34,8 +34,8 @@ import {
|
|
|
34
34
|
lodPivots2,
|
|
35
35
|
DEFAULT_LOD_GATES2,
|
|
36
36
|
runLodQa2
|
|
37
|
-
} from "../chunk-
|
|
38
|
-
import"../chunk-
|
|
37
|
+
} from "../chunk-jq4e17tj.js";
|
|
38
|
+
import"../chunk-hg7pf34j.js";
|
|
39
39
|
export {
|
|
40
40
|
CLASH_EPS2 as CLASH_EPS,
|
|
41
41
|
CONTACT_EPS2 as CONTACT_EPS,
|
package/dist/render/beauty.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface BeautyOptions {
|
|
|
13
13
|
readonly fovDeg?: number;
|
|
14
14
|
/** Samples per pixel edge (1–4). Default 2 (4 samples per pixel). */
|
|
15
15
|
readonly supersample?: number;
|
|
16
|
-
/** The painted trim sheet; default paints `DEFAULT_SHEET` at
|
|
16
|
+
/** The painted trim sheet; default paints `DEFAULT_SHEET` at 1024² once per process. */
|
|
17
17
|
readonly sheet?: PaintedSheet;
|
|
18
18
|
/** Zoom into the default framing: window centre (0–1, 0–1) of the full shot and a zoom ≥ 1. */
|
|
19
19
|
readonly window?: {
|
package/dist/render/index.js
CHANGED
|
@@ -18,9 +18,9 @@ import {
|
|
|
18
18
|
detailWindows2,
|
|
19
19
|
reviewPack2,
|
|
20
20
|
thumbnail2
|
|
21
|
-
} from "../chunk-
|
|
22
|
-
import"../chunk-
|
|
23
|
-
import"../chunk-
|
|
21
|
+
} from "../chunk-f6yme003.js";
|
|
22
|
+
import"../chunk-y63c6zcv.js";
|
|
23
|
+
import"../chunk-hg7pf34j.js";
|
|
24
24
|
export {
|
|
25
25
|
REVIEW_THRESHOLDS2 as REVIEW_THRESHOLDS,
|
|
26
26
|
VIEWS2 as VIEWS,
|
|
@@ -10,6 +10,13 @@ export interface Rig {
|
|
|
10
10
|
readonly exposure: number;
|
|
11
11
|
}
|
|
12
12
|
export declare function studioRig(cameraAzDeg: number): Rig;
|
|
13
|
+
/**
|
|
14
|
+
* The studio environment a reflection sees, channel `k`, along a reflection vector with up
|
|
15
|
+
* component `rz`: a bright cyclorama horizon fading to a cooler zenith, a dark band where the
|
|
16
|
+
* floor meets the wall, the lit floor below. Roughness pre-blurs it toward its mean, so polished
|
|
17
|
+
* metal shows the horizon line and rough metal a soft gradient — what makes metal read as metal.
|
|
18
|
+
*/
|
|
19
|
+
export declare function envRadiance(rz: number, rough: number, k: number): number;
|
|
13
20
|
export declare function pcfTaps(radius: number): Float32Array[];
|
|
14
21
|
/**
|
|
15
22
|
* Tone curve, linear in → linear display out: identity up to 0.6 (mid-tones and the palette stay
|
package/dist/render/shade.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PerspectiveCamera } from './camera.js';
|
|
2
2
|
import { type DepthMap } from './depth-map.js';
|
|
3
|
-
import type
|
|
3
|
+
import { type Rig } from './lighting.js';
|
|
4
4
|
import type { Scene } from './scene.js';
|
|
5
5
|
import { type SheetTextures } from './texture.js';
|
|
6
6
|
export interface ShadeContext {
|
package/dist/testing/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Ts23dError2,
|
|
3
3
|
compile2
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-hg7pf34j.js";
|
|
5
5
|
import {
|
|
6
6
|
glob2,
|
|
7
7
|
clashes2,
|
|
@@ -10,12 +10,12 @@ import {
|
|
|
10
10
|
runQa2,
|
|
11
11
|
DEFAULT_LOD_GATES2,
|
|
12
12
|
runLodQa2
|
|
13
|
-
} from "../chunk-
|
|
14
|
-
import"../chunk-
|
|
15
|
-
import"../chunk-
|
|
13
|
+
} from "../chunk-jq4e17tj.js";
|
|
14
|
+
import"../chunk-y63c6zcv.js";
|
|
15
|
+
import"../chunk-drna2rny.js";
|
|
16
16
|
import {
|
|
17
17
|
exportGlb2
|
|
18
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-dbq4qf52.js";
|
|
19
19
|
|
|
20
20
|
// src/testing/compiled.ts
|
|
21
21
|
var ALL_LODS = [0, 1, 2];
|
package/docs/README.md
CHANGED
|
@@ -30,6 +30,10 @@
|
|
|
30
30
|
| [07-what-fits.md](idea/07-what-fits.md) | Fit matrix by asset category |
|
|
31
31
|
| [08-neural-adapters.md](idea/08-neural-adapters.md) | Optional local-GPU models for organic shapes, image sources |
|
|
32
32
|
| [09-kinds.md](idea/09-kinds.md) | Domain builders: building, vehicle, vessel, vegetation, creature, scene |
|
|
33
|
+
| [10-sculpting.md](idea/10-sculpting.md) | How artists sculpt, mapped onto SDF edit lists; brushes, skin, FFD, forms |
|
|
34
|
+
| [11-materials-authoring.md](idea/11-materials-authoring.md) | Node-graph material authoring vs the trim-sheet painter; curvature and edge wear |
|
|
35
|
+
| [12-blender-bridge.md](idea/12-blender-bridge.md) | Optional headless Blender adapter: design, determinism, feasibility (planned) |
|
|
36
|
+
| [13-asset-pipeline-research.md](idea/13-asset-pipeline-research.md) | Pro asset pipeline, trim sheets, texel density, CC0 libraries and licences |
|
|
33
37
|
|
|
34
38
|
## Decisions
|
|
35
39
|
|
|
@@ -47,13 +47,50 @@ a test pins it. Rigid parts (`ctx.part`) stay node-animated: a tank has no skin.
|
|
|
47
47
|
| `KHR_texture_transform` | strip offset / scale without re-baking UVs | later |
|
|
48
48
|
| `EXT_mesh_gpu_instancing` | rivets, bolts, scatter trees: one mesh, N transforms | later |
|
|
49
49
|
| `KHR_mesh_quantization` + `EXT_meshopt_compression` | `web` profile (smaller files) | later (opt-in: Godot rejects *required* extensions) |
|
|
50
|
-
| `MSFT_lod` | three LODs in one file with screen-coverage hints |
|
|
50
|
+
| `MSFT_lod` (vendor, complete) | three LODs in one file with screen-coverage hints: `<asset>.glb`, `toLodGlb` | shipped (below) |
|
|
51
51
|
| `KHR_animation_pointer` | animate material / light values (blinking lamp, heat glow) | later |
|
|
52
52
|
| `KHR_texture_basisu` | KTX2 GPU-compressed trim sheet | later |
|
|
53
53
|
| `KHR_draco_mesh_compression` | — | no: meshopt is faster to decode and preferred |
|
|
54
54
|
| `KHR_gaussian_splatting` | — | no: we generate meshes, not captures |
|
|
55
55
|
| `KHR_interactivity` | — | no: behaviour is engine code, out of scope |
|
|
56
56
|
|
|
57
|
+
## LODs in one file: `MSFT_lod`
|
|
58
|
+
|
|
59
|
+
`t3d build` writes `lod0/1/2.glb` **and** `<asset>.glb` holding all three (`--lods split|single|both`,
|
|
60
|
+
default `both`). Default is `both` because separate files import into every engine's LOD tooling
|
|
61
|
+
today, while the single file serves MSFT_lod-aware loaders and one-file pipelines; disk is the only cost.
|
|
62
|
+
|
|
63
|
+
Layout (per the [spec](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Vendor/MSFT_lod)):
|
|
64
|
+
- LOD0's root node is the only scene root. It carries `extensions.MSFT_lod.ids` = the LOD1, LOD2 root
|
|
65
|
+
nodes (lower quality in order) and `extras.MSFT_screencoverage` = one value per LOD: the screen-height
|
|
66
|
+
fraction where that LOD hands over to the next, the last one being the cull (`[0.43, 0.086, 0.01]`
|
|
67
|
+
for tiger_1: LOD0 above 43 %, LOD1 to 8.6 %, LOD2 to 1 %, then nothing). Same convention as Unity
|
|
68
|
+
LODGroup's screen-relative transition height.
|
|
69
|
+
- LOD1/LOD2 roots (`<asset>_LOD1`, `<asset>_LOD2`) are parentless and **not** in `scenes[0].nodes`: the
|
|
70
|
+
spec says a client without the extension loads the highest LOD and ignores the rest, which only
|
|
71
|
+
holds if the alternates are outside the scene graph. Part names inside each LOD are unchanged
|
|
72
|
+
(`turret`, `wheel_l_0`), so game code binds the same names at any LOD.
|
|
73
|
+
- `MSFT_lod` is in `extensionsUsed`, never `extensionsRequired` (Godot refuses unknown required
|
|
74
|
+
extensions). Materials, the trim sheet, lights and identical motion samplers are written once; the
|
|
75
|
+
`rest` and `run` clips hold one channel per LOD node.
|
|
76
|
+
- Thresholds: `lodCoverage: [a, b, cull]` on `defineAsset`, else derived: a LOD with r × the
|
|
77
|
+
triangles reads equally dense at √r × the screen size (a ∈ [0.1, 0.6], b ∈ [0.02, a/2]); cull 1 %
|
|
78
|
+
for props, 0.2 % for anything ≥ 10 m so landmarks stay on the skyline.
|
|
79
|
+
|
|
80
|
+
| Loader | Reads MSFT_lod? | What a single file shows | Verified how |
|
|
81
|
+
|---|---|---|---|
|
|
82
|
+
| Babylon.js | yes, node + material level (progressive: loads low LODs first, ends on LOD0) | all LODs, switching | Babylon docs / spec "Known Implementations" |
|
|
83
|
+
| three.js `GLTFLoader` | no (community plugin `three-gltf-extensions`, in progress) | LOD0 only; the mixer warns "No target node found" for the alternates' tracks (harmless) | loaded tiger_1.glb with three 0.186: 23 meshes, same bounds as lod0.glb |
|
|
84
|
+
| Godot 4.5+ | no (not in `GLTFDocument` supported list) | LOD0 only (scene roots only, `gltf/naming_version` 2 default) | Godot master `gltf_document.cpp` |
|
|
85
|
+
| Godot 4.0–4.4 (or naming_version < 2) | no | **all three overlapping**: treats every parentless node as a root. Use `lod0.glb` there | same source, `_compute_node_heights` |
|
|
86
|
+
| Unity glTFast | "not investigated yet" in its docs | expected LOD0 only (instantiates the scene) — unverified | glTFast 6.0 features page |
|
|
87
|
+
| Unreal Interchange | not documented | unverified; use `lod0/1/2.glb` and Unreal's LOD import | — |
|
|
88
|
+
| Khronos glTF-Validator | info `UNSUPPORTED_EXTENSION` | 0 errors, 0 warnings (test `src/export/lod-glb.test.ts`) | test |
|
|
89
|
+
| ArcGIS Pro | yes, node level on the scene's first node | all LODs | Esri docs |
|
|
90
|
+
|
|
91
|
+
Engines that build LOD groups from names (Unity's FBX `_LOD0/_LOD1` convention) don't apply it to glTF
|
|
92
|
+
imports, so the split files plus each engine's own LOD importer remain the portable path.
|
|
93
|
+
|
|
57
94
|
## Physics (drafts: write behind a flag, plus engine conventions)
|
|
58
95
|
|
|
59
96
|
`KHR_physics_rigid_bodies` and `KHR_collision_shapes` (formerly `KHR_implicit_shapes`) are **Khronos
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Materials: one trim sheet, many physically distinct surfaces
|
|
2
|
+
|
|
3
|
+
Every asset is still **one material, one draw call**: a shared atlas with albedo, normal and ORM
|
|
4
|
+
(occlusion, roughness, metalness). A surface looks like oak, rusty iron or chrome because of what
|
|
5
|
+
its **strip** holds. Vertex colour multiplies the albedo: it tints a classic strip and sets the paint
|
|
6
|
+
colour of a painted strip.
|
|
7
|
+
|
|
8
|
+
## Layout (v2): 2048² atlas
|
|
9
|
+
|
|
10
|
+
| V range | Contents |
|
|
11
|
+
|---|---|
|
|
12
|
+
| 0 – 0.25 | 8 **classic** strips: `plain brick stone slate iron wood glass concrete`. Greyscale, tinted by vertex colour, unchanged from v1. |
|
|
13
|
+
| 0.25 – 0.75 | 16 **library** strips with their own colour (`MATERIAL_LIBRARY`, below) |
|
|
14
|
+
| 0.75 – 0.875 | 4 **custom** rows for project materials (`defineMaterial`) |
|
|
15
|
+
| 0.875 – 1 | 128 px cells: decals (row 0), foliage cards (from slot 8) |
|
|
16
|
+
|
|
17
|
+
Each strip row is 1/32 of the sheet: 64 px at 2048², the same V resolution v1 strips had at 1024².
|
|
18
|
+
The U density doubles, and cells keep their 128 px. That is why the house sheet moved to 2048².
|
|
19
|
+
|
|
20
|
+
**Cost.** Painting takes about 4 s at 2048² (about 1 s at 1024²). The three PNGs total about 3.6 MB at 2048² (v1 at 1024² was about 1 MB).
|
|
21
|
+
GPU memory is 16 MB per map plus mips. The site's trim-sheet set is shared by every gallery asset.
|
|
22
|
+
`--sheet-size 1024` still works (32 px strip rows). The CLI default is 1024 until `src/cli/args.ts`
|
|
23
|
+
moves it to 2048.
|
|
24
|
+
|
|
25
|
+
**Compatibility.** UVs come from `stripRect()`/`cellRect()` at compile time, so geometry and triangle
|
|
26
|
+
counts are unchanged and every recipe recompiles onto the new layout. Classic strips paint byte for
|
|
27
|
+
byte as before; only their atlas position moved.
|
|
28
|
+
|
|
29
|
+
## The library and its numbers
|
|
30
|
+
|
|
31
|
+
Metal colours are the measured reflectance (F0) from [physicallybased.info](https://physicallybased.info)
|
|
32
|
+
(API: `api.physicallybased.info/materials`, which cites refractiveindex.info). Each value is
|
|
33
|
+
converted to sRGB here. Dielectric albedos stay inside the 30–240 sRGB range from the
|
|
34
|
+
[PBR guide](https://www.adobe.com/learn/substance-3d-designer/web/the-pbr-guide-part-2). Roughness
|
|
35
|
+
ranges come from the [Roblox material reference](https://create.roblox.com/docs/art/modeling/material-reference),
|
|
36
|
+
which uses glTF metal/rough conventions.
|
|
37
|
+
|
|
38
|
+
| Strip | Kind | Albedo (sRGB) / 2nd | Rough | Metal | Basis |
|
|
39
|
+
|---|---|---|---|---|---|
|
|
40
|
+
| `oak` | planks + rings + ray flecks | `#a27b52` / latewood `#6a482b` | 0.68 | 0 | worn wood 0.7–0.9, glossy 0.2–0.3 (Roblox) |
|
|
41
|
+
| `pine` | planks + strong rings, knots | `#d2b183` / `#9b6a3c` | 0.72 | 0 | same |
|
|
42
|
+
| `wood_weathered` | planks + checks (eroded grain, splits) | `#9b968c` / `#4f463c` | 0.86 | 0 | worn wood upper range |
|
|
43
|
+
| `wood_painted` | planks + paint film + chips | paint `#ededed` (×vertex colour) / bare wood `#a08c74` | wood 0.84, film 0.62 | 0 | painted surfaces are dielectric |
|
|
44
|
+
| `iron_rust` | metal + oxide + streaks | iron `#8e8a85` / rust `#764323` | 0.55 → rust ≈ 0.9 | 0.9 → 0 in rust | Iron F0 0.53 linear; Rust (0.181, 0.057, 0.017) linear, non-metal (physicallybased) |
|
|
45
|
+
| `iron_cast` | metal, sand-cast pits, oxide | `#6c6966` / `#55372a` | 0.68 | 0.75 | darker weathered iron skin |
|
|
46
|
+
| `steel_painted` | metal + paint film + chips | paint `#eeeeee` (×vertex colour) / rusty steel `#5a3b2a` | film 0.42 | 0 film, 1 under | painted metal 0.3 (Roblox) |
|
|
47
|
+
| `steel_galvanised` | metal + zinc spangle | `#d5dadc` / white rust `#9a9c98` | 0.42 | 1 | Zinc (0.808, 0.844, 0.865) linear |
|
|
48
|
+
| `brass` | metal, light brush, tarnish | `#f5e5ae` / `#6b5a3a` | 0.28 | 1 | Brass (0.910, 0.778, 0.423) linear; pure metals 0.2–0.3 |
|
|
49
|
+
| `copper` | metal + verdigris | `#f2b89c` / `#6fa58f` | 0.32 | 1 | Copper (0.932, 0.623, 0.522) linear, nudged warmer to match photographs |
|
|
50
|
+
| `aluminium` | metal, brushed along U | `#e6e8e9` | 0.34 | 1 | Aluminium (0.916, 0.923, 0.924) linear, brushed is a little darker |
|
|
51
|
+
| `chrome` | metal, near mirror | `#d3d9dc` | 0.07 | 1 | Chromium (0.654, 0.685, 0.701) linear |
|
|
52
|
+
| `rubber` | noise | `#2c2c2c` | 0.78 | 0 | Tire 0.023 linear, roughness 0.7 (physicallybased); rubber 0.5–0.6 (Roblox) |
|
|
53
|
+
| `canvas` | weave + folds + stains | `#cbbea2` / `#8a7c62` | 0.88 | 0 | cotton/canvas 0.8–0.9 |
|
|
54
|
+
| `leather` | pebble grain + creases + wear | `#7c5438` / `#3e281a` | 0.58 | 0 | worn leather 0.5–0.7 |
|
|
55
|
+
| `plastic` | quiet noise | `#ececec` (×vertex colour) | 0.35 | 0 | matte plastic 0.4, glossy 0.1 |
|
|
56
|
+
|
|
57
|
+
Unverified: the wood albedos are chosen within the dielectric range and checked against photographs
|
|
58
|
+
by eye. They are not spectrometer data, because no published wood-species albedo table was found.
|
|
59
|
+
Emissive panels have no strip: glTF emission is a separate per-material slot, not ORM. Glowing parts
|
|
60
|
+
use `glow` rules in export.
|
|
61
|
+
|
|
62
|
+
**How to colour library strips.** Use `NATURAL` (white) as the vertex colour on library strips, so
|
|
63
|
+
the measured albedo reads true. On painted strips (`wood_painted`, `steel_painted`, `plastic`) the
|
|
64
|
+
vertex colour is the paint colour. Chips multiply the bare surface by it too, so they read as
|
|
65
|
+
darker, rougher wear.
|
|
66
|
+
|
|
67
|
+
## Painters (src/material/paint/)
|
|
68
|
+
|
|
69
|
+
Every painter writes float layers in real metres: `height`, `tone`, `rough`, `metal`, `mix` (blend
|
|
70
|
+
from albedo to albedo2) and `occl`. `compose` then derives the normal (Sobel of height) and the
|
|
71
|
+
cavity AO, and packs the result. These painters are new or extended in v2:
|
|
72
|
+
|
|
73
|
+
- `planks`: `rings` (latewood colour at a frequency the strip's V texels can hold), `rays`, `checks`, `paint`/`chips`/`paintRough`.
|
|
74
|
+
- `metal`: `brush`, `cast`, `spangle`, `oxide`, `paint`/`chips`, `streaks`.
|
|
75
|
+
- `weave`, `leather` (fabric.ts).
|
|
76
|
+
|
|
77
|
+
All painters are seeded and tileable along U, so the output is deterministic.
|
|
78
|
+
|
|
79
|
+
## Project materials: `defineMaterial`
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import { defineMaterial, NATURAL, box } from '@developerz.ai/ts23d';
|
|
83
|
+
|
|
84
|
+
export const teak = defineMaterial({
|
|
85
|
+
id: 'teak_deck',
|
|
86
|
+
kind: 'planks', // start from any painter kind
|
|
87
|
+
scaleMetres: 0.6,
|
|
88
|
+
roughness: 0.6,
|
|
89
|
+
albedo: '#8a5a32', // sRGB hex or linear RGB
|
|
90
|
+
albedo2: '#5a3a1e', // latewood
|
|
91
|
+
params: { width: 0.12, rings: 0.5, knots: 0.5 },
|
|
92
|
+
// optional: your own layer on top, deterministic via kit.noise / kit.hash
|
|
93
|
+
paint: (s, kit) => kit.each((i, xm, ym) => {
|
|
94
|
+
s.tone[i] *= 1 - 0.1 * kit.smooth(0.4, 0.8, kit.noise(xm, ym, 0.5, 0.05, 1));
|
|
95
|
+
}),
|
|
96
|
+
sources: ['https://…'],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
box({ size: [4, 2, 0.05], strip: teak, color: NATURAL });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- It validates first: bad input throws `T3D_MATERIAL_INVALID`, an unknown id `T3D_MATERIAL_UNKNOWN`, a
|
|
103
|
+
built-in or differently redefined id `T3D_MATERIAL_CONFLICT`, and a fifth custom row
|
|
104
|
+
`T3D_SHEET_FULL`. A painter that writes NaN or ∞ fails the paint with `T3D_MATERIAL_INVALID`.
|
|
105
|
+
- Rows are given out in registration order. Define materials in one shared module that every recipe
|
|
106
|
+
imports, so the same program always produces the same bytes. Redefining an id with the identical
|
|
107
|
+
definition is a no-op.
|
|
108
|
+
- `DEFAULT_SHEET` paints defined materials automatically. To restyle a built-in for one project,
|
|
109
|
+
override its recipe: `paintTrimSheet({ ...DEFAULT_SHEET, strips: { ...DEFAULT_SHEET.strips, oak: {...} } })`.
|
|
110
|
+
- `materialOf(id)` returns the recipe, origin (classic/library/defined), description and sources.
|
|
111
|
+
`resetMaterials()` is for tests and long-lived workers.
|
|
112
|
+
|
|
113
|
+
## CC0 image strips (opt-in)
|
|
114
|
+
|
|
115
|
+
A strip can come from a photo-scanned CC0 PBR set instead of a painter:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
defineMaterial({ id: 'ph_wood_planks_grey', roughness: 0.85, texture: {
|
|
119
|
+
source: 'polyhaven', id: 'wood_planks_grey', license: 'CC0-1.0',
|
|
120
|
+
page: 'https://polyhaven.com/a/wood_planks_grey', author: 'Rob Tuytel', metres: [1.5, 1.5],
|
|
121
|
+
maps: { albedo: { url: '…_diff_1k.png', sha256: '…' }, normal: { … nor_gl … }, arm: { … } },
|
|
122
|
+
} });
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`CC0_WOOD_PLANKS_GREY` ships as a working example. It is not registered by default.
|
|
126
|
+
|
|
127
|
+
- **Offline compile.** `bun run textures [module…]` downloads every declared map once into
|
|
128
|
+
`node_modules/.cache/ts23d/textures/<sha256>.png` (gitignored). It checks each file's sha256 and
|
|
129
|
+
refuses a mismatch. `provideTextures(await loadTextureCache())` (from `scripts/textures.ts`) hands
|
|
130
|
+
the bytes to the painter. The library never touches the network. If a map is missing, painting
|
|
131
|
+
throws `T3D_TEXTURE_MISSING`, and the error names the command to run.
|
|
132
|
+
- **Painting.** PNGs are 8- or 16-bit (JPEG is not decoded). They are box-filtered onto the strip at
|
|
133
|
+
true scale, with whole repeats along U so the strip still tiles. V is flipped so the image's top is
|
|
134
|
+
world up. The OpenGL normal's green channel is flipped to the sheet's convention. `arm` fills
|
|
135
|
+
occlusion, roughness and metalness.
|
|
136
|
+
- **Credits.** Only CC0 licences are accepted. When an asset samples an image strip, the GLB's XMP
|
|
137
|
+
packet gets a `dc:contributor` line with the set, author, licence and page. Assets without image
|
|
138
|
+
strips are byte-identical to before.
|
|
139
|
+
- **Next (planned).** ambientCG ships its sets as zips, and zip extraction is not built yet, so
|
|
140
|
+
ambientCG sets work only when pointed at a PNG URL or a local file (`source: 'file'`). The CLI
|
|
141
|
+
does not load the cache yet (see the roadmap request).
|
|
142
|
+
|
|
143
|
+
## Seeing it: the beauty renderer
|
|
144
|
+
|
|
145
|
+
`src/render/shade.ts` reads roughness and metalness per texel from the ORM. Specular uses a
|
|
146
|
+
normalised Blinn–Phong sun lobe plus a **studio environment reflection** (`envRadiance` in
|
|
147
|
+
`lighting.ts`). The environment has a bright cyclorama horizon, a cooler zenith, a dark band where
|
|
148
|
+
the floor meets the wall, and the lit floor. Roughness blurs it toward its mean, so chrome shows the
|
|
149
|
+
horizon line, brass looks gold, and oak stays matte. Fresnel uses Schlick with a roughness-limited
|
|
150
|
+
grazing term. Glass keeps its own path. The site viewer gets the same ORM through three.js
|
|
151
|
+
`MeshStandardMaterial`.
|