@pixygon/avatar 1.0.0 → 1.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.
- package/dist/chunk-OD5OQB7A.mjs +390 -0
- package/dist/components/index.d.mts +43 -1
- package/dist/components/index.d.ts +43 -1
- package/dist/components/index.js +452 -92
- package/dist/components/index.mjs +264 -92
- package/dist/index.d.mts +16 -1
- package/dist/index.d.ts +16 -1
- package/dist/index.js +162 -0
- package/dist/index.mjs +8 -54
- package/package.json +14 -2
- package/src/components/AvatarEditor.tsx +63 -25
- package/src/components/AvatarPreview.tsx +41 -0
- package/src/components/AvatarRenderer.tsx +119 -0
- package/src/components/AvatarThumbnail.tsx +40 -0
- package/src/components/index.ts +10 -1
- package/src/index.ts +3 -0
- package/src/utils/geometry.ts +190 -0
- package/dist/chunk-5QZCUXJW.mjs +0 -187
package/dist/components/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,12 +17,23 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/components/index.ts
|
|
21
31
|
var components_exports = {};
|
|
22
32
|
__export(components_exports, {
|
|
23
|
-
AvatarEditor: () => AvatarEditor
|
|
33
|
+
AvatarEditor: () => AvatarEditor,
|
|
34
|
+
AvatarPreview: () => AvatarPreview,
|
|
35
|
+
AvatarRenderer: () => AvatarRenderer,
|
|
36
|
+
AvatarThumbnail: () => AvatarThumbnail
|
|
24
37
|
});
|
|
25
38
|
module.exports = __toCommonJS(components_exports);
|
|
26
39
|
|
|
@@ -204,8 +217,271 @@ function useAvatarEditor(initial) {
|
|
|
204
217
|
return { appearance, tab, setTab, update, randomize, reset };
|
|
205
218
|
}
|
|
206
219
|
|
|
207
|
-
// src/components/
|
|
220
|
+
// src/components/AvatarRenderer.tsx
|
|
221
|
+
var import_react2 = require("react");
|
|
222
|
+
var import_fiber = require("@react-three/fiber");
|
|
223
|
+
var import_drei = require("@react-three/drei");
|
|
224
|
+
var THREE2 = __toESM(require("three"));
|
|
225
|
+
|
|
226
|
+
// src/skeleton.ts
|
|
227
|
+
function mirrorX(p) {
|
|
228
|
+
return [-p[0], p[1], p[2]];
|
|
229
|
+
}
|
|
230
|
+
function buildSkeleton(body) {
|
|
231
|
+
const h = 0.85 + body.height * 0.35;
|
|
232
|
+
const b = 0.75 + body.build * 0.5;
|
|
233
|
+
const footY = 0.02 * h;
|
|
234
|
+
const ankleY = 0.08 * h;
|
|
235
|
+
const kneeY = 0.45 * h;
|
|
236
|
+
const hipY = 0.88 * h;
|
|
237
|
+
const waistY = 0.95 * h;
|
|
238
|
+
const chestY = 1.18 * h;
|
|
239
|
+
const shoulderY = 1.3 * h;
|
|
240
|
+
const neckY = 1.38 * h;
|
|
241
|
+
const headBaseY = 1.42 * h;
|
|
242
|
+
const headCenterY = 1.55 * h;
|
|
243
|
+
const hipSpread = 0.09 * b;
|
|
244
|
+
const shoulderSpread = 0.16 * b;
|
|
245
|
+
const bones = [];
|
|
246
|
+
bones.push({ start: [0, hipY, 0], end: [0, waistY, 0], radius: 0.1 * b });
|
|
247
|
+
bones.push({ start: [0, waistY, 0], end: [0, chestY, 0], radius: 0.11 * b });
|
|
248
|
+
bones.push({ start: [0, chestY, 0], end: [0, shoulderY, 0], radius: 0.12 * b });
|
|
249
|
+
bones.push({ start: [0, neckY, 0], end: [0, headBaseY, 0], radius: 0.04 * b });
|
|
250
|
+
const lShoulder = [shoulderSpread, shoulderY, 0];
|
|
251
|
+
const lElbow = [shoulderSpread + 0.22 * h, shoulderY - 0.08 * h, 0];
|
|
252
|
+
const lWrist = [shoulderSpread + 0.42 * h, shoulderY - 0.18 * h, 0];
|
|
253
|
+
const lHand = [shoulderSpread + 0.5 * h, shoulderY - 0.22 * h, 0];
|
|
254
|
+
bones.push({ start: lShoulder, end: lElbow, radius: 0.04 * b });
|
|
255
|
+
bones.push({ start: lElbow, end: lWrist, radius: 0.035 * b });
|
|
256
|
+
bones.push({ start: lWrist, end: lHand, radius: 0.03 * b });
|
|
257
|
+
bones.push({ start: mirrorX(lShoulder), end: mirrorX(lElbow), radius: 0.04 * b });
|
|
258
|
+
bones.push({ start: mirrorX(lElbow), end: mirrorX(lWrist), radius: 0.035 * b });
|
|
259
|
+
bones.push({ start: mirrorX(lWrist), end: mirrorX(lHand), radius: 0.03 * b });
|
|
260
|
+
const lHip = [hipSpread, hipY, 0];
|
|
261
|
+
const lKnee = [hipSpread + 0.01, kneeY, 0];
|
|
262
|
+
const lAnkle = [hipSpread, ankleY, 0];
|
|
263
|
+
const lToe = [hipSpread, footY, 0.06];
|
|
264
|
+
bones.push({ start: lHip, end: lKnee, radius: 0.055 * b });
|
|
265
|
+
bones.push({ start: lKnee, end: lAnkle, radius: 0.045 * b });
|
|
266
|
+
bones.push({ start: lAnkle, end: lToe, radius: 0.035 * b });
|
|
267
|
+
bones.push({ start: mirrorX(lHip), end: mirrorX(lKnee), radius: 0.055 * b });
|
|
268
|
+
bones.push({ start: mirrorX(lKnee), end: mirrorX(lAnkle), radius: 0.045 * b });
|
|
269
|
+
bones.push({ start: mirrorX(lAnkle), end: mirrorX(lToe), radius: 0.035 * b });
|
|
270
|
+
const head = {
|
|
271
|
+
center: [0, headCenterY, 0],
|
|
272
|
+
radius_x: 0.11,
|
|
273
|
+
radius_y: 0.12,
|
|
274
|
+
radius_z: 0.1
|
|
275
|
+
};
|
|
276
|
+
return { bones, head };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/utils/geometry.ts
|
|
280
|
+
var THREE = __toESM(require("three"));
|
|
281
|
+
var RING_SEGMENTS = 10;
|
|
282
|
+
var CAP_RINGS = 4;
|
|
283
|
+
function capsuleBetweenGeometry(start, end, radius) {
|
|
284
|
+
const s = new THREE.Vector3(...start);
|
|
285
|
+
const e = new THREE.Vector3(...end);
|
|
286
|
+
const dir = new THREE.Vector3().subVectors(e, s);
|
|
287
|
+
const length = dir.length();
|
|
288
|
+
let up, right, forward;
|
|
289
|
+
if (length > 1e-4) {
|
|
290
|
+
up = dir.clone().divideScalar(length);
|
|
291
|
+
const ref = Math.abs(up.y) > 0.99 ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(0, 1, 0);
|
|
292
|
+
right = new THREE.Vector3().crossVectors(up, ref).normalize();
|
|
293
|
+
forward = new THREE.Vector3().crossVectors(right, up);
|
|
294
|
+
} else {
|
|
295
|
+
up = new THREE.Vector3(0, 1, 0);
|
|
296
|
+
right = new THREE.Vector3(1, 0, 0);
|
|
297
|
+
forward = new THREE.Vector3(0, 0, 1);
|
|
298
|
+
}
|
|
299
|
+
const positions = [];
|
|
300
|
+
const normals = [];
|
|
301
|
+
const indices = [];
|
|
302
|
+
const seg = RING_SEGMENTS;
|
|
303
|
+
const transformPos = (lx, ly, lz) => [
|
|
304
|
+
s.x + right.x * lx + up.x * ly + forward.x * lz,
|
|
305
|
+
s.y + right.y * lx + up.y * ly + forward.y * lz,
|
|
306
|
+
s.z + right.z * lx + up.z * ly + forward.z * lz
|
|
307
|
+
];
|
|
308
|
+
const transformNorm = (nx, ny, nz) => {
|
|
309
|
+
const v = new THREE.Vector3(
|
|
310
|
+
right.x * nx + up.x * ny + forward.x * nz,
|
|
311
|
+
right.y * nx + up.y * ny + forward.y * nz,
|
|
312
|
+
right.z * nx + up.z * ny + forward.z * nz
|
|
313
|
+
).normalize();
|
|
314
|
+
return [v.x, v.y, v.z];
|
|
315
|
+
};
|
|
316
|
+
for (let ring = 0; ring <= CAP_RINGS; ring++) {
|
|
317
|
+
const phi = Math.PI * 0.5 * (1 - ring / CAP_RINGS);
|
|
318
|
+
const ly = -Math.sin(phi) * radius;
|
|
319
|
+
const rr = Math.cos(phi) * radius;
|
|
320
|
+
for (let j = 0; j <= seg; j++) {
|
|
321
|
+
const theta = 2 * Math.PI * j / seg;
|
|
322
|
+
const lx = rr * Math.cos(theta);
|
|
323
|
+
const lz = rr * Math.sin(theta);
|
|
324
|
+
positions.push(...transformPos(lx, ly, lz));
|
|
325
|
+
normals.push(...transformNorm(lx / radius, -Math.sin(phi), lz / radius));
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
for (let ring = 0; ring <= 1; ring++) {
|
|
329
|
+
const ly = ring * length;
|
|
330
|
+
for (let j = 0; j <= seg; j++) {
|
|
331
|
+
const theta = 2 * Math.PI * j / seg;
|
|
332
|
+
const lx = radius * Math.cos(theta);
|
|
333
|
+
const lz = radius * Math.sin(theta);
|
|
334
|
+
positions.push(...transformPos(lx, ly, lz));
|
|
335
|
+
normals.push(...transformNorm(lx / radius, 0, lz / radius));
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
for (let ring = 0; ring <= CAP_RINGS; ring++) {
|
|
339
|
+
const phi = Math.PI * 0.5 * ring / CAP_RINGS;
|
|
340
|
+
const ly = length + Math.sin(phi) * radius;
|
|
341
|
+
const rr = Math.cos(phi) * radius;
|
|
342
|
+
for (let j = 0; j <= seg; j++) {
|
|
343
|
+
const theta = 2 * Math.PI * j / seg;
|
|
344
|
+
const lx = rr * Math.cos(theta);
|
|
345
|
+
const lz = rr * Math.sin(theta);
|
|
346
|
+
positions.push(...transformPos(lx, ly, lz));
|
|
347
|
+
normals.push(...transformNorm(lx / radius, Math.sin(phi), lz / radius));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
const totalRings = CAP_RINGS + 2 + CAP_RINGS;
|
|
351
|
+
for (let ring = 0; ring < totalRings; ring++) {
|
|
352
|
+
for (let j = 0; j < seg; j++) {
|
|
353
|
+
const cur = ring * (seg + 1) + j;
|
|
354
|
+
const next = cur + seg + 1;
|
|
355
|
+
indices.push(cur, next, cur + 1);
|
|
356
|
+
indices.push(cur + 1, next, next + 1);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
const geo = new THREE.BufferGeometry();
|
|
360
|
+
geo.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
|
|
361
|
+
geo.setAttribute("normal", new THREE.Float32BufferAttribute(normals, 3));
|
|
362
|
+
geo.setIndex(indices);
|
|
363
|
+
return geo;
|
|
364
|
+
}
|
|
365
|
+
function ellipsoidGeometry(rx, ry, rz) {
|
|
366
|
+
const segs = 14;
|
|
367
|
+
const rings = 12;
|
|
368
|
+
const positions = [];
|
|
369
|
+
const normals = [];
|
|
370
|
+
const indices = [];
|
|
371
|
+
for (let ring = 0; ring <= rings; ring++) {
|
|
372
|
+
const phi = Math.PI * ring / rings;
|
|
373
|
+
const y = ry * Math.cos(phi);
|
|
374
|
+
const rr = Math.sin(phi);
|
|
375
|
+
for (let seg = 0; seg <= segs; seg++) {
|
|
376
|
+
const theta = 2 * Math.PI * seg / segs;
|
|
377
|
+
const x = rx * rr * Math.cos(theta);
|
|
378
|
+
const z = rz * rr * Math.sin(theta);
|
|
379
|
+
positions.push(x, y, z);
|
|
380
|
+
const nx = x / (rx * rx);
|
|
381
|
+
const ny = y / (ry * ry);
|
|
382
|
+
const nz = z / (rz * rz);
|
|
383
|
+
const len = Math.sqrt(nx * nx + ny * ny + nz * nz) || 1;
|
|
384
|
+
normals.push(nx / len, ny / len, nz / len);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
for (let ring = 0; ring < rings; ring++) {
|
|
388
|
+
for (let seg = 0; seg < segs; seg++) {
|
|
389
|
+
const cur = ring * (segs + 1) + seg;
|
|
390
|
+
const next = cur + segs + 1;
|
|
391
|
+
indices.push(cur, next, cur + 1);
|
|
392
|
+
indices.push(cur + 1, next, next + 1);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
const geo = new THREE.BufferGeometry();
|
|
396
|
+
geo.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
|
|
397
|
+
geo.setAttribute("normal", new THREE.Float32BufferAttribute(normals, 3));
|
|
398
|
+
geo.setIndex(indices);
|
|
399
|
+
return geo;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// src/components/AvatarRenderer.tsx
|
|
208
403
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
404
|
+
function AvatarModel({ appearance }) {
|
|
405
|
+
const groupRef = (0, import_react2.useRef)(null);
|
|
406
|
+
const { boneGeos, headGeo, headPos, material } = (0, import_react2.useMemo)(() => {
|
|
407
|
+
const { bones, head } = buildSkeleton(appearance.body);
|
|
408
|
+
const skinColor = new THREE2.Color(...appearance.skin_color);
|
|
409
|
+
const mat = new THREE2.MeshStandardMaterial({
|
|
410
|
+
color: skinColor,
|
|
411
|
+
roughness: 0.65,
|
|
412
|
+
metalness: 0.05
|
|
413
|
+
});
|
|
414
|
+
const geos = bones.map((b) => capsuleBetweenGeometry(b.start, b.end, b.radius));
|
|
415
|
+
const hGeo = ellipsoidGeometry(
|
|
416
|
+
head.radius_x * appearance.head.width,
|
|
417
|
+
head.radius_y * appearance.head.height,
|
|
418
|
+
head.radius_z * appearance.head.width
|
|
419
|
+
);
|
|
420
|
+
return {
|
|
421
|
+
boneGeos: geos,
|
|
422
|
+
headGeo: hGeo,
|
|
423
|
+
headPos: head.center,
|
|
424
|
+
material: mat
|
|
425
|
+
};
|
|
426
|
+
}, [appearance]);
|
|
427
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("group", { ref: groupRef, children: [
|
|
428
|
+
boneGeos.map((geo, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("mesh", { geometry: geo, material }, i)),
|
|
429
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("mesh", { geometry: headGeo, material, position: headPos })
|
|
430
|
+
] });
|
|
431
|
+
}
|
|
432
|
+
function Scene({ appearance, autoRotate }) {
|
|
433
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
434
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ambientLight", { intensity: 0.5 }),
|
|
435
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("directionalLight", { position: [2, 4, 3], intensity: 1.2 }),
|
|
436
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("directionalLight", { position: [-1, 2, -2], intensity: 0.3 }),
|
|
437
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(AvatarModel, { appearance }),
|
|
438
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
439
|
+
import_drei.OrbitControls,
|
|
440
|
+
{
|
|
441
|
+
autoRotate,
|
|
442
|
+
autoRotateSpeed: 2,
|
|
443
|
+
enablePan: false,
|
|
444
|
+
minDistance: 0.8,
|
|
445
|
+
maxDistance: 4,
|
|
446
|
+
target: [0, 0.75, 0]
|
|
447
|
+
}
|
|
448
|
+
)
|
|
449
|
+
] });
|
|
450
|
+
}
|
|
451
|
+
function AvatarRenderer({
|
|
452
|
+
appearance,
|
|
453
|
+
width = "100%",
|
|
454
|
+
height = 300,
|
|
455
|
+
autoRotate = false,
|
|
456
|
+
background = "#15151f",
|
|
457
|
+
className,
|
|
458
|
+
style
|
|
459
|
+
}) {
|
|
460
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
461
|
+
"div",
|
|
462
|
+
{
|
|
463
|
+
className,
|
|
464
|
+
style: {
|
|
465
|
+
width,
|
|
466
|
+
height,
|
|
467
|
+
borderRadius: 8,
|
|
468
|
+
overflow: "hidden",
|
|
469
|
+
...style
|
|
470
|
+
},
|
|
471
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
472
|
+
import_fiber.Canvas,
|
|
473
|
+
{
|
|
474
|
+
camera: { position: [0, 1, 1.8], fov: 50 },
|
|
475
|
+
style: { background },
|
|
476
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Scene, { appearance, autoRotate })
|
|
477
|
+
}
|
|
478
|
+
)
|
|
479
|
+
}
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/components/AvatarEditor.tsx
|
|
484
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
209
485
|
var TABS = [
|
|
210
486
|
{ key: "body", label: "Body" },
|
|
211
487
|
{ key: "head", label: "Head" },
|
|
@@ -228,113 +504,127 @@ function AvatarEditor({
|
|
|
228
504
|
update(path, value);
|
|
229
505
|
setTimeout(() => onChange?.(appearance), 0);
|
|
230
506
|
};
|
|
231
|
-
return /* @__PURE__ */ (0,
|
|
232
|
-
/* @__PURE__ */ (0,
|
|
233
|
-
/* @__PURE__ */ (0,
|
|
234
|
-
/* @__PURE__ */ (0,
|
|
235
|
-
/* @__PURE__ */ (0,
|
|
236
|
-
/* @__PURE__ */ (0,
|
|
237
|
-
onCancel && /* @__PURE__ */ (0,
|
|
238
|
-
onDone && /* @__PURE__ */ (0,
|
|
507
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, style: rootStyle, children: [
|
|
508
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: headerStyle, children: [
|
|
509
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { fontWeight: 700, fontSize: 18 }, children: "Avatar Editor" }),
|
|
510
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", gap: 8 }, children: [
|
|
511
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: btnStyle, onClick: randomize, children: "Randomize" }),
|
|
512
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: btnStyle, onClick: reset, children: "Reset" }),
|
|
513
|
+
onCancel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: btnStyle, onClick: onCancel, children: "Cancel" }),
|
|
514
|
+
onDone && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: { ...btnStyle, background: "#3b6" }, onClick: () => onDone(appearance), children: "Done" })
|
|
239
515
|
] })
|
|
240
516
|
] }),
|
|
241
|
-
/* @__PURE__ */ (0,
|
|
242
|
-
"
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
517
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: mainStyle, children: [
|
|
518
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: previewPaneStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
519
|
+
AvatarRenderer,
|
|
520
|
+
{
|
|
521
|
+
appearance,
|
|
522
|
+
width: "100%",
|
|
523
|
+
height: "100%",
|
|
524
|
+
autoRotate: false,
|
|
525
|
+
background: "#12121a"
|
|
526
|
+
}
|
|
527
|
+
) }),
|
|
528
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: controlsPaneStyle, children: [
|
|
529
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: tabBarStyle, children: TABS.map((t) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
530
|
+
"button",
|
|
531
|
+
{
|
|
532
|
+
onClick: () => setTab(t.key),
|
|
533
|
+
style: {
|
|
534
|
+
...tabStyle,
|
|
535
|
+
...tab === t.key ? tabActiveStyle : {}
|
|
536
|
+
},
|
|
537
|
+
children: t.label
|
|
538
|
+
},
|
|
539
|
+
t.key
|
|
540
|
+
)) }),
|
|
541
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: contentStyle, children: [
|
|
542
|
+
tab === "body" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BodyTab, { appearance, onUpdate: handleUpdate }),
|
|
543
|
+
tab === "head" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HeadTab, { appearance, onUpdate: handleUpdate }),
|
|
544
|
+
tab === "eyes" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EyesTab, { appearance, onUpdate: handleUpdate }),
|
|
545
|
+
tab === "brows" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BrowsTab, { appearance, onUpdate: handleUpdate }),
|
|
546
|
+
tab === "nose" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(NoseTab, { appearance, onUpdate: handleUpdate }),
|
|
547
|
+
tab === "mouth" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MouthTab, { appearance, onUpdate: handleUpdate }),
|
|
548
|
+
tab === "hair" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HairTab, { appearance, onUpdate: handleUpdate }),
|
|
549
|
+
tab === "extras" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ExtrasTab, { appearance, onUpdate: handleUpdate })
|
|
550
|
+
] })
|
|
551
|
+
] })
|
|
262
552
|
] })
|
|
263
553
|
] });
|
|
264
554
|
}
|
|
265
555
|
function BodyTab({ appearance, onUpdate }) {
|
|
266
|
-
return /* @__PURE__ */ (0,
|
|
267
|
-
/* @__PURE__ */ (0,
|
|
268
|
-
/* @__PURE__ */ (0,
|
|
269
|
-
/* @__PURE__ */ (0,
|
|
270
|
-
/* @__PURE__ */ (0,
|
|
271
|
-
/* @__PURE__ */ (0,
|
|
556
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
557
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SectionLabel, { children: "Body" }),
|
|
558
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Height", value: appearance.body.height, onChange: (v) => onUpdate("body.height", v) }),
|
|
559
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Build", value: appearance.body.build, onChange: (v) => onUpdate("body.build", v) }),
|
|
560
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SectionLabel, { children: "Skin Colour" }),
|
|
561
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ColourPresets, { current: appearance.skin_color, presets: SKIN_PRESETS, onPick: (c) => onUpdate("skin_color", c) })
|
|
272
562
|
] });
|
|
273
563
|
}
|
|
274
564
|
function HeadTab({ appearance, onUpdate }) {
|
|
275
|
-
return /* @__PURE__ */ (0,
|
|
276
|
-
/* @__PURE__ */ (0,
|
|
277
|
-
/* @__PURE__ */ (0,
|
|
278
|
-
/* @__PURE__ */ (0,
|
|
565
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
566
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SectionLabel, { children: "Head Shape" }),
|
|
567
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Width", value: appearance.head.width, min: 0.5, max: 1.5, onChange: (v) => onUpdate("head.width", v) }),
|
|
568
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Height", value: appearance.head.height, min: 0.5, max: 1.5, onChange: (v) => onUpdate("head.height", v) })
|
|
279
569
|
] });
|
|
280
570
|
}
|
|
281
571
|
function EyesTab({ appearance, onUpdate }) {
|
|
282
|
-
return /* @__PURE__ */ (0,
|
|
283
|
-
/* @__PURE__ */ (0,
|
|
284
|
-
/* @__PURE__ */ (0,
|
|
285
|
-
/* @__PURE__ */ (0,
|
|
286
|
-
/* @__PURE__ */ (0,
|
|
287
|
-
/* @__PURE__ */ (0,
|
|
288
|
-
/* @__PURE__ */ (0,
|
|
289
|
-
/* @__PURE__ */ (0,
|
|
572
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
573
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SectionLabel, { children: "Eyes" }),
|
|
574
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StyleSelector, { label: "Style", value: appearance.head.eye_style, count: STYLE_COUNTS.eye, onChange: (v) => onUpdate("head.eye_style", v) }),
|
|
575
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ColourPresets, { current: appearance.head.eye_color, presets: EYE_COLOR_PRESETS, onPick: (c) => onUpdate("head.eye_color", c) }),
|
|
576
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Vertical Pos", value: appearance.head.eye_y, onChange: (v) => onUpdate("head.eye_y", v) }),
|
|
577
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Spacing", value: appearance.head.eye_spacing, onChange: (v) => onUpdate("head.eye_spacing", v) }),
|
|
578
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Size", value: appearance.head.eye_size, onChange: (v) => onUpdate("head.eye_size", v) }),
|
|
579
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Rotation", value: appearance.head.eye_rotation, onChange: (v) => onUpdate("head.eye_rotation", v) })
|
|
290
580
|
] });
|
|
291
581
|
}
|
|
292
582
|
function BrowsTab({ appearance, onUpdate }) {
|
|
293
|
-
return /* @__PURE__ */ (0,
|
|
294
|
-
/* @__PURE__ */ (0,
|
|
295
|
-
/* @__PURE__ */ (0,
|
|
296
|
-
/* @__PURE__ */ (0,
|
|
297
|
-
/* @__PURE__ */ (0,
|
|
298
|
-
/* @__PURE__ */ (0,
|
|
299
|
-
/* @__PURE__ */ (0,
|
|
300
|
-
/* @__PURE__ */ (0,
|
|
583
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
584
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SectionLabel, { children: "Eyebrows" }),
|
|
585
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StyleSelector, { label: "Style", value: appearance.head.brow_style, count: STYLE_COUNTS.brow, onChange: (v) => onUpdate("head.brow_style", v) }),
|
|
586
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ColourPresets, { current: appearance.head.brow_color, presets: HAIR_COLOR_PRESETS, onPick: (c) => onUpdate("head.brow_color", c) }),
|
|
587
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Vertical Pos", value: appearance.head.brow_y, onChange: (v) => onUpdate("head.brow_y", v) }),
|
|
588
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Spacing", value: appearance.head.brow_spacing, onChange: (v) => onUpdate("head.brow_spacing", v) }),
|
|
589
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Size", value: appearance.head.brow_size, onChange: (v) => onUpdate("head.brow_size", v) }),
|
|
590
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Rotation", value: appearance.head.brow_rotation, onChange: (v) => onUpdate("head.brow_rotation", v) })
|
|
301
591
|
] });
|
|
302
592
|
}
|
|
303
593
|
function NoseTab({ appearance, onUpdate }) {
|
|
304
|
-
return /* @__PURE__ */ (0,
|
|
305
|
-
/* @__PURE__ */ (0,
|
|
306
|
-
/* @__PURE__ */ (0,
|
|
307
|
-
/* @__PURE__ */ (0,
|
|
308
|
-
/* @__PURE__ */ (0,
|
|
594
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
595
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SectionLabel, { children: "Nose" }),
|
|
596
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StyleSelector, { label: "Style", value: appearance.head.nose_style, count: STYLE_COUNTS.nose, onChange: (v) => onUpdate("head.nose_style", v) }),
|
|
597
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Vertical Pos", value: appearance.head.nose_y, onChange: (v) => onUpdate("head.nose_y", v) }),
|
|
598
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Size", value: appearance.head.nose_size, onChange: (v) => onUpdate("head.nose_size", v) })
|
|
309
599
|
] });
|
|
310
600
|
}
|
|
311
601
|
function MouthTab({ appearance, onUpdate }) {
|
|
312
|
-
return /* @__PURE__ */ (0,
|
|
313
|
-
/* @__PURE__ */ (0,
|
|
314
|
-
/* @__PURE__ */ (0,
|
|
315
|
-
/* @__PURE__ */ (0,
|
|
316
|
-
/* @__PURE__ */ (0,
|
|
602
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
603
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SectionLabel, { children: "Mouth" }),
|
|
604
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StyleSelector, { label: "Style", value: appearance.head.mouth_style, count: STYLE_COUNTS.mouth, onChange: (v) => onUpdate("head.mouth_style", v) }),
|
|
605
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Vertical Pos", value: appearance.head.mouth_y, onChange: (v) => onUpdate("head.mouth_y", v) }),
|
|
606
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Slider, { label: "Size", value: appearance.head.mouth_size, onChange: (v) => onUpdate("head.mouth_size", v) })
|
|
317
607
|
] });
|
|
318
608
|
}
|
|
319
609
|
function HairTab({ appearance, onUpdate }) {
|
|
320
|
-
return /* @__PURE__ */ (0,
|
|
321
|
-
/* @__PURE__ */ (0,
|
|
322
|
-
/* @__PURE__ */ (0,
|
|
323
|
-
/* @__PURE__ */ (0,
|
|
324
|
-
/* @__PURE__ */ (0,
|
|
610
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
611
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SectionLabel, { children: "Hair" }),
|
|
612
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StyleSelector, { label: "Style", value: appearance.head.hair_style, count: STYLE_COUNTS.hair, onChange: (v) => onUpdate("head.hair_style", v) }),
|
|
613
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SectionLabel, { children: "Colour" }),
|
|
614
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ColourPresets, { current: appearance.head.hair_color, presets: HAIR_COLOR_PRESETS, onPick: (c) => onUpdate("head.hair_color", c) })
|
|
325
615
|
] });
|
|
326
616
|
}
|
|
327
617
|
function ExtrasTab({ appearance, onUpdate }) {
|
|
328
|
-
return /* @__PURE__ */ (0,
|
|
329
|
-
/* @__PURE__ */ (0,
|
|
330
|
-
/* @__PURE__ */ (0,
|
|
331
|
-
appearance.head.facial_hair_style > 0 && /* @__PURE__ */ (0,
|
|
332
|
-
/* @__PURE__ */ (0,
|
|
333
|
-
/* @__PURE__ */ (0,
|
|
618
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
619
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SectionLabel, { children: "Facial Hair" }),
|
|
620
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StyleSelector, { label: "Style", value: appearance.head.facial_hair_style, count: STYLE_COUNTS.facial_hair, onChange: (v) => onUpdate("head.facial_hair_style", v) }),
|
|
621
|
+
appearance.head.facial_hair_style > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ColourPresets, { current: appearance.head.facial_hair_color, presets: HAIR_COLOR_PRESETS, onPick: (c) => onUpdate("head.facial_hair_color", c) }),
|
|
622
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SectionLabel, { children: "Glasses" }),
|
|
623
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(StyleSelector, { label: "Style", value: appearance.head.glasses_style, count: STYLE_COUNTS.glasses, onChange: (v) => onUpdate("head.glasses_style", v) })
|
|
334
624
|
] });
|
|
335
625
|
}
|
|
336
626
|
function SectionLabel({ children }) {
|
|
337
|
-
return /* @__PURE__ */ (0,
|
|
627
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { color: "#ccc", fontWeight: 600, fontSize: 13, margin: "8px 0 4px" }, children });
|
|
338
628
|
}
|
|
339
629
|
function Slider({
|
|
340
630
|
label,
|
|
@@ -343,9 +633,9 @@ function Slider({
|
|
|
343
633
|
max = 1,
|
|
344
634
|
onChange
|
|
345
635
|
}) {
|
|
346
|
-
return /* @__PURE__ */ (0,
|
|
347
|
-
/* @__PURE__ */ (0,
|
|
348
|
-
/* @__PURE__ */ (0,
|
|
636
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { style: sliderRowStyle, children: [
|
|
637
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { minWidth: 90, color: "#aab" }, children: label }),
|
|
638
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
349
639
|
"input",
|
|
350
640
|
{
|
|
351
641
|
type: "range",
|
|
@@ -357,7 +647,7 @@ function Slider({
|
|
|
357
647
|
style: { flex: 1 }
|
|
358
648
|
}
|
|
359
649
|
),
|
|
360
|
-
/* @__PURE__ */ (0,
|
|
650
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { minWidth: 40, textAlign: "right", color: "#889", fontSize: 12 }, children: value.toFixed(2) })
|
|
361
651
|
] });
|
|
362
652
|
}
|
|
363
653
|
function StyleSelector({
|
|
@@ -366,15 +656,15 @@ function StyleSelector({
|
|
|
366
656
|
count,
|
|
367
657
|
onChange
|
|
368
658
|
}) {
|
|
369
|
-
return /* @__PURE__ */ (0,
|
|
370
|
-
/* @__PURE__ */ (0,
|
|
371
|
-
/* @__PURE__ */ (0,
|
|
372
|
-
/* @__PURE__ */ (0,
|
|
659
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 8, margin: "4px 0" }, children: [
|
|
660
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: { color: "#aab", minWidth: 50 }, children: label }),
|
|
661
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: smallBtnStyle, onClick: () => onChange(Math.max(0, value - 1)), disabled: value <= 0, children: "<" }),
|
|
662
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { color: "#fff", minWidth: 40, textAlign: "center" }, children: [
|
|
373
663
|
value + 1,
|
|
374
664
|
"/",
|
|
375
665
|
count
|
|
376
666
|
] }),
|
|
377
|
-
/* @__PURE__ */ (0,
|
|
667
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { style: smallBtnStyle, onClick: () => onChange(Math.min(count - 1, value + 1)), disabled: value >= count - 1, children: ">" })
|
|
378
668
|
] });
|
|
379
669
|
}
|
|
380
670
|
function ColourPresets({
|
|
@@ -382,9 +672,9 @@ function ColourPresets({
|
|
|
382
672
|
presets,
|
|
383
673
|
onPick
|
|
384
674
|
}) {
|
|
385
|
-
return /* @__PURE__ */ (0,
|
|
675
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { display: "flex", flexWrap: "wrap", gap: 4, margin: "4px 0" }, children: presets.map((p, i) => {
|
|
386
676
|
const selected = Math.abs(current[0] - p[0]) < 0.01 && Math.abs(current[1] - p[1]) < 0.01 && Math.abs(current[2] - p[2]) < 0.01;
|
|
387
|
-
return /* @__PURE__ */ (0,
|
|
677
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
388
678
|
"button",
|
|
389
679
|
{
|
|
390
680
|
onClick: () => onPick([...p]),
|
|
@@ -410,7 +700,24 @@ var rootStyle = {
|
|
|
410
700
|
borderRadius: 8,
|
|
411
701
|
overflow: "hidden",
|
|
412
702
|
fontFamily: "system-ui, sans-serif",
|
|
413
|
-
fontSize: 14
|
|
703
|
+
fontSize: 14,
|
|
704
|
+
minHeight: 400
|
|
705
|
+
};
|
|
706
|
+
var mainStyle = {
|
|
707
|
+
display: "flex",
|
|
708
|
+
flex: 1,
|
|
709
|
+
minHeight: 0
|
|
710
|
+
};
|
|
711
|
+
var previewPaneStyle = {
|
|
712
|
+
flex: "0 0 45%",
|
|
713
|
+
minHeight: 280,
|
|
714
|
+
borderRight: "1px solid #2a2a38"
|
|
715
|
+
};
|
|
716
|
+
var controlsPaneStyle = {
|
|
717
|
+
flex: 1,
|
|
718
|
+
display: "flex",
|
|
719
|
+
flexDirection: "column",
|
|
720
|
+
minWidth: 0
|
|
414
721
|
};
|
|
415
722
|
var headerStyle = {
|
|
416
723
|
display: "flex",
|
|
@@ -470,7 +777,60 @@ var sliderRowStyle = {
|
|
|
470
777
|
gap: 8,
|
|
471
778
|
margin: "4px 0"
|
|
472
779
|
};
|
|
780
|
+
|
|
781
|
+
// src/components/AvatarPreview.tsx
|
|
782
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
783
|
+
function AvatarPreview({
|
|
784
|
+
appearance,
|
|
785
|
+
size = 200,
|
|
786
|
+
autoRotate = true,
|
|
787
|
+
background,
|
|
788
|
+
className,
|
|
789
|
+
style
|
|
790
|
+
}) {
|
|
791
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
792
|
+
AvatarRenderer,
|
|
793
|
+
{
|
|
794
|
+
appearance,
|
|
795
|
+
width: size,
|
|
796
|
+
height: size,
|
|
797
|
+
autoRotate,
|
|
798
|
+
background,
|
|
799
|
+
className,
|
|
800
|
+
style: { borderRadius: 8, ...style }
|
|
801
|
+
}
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// src/components/AvatarThumbnail.tsx
|
|
806
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
807
|
+
function AvatarThumbnail({
|
|
808
|
+
appearance,
|
|
809
|
+
size = 48,
|
|
810
|
+
background = "#1a1a26",
|
|
811
|
+
className,
|
|
812
|
+
style
|
|
813
|
+
}) {
|
|
814
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
815
|
+
AvatarRenderer,
|
|
816
|
+
{
|
|
817
|
+
appearance,
|
|
818
|
+
width: size,
|
|
819
|
+
height: size,
|
|
820
|
+
autoRotate: false,
|
|
821
|
+
background,
|
|
822
|
+
className,
|
|
823
|
+
style: {
|
|
824
|
+
borderRadius: "50%",
|
|
825
|
+
...style
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
);
|
|
829
|
+
}
|
|
473
830
|
// Annotate the CommonJS export names for ESM import in node:
|
|
474
831
|
0 && (module.exports = {
|
|
475
|
-
AvatarEditor
|
|
832
|
+
AvatarEditor,
|
|
833
|
+
AvatarPreview,
|
|
834
|
+
AvatarRenderer,
|
|
835
|
+
AvatarThumbnail
|
|
476
836
|
});
|