@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,8 +1,590 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Ts23dError2,
|
|
3
3
|
createRng2
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-hg7pf34j.js";
|
|
5
5
|
|
|
6
|
+
// src/material/cc0.ts
|
|
7
|
+
var PH = "https://dl.polyhaven.org/file/ph-assets/Textures/png/1k/wood_planks_grey";
|
|
8
|
+
var CC0_WOOD_PLANKS_GREY2 = {
|
|
9
|
+
id: "ph_wood_planks_grey",
|
|
10
|
+
kind: "flat",
|
|
11
|
+
scaleMetres: 0.6,
|
|
12
|
+
roughness: 0.85,
|
|
13
|
+
description: "Photo-scanned weathered grey planks (Poly Haven, CC0).",
|
|
14
|
+
sources: ["https://polyhaven.com/a/wood_planks_grey"],
|
|
15
|
+
texture: {
|
|
16
|
+
source: "polyhaven",
|
|
17
|
+
id: "wood_planks_grey",
|
|
18
|
+
license: "CC0-1.0",
|
|
19
|
+
page: "https://polyhaven.com/a/wood_planks_grey",
|
|
20
|
+
author: "Rob Tuytel",
|
|
21
|
+
metres: [1.5, 1.5],
|
|
22
|
+
maps: {
|
|
23
|
+
albedo: {
|
|
24
|
+
url: `${PH}/wood_planks_grey_diff_1k.png`,
|
|
25
|
+
sha256: "3e6aeb842edd813759fdc96a6ffe6b695c888280f42501de589442311749ac5d"
|
|
26
|
+
},
|
|
27
|
+
normal: {
|
|
28
|
+
url: `${PH}/wood_planks_grey_nor_gl_1k.png`,
|
|
29
|
+
sha256: "a9f2ad602eca2d59e84f203b1087e819e62ae517532bb25f7eb02a13c826e08b"
|
|
30
|
+
},
|
|
31
|
+
arm: {
|
|
32
|
+
url: `${PH}/wood_planks_grey_arm_1k.png`,
|
|
33
|
+
sha256: "9b81e9a0b4f9db0a920c4c2ce8ff13e0411b3624d4de455acdabe41e3fafe20e"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var CC0_MATERIALS2 = [CC0_WOOD_PLANKS_GREY2];
|
|
39
|
+
// src/material/library.ts
|
|
40
|
+
var PB = "https://physicallybased.info";
|
|
41
|
+
var ROBLOX = "https://create.roblox.com/docs/art/modeling/material-reference";
|
|
42
|
+
var PBR_GUIDE = "https://www.adobe.com/learn/substance-3d-designer/web/the-pbr-guide-part-2";
|
|
43
|
+
var MATERIAL_LIBRARY2 = {
|
|
44
|
+
oak: {
|
|
45
|
+
id: "oak",
|
|
46
|
+
kind: "planks",
|
|
47
|
+
scaleMetres: 0.6,
|
|
48
|
+
roughness: 0.68,
|
|
49
|
+
albedo: "#a27b52",
|
|
50
|
+
albedo2: "#6a482b",
|
|
51
|
+
params: { width: 0.14, rings: 0.55, rays: 0.6, knots: 0.6 },
|
|
52
|
+
description: "Oak boards: warm brown, dark latewood rings, pale ray flecks.",
|
|
53
|
+
sources: [PBR_GUIDE, ROBLOX]
|
|
54
|
+
},
|
|
55
|
+
pine: {
|
|
56
|
+
id: "pine",
|
|
57
|
+
kind: "planks",
|
|
58
|
+
scaleMetres: 0.6,
|
|
59
|
+
roughness: 0.72,
|
|
60
|
+
albedo: "#d2b183",
|
|
61
|
+
albedo2: "#9b6a3c",
|
|
62
|
+
params: { width: 0.16, rings: 0.75, knots: 2.5 },
|
|
63
|
+
description: "Pine boards: pale yellow, strong orange latewood, frequent knots.",
|
|
64
|
+
sources: [PBR_GUIDE, ROBLOX]
|
|
65
|
+
},
|
|
66
|
+
wood_weathered: {
|
|
67
|
+
id: "wood_weathered",
|
|
68
|
+
kind: "planks",
|
|
69
|
+
scaleMetres: 0.6,
|
|
70
|
+
roughness: 0.86,
|
|
71
|
+
albedo: "#9b968c",
|
|
72
|
+
albedo2: "#4f463c",
|
|
73
|
+
params: { width: 0.2, rings: 0.35, checks: 1, knots: 1.5, grime: 0.15 },
|
|
74
|
+
description: "Barn-grey weathered boards: silvered, eroded grain, open checks.",
|
|
75
|
+
sources: [ROBLOX]
|
|
76
|
+
},
|
|
77
|
+
wood_painted: {
|
|
78
|
+
id: "wood_painted",
|
|
79
|
+
kind: "planks",
|
|
80
|
+
scaleMetres: 0.6,
|
|
81
|
+
roughness: 0.84,
|
|
82
|
+
albedo: "#ededed",
|
|
83
|
+
albedo2: "#a08c74",
|
|
84
|
+
params: { width: 0.14, paint: 1, paintRough: 0.62, chips: 0.38, knots: 0.8, grime: 0.3 },
|
|
85
|
+
description: "Painted boards (vertex colour = paint) worn through to grey wood at the edges.",
|
|
86
|
+
sources: [ROBLOX]
|
|
87
|
+
},
|
|
88
|
+
iron_rust: {
|
|
89
|
+
id: "iron_rust",
|
|
90
|
+
kind: "metal",
|
|
91
|
+
scaleMetres: 0.5,
|
|
92
|
+
roughness: 0.55,
|
|
93
|
+
metalness: 0.9,
|
|
94
|
+
albedo: "#8e8a85",
|
|
95
|
+
albedo2: "#764323",
|
|
96
|
+
params: { oxide: 0.62, cast: 0.25, streaks: 0.5 },
|
|
97
|
+
description: "Weathered wrought/rolled iron, rust blooming in patches and runs.",
|
|
98
|
+
sources: [PB, ROBLOX]
|
|
99
|
+
},
|
|
100
|
+
iron_cast: {
|
|
101
|
+
id: "iron_cast",
|
|
102
|
+
kind: "metal",
|
|
103
|
+
scaleMetres: 0.5,
|
|
104
|
+
roughness: 0.68,
|
|
105
|
+
metalness: 0.75,
|
|
106
|
+
albedo: "#6c6966",
|
|
107
|
+
albedo2: "#55372a",
|
|
108
|
+
params: { cast: 1, oxide: 0.35 },
|
|
109
|
+
description: "Sand-cast iron (wheels, frames): dark pitted skin, dull oxide.",
|
|
110
|
+
sources: [PB]
|
|
111
|
+
},
|
|
112
|
+
steel_painted: {
|
|
113
|
+
id: "steel_painted",
|
|
114
|
+
kind: "metal",
|
|
115
|
+
scaleMetres: 0.5,
|
|
116
|
+
roughness: 0.4,
|
|
117
|
+
metalness: 1,
|
|
118
|
+
albedo: "#eeeeee",
|
|
119
|
+
albedo2: "#5a3b2a",
|
|
120
|
+
params: { paint: 1, paintRough: 0.42, chips: 0.2, streaks: 0.2, grime: 0.2 },
|
|
121
|
+
description: "Painted steel (vertex colour = paint): chips show rusty steel beneath.",
|
|
122
|
+
sources: [ROBLOX]
|
|
123
|
+
},
|
|
124
|
+
steel_galvanised: {
|
|
125
|
+
id: "steel_galvanised",
|
|
126
|
+
kind: "metal",
|
|
127
|
+
scaleMetres: 0.5,
|
|
128
|
+
roughness: 0.42,
|
|
129
|
+
metalness: 1,
|
|
130
|
+
albedo: "#d5dadc",
|
|
131
|
+
albedo2: "#9a9c98",
|
|
132
|
+
params: { spangle: 1, oxide: 0.12 },
|
|
133
|
+
description: "Hot-dip galvanised steel: zinc spangle, white-rust bloom.",
|
|
134
|
+
sources: [PB]
|
|
135
|
+
},
|
|
136
|
+
brass: {
|
|
137
|
+
id: "brass",
|
|
138
|
+
kind: "metal",
|
|
139
|
+
scaleMetres: 0.5,
|
|
140
|
+
roughness: 0.28,
|
|
141
|
+
metalness: 1,
|
|
142
|
+
albedo: "#f5e5ae",
|
|
143
|
+
albedo2: "#6b5a3a",
|
|
144
|
+
params: { brush: 0.3, oxide: 0.1 },
|
|
145
|
+
description: "Brass fittings: warm gold reflectance, dark tarnish in patches.",
|
|
146
|
+
sources: [PB, ROBLOX]
|
|
147
|
+
},
|
|
148
|
+
copper: {
|
|
149
|
+
id: "copper",
|
|
150
|
+
kind: "metal",
|
|
151
|
+
scaleMetres: 0.5,
|
|
152
|
+
roughness: 0.32,
|
|
153
|
+
metalness: 1,
|
|
154
|
+
albedo: "#f2b89c",
|
|
155
|
+
albedo2: "#6fa58f",
|
|
156
|
+
params: { oxide: 0.15 },
|
|
157
|
+
description: "Copper: salmon reflectance with verdigris patches.",
|
|
158
|
+
sources: [PB]
|
|
159
|
+
},
|
|
160
|
+
aluminium: {
|
|
161
|
+
id: "aluminium",
|
|
162
|
+
kind: "metal",
|
|
163
|
+
scaleMetres: 0.5,
|
|
164
|
+
roughness: 0.34,
|
|
165
|
+
metalness: 1,
|
|
166
|
+
albedo: "#e6e8e9",
|
|
167
|
+
params: { brush: 1 },
|
|
168
|
+
description: "Brushed aluminium: bright, cool, streaked along U.",
|
|
169
|
+
sources: [PB]
|
|
170
|
+
},
|
|
171
|
+
chrome: {
|
|
172
|
+
id: "chrome",
|
|
173
|
+
kind: "metal",
|
|
174
|
+
scaleMetres: 0.5,
|
|
175
|
+
roughness: 0.07,
|
|
176
|
+
metalness: 1,
|
|
177
|
+
albedo: "#d3d9dc",
|
|
178
|
+
description: "Chrome plate: near-mirror.",
|
|
179
|
+
sources: [PB]
|
|
180
|
+
},
|
|
181
|
+
rubber: {
|
|
182
|
+
id: "rubber",
|
|
183
|
+
kind: "noise",
|
|
184
|
+
scaleMetres: 0.5,
|
|
185
|
+
roughness: 0.78,
|
|
186
|
+
albedo: "#2c2c2c",
|
|
187
|
+
params: { tone: 0.95, mottle: 0.06, relief: 0.0002, size: 0.05, grime: 0.2 },
|
|
188
|
+
description: "Vulcanised rubber (tyres, hoses, seals).",
|
|
189
|
+
sources: [PB, ROBLOX]
|
|
190
|
+
},
|
|
191
|
+
canvas: {
|
|
192
|
+
id: "canvas",
|
|
193
|
+
kind: "weave",
|
|
194
|
+
scaleMetres: 1,
|
|
195
|
+
roughness: 0.88,
|
|
196
|
+
albedo: "#cbbea2",
|
|
197
|
+
albedo2: "#8a7c62",
|
|
198
|
+
params: { pitch: 0.004, folds: 0.6, stains: 0.35 },
|
|
199
|
+
description: "Cotton duck canvas: natural, weave relief, folds and water stains.",
|
|
200
|
+
sources: [ROBLOX]
|
|
201
|
+
},
|
|
202
|
+
leather: {
|
|
203
|
+
id: "leather",
|
|
204
|
+
kind: "leather",
|
|
205
|
+
scaleMetres: 0.5,
|
|
206
|
+
roughness: 0.58,
|
|
207
|
+
albedo: "#7c5438",
|
|
208
|
+
albedo2: "#3e281a",
|
|
209
|
+
params: { creases: 0.5, wear: 0.4 },
|
|
210
|
+
description: "Worn brown leather: pebble grain, dark creases, burnished wear.",
|
|
211
|
+
sources: [ROBLOX]
|
|
212
|
+
},
|
|
213
|
+
plastic: {
|
|
214
|
+
id: "plastic",
|
|
215
|
+
kind: "noise",
|
|
216
|
+
scaleMetres: 0.5,
|
|
217
|
+
roughness: 0.35,
|
|
218
|
+
albedo: "#ececec",
|
|
219
|
+
params: { tone: 0.97, mottle: 0.01, relief: 0.00004, size: 0.2 },
|
|
220
|
+
description: "Moulded plastic (vertex colour = the plastic colour): satin, even.",
|
|
221
|
+
sources: [ROBLOX]
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// src/material/palette.ts
|
|
226
|
+
function hex2(value) {
|
|
227
|
+
const n = Number.parseInt(value.replace("#", ""), 16);
|
|
228
|
+
const lin = (c) => {
|
|
229
|
+
const s = c / 255;
|
|
230
|
+
return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
231
|
+
};
|
|
232
|
+
return [lin(n >> 16 & 255), lin(n >> 8 & 255), lin(n & 255)];
|
|
233
|
+
}
|
|
234
|
+
var NATURAL2 = [1, 1, 1];
|
|
235
|
+
function definePalette2(entries) {
|
|
236
|
+
return Object.fromEntries(Object.entries(entries).map(([k, v]) => [k, hex2(v)]));
|
|
237
|
+
}
|
|
238
|
+
var palettes2 = {
|
|
239
|
+
victorianRail: definePalette2({
|
|
240
|
+
paint: "#2f4a3a",
|
|
241
|
+
trim: "#b08d57",
|
|
242
|
+
iron: "#2b2b2e",
|
|
243
|
+
brass: "#c9a24a",
|
|
244
|
+
red: "#7a2a22"
|
|
245
|
+
}),
|
|
246
|
+
artDeco: definePalette2({
|
|
247
|
+
limestone: "#d8cfbd",
|
|
248
|
+
steel: "#9aa3ad",
|
|
249
|
+
glass: "#4a6a80",
|
|
250
|
+
bronze: "#8a6a3c"
|
|
251
|
+
}),
|
|
252
|
+
modernGlass: definePalette2({
|
|
253
|
+
glass: "#6f8fa8",
|
|
254
|
+
aluminium: "#c4c9cf",
|
|
255
|
+
concrete: "#a7a39b"
|
|
256
|
+
}),
|
|
257
|
+
harbour: definePalette2({ hull: "#1f2f45", deck: "#8a6f4e", white: "#e8e6df", red: "#a1302a" })
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// src/material/texture-source.ts
|
|
261
|
+
var SHA = /^[0-9a-f]{64}$/;
|
|
262
|
+
var provided = new Map;
|
|
263
|
+
function provideTextures2(cache) {
|
|
264
|
+
for (const [k, v] of cache)
|
|
265
|
+
provided.set(k, v);
|
|
266
|
+
}
|
|
267
|
+
function textureBytes(file, where) {
|
|
268
|
+
const hit = provided.get(file.sha256);
|
|
269
|
+
if (hit)
|
|
270
|
+
return hit;
|
|
271
|
+
throw new Ts23dError2("T3D_TEXTURE_MISSING", {
|
|
272
|
+
cause: `${where}: texture ${file.url} (sha256 ${file.sha256.slice(0, 12)}…) is not in the cache`,
|
|
273
|
+
fix: "run `bun run textures <module defining the material>` once (downloads + verifies into node_modules/.cache/ts23d/textures), then provideTextures(await loadTextureCache()) before painting"
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
function validateTexture(id, t) {
|
|
277
|
+
const fail = (cause, fix) => {
|
|
278
|
+
throw new Ts23dError2("T3D_MATERIAL_INVALID", { cause: `material '${id}': ${cause}`, fix });
|
|
279
|
+
};
|
|
280
|
+
if (!["polyhaven", "ambientcg", "file"].includes(t.source))
|
|
281
|
+
fail(`texture.source '${t.source}'`, "use 'polyhaven', 'ambientcg' or 'file'");
|
|
282
|
+
if (t.license !== "CC0-1.0")
|
|
283
|
+
fail(`texture.license '${t.license}'`, "only CC0-1.0 sets are accepted (Poly Haven and ambientCG are CC0)");
|
|
284
|
+
if (!(t.metres.length === 2 && t.metres.every((m) => Number.isFinite(m) && m > 0)))
|
|
285
|
+
fail(`texture.metres [${t.metres}]`, "give the real size the image covers, e.g. [1.5, 1.5]");
|
|
286
|
+
for (const [map, f] of Object.entries(t.maps)) {
|
|
287
|
+
if (!f)
|
|
288
|
+
continue;
|
|
289
|
+
if (!SHA.test(f.sha256))
|
|
290
|
+
fail(`texture.maps.${map}.sha256 '${f.sha256}'`, "lower-case hex sha256 of the file (sha256sum)");
|
|
291
|
+
if (!/\.png$/i.test(f.url))
|
|
292
|
+
fail(`texture.maps.${map}.url '${f.url}'`, "use the PNG download (8- or 16-bit); JPEG is not decoded");
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
var creditOf2 = (t) => `${t.id}${t.author ? ` by ${t.author}` : ""} (${t.source}, ${t.license}) ${t.page}`;
|
|
296
|
+
|
|
297
|
+
// src/material/material-def.ts
|
|
298
|
+
var ID = /^[a-z][a-z0-9_]{1,31}$/;
|
|
299
|
+
var unit = (v) => Number.isFinite(v) && v >= 0 && v <= 1;
|
|
300
|
+
function fail(id, cause, fix) {
|
|
301
|
+
throw new Ts23dError2("T3D_MATERIAL_INVALID", { cause: `material '${id}': ${cause}`, fix });
|
|
302
|
+
}
|
|
303
|
+
function colour(id, key, v) {
|
|
304
|
+
if (v === undefined)
|
|
305
|
+
return;
|
|
306
|
+
if (typeof v === "string") {
|
|
307
|
+
if (!/^#?[0-9a-fA-F]{6}$/.test(v))
|
|
308
|
+
fail(id, `${key} '${v}' is not a hex colour`, "use '#rrggbb'");
|
|
309
|
+
return hex2(v);
|
|
310
|
+
}
|
|
311
|
+
if (!(v.length === 3 && v.every(unit)))
|
|
312
|
+
fail(id, `${key} [${v}] is not linear RGB 0..1`, "use '#rrggbb' or three numbers in 0..1");
|
|
313
|
+
return v;
|
|
314
|
+
}
|
|
315
|
+
function toStripRecipe(d) {
|
|
316
|
+
const id = String(d.id);
|
|
317
|
+
if (!ID.test(id))
|
|
318
|
+
fail(id, "id is not snake_case (2–32 chars, starts with a letter)", "e.g. 'teak_deck'");
|
|
319
|
+
if (!unit(d.roughness))
|
|
320
|
+
fail(id, `roughness ${d.roughness}`, "roughness is 0..1");
|
|
321
|
+
if (d.metalness !== undefined && !unit(d.metalness))
|
|
322
|
+
fail(id, `metalness ${d.metalness}`, "metalness is 0..1 (0 dielectric, 1 metal)");
|
|
323
|
+
const scale = d.scaleMetres ?? 1;
|
|
324
|
+
if (!(Number.isFinite(scale) && scale > 0))
|
|
325
|
+
fail(id, `scaleMetres ${scale}`, "scaleMetres is the face height in metres, > 0");
|
|
326
|
+
if (d.paint !== undefined && typeof d.paint !== "function")
|
|
327
|
+
fail(id, "paint is not a function", "paint is (surface, kit) => void");
|
|
328
|
+
for (const [k, v] of Object.entries(d.params ?? {}))
|
|
329
|
+
if (!Number.isFinite(v))
|
|
330
|
+
fail(id, `params.${k} is ${v}`, "params are finite numbers");
|
|
331
|
+
if (d.texture)
|
|
332
|
+
validateTexture(id, d.texture);
|
|
333
|
+
const albedo = colour(id, "albedo", d.albedo);
|
|
334
|
+
const albedo2 = colour(id, "albedo2", d.albedo2);
|
|
335
|
+
return {
|
|
336
|
+
kind: d.kind ?? "noise",
|
|
337
|
+
scaleMetres: scale,
|
|
338
|
+
roughness: d.roughness,
|
|
339
|
+
...d.metalness !== undefined ? { metalness: d.metalness } : {},
|
|
340
|
+
...d.params ? { params: d.params } : {},
|
|
341
|
+
...albedo ? { albedo } : {},
|
|
342
|
+
...albedo2 ? { albedo2 } : {},
|
|
343
|
+
...d.paint ? { paint: d.paint } : {},
|
|
344
|
+
...d.texture ? { texture: d.texture } : {}
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/material/paint/default-sheet.ts
|
|
349
|
+
var leaf = { kind: "flat", scaleMetres: 1, roughness: 0.6 };
|
|
350
|
+
var library = Object.fromEntries(Object.values(MATERIAL_LIBRARY2).map((d) => [d.id, toStripRecipe(d)]));
|
|
351
|
+
var HOUSE_STRIPS = {
|
|
352
|
+
plain: { kind: "noise", scaleMetres: 1, roughness: 0.6 },
|
|
353
|
+
brick: { kind: "bond", scaleMetres: 0.45, roughness: 0.85, params: { grime: 0.2 } },
|
|
354
|
+
stone: {
|
|
355
|
+
kind: "bond",
|
|
356
|
+
scaleMetres: 0.9,
|
|
357
|
+
roughness: 0.9,
|
|
358
|
+
params: {
|
|
359
|
+
course: 0.22,
|
|
360
|
+
length: 0.4,
|
|
361
|
+
mortar: 0.016,
|
|
362
|
+
depth: 0.012,
|
|
363
|
+
bevel: 0.025,
|
|
364
|
+
irregular: 0.5,
|
|
365
|
+
relief: 0.004,
|
|
366
|
+
variation: 0.3,
|
|
367
|
+
grime: 0.2
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
slate: { kind: "slate", scaleMetres: 0.66, roughness: 0.7 },
|
|
371
|
+
iron: { kind: "plates", scaleMetres: 0.5, roughness: 0.55, metalness: 0 },
|
|
372
|
+
wood: { kind: "planks", scaleMetres: 0.6, roughness: 0.75 },
|
|
373
|
+
glass: { kind: "glass", scaleMetres: 1, roughness: 0.05 },
|
|
374
|
+
concrete: { kind: "concrete", scaleMetres: 1, roughness: 0.9, params: { grime: 0.3 } },
|
|
375
|
+
...library
|
|
376
|
+
};
|
|
377
|
+
var DEFAULT_SHEET2 = {
|
|
378
|
+
seed: 1,
|
|
379
|
+
size: 2048,
|
|
380
|
+
strips: HOUSE_STRIPS,
|
|
381
|
+
cells: {
|
|
382
|
+
window_sash: { kind: "noise", scaleMetres: 1, roughness: 0.5 },
|
|
383
|
+
door_panel: { kind: "noise", scaleMetres: 1, roughness: 0.6 },
|
|
384
|
+
clock: { kind: "noise", scaleMetres: 1, roughness: 0.3 },
|
|
385
|
+
smokebox: {
|
|
386
|
+
kind: "plates",
|
|
387
|
+
scaleMetres: 1,
|
|
388
|
+
roughness: 0.6,
|
|
389
|
+
metalness: 0.2,
|
|
390
|
+
params: { rust: 0.3 }
|
|
391
|
+
},
|
|
392
|
+
sign: { kind: "noise", scaleMetres: 1, roughness: 0.5 },
|
|
393
|
+
porthole: { kind: "plates", scaleMetres: 1, roughness: 0.5 },
|
|
394
|
+
foliage_broadleaf: leaf,
|
|
395
|
+
foliage_maple: leaf,
|
|
396
|
+
foliage_maple_autumn: leaf,
|
|
397
|
+
foliage_birch: leaf,
|
|
398
|
+
foliage_acacia: leaf,
|
|
399
|
+
foliage_palm: leaf,
|
|
400
|
+
foliage_spruce: leaf,
|
|
401
|
+
foliage_spruce_snow: leaf,
|
|
402
|
+
foliage_willow: leaf,
|
|
403
|
+
foliage_reeds: leaf,
|
|
404
|
+
foliage_needles: leaf
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// src/material/slots.ts
|
|
409
|
+
var custom = [];
|
|
410
|
+
var customIndex = (id) => custom.indexOf(id);
|
|
411
|
+
var customIds = () => custom;
|
|
412
|
+
function pushCustom(id) {
|
|
413
|
+
custom.push(id);
|
|
414
|
+
return custom.length - 1;
|
|
415
|
+
}
|
|
416
|
+
function clearCustom() {
|
|
417
|
+
custom.length = 0;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/material/trim-sheet.ts
|
|
421
|
+
var STRIP_ROWS2 = 28;
|
|
422
|
+
var STRIP_H = 1 / 32;
|
|
423
|
+
var classic = [
|
|
424
|
+
"plain",
|
|
425
|
+
"brick",
|
|
426
|
+
"stone",
|
|
427
|
+
"slate",
|
|
428
|
+
"iron",
|
|
429
|
+
"wood",
|
|
430
|
+
"glass",
|
|
431
|
+
"concrete"
|
|
432
|
+
];
|
|
433
|
+
var material = [
|
|
434
|
+
"oak",
|
|
435
|
+
"pine",
|
|
436
|
+
"wood_weathered",
|
|
437
|
+
"wood_painted",
|
|
438
|
+
"iron_rust",
|
|
439
|
+
"iron_cast",
|
|
440
|
+
"steel_painted",
|
|
441
|
+
"steel_galvanised",
|
|
442
|
+
"brass",
|
|
443
|
+
"copper",
|
|
444
|
+
"aluminium",
|
|
445
|
+
"chrome",
|
|
446
|
+
"rubber",
|
|
447
|
+
"canvas",
|
|
448
|
+
"leather",
|
|
449
|
+
"plastic"
|
|
450
|
+
];
|
|
451
|
+
var strips = [...classic, ...material];
|
|
452
|
+
var CUSTOM_STRIP_SLOTS2 = STRIP_ROWS2 - strips.length;
|
|
453
|
+
var decals = [
|
|
454
|
+
"window_sash",
|
|
455
|
+
"door_panel",
|
|
456
|
+
"clock",
|
|
457
|
+
"smokebox",
|
|
458
|
+
"sign",
|
|
459
|
+
"porthole"
|
|
460
|
+
];
|
|
461
|
+
var foliage = [
|
|
462
|
+
"foliage_broadleaf",
|
|
463
|
+
"foliage_maple",
|
|
464
|
+
"foliage_maple_autumn",
|
|
465
|
+
"foliage_birch",
|
|
466
|
+
"foliage_acacia",
|
|
467
|
+
"foliage_palm",
|
|
468
|
+
"foliage_spruce",
|
|
469
|
+
"foliage_spruce_snow",
|
|
470
|
+
"foliage_willow",
|
|
471
|
+
"foliage_reeds",
|
|
472
|
+
"foliage_needles"
|
|
473
|
+
];
|
|
474
|
+
var cells = [...decals, ...foliage];
|
|
475
|
+
var CELL = 1 / 16;
|
|
476
|
+
var CELL_V0 = STRIP_ROWS2 * STRIP_H;
|
|
477
|
+
var FOLIAGE_SLOT = 8;
|
|
478
|
+
var slotOf = (id) => {
|
|
479
|
+
const d = decals.indexOf(id);
|
|
480
|
+
return d >= 0 ? d : FOLIAGE_SLOT + foliage.indexOf(id);
|
|
481
|
+
};
|
|
482
|
+
var TRIM_SHEET_SIZE2 = 2048;
|
|
483
|
+
function stripRow2(id) {
|
|
484
|
+
const b = strips.indexOf(id);
|
|
485
|
+
if (b >= 0)
|
|
486
|
+
return b;
|
|
487
|
+
const c = customIndex(id);
|
|
488
|
+
return c >= 0 ? strips.length + c : -1;
|
|
489
|
+
}
|
|
490
|
+
function stripRect2(id) {
|
|
491
|
+
const i = stripRow2(id);
|
|
492
|
+
if (i < 0)
|
|
493
|
+
throw new Ts23dError2("T3D_MATERIAL_UNKNOWN", {
|
|
494
|
+
cause: `strip '${id}' is neither built in nor defined`,
|
|
495
|
+
fix: `use a built-in strip (${strips.join(", ")}) or register it first with defineMaterial({ id: '${id}', ... })`
|
|
496
|
+
});
|
|
497
|
+
return { u0: 0, v0: i * STRIP_H, u1: 1, v1: (i + 1) * STRIP_H, metres: 2 };
|
|
498
|
+
}
|
|
499
|
+
function cellRect2(id) {
|
|
500
|
+
const i = slotOf(id);
|
|
501
|
+
const u0 = i % 16 * CELL;
|
|
502
|
+
const v0 = CELL_V0 + Math.floor(i / 16) * CELL;
|
|
503
|
+
return { u0, v0, u1: u0 + CELL, v1: v0 + CELL, metres: 1 };
|
|
504
|
+
}
|
|
505
|
+
var STRIPS2 = strips;
|
|
506
|
+
var CLASSIC_STRIPS2 = classic;
|
|
507
|
+
var MATERIAL_STRIPS2 = material;
|
|
508
|
+
var CELLS2 = cells;
|
|
509
|
+
var DECAL_CELLS2 = decals;
|
|
510
|
+
var FOLIAGE_CELLS2 = foliage;
|
|
511
|
+
var isFoliage2 = (id) => id.startsWith("foliage_");
|
|
512
|
+
|
|
513
|
+
// src/material/define.ts
|
|
514
|
+
var defined = new Map;
|
|
515
|
+
function same(a, b) {
|
|
516
|
+
return JSON.stringify({ ...a, paint: 0 }) === JSON.stringify({ ...b, paint: 0 }) && a.paint === b.paint;
|
|
517
|
+
}
|
|
518
|
+
function defineMaterial2(def) {
|
|
519
|
+
const recipe = toStripRecipe(def);
|
|
520
|
+
const id = def.id;
|
|
521
|
+
if (STRIPS2.includes(id))
|
|
522
|
+
throw new Ts23dError2("T3D_MATERIAL_CONFLICT", {
|
|
523
|
+
cause: `material '${id}' is a built-in strip`,
|
|
524
|
+
fix: `pick another id (e.g. '${id}_custom'), or restyle the built-in via paintTrimSheet({ ...DEFAULT_SHEET, strips: { ...DEFAULT_SHEET.strips, ${id}: … } })`
|
|
525
|
+
});
|
|
526
|
+
const prior = defined.get(id);
|
|
527
|
+
if (prior) {
|
|
528
|
+
if (same(prior, def))
|
|
529
|
+
return id;
|
|
530
|
+
throw new Ts23dError2("T3D_MATERIAL_CONFLICT", {
|
|
531
|
+
cause: `material '${id}' is already defined differently`,
|
|
532
|
+
fix: "define each material once in a shared module and import it, or give the variant its own id"
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
if (customIds().length >= CUSTOM_STRIP_SLOTS2)
|
|
536
|
+
throw new Ts23dError2("T3D_SHEET_FULL", {
|
|
537
|
+
cause: `the trim sheet has ${CUSTOM_STRIP_SLOTS2} custom strip rows, all taken (${customIds().join(", ")})`,
|
|
538
|
+
fix: "reuse a built-in strip with a vertex colour, or restyle an unused built-in strip in your SheetRecipe"
|
|
539
|
+
});
|
|
540
|
+
defined.set(id, def);
|
|
541
|
+
pushCustom(id);
|
|
542
|
+
HOUSE_STRIPS[id] = recipe;
|
|
543
|
+
return id;
|
|
544
|
+
}
|
|
545
|
+
function resetMaterials2() {
|
|
546
|
+
for (const id of defined.keys())
|
|
547
|
+
delete HOUSE_STRIPS[id];
|
|
548
|
+
defined.clear();
|
|
549
|
+
clearCustom();
|
|
550
|
+
}
|
|
551
|
+
function materialOf2(id) {
|
|
552
|
+
const recipe = HOUSE_STRIPS[id];
|
|
553
|
+
const lib = MATERIAL_LIBRARY2[id];
|
|
554
|
+
const def = lib ?? defined.get(id);
|
|
555
|
+
if (!recipe || customIndex(id) < 0 && !STRIPS2.includes(id))
|
|
556
|
+
throw new Ts23dError2("T3D_MATERIAL_UNKNOWN", {
|
|
557
|
+
cause: `strip '${id}' is neither built in nor defined`,
|
|
558
|
+
fix: `use one of ${STRIPS2.join(", ")}, or defineMaterial({ id: '${id}', ... }) first`
|
|
559
|
+
});
|
|
560
|
+
const classic = CLASSIC_STRIPS2.includes(id);
|
|
561
|
+
return {
|
|
562
|
+
id,
|
|
563
|
+
origin: classic ? "classic" : lib ? "library" : "defined",
|
|
564
|
+
recipe,
|
|
565
|
+
description: def?.description ?? `classic ${recipe.kind} strip (greyscale, vertex colour tints)`,
|
|
566
|
+
sources: def?.sources ?? []
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
function textureSources2() {
|
|
570
|
+
return [...defined.values()].flatMap((d) => d.texture ? [d.texture] : []);
|
|
571
|
+
}
|
|
572
|
+
function stripCredits2(vs) {
|
|
573
|
+
const out = [];
|
|
574
|
+
for (const [id, d] of defined) {
|
|
575
|
+
if (!d.texture)
|
|
576
|
+
continue;
|
|
577
|
+
const r = stripRect2(id);
|
|
578
|
+
for (let i = 0;i < vs.length; i++) {
|
|
579
|
+
const v = vs[i] ?? -1;
|
|
580
|
+
if (v >= r.v0 && v <= r.v1) {
|
|
581
|
+
out.push(creditOf2(d.texture));
|
|
582
|
+
break;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return out;
|
|
587
|
+
}
|
|
6
588
|
// src/material/paint/noise.ts
|
|
7
589
|
function hash2(ix, iy, seed) {
|
|
8
590
|
let h = Math.imul(ix | 0, 668265261) ^ Math.imul(iy | 0, 374761393) ^ seed;
|
|
@@ -53,7 +635,10 @@ function createCanvas(w, h, metresU, metresV, wrapU) {
|
|
|
53
635
|
rough: new Float32Array(n).fill(0.8),
|
|
54
636
|
metal: new Float32Array(n),
|
|
55
637
|
occl: new Float32Array(n).fill(1),
|
|
56
|
-
|
|
638
|
+
mix: new Float32Array(n),
|
|
639
|
+
colours: null,
|
|
640
|
+
rgba: null,
|
|
641
|
+
normalRgba: null
|
|
57
642
|
};
|
|
58
643
|
}
|
|
59
644
|
function periodFor(c, featureMetres) {
|
|
@@ -73,63 +658,6 @@ var smooth = (e0, e1, v) => {
|
|
|
73
658
|
return t * t * (3 - 2 * t);
|
|
74
659
|
};
|
|
75
660
|
|
|
76
|
-
// src/material/trim-sheet.ts
|
|
77
|
-
var STRIP_H = 1 / 16;
|
|
78
|
-
var strips = [
|
|
79
|
-
"plain",
|
|
80
|
-
"brick",
|
|
81
|
-
"stone",
|
|
82
|
-
"slate",
|
|
83
|
-
"iron",
|
|
84
|
-
"wood",
|
|
85
|
-
"glass",
|
|
86
|
-
"concrete"
|
|
87
|
-
];
|
|
88
|
-
var decals = [
|
|
89
|
-
"window_sash",
|
|
90
|
-
"door_panel",
|
|
91
|
-
"clock",
|
|
92
|
-
"smokebox",
|
|
93
|
-
"sign",
|
|
94
|
-
"porthole"
|
|
95
|
-
];
|
|
96
|
-
var foliage = [
|
|
97
|
-
"foliage_broadleaf",
|
|
98
|
-
"foliage_maple",
|
|
99
|
-
"foliage_maple_autumn",
|
|
100
|
-
"foliage_birch",
|
|
101
|
-
"foliage_acacia",
|
|
102
|
-
"foliage_palm",
|
|
103
|
-
"foliage_spruce",
|
|
104
|
-
"foliage_spruce_snow",
|
|
105
|
-
"foliage_willow",
|
|
106
|
-
"foliage_reeds",
|
|
107
|
-
"foliage_needles"
|
|
108
|
-
];
|
|
109
|
-
var cells = [...decals, ...foliage];
|
|
110
|
-
var FOLIAGE_SLOT = 8;
|
|
111
|
-
var slotOf = (id) => {
|
|
112
|
-
const d = decals.indexOf(id);
|
|
113
|
-
return d >= 0 ? d : FOLIAGE_SLOT + foliage.indexOf(id);
|
|
114
|
-
};
|
|
115
|
-
var TRIM_SHEET_SIZE2 = 1024;
|
|
116
|
-
function stripRect2(id) {
|
|
117
|
-
const i = strips.indexOf(id);
|
|
118
|
-
return { u0: 0, v0: i * STRIP_H, u1: 1, v1: (i + 1) * STRIP_H, metres: 2 };
|
|
119
|
-
}
|
|
120
|
-
function cellRect2(id) {
|
|
121
|
-
const i = slotOf(id);
|
|
122
|
-
const size = 1 / 8;
|
|
123
|
-
const u0 = i % 8 * size;
|
|
124
|
-
const v0 = 0.5 + Math.floor(i / 8) * size;
|
|
125
|
-
return { u0, v0, u1: u0 + size, v1: v0 + size, metres: 1 };
|
|
126
|
-
}
|
|
127
|
-
var STRIPS2 = strips;
|
|
128
|
-
var CELLS2 = cells;
|
|
129
|
-
var DECAL_CELLS2 = decals;
|
|
130
|
-
var FOLIAGE_CELLS2 = foliage;
|
|
131
|
-
var isFoliage2 = (id) => id.startsWith("foliage_");
|
|
132
|
-
|
|
133
661
|
// src/material/paint/shapes.ts
|
|
134
662
|
function segment(px, py, ax, ay, bx, by) {
|
|
135
663
|
const dx = bx - ax;
|
|
@@ -460,10 +988,23 @@ function packOrm(c, ao) {
|
|
|
460
988
|
}
|
|
461
989
|
return out;
|
|
462
990
|
}
|
|
991
|
+
var enc = (t) => SRGB[t <= 0 ? 0 : t >= 1 ? 4095 : Math.round(t * 4095)] ?? 255;
|
|
463
992
|
function packAlbedo(c, ao) {
|
|
464
993
|
if (c.rgba)
|
|
465
994
|
return c.rgba;
|
|
466
995
|
const out = new Uint8Array(c.w * c.h * 4);
|
|
996
|
+
const pair = c.colours;
|
|
997
|
+
if (pair) {
|
|
998
|
+
const [a, b] = pair;
|
|
999
|
+
for (let i = 0, o = 0;i < ao.length; i++, o += 4) {
|
|
1000
|
+
const t = (c.tone[i] ?? 1) * (0.8 + 0.2 * (ao[i] ?? 1));
|
|
1001
|
+
const m = Math.min(1, Math.max(0, c.mix[i] ?? 0));
|
|
1002
|
+
for (let k = 0;k < 3; k++)
|
|
1003
|
+
out[o + k] = enc(((a[k] ?? 1) * (1 - m) + (b[k] ?? 1) * m) * t);
|
|
1004
|
+
out[o + 3] = 255;
|
|
1005
|
+
}
|
|
1006
|
+
return out;
|
|
1007
|
+
}
|
|
467
1008
|
for (let i = 0, o = 0;i < ao.length; i++, o += 4) {
|
|
468
1009
|
const t = (c.tone[i] ?? 1) * (0.8 + 0.2 * (ao[i] ?? 1));
|
|
469
1010
|
const g = SRGB[t <= 0 ? 0 : t >= 1 ? 4095 : Math.round(t * 4095)] ?? 255;
|
|
@@ -885,7 +1426,7 @@ var CHANNELS = { 0: 1, 2: 3, 3: 1, 4: 2, 6: 4 };
|
|
|
885
1426
|
function bad(cause) {
|
|
886
1427
|
throw new Ts23dError2("T3D_RECIPE_INVALID", {
|
|
887
1428
|
cause: `cell image: ${cause}`,
|
|
888
|
-
fix: "pass PNG bytes, 8 bits per channel, non-interlaced (e.g. re-save as RGBA8 PNG)"
|
|
1429
|
+
fix: "pass PNG bytes, 8 or 16 bits per channel, non-interlaced (e.g. re-save as RGBA8 PNG)"
|
|
889
1430
|
});
|
|
890
1431
|
}
|
|
891
1432
|
function paeth(a, b, c) {
|
|
@@ -902,6 +1443,7 @@ function decodePng(bytes) {
|
|
|
902
1443
|
let width = 0;
|
|
903
1444
|
let height = 0;
|
|
904
1445
|
let type = -1;
|
|
1446
|
+
let depth = 8;
|
|
905
1447
|
let palette = new Uint8Array;
|
|
906
1448
|
let trns = new Uint8Array;
|
|
907
1449
|
const idat = [];
|
|
@@ -913,8 +1455,9 @@ function decodePng(bytes) {
|
|
|
913
1455
|
width = dv.getUint32(o + 8);
|
|
914
1456
|
height = dv.getUint32(o + 12);
|
|
915
1457
|
type = body[9] ?? -1;
|
|
916
|
-
|
|
917
|
-
|
|
1458
|
+
depth = body[8] ?? 0;
|
|
1459
|
+
if (depth !== 8 && !(depth === 16 && type !== 3))
|
|
1460
|
+
bad(`bit depth ${depth} (8, or 16 for non-palette images, is supported)`);
|
|
918
1461
|
if (body[12] !== 0)
|
|
919
1462
|
bad("interlaced");
|
|
920
1463
|
} else if (name === "PLTE")
|
|
@@ -931,7 +1474,9 @@ function decodePng(bytes) {
|
|
|
931
1474
|
if (!ch || width === 0 || height === 0)
|
|
932
1475
|
bad(`colour type ${type} or empty image`);
|
|
933
1476
|
const raw = new Uint8Array(inflateSync(Buffer.concat(idat)));
|
|
934
|
-
const
|
|
1477
|
+
const B = depth / 8;
|
|
1478
|
+
const bpp = ch * B;
|
|
1479
|
+
const stride = width * bpp;
|
|
935
1480
|
if (raw.length < height * (stride + 1))
|
|
936
1481
|
bad("truncated image data");
|
|
937
1482
|
const px = new Uint8Array(height * stride);
|
|
@@ -940,9 +1485,9 @@ function decodePng(bytes) {
|
|
|
940
1485
|
const src = y * (stride + 1) + 1;
|
|
941
1486
|
const row = y * stride;
|
|
942
1487
|
for (let x = 0;x < stride; x++) {
|
|
943
|
-
const a = x >=
|
|
1488
|
+
const a = x >= bpp ? px[row + x - bpp] ?? 0 : 0;
|
|
944
1489
|
const b = y > 0 ? px[row - stride + x] ?? 0 : 0;
|
|
945
|
-
const c = x >=
|
|
1490
|
+
const c = x >= bpp && y > 0 ? px[row - stride + x - bpp] ?? 0 : 0;
|
|
946
1491
|
const v = raw[src + x] ?? 0;
|
|
947
1492
|
const pred = f === 1 ? a : f === 2 ? b : f === 3 ? a + b >> 1 : f === 4 ? paeth(a, b, c) : 0;
|
|
948
1493
|
px[row + x] = v + pred & 255;
|
|
@@ -950,8 +1495,9 @@ function decodePng(bytes) {
|
|
|
950
1495
|
}
|
|
951
1496
|
const data = new Uint8Array(width * height * 4);
|
|
952
1497
|
for (let i = 0;i < width * height; i++) {
|
|
953
|
-
const s = i *
|
|
954
|
-
const
|
|
1498
|
+
const s = i * bpp;
|
|
1499
|
+
const at = (k) => px[s + k * B] ?? 0;
|
|
1500
|
+
const g = at(0);
|
|
955
1501
|
const o = i * 4;
|
|
956
1502
|
if (type === 3) {
|
|
957
1503
|
data[o] = palette[g * 3] ?? 0;
|
|
@@ -960,14 +1506,14 @@ function decodePng(bytes) {
|
|
|
960
1506
|
data[o + 3] = trns[g] ?? 255;
|
|
961
1507
|
} else if (ch >= 3) {
|
|
962
1508
|
data[o] = g;
|
|
963
|
-
data[o + 1] =
|
|
964
|
-
data[o + 2] =
|
|
965
|
-
data[o + 3] = ch === 4 ?
|
|
1509
|
+
data[o + 1] = at(1);
|
|
1510
|
+
data[o + 2] = at(2);
|
|
1511
|
+
data[o + 3] = ch === 4 ? at(3) : 255;
|
|
966
1512
|
} else {
|
|
967
1513
|
data[o] = g;
|
|
968
1514
|
data[o + 1] = g;
|
|
969
1515
|
data[o + 2] = g;
|
|
970
|
-
data[o + 3] = ch === 2 ?
|
|
1516
|
+
data[o + 3] = ch === 2 ? at(1) : 255;
|
|
971
1517
|
}
|
|
972
1518
|
}
|
|
973
1519
|
return { width, height, data };
|
|
@@ -1169,6 +1715,158 @@ function paintConcrete(c, r, seed) {
|
|
|
1169
1715
|
}
|
|
1170
1716
|
}
|
|
1171
1717
|
|
|
1718
|
+
// src/material/paint/fabric.ts
|
|
1719
|
+
function paintWeave(c, r, seed) {
|
|
1720
|
+
const p = r.params;
|
|
1721
|
+
const pu = c.metresU / c.w;
|
|
1722
|
+
const pv = c.metresV / c.h;
|
|
1723
|
+
const pitchU = Math.max(param(p, "pitch", 0.004), 2 * pu);
|
|
1724
|
+
const pitchV = Math.max(param(p, "pitch", 0.004), 2 * pv);
|
|
1725
|
+
const folds = param(p, "folds", 0.6);
|
|
1726
|
+
const stains = param(p, "stains", 0.3);
|
|
1727
|
+
for (let y = 0;y < c.h; y++) {
|
|
1728
|
+
const ym = (y + 0.5) * pv;
|
|
1729
|
+
const wy = Math.sin(ym / pitchV * Math.PI * 2);
|
|
1730
|
+
for (let x = 0;x < c.w; x++) {
|
|
1731
|
+
const i = y * c.w + x;
|
|
1732
|
+
const xm = (x + 0.5) * pu;
|
|
1733
|
+
const wx = Math.sin(xm / pitchU * Math.PI * 2);
|
|
1734
|
+
const over = Math.floor(xm / pitchU) + Math.floor(ym / pitchV);
|
|
1735
|
+
const thread = over % 2 === 0 ? Math.abs(wy) : Math.abs(wx);
|
|
1736
|
+
const slub = noiseAt(c, xm, ym, 0.05, 0.01, seed, 2);
|
|
1737
|
+
const fold = noiseAt(c, xm, ym, 0.5, 0.35, seed + 1, 3);
|
|
1738
|
+
const stain = stains * smooth(0.1, 0.6, noiseAt(c, xm, ym, 0.2, 0.15, seed + 2, 4));
|
|
1739
|
+
c.height[i] = 0.0002 * thread + 0.003 * folds * fold;
|
|
1740
|
+
c.tone[i] = (0.9 + 0.06 * thread + 0.05 * slub) * (1 - 0.25 * stain);
|
|
1741
|
+
c.rough[i] = Math.min(1, r.roughness + 0.04 * slub - 0.03 * thread);
|
|
1742
|
+
c.metal[i] = 0;
|
|
1743
|
+
c.mix[i] = stain;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
function paintLeather(c, r, seed) {
|
|
1748
|
+
const p = r.params;
|
|
1749
|
+
const pu = c.metresU / c.w;
|
|
1750
|
+
const pv = c.metresV / c.h;
|
|
1751
|
+
const pebble = param(p, "pebble", 0.004);
|
|
1752
|
+
const creases = param(p, "creases", 0.5);
|
|
1753
|
+
const wear = param(p, "wear", 0.4);
|
|
1754
|
+
for (let y = 0;y < c.h; y++) {
|
|
1755
|
+
const ym = (y + 0.5) * pv;
|
|
1756
|
+
for (let x = 0;x < c.w; x++) {
|
|
1757
|
+
const i = y * c.w + x;
|
|
1758
|
+
const xm = (x + 0.5) * pu;
|
|
1759
|
+
const grain = noiseAt(c, xm, ym, pebble, pebble, seed, 3);
|
|
1760
|
+
const crease = smooth(0.4, 0.6, noiseAt(c, xm, ym, 0.12, 0.012, seed + 1, 3)) * creases;
|
|
1761
|
+
const rub = wear * smooth(0, 0.6, noiseAt(c, xm, ym, 0.3, 0.3, seed + 2, 4));
|
|
1762
|
+
c.height[i] = 0.00025 * grain - 0.0008 * crease;
|
|
1763
|
+
c.tone[i] = (0.95 + 0.05 * grain) * (1 - 0.45 * crease) * (1 + 0.12 * rub);
|
|
1764
|
+
c.rough[i] = Math.min(1, Math.max(0, r.roughness + 0.06 * grain - 0.2 * rub + 0.1 * crease));
|
|
1765
|
+
c.metal[i] = 0;
|
|
1766
|
+
c.mix[i] = crease;
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
// src/material/paint/image-strip.ts
|
|
1772
|
+
var decoded = new Map;
|
|
1773
|
+
function image(f, where) {
|
|
1774
|
+
const hit = decoded.get(f.sha256);
|
|
1775
|
+
if (hit)
|
|
1776
|
+
return hit;
|
|
1777
|
+
const img = decodePng(textureBytes(f, where));
|
|
1778
|
+
decoded.set(f.sha256, img);
|
|
1779
|
+
return img;
|
|
1780
|
+
}
|
|
1781
|
+
function resample(c, t, img, fn) {
|
|
1782
|
+
const reps = Math.max(1, Math.round(c.metresU / (t.metres[0] ?? 1)));
|
|
1783
|
+
const su = c.metresU / reps;
|
|
1784
|
+
const sv = t.metres[1] ?? 1;
|
|
1785
|
+
const W = img.width;
|
|
1786
|
+
const H = img.height;
|
|
1787
|
+
const px = new Float32Array(4);
|
|
1788
|
+
for (let y = 0;y < c.h; y++) {
|
|
1789
|
+
const v0 = y * c.metresV / c.h / sv * H;
|
|
1790
|
+
const v1 = (y + 1) * c.metresV / c.h / sv * H;
|
|
1791
|
+
for (let x = 0;x < c.w; x++) {
|
|
1792
|
+
const u0 = x * c.metresU / c.w / su * W;
|
|
1793
|
+
const u1 = (x + 1) * c.metresU / c.w / su * W;
|
|
1794
|
+
px.fill(0);
|
|
1795
|
+
let n = 0;
|
|
1796
|
+
for (let sy = Math.floor(v0);sy < Math.max(Math.floor(v0) + 1, Math.ceil(v1)); sy++) {
|
|
1797
|
+
const row = H - 1 - (sy % H + H) % H;
|
|
1798
|
+
for (let sx = Math.floor(u0);sx < Math.max(Math.floor(u0) + 1, Math.ceil(u1)); sx++) {
|
|
1799
|
+
const o = (row * W + (sx % W + W) % W) * 4;
|
|
1800
|
+
for (let k = 0;k < 4; k++)
|
|
1801
|
+
px[k] = (px[k] ?? 0) + (img.data[o + k] ?? 0);
|
|
1802
|
+
n++;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
for (let k = 0;k < 4; k++)
|
|
1806
|
+
px[k] = (px[k] ?? 0) / n;
|
|
1807
|
+
fn(y * c.w + x, px);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
function paintImageStrip(c, r, t, where) {
|
|
1812
|
+
const albedo = new Uint8Array(c.w * c.h * 4);
|
|
1813
|
+
resample(c, t, image(t.maps.albedo, where), (i, p) => {
|
|
1814
|
+
albedo.set([p[0] ?? 0, p[1] ?? 0, p[2] ?? 0, 255], i * 4);
|
|
1815
|
+
});
|
|
1816
|
+
c.rgba = albedo;
|
|
1817
|
+
c.height.fill(0);
|
|
1818
|
+
if (t.maps.normal) {
|
|
1819
|
+
const nrm = new Uint8Array(c.w * c.h * 4);
|
|
1820
|
+
resample(c, t, image(t.maps.normal, where), (i, p) => {
|
|
1821
|
+
const x = (p[0] ?? 128) / 127.5 - 1;
|
|
1822
|
+
const y = -((p[1] ?? 128) / 127.5 - 1);
|
|
1823
|
+
const z = (p[2] ?? 255) / 127.5 - 1;
|
|
1824
|
+
const l = Math.hypot(x, y, z) || 1;
|
|
1825
|
+
nrm.set([x / l * 127.5 + 127.5, y / l * 127.5 + 127.5, z / l * 127.5 + 127.5, 255], i * 4);
|
|
1826
|
+
});
|
|
1827
|
+
c.normalRgba = nrm;
|
|
1828
|
+
}
|
|
1829
|
+
if (t.maps.arm) {
|
|
1830
|
+
resample(c, t, image(t.maps.arm, where), (i, p) => {
|
|
1831
|
+
c.occl[i] = (p[0] ?? 255) / 255;
|
|
1832
|
+
c.rough[i] = (p[1] ?? 204) / 255;
|
|
1833
|
+
c.metal[i] = (p[2] ?? 0) / 255;
|
|
1834
|
+
});
|
|
1835
|
+
} else {
|
|
1836
|
+
c.rough.fill(r.roughness);
|
|
1837
|
+
c.metal.fill(r.metalness ?? 0);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
// src/material/paint/kit.ts
|
|
1842
|
+
function paintKit(c, seed) {
|
|
1843
|
+
const pu = c.metresU / c.w;
|
|
1844
|
+
const pv = c.metresV / c.h;
|
|
1845
|
+
return {
|
|
1846
|
+
seed,
|
|
1847
|
+
noise: (xm, ym, sizeU, sizeV, salt = 0, octaves = 4) => noiseAt(c, xm, ym, sizeU, sizeV, seed + salt * 7919, octaves),
|
|
1848
|
+
hash: (a, b, salt = 0) => hash2(a, b, seed + salt),
|
|
1849
|
+
smooth,
|
|
1850
|
+
each(fn) {
|
|
1851
|
+
for (let y = 0;y < c.h; y++) {
|
|
1852
|
+
const ym = (y + 0.5) * pv;
|
|
1853
|
+
for (let x = 0;x < c.w; x++)
|
|
1854
|
+
fn(y * c.w + x, (x + 0.5) * pu, ym);
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
function checkFinite(c, where) {
|
|
1860
|
+
const layers = { height: c.height, tone: c.tone, rough: c.rough, metal: c.metal, mix: c.mix };
|
|
1861
|
+
for (const [name, a] of Object.entries(layers))
|
|
1862
|
+
for (let i = 0;i < a.length; i++)
|
|
1863
|
+
if (!Number.isFinite(a[i] ?? 0))
|
|
1864
|
+
throw new Ts23dError2("T3D_MATERIAL_INVALID", {
|
|
1865
|
+
cause: `${where}: the painter wrote ${a[i]} into '${name}' at texel ${i}`,
|
|
1866
|
+
fix: "write finite numbers only (guard divisions; clamp with Math.min/Math.max)"
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1172
1870
|
// src/material/paint/masks.ts
|
|
1173
1871
|
function rustStreaks(c, amount, seed) {
|
|
1174
1872
|
if (amount <= 0)
|
|
@@ -1188,6 +1886,7 @@ function rustStreaks(c, amount, seed) {
|
|
|
1188
1886
|
c.tone[i] = (c.tone[i] ?? 1) * (1 - 0.4 * m);
|
|
1189
1887
|
c.rough[i] = Math.min(1, (c.rough[i] ?? 0.8) + 0.2 * m);
|
|
1190
1888
|
c.metal[i] = (c.metal[i] ?? 0) * (1 - m);
|
|
1889
|
+
c.mix[i] = Math.min(1, (c.mix[i] ?? 0) + m);
|
|
1191
1890
|
}
|
|
1192
1891
|
}
|
|
1193
1892
|
}
|
|
@@ -1211,6 +1910,99 @@ function grime(c, amount, seed) {
|
|
|
1211
1910
|
}
|
|
1212
1911
|
}
|
|
1213
1912
|
|
|
1913
|
+
// src/material/paint/metal.ts
|
|
1914
|
+
function crystal(xm, ym, size, cellsU, seed) {
|
|
1915
|
+
const gx = Math.floor(xm / size);
|
|
1916
|
+
const gy = Math.floor(ym / size);
|
|
1917
|
+
let best = 1e9;
|
|
1918
|
+
let id = 0;
|
|
1919
|
+
for (let j = -1;j <= 1; j++)
|
|
1920
|
+
for (let i = -1;i <= 1; i++) {
|
|
1921
|
+
const cx = ((gx + i) % cellsU + cellsU) % cellsU;
|
|
1922
|
+
const px = (gx + i + hash2(cx, gy + j, seed)) * size;
|
|
1923
|
+
const py = (gy + j + hash2(cx, gy + j, seed + 1)) * size;
|
|
1924
|
+
const d = (xm - px) ** 2 + (ym - py) ** 2;
|
|
1925
|
+
if (d < best) {
|
|
1926
|
+
best = d;
|
|
1927
|
+
id = hash2(cx, gy + j, seed + 2) * 1e6;
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
return id;
|
|
1931
|
+
}
|
|
1932
|
+
function paintMetal(c, r, seed) {
|
|
1933
|
+
const p = r.params;
|
|
1934
|
+
const brush = param(p, "brush", 0);
|
|
1935
|
+
const cast = param(p, "cast", 0);
|
|
1936
|
+
const spangle = param(p, "spangle", 0);
|
|
1937
|
+
const oxide = param(p, "oxide", 0);
|
|
1938
|
+
const paint = param(p, "paint", 0);
|
|
1939
|
+
const paintRough = param(p, "paintRough", 0.45);
|
|
1940
|
+
const chips = param(p, "chips", 0.25);
|
|
1941
|
+
const size = param(p, "crystal", 0.03);
|
|
1942
|
+
const metal = r.metalness ?? 1;
|
|
1943
|
+
const cellsU = Math.max(1, Math.round(c.metresU / size));
|
|
1944
|
+
const cs = c.metresU / cellsU;
|
|
1945
|
+
const pu = c.metresU / c.w;
|
|
1946
|
+
const pv = c.metresV / c.h;
|
|
1947
|
+
for (let y = 0;y < c.h; y++) {
|
|
1948
|
+
const ym = (y + 0.5) * pv;
|
|
1949
|
+
for (let x = 0;x < c.w; x++) {
|
|
1950
|
+
const i = y * c.w + x;
|
|
1951
|
+
const xm = (x + 0.5) * pu;
|
|
1952
|
+
const big = noiseAt(c, xm, ym, 0.25, 0.25, seed, 4);
|
|
1953
|
+
const fine = noiseAt(c, xm, ym, 0.01, 0.01, seed + 1, 2);
|
|
1954
|
+
let tone = 1 + 0.05 * big;
|
|
1955
|
+
let rough = r.roughness + 0.06 * big + 0.03 * fine;
|
|
1956
|
+
let h = 0.00015 * big;
|
|
1957
|
+
if (brush > 0) {
|
|
1958
|
+
const s = noiseAt(c, xm, ym, 0.6, 0.002, seed + 2, 3);
|
|
1959
|
+
tone *= 1 + 0.06 * brush * s;
|
|
1960
|
+
rough += 0.08 * brush * s;
|
|
1961
|
+
h += 0.00003 * brush * s;
|
|
1962
|
+
}
|
|
1963
|
+
if (cast > 0) {
|
|
1964
|
+
const pit = noiseAt(c, xm, ym, 0.012, 0.012, seed + 3, 3);
|
|
1965
|
+
const lump = noiseAt(c, xm, ym, 0.08, 0.08, seed + 4, 3);
|
|
1966
|
+
h += cast * (0.0006 * pit + 0.0008 * lump);
|
|
1967
|
+
rough += cast * (0.1 + 0.12 * pit);
|
|
1968
|
+
tone *= 1 + cast * (0.16 * pit + 0.08 * lump - 0.04);
|
|
1969
|
+
}
|
|
1970
|
+
if (spangle > 0) {
|
|
1971
|
+
const k = crystal(xm, ym, cs, cellsU, seed + 5);
|
|
1972
|
+
const v = hash2(k, 7, seed) * 2 - 1;
|
|
1973
|
+
tone *= 1 + 0.12 * spangle * v;
|
|
1974
|
+
rough += 0.12 * spangle * v;
|
|
1975
|
+
}
|
|
1976
|
+
let ox = 0;
|
|
1977
|
+
if (oxide > 0) {
|
|
1978
|
+
const blot = noiseAt(c, xm, ym, 0.18, 0.18, seed + 6, 5);
|
|
1979
|
+
const grain = noiseAt(c, xm, ym, 0.02, 0.02, seed + 7, 3);
|
|
1980
|
+
ox = smooth(0.45, 0.75, blot * 0.5 + 0.5 + oxide - 0.5 + 0.15 * grain);
|
|
1981
|
+
h += ox * 0.0004 * (1 + grain);
|
|
1982
|
+
}
|
|
1983
|
+
let m = metal * (1 - ox);
|
|
1984
|
+
let mix = ox;
|
|
1985
|
+
rough = rough * (1 - ox) + (0.88 + 0.06 * fine) * ox;
|
|
1986
|
+
if (paint > 0) {
|
|
1987
|
+
const n = noiseAt(c, xm, ym, 0.05, 0.05, seed + 8, 4);
|
|
1988
|
+
const chip = smooth(0.42, 0.5, n * 0.5 + 0.5 - (1 - chips) * 0.5 + 0.1);
|
|
1989
|
+
const film = paint * (1 - chip);
|
|
1990
|
+
m *= 1 - film;
|
|
1991
|
+
mix = mix * (1 - film) + chip * paint;
|
|
1992
|
+
rough = rough * (1 - film) + (paintRough + 0.05 * big) * film;
|
|
1993
|
+
tone = tone * (1 - film) + (0.96 + 0.03 * big) * film;
|
|
1994
|
+
h -= chip * paint * 0.00015;
|
|
1995
|
+
}
|
|
1996
|
+
c.height[i] = h;
|
|
1997
|
+
c.tone[i] = tone;
|
|
1998
|
+
c.rough[i] = Math.min(1, Math.max(0, rough));
|
|
1999
|
+
c.metal[i] = Math.min(1, Math.max(0, m));
|
|
2000
|
+
c.mix[i] = mix;
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
rustStreaks(c, param(p, "streaks", 0), seed + 77);
|
|
2004
|
+
}
|
|
2005
|
+
|
|
1214
2006
|
// src/material/paint/planks.ts
|
|
1215
2007
|
function paintPlanks(c, r, seed) {
|
|
1216
2008
|
const p = r.params;
|
|
@@ -1219,9 +2011,16 @@ function paintPlanks(c, r, seed) {
|
|
|
1219
2011
|
const gap = param(p, "gap", 0.004);
|
|
1220
2012
|
const depth = param(p, "depth", 0.004);
|
|
1221
2013
|
const knots = param(p, "knots", 1.5);
|
|
2014
|
+
const rings = param(p, "rings", 0);
|
|
2015
|
+
const rays = param(p, "rays", 0);
|
|
2016
|
+
const checks = param(p, "checks", 0);
|
|
2017
|
+
const paint = param(p, "paint", 0);
|
|
2018
|
+
const paintRough = param(p, "paintRough", 0.5);
|
|
2019
|
+
const chips = param(p, "chips", 0.3);
|
|
1222
2020
|
const boards = new Courses(c, { course: width, length, irregular: 0.6, stagger: 0, seed });
|
|
1223
2021
|
const pu = c.metresU / c.w;
|
|
1224
2022
|
const pv = c.metresV / c.h;
|
|
2023
|
+
const ringsN = Math.max(1, Math.min(9, Math.floor(width / pv / 4)));
|
|
1225
2024
|
for (let y = 0;y < c.h; y++) {
|
|
1226
2025
|
const ym = (y + 0.5) * pv;
|
|
1227
2026
|
const k = boards.at(ym);
|
|
@@ -1249,9 +2048,39 @@ function paintPlanks(c, r, seed) {
|
|
|
1249
2048
|
if (rr < 1)
|
|
1250
2049
|
knot = Math.max(knot, 1 - rr);
|
|
1251
2050
|
}
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
2051
|
+
let height = -depth * (1 - edge) + 0.0004 * ring * edge + 0.0003 * fibre - 0.0008 * knot;
|
|
2052
|
+
let tone = (0.66 + 0.22 * h) * (1 + 0.08 * ring + 0.06 * fibre) * (1 - 0.5 * knot) * (0.5 + 0.5 * edge);
|
|
2053
|
+
let rough = r.roughness + 0.05 * fibre + 0.1 * (1 - edge);
|
|
2054
|
+
const ring2 = Math.sin((vy * ringsN + warp * 2.5 + h * 6) * Math.PI * 2);
|
|
2055
|
+
const late = smooth(0.1, 0.8, ring2) * (0.75 + 0.25 * fibre);
|
|
2056
|
+
let mix = rings * Math.max(late, knot);
|
|
2057
|
+
if (rays > 0) {
|
|
2058
|
+
const fleck = smooth(0.55, 0.8, noiseAt(c, xm, ym, 0.03, 0.004, seed + 23, 2));
|
|
2059
|
+
tone *= 1 + 0.25 * rays * fleck;
|
|
2060
|
+
mix *= 1 - rays * fleck;
|
|
2061
|
+
}
|
|
2062
|
+
if (checks > 0) {
|
|
2063
|
+
const split = smooth(0.45, 0.6, noiseAt(c, xm, ym, 0.3, 0.003, seed + 29 + id, 3));
|
|
2064
|
+
height += checks * (0.0006 * late - 0.002 * split);
|
|
2065
|
+
tone *= 1 - checks * (0.45 * split + 0.08 * (1 - late));
|
|
2066
|
+
rough += checks * 0.1 * (1 - late);
|
|
2067
|
+
mix = Math.max(mix, checks * split);
|
|
2068
|
+
}
|
|
2069
|
+
if (paint > 0) {
|
|
2070
|
+
const n = noiseAt(c, xm, ym, 0.04, 0.03, seed + 31, 4) * 0.5 + 0.5;
|
|
2071
|
+
const nearEdge = 1 - smooth(gap, gap * 6, d);
|
|
2072
|
+
const chip = smooth(0.5, 0.56, n + chips * 0.5 + nearEdge * 0.15 - 0.45);
|
|
2073
|
+
const film = paint * (1 - chip);
|
|
2074
|
+
height = height * (1 - 0.4 * film) - chip * paint * 0.0002;
|
|
2075
|
+
const under = (0.9 + 0.06 * h) * (1 + 0.05 * ring + 0.04 * fibre) * (0.55 + 0.45 * edge);
|
|
2076
|
+
tone = tone * (1 - film) + under * film;
|
|
2077
|
+
rough = rough * (1 - film) + (paintRough + 0.04 * fibre) * film;
|
|
2078
|
+
mix = mix * (1 - film) + paint * chip;
|
|
2079
|
+
}
|
|
2080
|
+
c.height[i] = height;
|
|
2081
|
+
c.tone[i] = tone;
|
|
2082
|
+
c.rough[i] = Math.min(1, rough);
|
|
2083
|
+
c.mix[i] = mix;
|
|
1255
2084
|
}
|
|
1256
2085
|
}
|
|
1257
2086
|
}
|
|
@@ -1400,34 +2229,33 @@ function paintGlass(c, r, seed) {
|
|
|
1400
2229
|
}
|
|
1401
2230
|
|
|
1402
2231
|
// src/material/paint/strip.ts
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
2232
|
+
var PAINTERS = {
|
|
2233
|
+
bond: paintBond,
|
|
2234
|
+
planks: paintPlanks,
|
|
2235
|
+
plates: paintPlates,
|
|
2236
|
+
slate: paintSlate,
|
|
2237
|
+
concrete: paintConcrete,
|
|
2238
|
+
metal: paintMetal,
|
|
2239
|
+
weave: paintWeave,
|
|
2240
|
+
leather: paintLeather,
|
|
2241
|
+
noise: paintNoise,
|
|
2242
|
+
glass: paintGlass,
|
|
2243
|
+
flat: (c, r) => paintFlat(c, r)
|
|
2244
|
+
};
|
|
2245
|
+
function paintStrip(c, r, seed, where = "strip") {
|
|
2246
|
+
if (r.texture)
|
|
2247
|
+
paintImageStrip(c, r, r.texture, where);
|
|
2248
|
+
else {
|
|
2249
|
+
if (r.albedo || r.albedo2)
|
|
2250
|
+
c.colours = [r.albedo ?? [1, 1, 1], r.albedo2 ?? r.albedo ?? [1, 1, 1]];
|
|
2251
|
+
PAINTERS[r.kind](c, r, seed);
|
|
2252
|
+
if (r.kind !== "glass" && r.kind !== "flat")
|
|
2253
|
+
grime(c, param(r.params, "grime", 0), seed + 101);
|
|
2254
|
+
}
|
|
2255
|
+
if (r.paint) {
|
|
2256
|
+
r.paint(c, paintKit(c, seed + 211));
|
|
2257
|
+
checkFinite(c, where);
|
|
1429
2258
|
}
|
|
1430
|
-
grime(c, param(r.params, "grime", 0), seed + 101);
|
|
1431
2259
|
}
|
|
1432
2260
|
|
|
1433
2261
|
// src/material/paint/cell.ts
|
|
@@ -1519,7 +2347,7 @@ function composeRegion(sheet, c, x0, y0, pad) {
|
|
|
1519
2347
|
const ao = cavityOcclusion(c);
|
|
1520
2348
|
const maps = [
|
|
1521
2349
|
[sheet.albedo, packAlbedo(c, ao)],
|
|
1522
|
-
[sheet.normal, sobelNormals(c)],
|
|
2350
|
+
[sheet.normal, c.normalRgba ?? sobelNormals(c)],
|
|
1523
2351
|
[sheet.orm, packOrm(c, ao)]
|
|
1524
2352
|
];
|
|
1525
2353
|
const padX = c.wrapU ? 0 : pad;
|
|
@@ -1539,63 +2367,6 @@ function composeRegion(sheet, c, x0, y0, pad) {
|
|
|
1539
2367
|
}
|
|
1540
2368
|
}
|
|
1541
2369
|
|
|
1542
|
-
// src/material/paint/default-sheet.ts
|
|
1543
|
-
var leaf = { kind: "flat", scaleMetres: 1, roughness: 0.6 };
|
|
1544
|
-
var DEFAULT_SHEET2 = {
|
|
1545
|
-
seed: 1,
|
|
1546
|
-
size: 1024,
|
|
1547
|
-
strips: {
|
|
1548
|
-
plain: { kind: "noise", scaleMetres: 1, roughness: 0.6 },
|
|
1549
|
-
brick: { kind: "bond", scaleMetres: 0.45, roughness: 0.85, params: { grime: 0.2 } },
|
|
1550
|
-
stone: {
|
|
1551
|
-
kind: "bond",
|
|
1552
|
-
scaleMetres: 0.9,
|
|
1553
|
-
roughness: 0.9,
|
|
1554
|
-
params: {
|
|
1555
|
-
course: 0.22,
|
|
1556
|
-
length: 0.4,
|
|
1557
|
-
mortar: 0.016,
|
|
1558
|
-
depth: 0.012,
|
|
1559
|
-
bevel: 0.025,
|
|
1560
|
-
irregular: 0.5,
|
|
1561
|
-
relief: 0.004,
|
|
1562
|
-
variation: 0.3,
|
|
1563
|
-
grime: 0.2
|
|
1564
|
-
}
|
|
1565
|
-
},
|
|
1566
|
-
slate: { kind: "slate", scaleMetres: 0.66, roughness: 0.7 },
|
|
1567
|
-
iron: { kind: "plates", scaleMetres: 0.5, roughness: 0.55, metalness: 0 },
|
|
1568
|
-
wood: { kind: "planks", scaleMetres: 0.6, roughness: 0.75 },
|
|
1569
|
-
glass: { kind: "glass", scaleMetres: 1, roughness: 0.05 },
|
|
1570
|
-
concrete: { kind: "concrete", scaleMetres: 1, roughness: 0.9, params: { grime: 0.3 } }
|
|
1571
|
-
},
|
|
1572
|
-
cells: {
|
|
1573
|
-
window_sash: { kind: "noise", scaleMetres: 1, roughness: 0.5 },
|
|
1574
|
-
door_panel: { kind: "noise", scaleMetres: 1, roughness: 0.6 },
|
|
1575
|
-
clock: { kind: "noise", scaleMetres: 1, roughness: 0.3 },
|
|
1576
|
-
smokebox: {
|
|
1577
|
-
kind: "plates",
|
|
1578
|
-
scaleMetres: 1,
|
|
1579
|
-
roughness: 0.6,
|
|
1580
|
-
metalness: 0.2,
|
|
1581
|
-
params: { rust: 0.3 }
|
|
1582
|
-
},
|
|
1583
|
-
sign: { kind: "noise", scaleMetres: 1, roughness: 0.5 },
|
|
1584
|
-
porthole: { kind: "plates", scaleMetres: 1, roughness: 0.5 },
|
|
1585
|
-
foliage_broadleaf: leaf,
|
|
1586
|
-
foliage_maple: leaf,
|
|
1587
|
-
foliage_maple_autumn: leaf,
|
|
1588
|
-
foliage_birch: leaf,
|
|
1589
|
-
foliage_acacia: leaf,
|
|
1590
|
-
foliage_palm: leaf,
|
|
1591
|
-
foliage_spruce: leaf,
|
|
1592
|
-
foliage_spruce_snow: leaf,
|
|
1593
|
-
foliage_willow: leaf,
|
|
1594
|
-
foliage_reeds: leaf,
|
|
1595
|
-
foliage_needles: leaf
|
|
1596
|
-
}
|
|
1597
|
-
};
|
|
1598
|
-
|
|
1599
2370
|
// src/material/paint/validate.ts
|
|
1600
2371
|
var KINDS = [
|
|
1601
2372
|
"bond",
|
|
@@ -1605,45 +2376,55 @@ var KINDS = [
|
|
|
1605
2376
|
"flat",
|
|
1606
2377
|
"slate",
|
|
1607
2378
|
"concrete",
|
|
1608
|
-
"glass"
|
|
2379
|
+
"glass",
|
|
2380
|
+
"metal",
|
|
2381
|
+
"weave",
|
|
2382
|
+
"leather"
|
|
1609
2383
|
];
|
|
1610
2384
|
var SIZES = [512, 1024, 2048];
|
|
1611
|
-
function
|
|
2385
|
+
function fail2(cause, fix) {
|
|
1612
2386
|
throw new Ts23dError2("T3D_RECIPE_INVALID", { cause: `sheet recipe: ${cause}`, fix });
|
|
1613
2387
|
}
|
|
1614
|
-
var
|
|
2388
|
+
var unit2 = (v) => Number.isFinite(v) && v >= 0 && v <= 1;
|
|
1615
2389
|
function validateStrip(where, r) {
|
|
1616
2390
|
if (!KINDS.includes(r.kind))
|
|
1617
|
-
|
|
2391
|
+
fail2(`${where}.kind '${r.kind}' is unknown`, `use one of ${KINDS.join(" | ")}`);
|
|
1618
2392
|
if (!(Number.isFinite(r.scaleMetres) && r.scaleMetres > 0))
|
|
1619
|
-
|
|
1620
|
-
if (!
|
|
1621
|
-
|
|
1622
|
-
if (r.metalness !== undefined && !
|
|
1623
|
-
|
|
2393
|
+
fail2(`${where}.scaleMetres is ${r.scaleMetres}`, "set the metres the strip V span represents, > 0 (e.g. 0.6 for brick)");
|
|
2394
|
+
if (!unit2(r.roughness))
|
|
2395
|
+
fail2(`${where}.roughness is ${r.roughness}`, "roughness is 0..1");
|
|
2396
|
+
if (r.metalness !== undefined && !unit2(r.metalness))
|
|
2397
|
+
fail2(`${where}.metalness is ${r.metalness}`, "metalness is 0..1");
|
|
2398
|
+
for (const key of ["albedo", "albedo2"]) {
|
|
2399
|
+
const a = r[key];
|
|
2400
|
+
if (a !== undefined && !(a.length === 3 && a.every(unit2)))
|
|
2401
|
+
fail2(`${where}.${key} is [${a}]`, `${key} is linear RGB, three numbers 0..1 (use hex('#rrggbb'))`);
|
|
2402
|
+
}
|
|
2403
|
+
if (r.paint !== undefined && typeof r.paint !== "function")
|
|
2404
|
+
fail2(`${where}.paint is not a function`, "paint is (surface, kit) => void");
|
|
1624
2405
|
for (const [k, v] of Object.entries(r.params ?? {}))
|
|
1625
2406
|
if (!Number.isFinite(v))
|
|
1626
|
-
|
|
2407
|
+
fail2(`${where}.params.${k} is ${v}`, "params are finite numbers");
|
|
1627
2408
|
}
|
|
1628
2409
|
function validateSheet(recipe) {
|
|
1629
2410
|
if (!Number.isInteger(recipe.seed))
|
|
1630
|
-
|
|
2411
|
+
fail2(`seed ${recipe.seed}`, "seed is an integer");
|
|
1631
2412
|
if (recipe.size !== undefined && !SIZES.includes(recipe.size))
|
|
1632
|
-
|
|
2413
|
+
fail2(`size ${recipe.size}`, "size is 512, 1024 or 2048");
|
|
1633
2414
|
for (const [id, r] of Object.entries(recipe.strips)) {
|
|
1634
|
-
if (
|
|
1635
|
-
|
|
2415
|
+
if (stripRow2(id) < 0)
|
|
2416
|
+
fail2(`strip '${id}' is not in the layout`, `strips are ${STRIPS2.join(", ")} or a defineMaterial() id`);
|
|
1636
2417
|
if (r)
|
|
1637
2418
|
validateStrip(`strips.${id}`, r);
|
|
1638
2419
|
}
|
|
1639
2420
|
for (const [id, e] of Object.entries(recipe.cells ?? {})) {
|
|
1640
2421
|
if (!CELLS2.includes(id))
|
|
1641
|
-
|
|
2422
|
+
fail2(`cell '${id}' is not in the layout`, `cells are ${CELLS2.join(", ")}`);
|
|
1642
2423
|
if (!e)
|
|
1643
2424
|
continue;
|
|
1644
2425
|
if ("image" in e) {
|
|
1645
2426
|
if (!(e.image instanceof Uint8Array))
|
|
1646
|
-
|
|
2427
|
+
fail2(`cells.${id}.image is not bytes`, "pass PNG file bytes as a Uint8Array");
|
|
1647
2428
|
} else
|
|
1648
2429
|
validateStrip(`cells.${id}`, e);
|
|
1649
2430
|
}
|
|
@@ -1653,18 +2434,19 @@ function validateSheet(recipe) {
|
|
|
1653
2434
|
var FALLBACK = { kind: "noise", scaleMetres: 1, roughness: 0.6 };
|
|
1654
2435
|
function paintTrimSheet2(recipe = DEFAULT_SHEET2) {
|
|
1655
2436
|
validateSheet(recipe);
|
|
1656
|
-
const size = recipe.size ??
|
|
2437
|
+
const size = recipe.size ?? 2048;
|
|
1657
2438
|
const sheet = blankSheet(size);
|
|
1658
2439
|
const rng = createRng2(recipe.seed);
|
|
1659
2440
|
const seedOf = (name) => rng.fork(name).int(0, 2147483647);
|
|
1660
2441
|
const pad = Math.max(2, size / 256);
|
|
1661
|
-
|
|
2442
|
+
const all = [...STRIPS2, ...customIds()];
|
|
2443
|
+
for (const id of all) {
|
|
1662
2444
|
const r = recipe.strips[id] ?? DEFAULT_SHEET2.strips[id] ?? FALLBACK;
|
|
1663
2445
|
validateStrip(`strips.${id}`, r);
|
|
1664
2446
|
const rect = stripRect2(id);
|
|
1665
2447
|
const h = Math.round((rect.v1 - rect.v0) * size);
|
|
1666
2448
|
const c = createCanvas(size, h, rect.metres, r.scaleMetres, true);
|
|
1667
|
-
paintStrip(c, r, seedOf(id));
|
|
2449
|
+
paintStrip(c, r, seedOf(id), `strips.${id}`);
|
|
1668
2450
|
composeRegion(sheet, c, 0, Math.round(rect.v0 * size), pad);
|
|
1669
2451
|
}
|
|
1670
2452
|
for (const id of CELLS2) {
|
|
@@ -1677,39 +2459,6 @@ function paintTrimSheet2(recipe = DEFAULT_SHEET2) {
|
|
|
1677
2459
|
}
|
|
1678
2460
|
return sheet;
|
|
1679
2461
|
}
|
|
1680
|
-
// src/material/palette.ts
|
|
1681
|
-
function hex2(value) {
|
|
1682
|
-
const n = Number.parseInt(value.replace("#", ""), 16);
|
|
1683
|
-
const lin = (c) => {
|
|
1684
|
-
const s = c / 255;
|
|
1685
|
-
return s <= 0.04045 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
|
|
1686
|
-
};
|
|
1687
|
-
return [lin(n >> 16 & 255), lin(n >> 8 & 255), lin(n & 255)];
|
|
1688
|
-
}
|
|
1689
|
-
function definePalette2(entries) {
|
|
1690
|
-
return Object.fromEntries(Object.entries(entries).map(([k, v]) => [k, hex2(v)]));
|
|
1691
|
-
}
|
|
1692
|
-
var palettes2 = {
|
|
1693
|
-
victorianRail: definePalette2({
|
|
1694
|
-
paint: "#2f4a3a",
|
|
1695
|
-
trim: "#b08d57",
|
|
1696
|
-
iron: "#2b2b2e",
|
|
1697
|
-
brass: "#c9a24a",
|
|
1698
|
-
red: "#7a2a22"
|
|
1699
|
-
}),
|
|
1700
|
-
artDeco: definePalette2({
|
|
1701
|
-
limestone: "#d8cfbd",
|
|
1702
|
-
steel: "#9aa3ad",
|
|
1703
|
-
glass: "#4a6a80",
|
|
1704
|
-
bronze: "#8a6a3c"
|
|
1705
|
-
}),
|
|
1706
|
-
modernGlass: definePalette2({
|
|
1707
|
-
glass: "#6f8fa8",
|
|
1708
|
-
aluminium: "#c4c9cf",
|
|
1709
|
-
concrete: "#a7a39b"
|
|
1710
|
-
}),
|
|
1711
|
-
harbour: definePalette2({ hull: "#1f2f45", deck: "#8a6f4e", white: "#e8e6df", red: "#a1302a" })
|
|
1712
|
-
};
|
|
1713
2462
|
// src/material/shader-godot.ts
|
|
1714
2463
|
var f = (n) => Number.isInteger(n) ? `${n}.0` : `${+n.toFixed(6)}`;
|
|
1715
2464
|
var rect2 = (r) => `in_rect(UV, vec4(${f(r.u0)}, ${f(r.v0)}, ${f(r.u1)}, ${f(r.v1)}))`;
|
|
@@ -1878,18 +2627,18 @@ export function upgradeTrimMaterials(root: Object3D): void {
|
|
|
1878
2627
|
}
|
|
1879
2628
|
|
|
1880
2629
|
// src/material/shader.ts
|
|
1881
|
-
function
|
|
2630
|
+
function fail3(cause, fix) {
|
|
1882
2631
|
throw new Ts23dError2("T3D_RECIPE_INVALID", { cause: `shader hints: ${cause}`, fix });
|
|
1883
2632
|
}
|
|
1884
2633
|
function srgbHex(value) {
|
|
1885
2634
|
if (!/^#[0-9a-fA-F]{6}$/.test(value))
|
|
1886
|
-
|
|
2635
|
+
fail3(`colour '${value}'`, "use '#rrggbb' sRGB hex");
|
|
1887
2636
|
const n = Number.parseInt(value.slice(1), 16);
|
|
1888
2637
|
return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
|
|
1889
2638
|
}
|
|
1890
2639
|
function resolveHints(h) {
|
|
1891
|
-
const unit = (v, what) => Number.isFinite(v) && v >= 0 && v <= 1 ? v :
|
|
1892
|
-
const pos = (v, what) => Number.isFinite(v) && v > 0 ? v :
|
|
2640
|
+
const unit = (v, what) => Number.isFinite(v) && v >= 0 && v <= 1 ? v : fail3(`${what} is ${v}`, `${what} is 0..1`);
|
|
2641
|
+
const pos = (v, what) => Number.isFinite(v) && v > 0 ? v : fail3(`${what} is ${v}`, `${what} is a positive number`);
|
|
1893
2642
|
const strength = h.wind ? pos(h.wind.strength, "wind.strength") : 0;
|
|
1894
2643
|
return {
|
|
1895
2644
|
windStrength: strength,
|
|
@@ -1913,7 +2662,7 @@ function threeMaterialSource2(hints = {}) {
|
|
|
1913
2662
|
function godotShaderSource2(hints = {}) {
|
|
1914
2663
|
return godotSource(resolveHints(hints));
|
|
1915
2664
|
}
|
|
1916
|
-
export { TRIM_SHEET_SIZE2, stripRect2, cellRect2, STRIPS2, CELLS2, DECAL_CELLS2, FOLIAGE_CELLS2, isFoliage2,
|
|
2665
|
+
export { CC0_WOOD_PLANKS_GREY2, CC0_MATERIALS2, MATERIAL_LIBRARY2, hex2, NATURAL2, definePalette2, palettes2, provideTextures2, creditOf2, DEFAULT_SHEET2, STRIP_ROWS2, CUSTOM_STRIP_SLOTS2, TRIM_SHEET_SIZE2, stripRow2, stripRect2, cellRect2, STRIPS2, CLASSIC_STRIPS2, MATERIAL_STRIPS2, CELLS2, DECAL_CELLS2, FOLIAGE_CELLS2, isFoliage2, defineMaterial2, resetMaterials2, materialOf2, textureSources2, stripCredits2, paintTrimSheet2, threeMaterialSource2, godotShaderSource2 };
|
|
1917
2666
|
|
|
1918
|
-
//# debugId=
|
|
1919
|
-
//# sourceMappingURL=chunk-
|
|
2667
|
+
//# debugId=06EA6C9948471A9664756E2164756E21
|
|
2668
|
+
//# sourceMappingURL=chunk-y63c6zcv.js.map
|