@pieai/swimmer-avatar-kit 0.1.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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +23 -0
  3. package/README.md +125 -0
  4. package/UPSTREAM.md +47 -0
  5. package/dist/avatar.d.ts +2 -0
  6. package/dist/avatar.js +69 -0
  7. package/dist/avatar.js.map +1 -0
  8. package/dist/index.d.ts +3 -0
  9. package/dist/index.js +3 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/materials.d.ts +20 -0
  12. package/dist/materials.js +61 -0
  13. package/dist/materials.js.map +1 -0
  14. package/dist/react-three-fiber.d.ts +16 -0
  15. package/dist/react-three-fiber.js +52 -0
  16. package/dist/react-three-fiber.js.map +1 -0
  17. package/dist/recipe.d.ts +18 -0
  18. package/dist/recipe.js +118 -0
  19. package/dist/recipe.js.map +1 -0
  20. package/dist/types.d.ts +62 -0
  21. package/dist/types.js +2 -0
  22. package/dist/types.js.map +1 -0
  23. package/package.json +102 -0
  24. package/upstream-lock.json +38 -0
  25. package/vendor/kindergrimm/LICENSE +24 -0
  26. package/vendor/kindergrimm/src/gloss/catmullClark.js +129 -0
  27. package/vendor/kindergrimm/src/gloss/gface.d.ts +25 -0
  28. package/vendor/kindergrimm/src/gloss/gface.js +226 -0
  29. package/vendor/kindergrimm/src/gloss/gform.js +201 -0
  30. package/vendor/kindergrimm/src/gloss/ghair.js +534 -0
  31. package/vendor/kindergrimm/src/gloss/glayout.js +365 -0
  32. package/vendor/kindergrimm/src/gloss/gmedia.d.ts +28 -0
  33. package/vendor/kindergrimm/src/gloss/gmedia.js +470 -0
  34. package/vendor/kindergrimm/src/gloss/gpalette.d.ts +10 -0
  35. package/vendor/kindergrimm/src/gloss/gpalette.js +205 -0
  36. package/vendor/kindergrimm/src/gloss/gparts/blush.js +57 -0
  37. package/vendor/kindergrimm/src/gloss/gparts/body.js +47 -0
  38. package/vendor/kindergrimm/src/gloss/gparts/brows.js +55 -0
  39. package/vendor/kindergrimm/src/gloss/gparts/crest.js +88 -0
  40. package/vendor/kindergrimm/src/gloss/gparts/eyes.js +357 -0
  41. package/vendor/kindergrimm/src/gloss/gparts/frame.js +225 -0
  42. package/vendor/kindergrimm/src/gloss/gparts/hair.js +100 -0
  43. package/vendor/kindergrimm/src/gloss/gparts/hat.js +163 -0
  44. package/vendor/kindergrimm/src/gloss/gparts/index.js +33 -0
  45. package/vendor/kindergrimm/src/gloss/gparts/mark.js +139 -0
  46. package/vendor/kindergrimm/src/gloss/gparts/mouth.js +170 -0
  47. package/vendor/kindergrimm/src/gloss/gparts/nose.js +63 -0
  48. package/vendor/kindergrimm/src/gloss/gparts/specs.js +124 -0
  49. package/vendor/kindergrimm/src/gloss/grig.d.ts +41 -0
  50. package/vendor/kindergrimm/src/gloss/grig.js +286 -0
  51. package/vendor/kindergrimm/src/gloss/gshape.js +421 -0
  52. package/vendor/kindergrimm/src/gloss/gspecies.d.ts +10 -0
  53. package/vendor/kindergrimm/src/gloss/gspecies.js +338 -0
  54. package/vendor/kindergrimm/src/gloss/gtexture.js +406 -0
  55. package/vendor/kindergrimm/src/rng.d.ts +11 -0
  56. package/vendor/kindergrimm/src/rng.js +27 -0
@@ -0,0 +1,365 @@
1
+ // ---------------------------------------------------------------
2
+ // THE LAYOUT — everything two parts must agree on, measured once.
3
+ //
4
+ // Coordinates: world units, y up, +z toward the viewer (the face is on
5
+ // the +z side), origin at the FLOOR under the character. Same convention as
6
+ // the voxel lab, and for the same reason: a solid wants measuring from
7
+ // the ground it sits on.
8
+ //
9
+ // The whole thing is one function. `L.at(ax, ay)` takes a FACE
10
+ // COORDINATE — ax across, ay up, both roughly −1…1 — and returns the
11
+ // point on the body and the normal there. On an ellipsoid that is
12
+ // arithmetic; on the humanoid's SKULL (a modeled, subdivided mesh —
13
+ // gskull.js) it is a raycast against the real surface. Either way a
14
+ // feature asks where it lands and never learns how the body is made.
15
+ //
16
+ // Every face part places itself through `at` and nothing else. That is
17
+ // what lets a second body shape arrive later — the skull already did —
18
+ // and inherit the entire face catalogue: whatever `at` returns, the
19
+ // features land on it.
20
+ // ---------------------------------------------------------------
21
+ // The one thing the layout asks a PART for. How tall an eye is lives
22
+ // in the eye style table and nowhere else, but the layout is what has
23
+ // to keep a mouth out from under one — so the eyes publish their reach
24
+ // as a pure function of the recipe and this asks. It is the only edge
25
+ // pointing this way, and it beats copying the style table in here.
26
+ import { eyeReach, eyeSpan, eyeProud } from './gparts/eyes.js';
27
+ import { hairOuter } from './ghair.js';
28
+ import { hatBare } from './gparts/hat.js';
29
+ import { mouthReach, mouthSpan } from './gparts/mouth.js';
30
+ import { frameLayout } from './gparts/frame.js';
31
+ // the ONE copy of each surface: the same functions/arrays the body is
32
+ // BUILT from, so where a feature lands and where the skin is can
33
+ // never disagree (see gshape.js and gskull.js)
34
+ import { surfT, surfN } from './gshape.js';
35
+ import { formSurface, isMeshForm } from './gform.js';
36
+ import { HAIR_BY_ID, INK, mix, luma, pickAcc, pickOutfit } from './gpalette.js';
37
+
38
+ // how far off centre ax = ±1 reaches, in radians. Beyond ~1.05 a
39
+ // feature starts wrapping onto the side of the head where the camera
40
+ // cannot see it.
41
+ const SPREAD = .92;
42
+ const clamp = (v, a, b) => Math.min(b, Math.max(a, v));
43
+
44
+ export function buildGlossLayout(P, colors, form = 'sphere', stance = 'none') {
45
+ const B = P.body;
46
+
47
+ // ---- the surface, per form ------------------------------------------
48
+ // Both branches define the same six things and nothing downstream
49
+ // knows which branch ran: rx/ry/rz (extents), cy (centre height —
50
+ // the floor is y = 0), topY (the crown), at/top/hwAt.
51
+ const rx = B.r * B.wide;
52
+ // NEVER THINNER THAN SQUARE. Guaranteed here and not just in the
53
+ // part's ranges, because a species can cast its own `tall` and
54
+ // outvote them — that has already happened twice with sizes. One
55
+ // clamp in the layout and no profile can make a narrow head.
56
+ const ry = B.r * Math.min(B.tall, B.wide);
57
+ const rz = B.r * B.deep;
58
+ // The FORM is one exponent, not a second body of code: n = 2 is a
59
+ // ball, and squaring it off is turning that number up. A sphere is
60
+ // pinned at 2 so it stays exact; only a cube reads the roll.
61
+ //
62
+ // There WAS a third form here — `head`, a modeled chibi skull from a
63
+ // subdivided control cage. It is gone, and so is `gskull.js`. It was
64
+ // a good skull and it was the wrong object: this lab makes CHARACTERS, and
65
+ // a sphere or a cube with a face on it is one. Everything the skull
66
+ // was carrying — the hair, the proportions, the face catalogue — sits
67
+ // on these two perfectly well, and one exponent is a knob you can
68
+ // scrub where a cage is a thing you have to sculpt.
69
+ // `rock` and `slime` keep the ball's exponent and bend the SURFACE
70
+ // instead — see `formK`. A cube is the only form that reads `corner`.
71
+ const exp = form === 'cube' ? B.corner : 2;
72
+ const meshed = isMeshForm(form);
73
+
74
+ // THE STANCE — what the head stands on. Computed FIRST, because the
75
+ // head's whole height depends on it; everything after this only sees
76
+ // a bigger `cy`, which is what lets the face catalogue survive a
77
+ // torso arriving under it untouched. A modeled form (rock, slime) is
78
+ // already a whole creature — its foot or its base IS its bottom — so
79
+ // it never takes a frame.
80
+ if (meshed) stance = 'none';
81
+ const frame = stance !== 'none'
82
+ ? frameLayout(P.frame, { r: B.r, ry }, stance) : null;
83
+ const lift = frame ? frame.headBase : 0;
84
+
85
+ let cy = ry + lift, topY = ry * 2 + lift, at, top, hwAt, formMesh = null;
86
+ // the HEAD's lowest point. The floor and the bottom of the head used
87
+ // to be the same y, and the chin clamps below leaned on that — with
88
+ // a frame they split, and a clamp still measuring off the floor
89
+ // would happily run a maw down onto the chest.
90
+ let botY = lift;
91
+
92
+ if (meshed) {
93
+ // MODELED: a control cage through Catmull-Clark (`gform.js`), and
94
+ // `at` raycasts the very arrays the body will be stamped from, so
95
+ // a feature lands on the real subdivided surface rather than on an
96
+ // idea of it.
97
+ const F = formSurface(form, { scale: B.r, width: B.wide, height: B.tall, depth: B.deep });
98
+ formMesh = F.mesh;
99
+ cy = -F.minY;
100
+ topY = cy + F.maxY;
101
+ botY = 0;
102
+ const cast = (dx, dy, dz) =>
103
+ // the jittered retry is for a ray grazing an edge exactly: rare,
104
+ // but a null here would take the whole build down
105
+ F.pick(dx, dy, dz) ?? F.pick(dx + 1e-4, dy + 1e-4, dz);
106
+ at = (ax, ay) => {
107
+ const u = ax * SPREAD, v = ay * SPREAD, cv = Math.cos(v);
108
+ const h = cast(Math.sin(u) * cv, Math.sin(v), Math.cos(u) * cv);
109
+ return { p: [h.p[0], h.p[1] + cy, h.p[2]], n: h.n };
110
+ };
111
+ top = t => {
112
+ const h = cast(Math.sin(t), Math.cos(t), 0);
113
+ return { p: [h.p[0], h.p[1] + cy, h.p[2]], n: h.n };
114
+ };
115
+ hwAt = yy => F.halfWidthAt(yy - cy);
116
+ } else {
117
+ at = (ax, ay) => {
118
+ const u = ax * SPREAD, v = ay * SPREAD, cv = Math.cos(v);
119
+ // a direction, then pushed out to wherever the surface actually
120
+ // is; the normal is the GRADIENT of the implicit surface, never
121
+ // the direction from the centre — get this wrong and every
122
+ // feature on a squashed body tips, and on a cube they would all
123
+ // point at the corners instead of lying flat on the face
124
+ const d = [Math.sin(u) * cv, Math.sin(v), Math.cos(u) * cv];
125
+ const t = surfT(d[0], d[1], d[2], rx, ry, rz, exp);
126
+ const x = d[0] * t, y = d[1] * t, z = d[2] * t;
127
+ return { p: [x, y + cy, z], n: surfN(x, y, z, rx, ry, rz, exp) };
128
+ };
129
+ top = t => {
130
+ const d = [Math.sin(t), Math.cos(t), 0];
131
+ const s2 = surfT(d[0], d[1], 0, rx, ry, rz, exp);
132
+ const x = d[0] * s2, y = d[1] * s2;
133
+ return { p: [x, y + cy, 0], n: surfN(x, y, 0, rx, ry, rz, exp) };
134
+ };
135
+ hwAt = () => rx;
136
+ }
137
+
138
+ // WHERE THE FACE'S ROWS SIT, published once. Brows must clear the
139
+ // eyes, a nose must land between the eyes and the mouth, cheeks sit
140
+ // outboard of both — all of that is agreement BETWEEN parts, so it
141
+ // belongs here rather than in each part reaching into another's
142
+ // params.
143
+ // THE FACE FOLLOWS THE WIDTH. On a drop the upper half is the peak,
144
+ // and the lab's own upper-half rule would put the eyes on a spike —
145
+ // so a form may pull the face down toward the part of itself that is
146
+ // actually wide enough to carry one.
147
+ const faceBias = form === 'slime' ? -.34 : 0;
148
+ const eyeX = P.eyes.x, eyeY = P.eyes.y + faceBias;
149
+ const eyeR = B.r * P.eyes.size;
150
+ // a cube's face is FLAT, so its eyes have room a sphere's curvature
151
+ // never gives them. Only the eyes grow — the nose, mouth and cheeks
152
+ // keep sizing off `eyeR`, because they got no extra room
153
+ let eyeSize = eyeR * (form === 'cube' ? 1.22 : 1);
154
+
155
+ // AN EYE HAS TO FIT THE HEAD IT IS ON. The styles are free to ask for
156
+ // whatever they want — a `slab` asks for two and a half times the eye
157
+ // unit in height — and this is what makes that safe: the eyes publish
158
+ // how far they reach and how wide they span, and the size is cut back
159
+ // until they are on the head and clear of each other. Without it,
160
+ // going big on a style means going off the silhouette on the small
161
+ // bodies, and the only fix left is timid numbers everywhere.
162
+ {
163
+ const reachY = eyeReach(P), spanX = eyeSpan(P);
164
+ const a0 = at(eyeX, eyeY);
165
+ const cxOff = Math.abs(a0.p[0]);
166
+ // the two vertical rooms measured separately: on a skull the jaw
167
+ // is shallower than the cranium, so an eye set low runs out of
168
+ // chin long before it runs out of forehead
169
+ const roomUp = topY * .93 - a0.p[1];
170
+ const roomDn = a0.p[1] - (botY + (cy - botY) * .07);
171
+ eyeSize = Math.min(
172
+ eyeSize,
173
+ Math.min(roomUp, roomDn) / reachY, // stays on the head, top and bottom
174
+ (hwAt(a0.p[1]) * .95 - cxOff) / spanX, // and inside the cheeks at THIS height
175
+ cxOff / (spanX * 1.1), // and out of the other eye
176
+ );
177
+ // The floor exists so a weird roll cannot erase the eyes — but it
178
+ // must stay BELOW what containment asks for, or it wins the fight
179
+ // and tall eyes dangle off the chin of every squat head. At .3 of
180
+ // the unit it did exactly that, a row of them, like legs.
181
+ eyeSize = Math.max(eyeSize, eyeR * .15);
182
+ }
183
+
184
+ // THE MOUTH IS PUSHED OUT FROM UNDER THE EYES. The eye and mouth
185
+ // height ranges overlap on their own — a low eye and a high mouth
186
+ // collide even with the old shapes — and a tall style like `slab`
187
+ // reaches so far down that it lands on the mouth three times in
188
+ // four. So the rolled `mouth.y` is a WISH, and this is where it
189
+ // gets to be true: the eyes declare how far they reach
190
+ // (`eyeReach`, which only they can know), and the mouth is moved
191
+ // down until it is clear of them.
192
+ // Solved against the REAL surface by bisection, not by inverting a
193
+ // sine. The height a face coordinate lands at depends on the depth
194
+ // radius and the exponent as well as `ry`, so `asin(y / ry)` is only
195
+ // right on a perfect sphere — and it was wrong often enough to leave
196
+ // one character in twenty still overlapping.
197
+ const surfY = v => at(0, v).p[1];
198
+ const clearY = at(eyeX, eyeY).p[1] // the eye's centre
199
+ - eyeReach(P) * eyeSize // down to its lowest ink
200
+ - mouthReach(P) * eyeR // the mouth's real reach —
201
+ // a maw is EYES tall
202
+ - eyeR * .1; // and air between them
203
+
204
+ let mouthY = P.mouth.y + faceBias;
205
+ let mouthFit = 1;
206
+ if (surfY(mouthY) > clearY) {
207
+ let lo = -1.05, hi = mouthY; // lo: as low as a mouth may go
208
+ for (let i = 0; i < 24; i++) {
209
+ const m = (lo + hi) / 2;
210
+ surfY(m) > clearY ? (hi = m) : (lo = m);
211
+ }
212
+ mouthY = lo;
213
+ // A maw can be taller than the room the head has under the eyes,
214
+ // and then no amount of pushing down fits it — the chin runs out.
215
+ // What gives is the SIZE: the deficit comes off the mouth's scale,
216
+ // so the huge ones stay huge wherever huge fits.
217
+ const deficit = surfY(mouthY) - clearY;
218
+ if (deficit > 0) {
219
+ const need = mouthReach(P) * eyeR;
220
+ mouthFit = Math.max(.4, (need - deficit) / need);
221
+ }
222
+ }
223
+
224
+ // THE CHIN. Pushed down to clear the eyes, a big maw runs off the
225
+ // bottom of the head — and there is nothing below it to push against,
226
+ // so the SIZE is what gives. This was missing: the clamp only ever
227
+ // looked upward at the eyes.
228
+ {
229
+ const need = mouthReach(P) * eyeR * mouthFit;
230
+ const room = surfY(mouthY) - (botY + (cy - botY) * .12);
231
+ if (need > room) mouthFit *= Math.max(.35, room / need);
232
+ }
233
+
234
+ // and SIDEWAYS: a head narrows toward the chin, and a maw pushed
235
+ // down there was overflowing the silhouette on the squat bodies.
236
+ // At .88 of the half-width a mouth spans almost the whole face and
237
+ // reads as a letterbox cut in the head rather than a mouth on it.
238
+ {
239
+ const spanNeed = mouthSpan(P) * eyeR * mouthFit;
240
+ const hw = Math.abs(at(1, mouthY).p[0]);
241
+ if (spanNeed > hw * .7) mouthFit *= Math.max(.35, hw * .7 / spanNeed);
242
+ }
243
+
244
+ /**
245
+ * `at`, but pulled back onto the FRONT of the head.
246
+ *
247
+ * Out near the silhouette the surface turns away, and a plate landed
248
+ * there stands almost edge-on: it reads as a fin sticking off the
249
+ * outline rather than a mark on the face. Cheeks are the ones that
250
+ * do it — they are placed OUTBOARD of the eyes by design, and one
251
+ * character in five had one hanging off the side.
252
+ *
253
+ * The guard used to live in the face part, and was lost when the
254
+ * face was split into six of them. It belongs here: it is a fact
255
+ * about the BODY, and every part that lands on a body wants it.
256
+ */
257
+ // `halfW` is the feature's own half-width: testing only where its
258
+ // CENTRE lands leaves a wide cheek overhanging by half of itself,
259
+ // because the head is curving away under it the whole time.
260
+ function onFace(ax, ay, { facing = .6, halfW = 0 } = {}) {
261
+ const ok = v => {
262
+ const a = at(v, ay);
263
+ // the head's half-width at THIS height, not the crown's — a
264
+ // skull's chin is narrower than rx and a cheek tested against
265
+ // rx would overhang it
266
+ return a.n[2] >= facing && Math.abs(a.p[0]) + halfW <= hwAt(a.p[1]) * .96;
267
+ };
268
+ if (ax === 0 || ok(ax)) return at(ax, ay);
269
+ const sgn = Math.sign(ax);
270
+ let lo = 0, hi = Math.abs(ax);
271
+ for (let i = 0; i < 18; i++) {
272
+ const m = (lo + hi) / 2;
273
+ ok(sgn * m) ? (lo = m) : (hi = m);
274
+ }
275
+ return at(sgn * lo, ay);
276
+ }
277
+
278
+ // HAIR'S COLOUR, resolved once. Two parts read it — the hair itself
279
+ // and the BROW, which has to be the same head's hair or the face
280
+ // reads as somebody else's eyebrows glued on. That is exactly the
281
+ // "anything two parts must agree on lives here" rule.
282
+ let hairHex = HAIR_BY_ID[P.hair?.color]?.hex ?? INK;
283
+ // A beanie or a headband is worn on a bare head: there is no size at
284
+ // which one shares a skull with a haircut without fighting it.
285
+ const bare = hatBare(P);
286
+ const hasHair = !!(P.hair && P.hair.style !== 'bald') && !bare;
287
+
288
+ // HAIR HAS TO COME OFF THE HEAD IT IS ON. Skin and hair are rolled
289
+ // from two independent tables, so nothing stops caramel hair landing
290
+ // on caramel skin — and a third of the sheet came out with a haircut
291
+ // you could only find by its silhouette. Where the two are within a
292
+ // sixth of each other in luma, the hair is pushed the way it is
293
+ // already leaning, hardest when the clash is worst.
294
+ //
295
+ // A TIE GOES DARKER: hair darker than skin is the overwhelmingly
296
+ // common pairing, and it is the one that reads at sheet scale.
297
+ // This is not the painted-shadow rule being broken — nothing here is
298
+ // standing in for a shadow. It is two objects that must not be the
299
+ // same colour, which is the same job `luma` was written for.
300
+ if (hasHair) {
301
+ const NEED = .17;
302
+ const d = luma(hairHex) - luma(colors.body);
303
+ if (Math.abs(d) < NEED)
304
+ hairHex = mix(hairHex, d > .02 ? '#FFF6EC' : INK,
305
+ .18 + .42 * (NEED - Math.abs(d)) / NEED);
306
+ }
307
+
308
+ // THE OUTFIT — colours the frame wears, resolved HERE because they
309
+ // are agreements: cloth must clear the skin and the hair, gloves and
310
+ // shoes must clear the cloth, and the print's ink must clear its own
311
+ // cloth (light ink on dark cloth, dark on light — a tone-on-tone
312
+ // print vanishes at sheet scale). The frame part just reads it.
313
+ let outfit = null;
314
+ if (frame) {
315
+ const F = P.frame;
316
+ const { cloth, glove, shoe } = pickOutfit(F.clothIx ?? 0, colors.body, hairHex);
317
+ outfit = {
318
+ dressed: !!F.dressed, cloth, glove, shoe,
319
+ motif: F.dressed ? (F.motif ?? 'none') : 'none',
320
+ ink: luma(cloth) < .45 ? '#F2ECE2' : '#2B2422',
321
+ };
322
+ }
323
+
324
+ return {
325
+ outfit,
326
+ rx, ry, rz,
327
+ form, exp, // the body's shape family, for the part
328
+ // the body as raw numbers — the only thing HAIR needs in order to
329
+ // grow on it, and the reason one hair generator covers both forms
330
+ shape: { rx, ry, rz, exp, cy },
331
+ formMesh, // the modeled bodies' arrays, to stamp
332
+ // the STANCE, measured. `frame` is null for a head-only character, and
333
+ // the frame part stamps exactly what is in here — the layout is
334
+ // the one place the head and the torso can agree on a socket.
335
+ stance, frame,
336
+ hair: hairHex,
337
+ // pulled toward ink: a platinum brow at full strength vanishes, and
338
+ // an ink brow under blonde hair belongs to a different face
339
+ browColor: hasHair ? mix(hairHex, INK, .38) : INK,
340
+ // H is the HEAD's height, not the character's: the sheet normalises faces
341
+ // by it, and a head that shrank because it grew a body would defeat
342
+ // the point of having one. The whole character's height is in `bounds`.
343
+ H: topY - botY, W: rx * 2,
344
+ cy, // the body's centre, sitting on the floor
345
+ s: B.r, // the one number every feature scales off
346
+ eyeX, eyeY, mouthY, mouthFit,
347
+ // what the EXTRAS have to clear. Spectacles must sit in front of a
348
+ // ball eye, a hat on top of a big soft cut — and only the eyes and
349
+ // the hair can know how far out they reach.
350
+ eyeProud: eyeProud(P),
351
+ hairTop: hasHair ? hairOuter(P) : 1,
352
+ // a pulled-on hat is worn on a BARE head — see `hatBare`
353
+ hatBare: bare,
354
+ // what the EXTRAS are made of: never the character's own five, and
355
+ // guaranteed clear of both the skin and the hair
356
+ acc: pickAcc(P.hat?.accIx ?? 0, colors.body, hairHex),
357
+ eyeR, // the unit the OTHER face parts size off
358
+ eyeSize, // what the eyes themselves use
359
+ // the midpoint by construction, so a nose can never collide with a
360
+ // mouth however either one is dragged
361
+ noseY: (eyeY + mouthY) / 2,
362
+ at, top, onFace,
363
+ ...colors, // body, warm, ink, sclera, maw, lite
364
+ };
365
+ }
@@ -0,0 +1,28 @@
1
+ import type { Material, Scene, Texture, WebGLRenderer } from "three";
2
+
3
+ import type { MaterialFor } from "./grig.js";
4
+
5
+ export interface GlossMaterialDefinition {
6
+ id: string;
7
+ label: string;
8
+ }
9
+
10
+ export function studioEnv(renderer: WebGLRenderer): Texture;
11
+ export function makeMaterialFactory(environment: Texture): MaterialFor;
12
+
13
+ export function dressScene(
14
+ scene: Scene,
15
+ renderer: WebGLRenderer,
16
+ options?: {
17
+ span?: number;
18
+ pool?: number;
19
+ shadows?: boolean | "wall";
20
+ wallZ?: number;
21
+ },
22
+ ): { key: unknown; floor: unknown; wall?: unknown };
23
+
24
+ export const MATERIALS: readonly GlossMaterialDefinition[];
25
+ export const MATERIAL_IDS: readonly string[];
26
+ export const MATERIAL_WEIGHTS: readonly [string, number][];
27
+
28
+ export type { Material, MaterialFor };