@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,38 +1,50 @@
|
|
|
1
|
-
// The house sheet: every strip and cell
|
|
2
|
-
// colour carries hue)
|
|
1
|
+
// The house sheet: every strip and cell. Classic strips stay near-greyscale and bright (vertex
|
|
2
|
+
// colour carries hue); the material library strips carry measured colour. Each strip reads at its
|
|
3
|
+
// typical face height (`scaleMetres`). `HOUSE_STRIPS` is the one mutable table: defineMaterial()
|
|
4
|
+
// adds project strips to it, so `DEFAULT_SHEET` always paints every strip the layout knows.
|
|
3
5
|
|
|
6
|
+
import { MATERIAL_LIBRARY } from '../library.ts';
|
|
7
|
+
import { toStripRecipe } from '../material-def.ts';
|
|
8
|
+
import type { StripId } from '../trim-sheet.ts';
|
|
4
9
|
import type { SheetRecipe, StripRecipe } from './types.ts';
|
|
5
10
|
|
|
6
11
|
const leaf: StripRecipe = { kind: 'flat', scaleMetres: 1, roughness: 0.6 };
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
13
|
+
const library = Object.fromEntries(
|
|
14
|
+
Object.values(MATERIAL_LIBRARY).map((d) => [d.id, toStripRecipe(d)]),
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
export const HOUSE_STRIPS: Partial<Record<StripId, StripRecipe>> = {
|
|
18
|
+
plain: { kind: 'noise', scaleMetres: 1, roughness: 0.6 },
|
|
19
|
+
brick: { kind: 'bond', scaleMetres: 0.45, roughness: 0.85, params: { grime: 0.2 } },
|
|
20
|
+
stone: {
|
|
21
|
+
kind: 'bond',
|
|
22
|
+
scaleMetres: 0.9,
|
|
23
|
+
roughness: 0.9,
|
|
24
|
+
params: {
|
|
25
|
+
course: 0.22,
|
|
26
|
+
length: 0.4,
|
|
27
|
+
mortar: 0.016,
|
|
28
|
+
depth: 0.012,
|
|
29
|
+
bevel: 0.025,
|
|
30
|
+
irregular: 0.5,
|
|
31
|
+
relief: 0.004,
|
|
32
|
+
variation: 0.3,
|
|
33
|
+
grime: 0.2,
|
|
29
34
|
},
|
|
30
|
-
slate: { kind: 'slate', scaleMetres: 0.66, roughness: 0.7 },
|
|
31
|
-
iron: { kind: 'plates', scaleMetres: 0.5, roughness: 0.55, metalness: 0 },
|
|
32
|
-
wood: { kind: 'planks', scaleMetres: 0.6, roughness: 0.75 },
|
|
33
|
-
glass: { kind: 'glass', scaleMetres: 1, roughness: 0.05 },
|
|
34
|
-
concrete: { kind: 'concrete', scaleMetres: 1, roughness: 0.9, params: { grime: 0.3 } },
|
|
35
35
|
},
|
|
36
|
+
slate: { kind: 'slate', scaleMetres: 0.66, roughness: 0.7 },
|
|
37
|
+
iron: { kind: 'plates', scaleMetres: 0.5, roughness: 0.55, metalness: 0 },
|
|
38
|
+
wood: { kind: 'planks', scaleMetres: 0.6, roughness: 0.75 },
|
|
39
|
+
glass: { kind: 'glass', scaleMetres: 1, roughness: 0.05 },
|
|
40
|
+
concrete: { kind: 'concrete', scaleMetres: 1, roughness: 0.9, params: { grime: 0.3 } },
|
|
41
|
+
...library,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const DEFAULT_SHEET: SheetRecipe = {
|
|
45
|
+
seed: 1,
|
|
46
|
+
size: 2048,
|
|
47
|
+
strips: HOUSE_STRIPS,
|
|
36
48
|
cells: {
|
|
37
49
|
window_sash: { kind: 'noise', scaleMetres: 1, roughness: 0.5 },
|
|
38
50
|
door_panel: { kind: 'noise', scaleMetres: 1, roughness: 0.6 },
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// Soft goods. 'weave': canvas / duck cloth — an over-under thread lattice (warp along U, weft
|
|
2
|
+
// along V, never finer than the texel), slubbed threads, soft folds and water stains toward the
|
|
3
|
+
// bottom. 'leather': pebbled grain, creases running across the hide and burnished wear where it
|
|
4
|
+
// is handled (smoother, a little lighter, darker in the creases).
|
|
5
|
+
// params (weave): pitch 0.004 · folds 0.6 · stains 0.3
|
|
6
|
+
// params (leather): pebble 0.004 · creases 0.5 · wear 0.4
|
|
7
|
+
|
|
8
|
+
import { type Canvas, noiseAt, param, smooth } from './canvas.ts';
|
|
9
|
+
import type { StripRecipe } from './types.ts';
|
|
10
|
+
|
|
11
|
+
export function paintWeave(c: Canvas, r: StripRecipe, seed: number): void {
|
|
12
|
+
const p = r.params;
|
|
13
|
+
const pu = c.metresU / c.w;
|
|
14
|
+
const pv = c.metresV / c.h;
|
|
15
|
+
// Threads below ~2 texels alias into moiré: keep the lattice resolvable on both axes.
|
|
16
|
+
const pitchU = Math.max(param(p, 'pitch', 0.004), 2 * pu);
|
|
17
|
+
const pitchV = Math.max(param(p, 'pitch', 0.004), 2 * pv);
|
|
18
|
+
const folds = param(p, 'folds', 0.6);
|
|
19
|
+
const stains = param(p, 'stains', 0.3);
|
|
20
|
+
for (let y = 0; y < c.h; y++) {
|
|
21
|
+
const ym = (y + 0.5) * pv;
|
|
22
|
+
const wy = Math.sin((ym / pitchV) * Math.PI * 2);
|
|
23
|
+
for (let x = 0; x < c.w; x++) {
|
|
24
|
+
const i = y * c.w + x;
|
|
25
|
+
const xm = (x + 0.5) * pu;
|
|
26
|
+
const wx = Math.sin((xm / pitchU) * Math.PI * 2);
|
|
27
|
+
const over = Math.floor(xm / pitchU) + Math.floor(ym / pitchV);
|
|
28
|
+
const thread = over % 2 === 0 ? Math.abs(wy) : Math.abs(wx);
|
|
29
|
+
const slub = noiseAt(c, xm, ym, 0.05, 0.01, seed, 2);
|
|
30
|
+
const fold = noiseAt(c, xm, ym, 0.5, 0.35, seed + 1, 3);
|
|
31
|
+
const stain = stains * smooth(0.1, 0.6, noiseAt(c, xm, ym, 0.2, 0.15, seed + 2, 4));
|
|
32
|
+
c.height[i] = 0.0002 * thread + 0.003 * folds * fold;
|
|
33
|
+
c.tone[i] = (0.9 + 0.06 * thread + 0.05 * slub) * (1 - 0.25 * stain);
|
|
34
|
+
c.rough[i] = Math.min(1, r.roughness + 0.04 * slub - 0.03 * thread);
|
|
35
|
+
c.metal[i] = 0;
|
|
36
|
+
c.mix[i] = stain;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function paintLeather(c: Canvas, r: StripRecipe, seed: number): void {
|
|
42
|
+
const p = r.params;
|
|
43
|
+
const pu = c.metresU / c.w;
|
|
44
|
+
const pv = c.metresV / c.h;
|
|
45
|
+
const pebble = param(p, 'pebble', 0.004);
|
|
46
|
+
const creases = param(p, 'creases', 0.5);
|
|
47
|
+
const wear = param(p, 'wear', 0.4);
|
|
48
|
+
for (let y = 0; y < c.h; y++) {
|
|
49
|
+
const ym = (y + 0.5) * pv;
|
|
50
|
+
for (let x = 0; x < c.w; x++) {
|
|
51
|
+
const i = y * c.w + x;
|
|
52
|
+
const xm = (x + 0.5) * pu;
|
|
53
|
+
const grain = noiseAt(c, xm, ym, pebble, pebble, seed, 3);
|
|
54
|
+
const crease = smooth(0.4, 0.6, noiseAt(c, xm, ym, 0.12, 0.012, seed + 1, 3)) * creases;
|
|
55
|
+
const rub = wear * smooth(0, 0.6, noiseAt(c, xm, ym, 0.3, 0.3, seed + 2, 4));
|
|
56
|
+
c.height[i] = 0.00025 * grain - 0.0008 * crease;
|
|
57
|
+
c.tone[i] = (0.95 + 0.05 * grain) * (1 - 0.45 * crease) * (1 + 0.12 * rub);
|
|
58
|
+
c.rough[i] = Math.min(1, Math.max(0, r.roughness + 0.06 * grain - 0.2 * rub + 0.1 * crease));
|
|
59
|
+
c.metal[i] = 0;
|
|
60
|
+
c.mix[i] = crease;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Paint a strip from a CC0 texture set: decode the cached PNGs, box-filter them onto the strip's
|
|
2
|
+
// texel grid at true scale (the set's `metres`, snapped so a whole number of repeats spans the
|
|
3
|
+
// strip's U length — it still tiles), V flipped so the image's top is world up. Albedo stays sRGB
|
|
4
|
+
// colour (tint with NATURAL); the OpenGL normal's green is flipped to the sheet's +Y = −V; `arm`
|
|
5
|
+
// fills occlusion / roughness / metalness, else the recipe's constants.
|
|
6
|
+
|
|
7
|
+
import type { TextureFile, TextureSource } from '../texture-source.ts';
|
|
8
|
+
import { textureBytes } from '../texture-source.ts';
|
|
9
|
+
import type { Canvas } from './canvas.ts';
|
|
10
|
+
import { decodePng, type Rgba } from './decode-png.ts';
|
|
11
|
+
import type { StripRecipe } from './types.ts';
|
|
12
|
+
|
|
13
|
+
const decoded = new Map<string, Rgba>();
|
|
14
|
+
function image(f: TextureFile, where: string): Rgba {
|
|
15
|
+
const hit = decoded.get(f.sha256);
|
|
16
|
+
if (hit) return hit;
|
|
17
|
+
const img = decodePng(textureBytes(f, where));
|
|
18
|
+
decoded.set(f.sha256, img);
|
|
19
|
+
return img;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Average of `img` over the canvas texel footprint, per channel, into out[0..3]. */
|
|
23
|
+
function resample(
|
|
24
|
+
c: Canvas,
|
|
25
|
+
t: TextureSource,
|
|
26
|
+
img: Rgba,
|
|
27
|
+
fn: (i: number, px: Float32Array) => void,
|
|
28
|
+
) {
|
|
29
|
+
const reps = Math.max(1, Math.round(c.metresU / (t.metres[0] ?? 1)));
|
|
30
|
+
const su = c.metresU / reps;
|
|
31
|
+
const sv = t.metres[1] ?? 1;
|
|
32
|
+
const W = img.width;
|
|
33
|
+
const H = img.height;
|
|
34
|
+
const px = new Float32Array(4);
|
|
35
|
+
for (let y = 0; y < c.h; y++) {
|
|
36
|
+
const v0 = ((y * c.metresV) / c.h / sv) * H;
|
|
37
|
+
const v1 = (((y + 1) * c.metresV) / c.h / sv) * H;
|
|
38
|
+
for (let x = 0; x < c.w; x++) {
|
|
39
|
+
const u0 = ((x * c.metresU) / c.w / su) * W;
|
|
40
|
+
const u1 = (((x + 1) * c.metresU) / c.w / su) * W;
|
|
41
|
+
px.fill(0);
|
|
42
|
+
let n = 0;
|
|
43
|
+
for (let sy = Math.floor(v0); sy < Math.max(Math.floor(v0) + 1, Math.ceil(v1)); sy++) {
|
|
44
|
+
const row = H - 1 - (((sy % H) + H) % H);
|
|
45
|
+
for (let sx = Math.floor(u0); sx < Math.max(Math.floor(u0) + 1, Math.ceil(u1)); sx++) {
|
|
46
|
+
const o = (row * W + (((sx % W) + W) % W)) * 4;
|
|
47
|
+
for (let k = 0; k < 4; k++) px[k] = (px[k] ?? 0) + (img.data[o + k] ?? 0);
|
|
48
|
+
n++;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
for (let k = 0; k < 4; k++) px[k] = (px[k] ?? 0) / n;
|
|
52
|
+
fn(y * c.w + x, px);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function paintImageStrip(c: Canvas, r: StripRecipe, t: TextureSource, where: string): void {
|
|
58
|
+
const albedo = new Uint8Array(c.w * c.h * 4);
|
|
59
|
+
resample(c, t, image(t.maps.albedo, where), (i, p) => {
|
|
60
|
+
albedo.set([p[0] ?? 0, p[1] ?? 0, p[2] ?? 0, 255], i * 4);
|
|
61
|
+
});
|
|
62
|
+
c.rgba = albedo;
|
|
63
|
+
c.height.fill(0);
|
|
64
|
+
if (t.maps.normal) {
|
|
65
|
+
const nrm = new Uint8Array(c.w * c.h * 4);
|
|
66
|
+
resample(c, t, image(t.maps.normal, where), (i, p) => {
|
|
67
|
+
const x = (p[0] ?? 128) / 127.5 - 1;
|
|
68
|
+
const y = -((p[1] ?? 128) / 127.5 - 1);
|
|
69
|
+
const z = (p[2] ?? 255) / 127.5 - 1;
|
|
70
|
+
const l = Math.hypot(x, y, z) || 1;
|
|
71
|
+
nrm.set(
|
|
72
|
+
[(x / l) * 127.5 + 127.5, (y / l) * 127.5 + 127.5, (z / l) * 127.5 + 127.5, 255],
|
|
73
|
+
i * 4,
|
|
74
|
+
);
|
|
75
|
+
});
|
|
76
|
+
c.normalRgba = nrm;
|
|
77
|
+
}
|
|
78
|
+
if (t.maps.arm) {
|
|
79
|
+
resample(c, t, image(t.maps.arm, where), (i, p) => {
|
|
80
|
+
c.occl[i] = (p[0] ?? 255) / 255;
|
|
81
|
+
c.rough[i] = (p[1] ?? 204) / 255;
|
|
82
|
+
c.metal[i] = (p[2] ?? 0) / 255;
|
|
83
|
+
});
|
|
84
|
+
} else {
|
|
85
|
+
c.rough.fill(r.roughness);
|
|
86
|
+
c.metal.fill(r.metalness ?? 0);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// The helper kit a project painter (`defineMaterial({ paint })`) receives, and the post-check that
|
|
2
|
+
// its layers stayed finite: a NaN written by user code fails loudly instead of corrupting a sheet.
|
|
3
|
+
|
|
4
|
+
import { Ts23dError } from '../../core/index.ts';
|
|
5
|
+
import { type Canvas, noiseAt, smooth } from './canvas.ts';
|
|
6
|
+
import { hash2 } from './noise.ts';
|
|
7
|
+
import type { PaintKit } from './types.ts';
|
|
8
|
+
|
|
9
|
+
export function paintKit(c: Canvas, seed: number): PaintKit {
|
|
10
|
+
const pu = c.metresU / c.w;
|
|
11
|
+
const pv = c.metresV / c.h;
|
|
12
|
+
return {
|
|
13
|
+
seed,
|
|
14
|
+
noise: (xm, ym, sizeU, sizeV, salt = 0, octaves = 4) =>
|
|
15
|
+
noiseAt(c, xm, ym, sizeU, sizeV, seed + salt * 7919, octaves),
|
|
16
|
+
hash: (a, b, salt = 0) => hash2(a, b, seed + salt),
|
|
17
|
+
smooth,
|
|
18
|
+
each(fn) {
|
|
19
|
+
for (let y = 0; y < c.h; y++) {
|
|
20
|
+
const ym = (y + 0.5) * pv;
|
|
21
|
+
for (let x = 0; x < c.w; x++) fn(y * c.w + x, (x + 0.5) * pu, ym);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Throw T3D_MATERIAL_INVALID if any layer holds a non-finite value. */
|
|
28
|
+
export function checkFinite(c: Canvas, where: string): void {
|
|
29
|
+
const layers = { height: c.height, tone: c.tone, rough: c.rough, metal: c.metal, mix: c.mix };
|
|
30
|
+
for (const [name, a] of Object.entries(layers))
|
|
31
|
+
for (let i = 0; i < a.length; i++)
|
|
32
|
+
if (!Number.isFinite(a[i] ?? 0))
|
|
33
|
+
throw new Ts23dError('T3D_MATERIAL_INVALID', {
|
|
34
|
+
cause: `${where}: the painter wrote ${a[i]} into '${name}' at texel ${i}`,
|
|
35
|
+
fix: 'write finite numbers only (guard divisions; clamp with Math.min/Math.max)',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Weathering masks applied over a painted canvas: rust streaks (vertical runs from a patchy
|
|
2
2
|
// source, darker and rougher, killing metalness) and grime (dirt pooling toward the bottom of the
|
|
3
|
-
// region — world-down is low V — plus soft vertical rain streaks).
|
|
3
|
+
// region — world-down is low V — plus soft vertical rain streaks). Rust also pushes `mix` toward
|
|
4
|
+
// albedo2, which only shows on material strips that carry a colour pair.
|
|
4
5
|
|
|
5
6
|
import { type Canvas, noiseAt, smooth } from './canvas.ts';
|
|
6
7
|
|
|
@@ -20,6 +21,7 @@ export function rustStreaks(c: Canvas, amount: number, seed: number): void {
|
|
|
20
21
|
c.tone[i] = (c.tone[i] ?? 1) * (1 - 0.4 * m);
|
|
21
22
|
c.rough[i] = Math.min(1, (c.rough[i] ?? 0.8) + 0.2 * m);
|
|
22
23
|
c.metal[i] = (c.metal[i] ?? 0) * (1 - m);
|
|
24
|
+
c.mix[i] = Math.min(1, (c.mix[i] ?? 0) + m);
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Bare and finished metal sheet (kind 'metal'): a continuous metallic base with polish mottling,
|
|
2
|
+
// then optional finishes, each a param in 0..1 — `brush` (fine streaks along U, brushed aluminium),
|
|
3
|
+
// `cast` (sand-cast pits and a rougher skin), `spangle` (galvanised zinc crystals), `oxide`
|
|
4
|
+
// (patches of rust / verdigris: blend to albedo2, dielectric, rough, slightly raised) and `paint`
|
|
5
|
+
// (a dielectric paint film over it whose `chips` reveal the oxidised metal). Rust runs down from
|
|
6
|
+
// the oxide (`streaks`).
|
|
7
|
+
// params: brush 0 · cast 0 · spangle 0 · oxide 0 · paint 0 · paintRough 0.45 · chips 0.25
|
|
8
|
+
// crystal 0.03 (spangle size, m) · streaks 0
|
|
9
|
+
|
|
10
|
+
import { type Canvas, noiseAt, param, smooth } from './canvas.ts';
|
|
11
|
+
import { rustStreaks } from './masks.ts';
|
|
12
|
+
import { hash2 } from './noise.ts';
|
|
13
|
+
import type { StripRecipe } from './types.ts';
|
|
14
|
+
|
|
15
|
+
/** Nearest jittered grid point id on a `size` m lattice that wraps after `wrapU` metres. */
|
|
16
|
+
function crystal(xm: number, ym: number, size: number, cellsU: number, seed: number): number {
|
|
17
|
+
const gx = Math.floor(xm / size);
|
|
18
|
+
const gy = Math.floor(ym / size);
|
|
19
|
+
let best = 1e9;
|
|
20
|
+
let id = 0;
|
|
21
|
+
for (let j = -1; j <= 1; j++)
|
|
22
|
+
for (let i = -1; i <= 1; i++) {
|
|
23
|
+
const cx = (((gx + i) % cellsU) + cellsU) % cellsU;
|
|
24
|
+
const px = (gx + i + hash2(cx, gy + j, seed)) * size;
|
|
25
|
+
const py = (gy + j + hash2(cx, gy + j, seed + 1)) * size;
|
|
26
|
+
const d = (xm - px) ** 2 + (ym - py) ** 2;
|
|
27
|
+
if (d < best) {
|
|
28
|
+
best = d;
|
|
29
|
+
id = hash2(cx, gy + j, seed + 2) * 1e6;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return id;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function paintMetal(c: Canvas, r: StripRecipe, seed: number): void {
|
|
36
|
+
const p = r.params;
|
|
37
|
+
const brush = param(p, 'brush', 0);
|
|
38
|
+
const cast = param(p, 'cast', 0);
|
|
39
|
+
const spangle = param(p, 'spangle', 0);
|
|
40
|
+
const oxide = param(p, 'oxide', 0);
|
|
41
|
+
const paint = param(p, 'paint', 0);
|
|
42
|
+
const paintRough = param(p, 'paintRough', 0.45);
|
|
43
|
+
const chips = param(p, 'chips', 0.25);
|
|
44
|
+
const size = param(p, 'crystal', 0.03);
|
|
45
|
+
const metal = r.metalness ?? 1;
|
|
46
|
+
const cellsU = Math.max(1, Math.round(c.metresU / size));
|
|
47
|
+
const cs = c.metresU / cellsU;
|
|
48
|
+
const pu = c.metresU / c.w;
|
|
49
|
+
const pv = c.metresV / c.h;
|
|
50
|
+
for (let y = 0; y < c.h; y++) {
|
|
51
|
+
const ym = (y + 0.5) * pv;
|
|
52
|
+
for (let x = 0; x < c.w; x++) {
|
|
53
|
+
const i = y * c.w + x;
|
|
54
|
+
const xm = (x + 0.5) * pu;
|
|
55
|
+
const big = noiseAt(c, xm, ym, 0.25, 0.25, seed, 4);
|
|
56
|
+
const fine = noiseAt(c, xm, ym, 0.01, 0.01, seed + 1, 2);
|
|
57
|
+
let tone = 1 + 0.05 * big;
|
|
58
|
+
let rough = r.roughness + 0.06 * big + 0.03 * fine;
|
|
59
|
+
let h = 0.00015 * big;
|
|
60
|
+
if (brush > 0) {
|
|
61
|
+
const s = noiseAt(c, xm, ym, 0.6, 0.002, seed + 2, 3);
|
|
62
|
+
tone *= 1 + 0.06 * brush * s;
|
|
63
|
+
rough += 0.08 * brush * s;
|
|
64
|
+
h += 0.00003 * brush * s;
|
|
65
|
+
}
|
|
66
|
+
if (cast > 0) {
|
|
67
|
+
const pit = noiseAt(c, xm, ym, 0.012, 0.012, seed + 3, 3);
|
|
68
|
+
const lump = noiseAt(c, xm, ym, 0.08, 0.08, seed + 4, 3);
|
|
69
|
+
h += cast * (0.0006 * pit + 0.0008 * lump);
|
|
70
|
+
rough += cast * (0.1 + 0.12 * pit);
|
|
71
|
+
tone *= 1 + cast * (0.16 * pit + 0.08 * lump - 0.04);
|
|
72
|
+
}
|
|
73
|
+
if (spangle > 0) {
|
|
74
|
+
const k = crystal(xm, ym, cs, cellsU, seed + 5);
|
|
75
|
+
const v = hash2(k, 7, seed) * 2 - 1;
|
|
76
|
+
tone *= 1 + 0.12 * spangle * v;
|
|
77
|
+
rough += 0.12 * spangle * v;
|
|
78
|
+
}
|
|
79
|
+
// Oxide: patchy, gathering in low-frequency blotches; dielectric, rough and a touch proud.
|
|
80
|
+
let ox = 0;
|
|
81
|
+
if (oxide > 0) {
|
|
82
|
+
const blot = noiseAt(c, xm, ym, 0.18, 0.18, seed + 6, 5);
|
|
83
|
+
const grain = noiseAt(c, xm, ym, 0.02, 0.02, seed + 7, 3);
|
|
84
|
+
ox = smooth(0.45, 0.75, blot * 0.5 + 0.5 + oxide - 0.5 + 0.15 * grain);
|
|
85
|
+
h += ox * 0.0004 * (1 + grain);
|
|
86
|
+
}
|
|
87
|
+
let m = metal * (1 - ox);
|
|
88
|
+
let mix = ox;
|
|
89
|
+
rough = rough * (1 - ox) + (0.88 + 0.06 * fine) * ox;
|
|
90
|
+
if (paint > 0) {
|
|
91
|
+
// Chips gather where the noise peaks; the film is everywhere else.
|
|
92
|
+
const n = noiseAt(c, xm, ym, 0.05, 0.05, seed + 8, 4);
|
|
93
|
+
const chip = smooth(0.42, 0.5, n * 0.5 + 0.5 - (1 - chips) * 0.5 + 0.1);
|
|
94
|
+
const film = paint * (1 - chip);
|
|
95
|
+
m *= 1 - film;
|
|
96
|
+
mix = mix * (1 - film) + chip * paint;
|
|
97
|
+
rough = rough * (1 - film) + (paintRough + 0.05 * big) * film;
|
|
98
|
+
tone = tone * (1 - film) + (0.96 + 0.03 * big) * film;
|
|
99
|
+
h -= chip * paint * 0.00015;
|
|
100
|
+
}
|
|
101
|
+
c.height[i] = h;
|
|
102
|
+
c.tone[i] = tone;
|
|
103
|
+
c.rough[i] = Math.min(1, Math.max(0, rough));
|
|
104
|
+
c.metal[i] = Math.min(1, Math.max(0, m));
|
|
105
|
+
c.mix[i] = mix;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
rustStreaks(c, param(p, 'streaks', 0), seed + 77);
|
|
109
|
+
}
|
|
@@ -79,10 +79,23 @@ export function packOrm(c: Canvas, ao: Float32Array): Uint8Array {
|
|
|
79
79
|
return out;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
const enc = (t: number): number => SRGB[t <= 0 ? 0 : t >= 1 ? 4095 : Math.round(t * 4095)] ?? 255;
|
|
83
|
+
|
|
84
|
+
/** RGBA8 sRGB albedo: the stamped image if any, else (colour pair by mix or grey) × tone × cavity. */
|
|
83
85
|
export function packAlbedo(c: Canvas, ao: Float32Array): Uint8Array {
|
|
84
86
|
if (c.rgba) return c.rgba;
|
|
85
87
|
const out = new Uint8Array(c.w * c.h * 4);
|
|
88
|
+
const pair = c.colours;
|
|
89
|
+
if (pair) {
|
|
90
|
+
const [a, b] = pair;
|
|
91
|
+
for (let i = 0, o = 0; i < ao.length; i++, o += 4) {
|
|
92
|
+
const t = (c.tone[i] ?? 1) * (0.8 + 0.2 * (ao[i] ?? 1));
|
|
93
|
+
const m = Math.min(1, Math.max(0, c.mix[i] ?? 0));
|
|
94
|
+
for (let k = 0; k < 3; k++) out[o + k] = enc(((a[k] ?? 1) * (1 - m) + (b[k] ?? 1) * m) * t);
|
|
95
|
+
out[o + 3] = 255;
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
86
99
|
for (let i = 0, o = 0; i < ao.length; i++, o += 4) {
|
|
87
100
|
const t = (c.tone[i] ?? 1) * (0.8 + 0.2 * (ao[i] ?? 1));
|
|
88
101
|
const g = SRGB[t <= 0 ? 0 : t >= 1 ? 4095 : Math.round(t * 4095)] ?? 255;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// Timber boards (kind 'planks'): boards run along U, butt joints staggered at random, dark gaps,
|
|
2
|
-
// stretched grain with growth-ring streaks, occasional knots, per-board tone.
|
|
2
|
+
// stretched grain with growth-ring streaks, occasional knots, per-board tone. Species and finish
|
|
3
|
+
// come from params on top: `rings` blends latewood to albedo2, `rays` adds oak's ray flecks,
|
|
4
|
+
// `checks` weathers (eroded soft grain, splits along it, silvering), `paint` lays a film over the
|
|
5
|
+
// grain whose `chips` reveal bare wood (albedo2), mostly along board edges.
|
|
3
6
|
// params: width 0.15 · length 1.0 · gap 0.004 · depth 0.004 · knots 1.5 (per board-metre)
|
|
7
|
+
// rings 0 · rays 0 · checks 0 · paint 0 · paintRough 0.5 · chips 0.3
|
|
4
8
|
|
|
5
9
|
import { type Canvas, noiseAt, param, smooth } from './canvas.ts';
|
|
6
10
|
import { Courses } from './courses.ts';
|
|
@@ -14,9 +18,16 @@ export function paintPlanks(c: Canvas, r: StripRecipe, seed: number): void {
|
|
|
14
18
|
const gap = param(p, 'gap', 0.004);
|
|
15
19
|
const depth = param(p, 'depth', 0.004);
|
|
16
20
|
const knots = param(p, 'knots', 1.5);
|
|
21
|
+
const rings = param(p, 'rings', 0);
|
|
22
|
+
const rays = param(p, 'rays', 0);
|
|
23
|
+
const checks = param(p, 'checks', 0);
|
|
24
|
+
const paint = param(p, 'paint', 0);
|
|
25
|
+
const paintRough = param(p, 'paintRough', 0.5);
|
|
26
|
+
const chips = param(p, 'chips', 0.3);
|
|
17
27
|
const boards = new Courses(c, { course: width, length, irregular: 0.6, stagger: 0, seed });
|
|
18
28
|
const pu = c.metresU / c.w;
|
|
19
29
|
const pv = c.metresV / c.h;
|
|
30
|
+
const ringsN = Math.max(1, Math.min(9, Math.floor(width / pv / 4)));
|
|
20
31
|
for (let y = 0; y < c.h; y++) {
|
|
21
32
|
const ym = (y + 0.5) * pv;
|
|
22
33
|
const k = boards.at(ym);
|
|
@@ -44,13 +55,47 @@ export function paintPlanks(c: Canvas, r: StripRecipe, seed: number): void {
|
|
|
44
55
|
const rr = Math.sqrt((ddx / 2.2) ** 2 + (ym - ky) ** 2) / (width * 0.12);
|
|
45
56
|
if (rr < 1) knot = Math.max(knot, 1 - rr);
|
|
46
57
|
}
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
let height = -depth * (1 - edge) + 0.0004 * ring * edge + 0.0003 * fibre - 0.0008 * knot;
|
|
59
|
+
let tone =
|
|
49
60
|
(0.66 + 0.22 * h) *
|
|
50
61
|
(1 + 0.08 * ring + 0.06 * fibre) *
|
|
51
62
|
(1 - 0.5 * knot) *
|
|
52
63
|
(0.5 + 0.5 * edge);
|
|
53
|
-
|
|
64
|
+
let rough = r.roughness + 0.05 * fibre + 0.1 * (1 - edge);
|
|
65
|
+
// Colour rings at a frequency the strip's V texels can hold (≥ ~4 texels per ring).
|
|
66
|
+
const ring2 = Math.sin((vy * ringsN + warp * 2.5 + h * 6) * Math.PI * 2);
|
|
67
|
+
const late = smooth(0.1, 0.8, ring2) * (0.75 + 0.25 * fibre);
|
|
68
|
+
let mix = rings * Math.max(late, knot);
|
|
69
|
+
if (rays > 0) {
|
|
70
|
+
const fleck = smooth(0.55, 0.8, noiseAt(c, xm, ym, 0.03, 0.004, seed + 23, 2));
|
|
71
|
+
tone *= 1 + 0.25 * rays * fleck;
|
|
72
|
+
mix *= 1 - rays * fleck;
|
|
73
|
+
}
|
|
74
|
+
if (checks > 0) {
|
|
75
|
+
// Soft earlywood erodes, leaving latewood ridges; splits open along the grain.
|
|
76
|
+
const split = smooth(0.45, 0.6, noiseAt(c, xm, ym, 0.3, 0.003, seed + 29 + id, 3));
|
|
77
|
+
height += checks * (0.0006 * late - 0.002 * split);
|
|
78
|
+
tone *= 1 - checks * (0.45 * split + 0.08 * (1 - late));
|
|
79
|
+
rough += checks * 0.1 * (1 - late);
|
|
80
|
+
mix = Math.max(mix, checks * split);
|
|
81
|
+
}
|
|
82
|
+
if (paint > 0) {
|
|
83
|
+
// Paint fails first at board edges and ends, then in blotches across the face.
|
|
84
|
+
const n = noiseAt(c, xm, ym, 0.04, 0.03, seed + 31, 4) * 0.5 + 0.5;
|
|
85
|
+
const nearEdge = 1 - smooth(gap, gap * 6, d);
|
|
86
|
+
const chip = smooth(0.5, 0.56, n + chips * 0.5 + nearEdge * 0.15 - 0.45);
|
|
87
|
+
const film = paint * (1 - chip);
|
|
88
|
+
height = height * (1 - 0.4 * film) - chip * paint * 0.0002;
|
|
89
|
+
// The film follows the grain: board tone and rings still read through it, faintly.
|
|
90
|
+
const under = (0.9 + 0.06 * h) * (1 + 0.05 * ring + 0.04 * fibre) * (0.55 + 0.45 * edge);
|
|
91
|
+
tone = tone * (1 - film) + under * film;
|
|
92
|
+
rough = rough * (1 - film) + (paintRough + 0.04 * fibre) * film;
|
|
93
|
+
mix = mix * (1 - film) + paint * chip;
|
|
94
|
+
}
|
|
95
|
+
c.height[i] = height;
|
|
96
|
+
c.tone[i] = tone;
|
|
97
|
+
c.rough[i] = Math.min(1, rough);
|
|
98
|
+
c.mix[i] = mix;
|
|
54
99
|
}
|
|
55
100
|
}
|
|
56
101
|
}
|
|
@@ -1,42 +1,48 @@
|
|
|
1
|
-
// Dispatch a StripRecipe to its painter, then the shared grime pass (`params.grime`, 0..1)
|
|
1
|
+
// Dispatch a StripRecipe to its painter, then the shared grime pass (`params.grime`, 0..1), then
|
|
2
|
+
// the recipe's own `paint` layer (project materials), checked for finite output. A `texture`
|
|
3
|
+
// recipe paints from its cached CC0 image set instead of a kind (no grime: the photo has its own).
|
|
2
4
|
|
|
3
5
|
import { paintBond } from './bond.ts';
|
|
4
6
|
import type { Canvas } from './canvas.ts';
|
|
5
7
|
import { param } from './canvas.ts';
|
|
6
8
|
import { paintConcrete } from './concrete.ts';
|
|
9
|
+
import { paintLeather, paintWeave } from './fabric.ts';
|
|
10
|
+
import { paintImageStrip } from './image-strip.ts';
|
|
11
|
+
import { checkFinite, paintKit } from './kit.ts';
|
|
7
12
|
import { grime } from './masks.ts';
|
|
13
|
+
import { paintMetal } from './metal.ts';
|
|
8
14
|
import { paintPlanks } from './planks.ts';
|
|
9
15
|
import { paintPlates } from './plates.ts';
|
|
10
16
|
import { paintSlate } from './slate.ts';
|
|
11
17
|
import { paintFlat, paintGlass, paintNoise } from './surface.ts';
|
|
12
|
-
import type { StripRecipe } from './types.ts';
|
|
18
|
+
import type { StripKind, StripRecipe } from './types.ts';
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
20
|
+
type Painter = (c: Canvas, r: StripRecipe, seed: number) => void;
|
|
21
|
+
|
|
22
|
+
const PAINTERS: Readonly<Record<StripKind, Painter>> = {
|
|
23
|
+
bond: paintBond,
|
|
24
|
+
planks: paintPlanks,
|
|
25
|
+
plates: paintPlates,
|
|
26
|
+
slate: paintSlate,
|
|
27
|
+
concrete: paintConcrete,
|
|
28
|
+
metal: paintMetal,
|
|
29
|
+
weave: paintWeave,
|
|
30
|
+
leather: paintLeather,
|
|
31
|
+
noise: paintNoise,
|
|
32
|
+
glass: paintGlass,
|
|
33
|
+
flat: (c, r) => paintFlat(c, r),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export function paintStrip(c: Canvas, r: StripRecipe, seed: number, where = 'strip'): void {
|
|
37
|
+
if (r.texture) paintImageStrip(c, r, r.texture, where);
|
|
38
|
+
else {
|
|
39
|
+
if (r.albedo || r.albedo2)
|
|
40
|
+
c.colours = [r.albedo ?? [1, 1, 1], r.albedo2 ?? r.albedo ?? [1, 1, 1]];
|
|
41
|
+
PAINTERS[r.kind](c, r, seed);
|
|
42
|
+
if (r.kind !== 'glass' && r.kind !== 'flat') grime(c, param(r.params, 'grime', 0), seed + 101);
|
|
43
|
+
}
|
|
44
|
+
if (r.paint) {
|
|
45
|
+
r.paint(c, paintKit(c, seed + 211));
|
|
46
|
+
checkFinite(c, where);
|
|
40
47
|
}
|
|
41
|
-
grime(c, param(r.params, 'grime', 0), seed + 101);
|
|
42
48
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
// The generator's public data contract (re-exported by generator.ts): what a sheet recipe says
|
|
2
2
|
// and what the painter returns.
|
|
3
3
|
|
|
4
|
+
import type { Vec3 } from '../../core/index.ts';
|
|
5
|
+
import type { TextureSource } from '../texture-source.ts';
|
|
4
6
|
import type { CellId, StripId } from '../trim-sheet.ts';
|
|
7
|
+
import type { Canvas } from './canvas.ts';
|
|
5
8
|
|
|
6
9
|
export interface PaintedSheet {
|
|
7
10
|
readonly size: number;
|
|
8
|
-
/** RGBA8, sRGB.
|
|
11
|
+
/** RGBA8, sRGB. Classic strips are greyscale (vertex colour carries hue); materials carry colour. */
|
|
9
12
|
readonly albedo: Uint8Array;
|
|
10
13
|
/** RGBA8, tangent-space (glTF / OpenGL, +Y toward decreasing V). */
|
|
11
14
|
readonly normal: Uint8Array;
|
|
@@ -21,7 +24,39 @@ export type StripKind =
|
|
|
21
24
|
| 'flat'
|
|
22
25
|
| 'slate'
|
|
23
26
|
| 'concrete'
|
|
24
|
-
| 'glass'
|
|
27
|
+
| 'glass'
|
|
28
|
+
| 'metal'
|
|
29
|
+
| 'weave'
|
|
30
|
+
| 'leather';
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The surface a painter writes, one float per texel per layer, in real metres. Row y grows with
|
|
34
|
+
* world up (+V); strips wrap along x. Layers: `height` (metres, + is out), `tone` (linear
|
|
35
|
+
* brightness multiplier), `rough` and `metal` (0–1), `mix` (0 = albedo, 1 = albedo2), `occl`.
|
|
36
|
+
*/
|
|
37
|
+
export type MaterialSurface = Canvas;
|
|
38
|
+
|
|
39
|
+
/** Helpers handed to a custom painter: seeded, tileable noise and smoothstep, all in metres. */
|
|
40
|
+
export interface PaintKit {
|
|
41
|
+
readonly seed: number;
|
|
42
|
+
/** Tileable fBm in [−1, 1] with feature size `sizeU` × `sizeV` metres; `salt` varies it. */
|
|
43
|
+
noise(
|
|
44
|
+
xm: number,
|
|
45
|
+
ym: number,
|
|
46
|
+
sizeU: number,
|
|
47
|
+
sizeV: number,
|
|
48
|
+
salt?: number,
|
|
49
|
+
octaves?: number,
|
|
50
|
+
): number;
|
|
51
|
+
/** Integer hash in [0, 1). */
|
|
52
|
+
hash(a: number, b: number, salt?: number): number;
|
|
53
|
+
smooth(e0: number, e1: number, v: number): number;
|
|
54
|
+
/** Visit every texel: index into the layers and its centre in metres. */
|
|
55
|
+
each(fn: (i: number, xm: number, ym: number) => void): void;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A project painter: runs after the base `kind`, layering its own pattern over it. */
|
|
59
|
+
export type MaterialPainter = (s: MaterialSurface, kit: PaintKit) => void;
|
|
25
60
|
|
|
26
61
|
export interface StripRecipe {
|
|
27
62
|
readonly kind: StripKind;
|
|
@@ -34,6 +69,14 @@ export interface StripRecipe {
|
|
|
34
69
|
readonly metalness?: number;
|
|
35
70
|
/** Brick/plank courses, rivet pitch, etc. — read per kind (see each painter's header). */
|
|
36
71
|
readonly params?: Readonly<Record<string, number>>;
|
|
72
|
+
/** Intrinsic linear albedo; omitted = white (greyscale strip, vertex colour carries hue). */
|
|
73
|
+
readonly albedo?: Vec3;
|
|
74
|
+
/** Second linear albedo the painter blends to through `mix` (latewood, rust, bare wood…). */
|
|
75
|
+
readonly albedo2?: Vec3;
|
|
76
|
+
/** Extra layer painted after `kind` (see `defineMaterial`). */
|
|
77
|
+
readonly paint?: MaterialPainter;
|
|
78
|
+
/** Paint from a cached CC0 texture set instead of `kind` (see texture-source.ts). */
|
|
79
|
+
readonly texture?: TextureSource;
|
|
37
80
|
}
|
|
38
81
|
|
|
39
82
|
/** A cell stamped from PNG bytes (8-bit grey/RGB/RGBA, non-interlaced), e.g. a kubeez.com decal. */
|