@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.
- package/LICENSE +21 -0
- package/NOTICE.md +23 -0
- package/README.md +125 -0
- package/UPSTREAM.md +47 -0
- package/dist/avatar.d.ts +2 -0
- package/dist/avatar.js +69 -0
- package/dist/avatar.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/materials.d.ts +20 -0
- package/dist/materials.js +61 -0
- package/dist/materials.js.map +1 -0
- package/dist/react-three-fiber.d.ts +16 -0
- package/dist/react-three-fiber.js +52 -0
- package/dist/react-three-fiber.js.map +1 -0
- package/dist/recipe.d.ts +18 -0
- package/dist/recipe.js +118 -0
- package/dist/recipe.js.map +1 -0
- package/dist/types.d.ts +62 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +102 -0
- package/upstream-lock.json +38 -0
- package/vendor/kindergrimm/LICENSE +24 -0
- package/vendor/kindergrimm/src/gloss/catmullClark.js +129 -0
- package/vendor/kindergrimm/src/gloss/gface.d.ts +25 -0
- package/vendor/kindergrimm/src/gloss/gface.js +226 -0
- package/vendor/kindergrimm/src/gloss/gform.js +201 -0
- package/vendor/kindergrimm/src/gloss/ghair.js +534 -0
- package/vendor/kindergrimm/src/gloss/glayout.js +365 -0
- package/vendor/kindergrimm/src/gloss/gmedia.d.ts +28 -0
- package/vendor/kindergrimm/src/gloss/gmedia.js +470 -0
- package/vendor/kindergrimm/src/gloss/gpalette.d.ts +10 -0
- package/vendor/kindergrimm/src/gloss/gpalette.js +205 -0
- package/vendor/kindergrimm/src/gloss/gparts/blush.js +57 -0
- package/vendor/kindergrimm/src/gloss/gparts/body.js +47 -0
- package/vendor/kindergrimm/src/gloss/gparts/brows.js +55 -0
- package/vendor/kindergrimm/src/gloss/gparts/crest.js +88 -0
- package/vendor/kindergrimm/src/gloss/gparts/eyes.js +357 -0
- package/vendor/kindergrimm/src/gloss/gparts/frame.js +225 -0
- package/vendor/kindergrimm/src/gloss/gparts/hair.js +100 -0
- package/vendor/kindergrimm/src/gloss/gparts/hat.js +163 -0
- package/vendor/kindergrimm/src/gloss/gparts/index.js +33 -0
- package/vendor/kindergrimm/src/gloss/gparts/mark.js +139 -0
- package/vendor/kindergrimm/src/gloss/gparts/mouth.js +170 -0
- package/vendor/kindergrimm/src/gloss/gparts/nose.js +63 -0
- package/vendor/kindergrimm/src/gloss/gparts/specs.js +124 -0
- package/vendor/kindergrimm/src/gloss/grig.d.ts +41 -0
- package/vendor/kindergrimm/src/gloss/grig.js +286 -0
- package/vendor/kindergrimm/src/gloss/gshape.js +421 -0
- package/vendor/kindergrimm/src/gloss/gspecies.d.ts +10 -0
- package/vendor/kindergrimm/src/gloss/gspecies.js +338 -0
- package/vendor/kindergrimm/src/gloss/gtexture.js +406 -0
- package/vendor/kindergrimm/src/rng.d.ts +11 -0
- package/vendor/kindergrimm/src/rng.js +27 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PieAI
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/NOTICE.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Third-Party Notice
|
|
2
|
+
|
|
3
|
+
SwimmerAvatarKit includes an unmodified dependency closure from
|
|
4
|
+
[kindergrimm](https://github.com/albertobeiz/kindergrimm):
|
|
5
|
+
|
|
6
|
+
- Source repository: `https://github.com/albertobeiz/kindergrimm.git`
|
|
7
|
+
- Pinned commit: `811214c6dd5de18cc20335cd3d4ab0a06e45ffd4`
|
|
8
|
+
- Commit date: 2026-08-21
|
|
9
|
+
- Imported: 2026-08-22
|
|
10
|
+
- Upstream license: Unlicense (public domain)
|
|
11
|
+
|
|
12
|
+
The imported files live under `vendor/kindergrimm/`. The original license is
|
|
13
|
+
preserved at `vendor/kindergrimm/LICENSE`. It expressly permits copying,
|
|
14
|
+
modification, publication, commercial use, sale, and distribution.
|
|
15
|
+
|
|
16
|
+
`upstream-lock.json` records every imported file's Git blob hash. The two
|
|
17
|
+
upstream browser demo entry points, `src/gloss/gloss.js` and
|
|
18
|
+
`src/gloss/gcrowd.js`, are intentionally excluded; they are applications, not
|
|
19
|
+
part of the reusable generator dependency closure.
|
|
20
|
+
|
|
21
|
+
PieAI's TypeScript adapters, tests, automation, and documentation are licensed
|
|
22
|
+
under the root MIT license. The imported Kindergrimm files remain under the
|
|
23
|
+
Unlicense.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# SwimmerAvatarKit
|
|
2
|
+
|
|
3
|
+
SwimmerAvatarKit is PieAI's shared procedural 3D avatar package. It turns a
|
|
4
|
+
small, reproducible JSON recipe into a glossy chibi Three.js character and
|
|
5
|
+
provides an optional React Three Fiber component.
|
|
6
|
+
|
|
7
|
+
The GLOSS art generator is vendored unchanged from Kindergrimm under the
|
|
8
|
+
Unlicense. SwimmerAvatarKit owns the stable TypeScript API, recipe persistence,
|
|
9
|
+
resource lifecycle, tests, and release process around it.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm add @pieai/swimmer-avatar-kit three
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
React Three Fiber consumers also install compatible React and R3F versions:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add react @react-three/fiber
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## React Three Fiber
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { Canvas } from "@react-three/fiber";
|
|
27
|
+
import { randomRecipe } from "@pieai/swimmer-avatar-kit";
|
|
28
|
+
import { Avatar } from "@pieai/swimmer-avatar-kit/react-three-fiber";
|
|
29
|
+
|
|
30
|
+
const recipe = randomRecipe("user-42");
|
|
31
|
+
|
|
32
|
+
export function ProfileAvatar() {
|
|
33
|
+
return (
|
|
34
|
+
<Canvas camera={{ position: [0, 1, 5] }}>
|
|
35
|
+
<ambientLight intensity={1.2} />
|
|
36
|
+
<Avatar recipe={recipe} gaze scale={0.8} position={[0, -1, 0]} />
|
|
37
|
+
</Canvas>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`Avatar` acquires a renderer-scoped material-library lease by default. Many
|
|
43
|
+
avatars on one canvas therefore share materials and one studio environment;
|
|
44
|
+
the last unmount releases them.
|
|
45
|
+
|
|
46
|
+
## Framework-neutral Three.js
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import {
|
|
50
|
+
buildAvatar,
|
|
51
|
+
randomRecipe,
|
|
52
|
+
} from "@pieai/swimmer-avatar-kit";
|
|
53
|
+
import {
|
|
54
|
+
createStudioMaterialLibrary,
|
|
55
|
+
} from "@pieai/swimmer-avatar-kit/materials";
|
|
56
|
+
|
|
57
|
+
const materials = createStudioMaterialLibrary(renderer);
|
|
58
|
+
const avatar = buildAvatar(randomRecipe("user-42"), {
|
|
59
|
+
materialFor: materials.materialFor,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
scene.add(avatar.object);
|
|
63
|
+
|
|
64
|
+
function frame(deltaSeconds: number) {
|
|
65
|
+
avatar.update(deltaSeconds);
|
|
66
|
+
renderer.render(scene, camera);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Avatar geometry and shared materials have separate owners.
|
|
70
|
+
avatar.dispose();
|
|
71
|
+
materials.dispose();
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Never dispose mesh materials while another avatar can still share them.
|
|
75
|
+
`avatar.dispose()` intentionally disposes geometry only. The material library
|
|
76
|
+
owns and disposes its cache.
|
|
77
|
+
|
|
78
|
+
## Reproducible and persistable recipes
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
import {
|
|
82
|
+
deserializeAvatarRecipe,
|
|
83
|
+
randomRecipe,
|
|
84
|
+
serializeAvatarRecipe,
|
|
85
|
+
} from "@pieai/swimmer-avatar-kit";
|
|
86
|
+
|
|
87
|
+
const recipe = randomRecipe("account:42");
|
|
88
|
+
const stored = serializeAvatarRecipe(recipe);
|
|
89
|
+
const restored = deserializeAvatarRecipe(stored);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
The serialized form uses a versioned SwimmerAvatarKit envelope. Where that
|
|
93
|
+
string is stored is a product decision: v0.1 supports local or product-owned
|
|
94
|
+
storage and does not couple avatars to SwimmerBackend or declare a cross-product
|
|
95
|
+
identity policy.
|
|
96
|
+
|
|
97
|
+
## Entry points
|
|
98
|
+
|
|
99
|
+
| Entry point | Purpose | Runtime |
|
|
100
|
+
| --- | --- | --- |
|
|
101
|
+
| `@pieai/swimmer-avatar-kit` | Recipes, metadata, build/update/dispose | Three.js |
|
|
102
|
+
| `@pieai/swimmer-avatar-kit/materials` | Gloss materials and studio environment | Browser/WebGL |
|
|
103
|
+
| `@pieai/swimmer-avatar-kit/react-three-fiber` | `<Avatar />` adapter | React + R3F |
|
|
104
|
+
|
|
105
|
+
## Current boundary
|
|
106
|
+
|
|
107
|
+
v0.1 supports GLOSS avatars with autonomous blinking, gaze, expressions, and
|
|
108
|
+
breathing. It does not yet promise walking/running poses. Kindergrimm's DRAWN
|
|
109
|
+
poses target a different rig, while the GLOSS biped is currently a static set
|
|
110
|
+
of meshes rather than an articulated locomotion skeleton. That work requires a
|
|
111
|
+
separate rig/retargeting spike before OwnMySpace should depend on it.
|
|
112
|
+
|
|
113
|
+
DRAWN, VOXEL, and OBJECT are also outside this package version. In particular,
|
|
114
|
+
procedural OBJECT plants may become another shared capability, but they should
|
|
115
|
+
not be hidden inside the avatar API.
|
|
116
|
+
|
|
117
|
+
## Upstream and release
|
|
118
|
+
|
|
119
|
+
- Provenance: [NOTICE.md](NOTICE.md)
|
|
120
|
+
- Update process: [UPSTREAM.md](UPSTREAM.md)
|
|
121
|
+
- Local gate: `pnpm verify`
|
|
122
|
+
- Network update check: `pnpm upstream:check`
|
|
123
|
+
|
|
124
|
+
npm publication is intentionally absent from local scripts. Releases run only
|
|
125
|
+
through the manually triggered Trusted Publishing workflow on `main`.
|
package/UPSTREAM.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Kindergrimm Upstream Maintenance
|
|
2
|
+
|
|
3
|
+
SwimmerAvatarKit owns the stable product contract; Kindergrimm remains the art
|
|
4
|
+
generator upstream. An upstream change is never merged automatically.
|
|
5
|
+
|
|
6
|
+
## Check for updates
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pnpm upstream:check
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The command compares the pinned commit in `upstream-lock.json` with the current
|
|
13
|
+
tip of Kindergrimm's `main` branch. It exits successfully when they match and
|
|
14
|
+
fails with both commit IDs when review is needed. The scheduled GitHub workflow
|
|
15
|
+
runs the same read-only check weekly.
|
|
16
|
+
|
|
17
|
+
## Review and import an update
|
|
18
|
+
|
|
19
|
+
1. Clone Kindergrimm into a temporary directory and check out the candidate
|
|
20
|
+
commit. Do not add it as a mutable Git submodule.
|
|
21
|
+
2. Review the full range from the pinned commit to the candidate, especially
|
|
22
|
+
`src/gloss/**`, `src/rng.js`, recipe shape, material callback arguments,
|
|
23
|
+
returned rig fields, and browser-only API use.
|
|
24
|
+
3. Replace only the imported dependency closure under
|
|
25
|
+
`vendor/kindergrimm/`. Keep `src/gloss/gloss.js` and
|
|
26
|
+
`src/gloss/gcrowd.js` excluded unless the package contract deliberately
|
|
27
|
+
changes.
|
|
28
|
+
4. Do not repair compatibility inside vendored JavaScript. Adapt the typed
|
|
29
|
+
boundary under `src/**` and add regression tests.
|
|
30
|
+
5. Update the commit metadata and Git blob hashes in `upstream-lock.json`, then
|
|
31
|
+
run `pnpm vendor:verify` before any other test.
|
|
32
|
+
6. Run `pnpm verify`, `pnpm docs:check`, and inspect `pnpm pack --dry-run`.
|
|
33
|
+
7. Choose semver from the public package impact, not from the upstream commit
|
|
34
|
+
label. A compatible art/bug refresh is normally a patch; changed recipes or
|
|
35
|
+
APIs may require a minor or major release.
|
|
36
|
+
8. Merge the reviewed package change, then publish only through the trusted
|
|
37
|
+
GitHub Actions workflow. Products upgrade to that released version in their
|
|
38
|
+
own PRs.
|
|
39
|
+
|
|
40
|
+
This creates a deliberate two-step buffer:
|
|
41
|
+
|
|
42
|
+
```text
|
|
43
|
+
Kindergrimm update -> reviewed SwimmerAvatarKit release -> product dependency update
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
An upstream bug fix can therefore be adopted quickly without giving an
|
|
47
|
+
unreviewed external commit authority over every PieAI product.
|
package/dist/avatar.d.ts
ADDED
package/dist/avatar.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { createGlossFace } from "../vendor/kindergrimm/src/gloss/gface.js";
|
|
2
|
+
import { buildGloss } from "../vendor/kindergrimm/src/gloss/grig.js";
|
|
3
|
+
import { completeRecipe, EXPRESSIONS } from "./recipe.js";
|
|
4
|
+
const expressionIds = new Set(EXPRESSIONS);
|
|
5
|
+
function disposeGeometries(root) {
|
|
6
|
+
const geometries = new Set();
|
|
7
|
+
root.traverse((child) => {
|
|
8
|
+
const mesh = child;
|
|
9
|
+
if (mesh.isMesh)
|
|
10
|
+
geometries.add(mesh.geometry);
|
|
11
|
+
});
|
|
12
|
+
for (const geometry of geometries)
|
|
13
|
+
geometry.dispose();
|
|
14
|
+
}
|
|
15
|
+
export function buildAvatar(recipe, options) {
|
|
16
|
+
if (typeof options?.materialFor !== "function") {
|
|
17
|
+
throw new TypeError("buildAvatar requires a materialFor function.");
|
|
18
|
+
}
|
|
19
|
+
const completedRecipe = completeRecipe(recipe);
|
|
20
|
+
const built = buildGloss(completedRecipe, {
|
|
21
|
+
materialFor: options.materialFor,
|
|
22
|
+
});
|
|
23
|
+
const life = createGlossFace(built, { gaze: options.gaze ?? true });
|
|
24
|
+
const restY = Number(built.head.userData.restY ?? built.L.cy);
|
|
25
|
+
let elapsedSeconds = 0;
|
|
26
|
+
let disposed = false;
|
|
27
|
+
return {
|
|
28
|
+
object: built.group,
|
|
29
|
+
recipe: completedRecipe,
|
|
30
|
+
stats: { ...built.stats },
|
|
31
|
+
bounds: { ...built.bounds },
|
|
32
|
+
update(deltaSeconds) {
|
|
33
|
+
if (disposed)
|
|
34
|
+
return;
|
|
35
|
+
if (!Number.isFinite(deltaSeconds) || deltaSeconds < 0) {
|
|
36
|
+
throw new RangeError("Avatar deltaSeconds must be a finite non-negative number.");
|
|
37
|
+
}
|
|
38
|
+
elapsedSeconds += deltaSeconds;
|
|
39
|
+
const head = life.update(elapsedSeconds, deltaSeconds);
|
|
40
|
+
const breath = 1 + Math.sin(elapsedSeconds * 1.8) * 0.007;
|
|
41
|
+
built.group.scale.set(1 / Math.sqrt(breath), breath, 1 / Math.sqrt(breath));
|
|
42
|
+
built.head.position.set(head.x, restY + head.y, 0);
|
|
43
|
+
built.head.rotation.set(head.pitch, head.yaw, head.rot);
|
|
44
|
+
},
|
|
45
|
+
getExpression() {
|
|
46
|
+
return life.face();
|
|
47
|
+
},
|
|
48
|
+
setExpression(expression) {
|
|
49
|
+
if (disposed)
|
|
50
|
+
return;
|
|
51
|
+
if (!expressionIds.has(expression)) {
|
|
52
|
+
throw new RangeError(`Unknown avatar expression: ${expression}`);
|
|
53
|
+
}
|
|
54
|
+
life.setFace(expression);
|
|
55
|
+
},
|
|
56
|
+
isDisposed() {
|
|
57
|
+
return disposed;
|
|
58
|
+
},
|
|
59
|
+
dispose() {
|
|
60
|
+
if (disposed)
|
|
61
|
+
return;
|
|
62
|
+
disposed = true;
|
|
63
|
+
built.group.removeFromParent();
|
|
64
|
+
disposeGeometries(built.group);
|
|
65
|
+
built.group.clear();
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=avatar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"avatar.js","sourceRoot":"","sources":["../src/avatar.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { buildAvatar } from "./avatar.js";
|
|
2
|
+
export { assertAvatarRecipe, AVATAR_RECIPE_SCHEMA, AVATAR_RECIPE_VERSION, cloneAvatarRecipe, completeRecipe, createAvatarRecipe, createAvatarRecipeDocument, deserializeAvatarRecipe, EXPRESSIONS, fillRecipe, PALETTES, parseAvatarRecipeDocument, PARTS, randomRecipe, rerollPart, serializeAvatarRecipe, SPECIES, } from "./recipe.js";
|
|
3
|
+
export type { AvatarBounds, AvatarBuildOptions, AvatarBuildStats, AvatarExpression, AvatarHandle, AvatarMaterialFor, AvatarOption, AvatarPaletteOption, AvatarPartRecipe, AvatarRecipe, AvatarRecipeDocument, AvatarRecipeDocumentV1, AvatarSeed, } from "./types.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { buildAvatar } from "./avatar.js";
|
|
2
|
+
export { assertAvatarRecipe, AVATAR_RECIPE_SCHEMA, AVATAR_RECIPE_VERSION, cloneAvatarRecipe, completeRecipe, createAvatarRecipe, createAvatarRecipeDocument, deserializeAvatarRecipe, EXPRESSIONS, fillRecipe, PALETTES, parseAvatarRecipeDocument, PARTS, randomRecipe, rerollPart, serializeAvatarRecipe, SPECIES, } from "./recipe.js";
|
|
3
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EACrB,iBAAiB,EACjB,cAAc,EACd,kBAAkB,EAClB,0BAA0B,EAC1B,uBAAuB,EACvB,WAAW,EACX,UAAU,EACV,QAAQ,EACR,yBAAyB,EACzB,KAAK,EACL,YAAY,EACZ,UAAU,EACV,qBAAqB,EACrB,OAAO,GACR,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Texture, WebGLRenderer } from "three";
|
|
2
|
+
import type { AvatarMaterialFor, AvatarOption } from "./types.js";
|
|
3
|
+
export interface AvatarMaterialLibrary {
|
|
4
|
+
readonly environment: Texture;
|
|
5
|
+
readonly materialFor: AvatarMaterialFor;
|
|
6
|
+
readonly disposed: boolean;
|
|
7
|
+
dispose(): void;
|
|
8
|
+
}
|
|
9
|
+
export interface AvatarMaterialLibraryOptions {
|
|
10
|
+
disposeEnvironment?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface AvatarMaterialLibraryLease {
|
|
13
|
+
readonly library: AvatarMaterialLibrary;
|
|
14
|
+
readonly released: boolean;
|
|
15
|
+
release(): void;
|
|
16
|
+
}
|
|
17
|
+
export declare const FINISHES: readonly AvatarOption[];
|
|
18
|
+
export declare function createGlossMaterialLibrary(environment: Texture, options?: AvatarMaterialLibraryOptions): AvatarMaterialLibrary;
|
|
19
|
+
export declare function createStudioMaterialLibrary(renderer: WebGLRenderer): AvatarMaterialLibrary;
|
|
20
|
+
export declare function acquireStudioMaterialLibrary(renderer: WebGLRenderer): AvatarMaterialLibraryLease;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { makeMaterialFactory, MATERIALS, studioEnv, } from "../vendor/kindergrimm/src/gloss/gmedia.js";
|
|
2
|
+
export const FINISHES = Object.freeze(MATERIALS.map((material) => ({ id: material.id, label: material.label })));
|
|
3
|
+
export function createGlossMaterialLibrary(environment, options = {}) {
|
|
4
|
+
const materialForUpstream = makeMaterialFactory(environment);
|
|
5
|
+
const materials = new Set();
|
|
6
|
+
let disposed = false;
|
|
7
|
+
const materialFor = (finish, color, shell, print) => {
|
|
8
|
+
if (disposed)
|
|
9
|
+
throw new Error("Avatar material library has been disposed.");
|
|
10
|
+
const material = materialForUpstream(finish, color, shell, print);
|
|
11
|
+
materials.add(material);
|
|
12
|
+
return material;
|
|
13
|
+
};
|
|
14
|
+
return {
|
|
15
|
+
environment,
|
|
16
|
+
materialFor,
|
|
17
|
+
get disposed() {
|
|
18
|
+
return disposed;
|
|
19
|
+
},
|
|
20
|
+
dispose() {
|
|
21
|
+
if (disposed)
|
|
22
|
+
return;
|
|
23
|
+
disposed = true;
|
|
24
|
+
for (const material of materials)
|
|
25
|
+
material.dispose();
|
|
26
|
+
materials.clear();
|
|
27
|
+
if (options.disposeEnvironment === true)
|
|
28
|
+
environment.dispose();
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function createStudioMaterialLibrary(renderer) {
|
|
33
|
+
return createGlossMaterialLibrary(studioEnv(renderer), { disposeEnvironment: true });
|
|
34
|
+
}
|
|
35
|
+
const rendererPool = new WeakMap();
|
|
36
|
+
export function acquireStudioMaterialLibrary(renderer) {
|
|
37
|
+
let entry = rendererPool.get(renderer);
|
|
38
|
+
if (!entry || entry.library.disposed) {
|
|
39
|
+
entry = { library: createStudioMaterialLibrary(renderer), references: 0 };
|
|
40
|
+
rendererPool.set(renderer, entry);
|
|
41
|
+
}
|
|
42
|
+
entry.references += 1;
|
|
43
|
+
let released = false;
|
|
44
|
+
return {
|
|
45
|
+
library: entry.library,
|
|
46
|
+
get released() {
|
|
47
|
+
return released;
|
|
48
|
+
},
|
|
49
|
+
release() {
|
|
50
|
+
if (released)
|
|
51
|
+
return;
|
|
52
|
+
released = true;
|
|
53
|
+
entry.references -= 1;
|
|
54
|
+
if (entry.references === 0) {
|
|
55
|
+
entry.library.dispose();
|
|
56
|
+
rendererPool.delete(renderer);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=materials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"materials.js","sourceRoot":"","sources":["../src/materials.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,mBAAmB,EACnB,SAAS,EACT,SAAS,GACV,MAAM,2CAA2C,CAAC;AAqBnD,MAAM,CAAC,MAAM,QAAQ,GAA4B,MAAM,CAAC,MAAM,CAC5D,SAAS,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAC1E,CAAC;AAEF,MAAM,UAAU,0BAA0B,CACxC,WAAoB,EACpB,UAAwC,EAAE;IAE1C,MAAM,mBAAmB,GAAG,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG,IAAI,GAAG,EAAY,CAAC;IACtC,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,MAAM,WAAW,GAAsB,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QACrE,IAAI,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC5E,MAAM,QAAQ,GAAG,mBAAmB,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;QAClE,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxB,OAAO,QAAQ,CAAC;IAClB,CAAC,CAAC;IAEF,OAAO;QACL,WAAW;QACX,WAAW;QACX,IAAI,QAAQ;YACV,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,OAAO;YACL,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,KAAK,MAAM,QAAQ,IAAI,SAAS;gBAAE,QAAQ,CAAC,OAAO,EAAE,CAAC;YACrD,SAAS,CAAC,KAAK,EAAE,CAAC;YAClB,IAAI,OAAO,CAAC,kBAAkB,KAAK,IAAI;gBAAE,WAAW,CAAC,OAAO,EAAE,CAAC;QACjE,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,2BAA2B,CAAC,QAAuB;IACjE,OAAO,0BAA0B,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC,CAAC;AACvF,CAAC;AAOD,MAAM,YAAY,GAAG,IAAI,OAAO,EAAoC,CAAC;AAErE,MAAM,UAAU,4BAA4B,CAAC,QAAuB;IAClE,IAAI,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrC,KAAK,GAAG,EAAE,OAAO,EAAE,2BAA2B,CAAC,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;QAC1E,YAAY,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;IACpC,CAAC;IACD,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;IACtB,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,OAAO;QACL,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI,QAAQ;YACV,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,OAAO;YACL,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,KAAK,CAAC,UAAU,IAAI,CAAC,CAAC;YACtB,IAAI,KAAK,CAAC,UAAU,KAAK,CAAC,EAAE,CAAC;gBAC3B,KAAK,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;gBACxB,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type ThreeElements } from "@react-three/fiber";
|
|
2
|
+
import type { Group } from "three";
|
|
3
|
+
import { type AvatarMaterialLibrary } from "./materials.js";
|
|
4
|
+
import type { AvatarHandle, AvatarRecipe } from "./types.js";
|
|
5
|
+
export type AvatarProps = Omit<ThreeElements["group"], "children" | "ref"> & {
|
|
6
|
+
recipe: AvatarRecipe;
|
|
7
|
+
gaze?: boolean;
|
|
8
|
+
materialLibrary?: AvatarMaterialLibrary;
|
|
9
|
+
onBuilt?: (avatar: AvatarHandle) => void;
|
|
10
|
+
};
|
|
11
|
+
export declare const Avatar: import("react").ForwardRefExoticComponent<Omit<import("@react-three/fiber/dist/declarations/src/core/utils.js").Mutable<import("@react-three/fiber/dist/declarations/src/core/utils.js").Overwrite<Partial<import("@react-three/fiber/dist/declarations/src/core/utils.js").Overwrite<Group<import("three").Object3DEventMap>, import("@react-three/fiber").MathProps<Group<import("three").Object3DEventMap>> & import("@react-three/fiber").ReactProps<Group<import("three").Object3DEventMap>> & Partial<import("@react-three/fiber").EventHandlers>>>, Omit<import("@react-three/fiber").InstanceProps<Group<import("three").Object3DEventMap>, typeof Group>, "object">>>, "children" | "ref"> & {
|
|
12
|
+
recipe: AvatarRecipe;
|
|
13
|
+
gaze?: boolean;
|
|
14
|
+
materialLibrary?: AvatarMaterialLibrary;
|
|
15
|
+
onBuilt?: (avatar: AvatarHandle) => void;
|
|
16
|
+
} & import("react").RefAttributes<AvatarHandle>>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { forwardRef, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from "react";
|
|
3
|
+
import { useFrame, useThree } from "@react-three/fiber";
|
|
4
|
+
import { buildAvatar } from "./avatar.js";
|
|
5
|
+
import { acquireStudioMaterialLibrary } from "./materials.js";
|
|
6
|
+
export const Avatar = forwardRef(function Avatar({ recipe, gaze = true, materialLibrary, onBuilt, ...groupProps }, forwardedRef) {
|
|
7
|
+
const { gl } = useThree();
|
|
8
|
+
const [ownedLibrary, setOwnedLibrary] = useState(null);
|
|
9
|
+
const [avatar, setAvatar] = useState(null);
|
|
10
|
+
const [object, setObject] = useState(null);
|
|
11
|
+
const avatarRef = useRef(null);
|
|
12
|
+
const onBuiltRef = useRef(onBuilt);
|
|
13
|
+
onBuiltRef.current = onBuilt;
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
if (materialLibrary) {
|
|
16
|
+
setOwnedLibrary(null);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const lease = acquireStudioMaterialLibrary(gl);
|
|
20
|
+
setOwnedLibrary(lease.library);
|
|
21
|
+
return () => {
|
|
22
|
+
setOwnedLibrary(null);
|
|
23
|
+
lease.release();
|
|
24
|
+
};
|
|
25
|
+
}, [gl, materialLibrary]);
|
|
26
|
+
const activeLibrary = materialLibrary ?? ownedLibrary;
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
if (!activeLibrary)
|
|
29
|
+
return;
|
|
30
|
+
const next = buildAvatar(recipe, {
|
|
31
|
+
gaze,
|
|
32
|
+
materialFor: activeLibrary.materialFor,
|
|
33
|
+
});
|
|
34
|
+
avatarRef.current = next;
|
|
35
|
+
setAvatar(next);
|
|
36
|
+
setObject(next.object);
|
|
37
|
+
onBuiltRef.current?.(next);
|
|
38
|
+
return () => {
|
|
39
|
+
if (avatarRef.current === next)
|
|
40
|
+
avatarRef.current = null;
|
|
41
|
+
setAvatar(null);
|
|
42
|
+
setObject(null);
|
|
43
|
+
next.dispose();
|
|
44
|
+
};
|
|
45
|
+
}, [activeLibrary, gaze, recipe]);
|
|
46
|
+
useFrame((_, deltaSeconds) => {
|
|
47
|
+
avatarRef.current?.update(deltaSeconds);
|
|
48
|
+
});
|
|
49
|
+
useImperativeHandle(forwardedRef, () => avatar, [avatar]);
|
|
50
|
+
return (_jsx("group", { ...groupProps, children: object ? _jsx("primitive", { object: object, dispose: null }) : null }));
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=react-three-fiber.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-three-fiber.js","sourceRoot":"","sources":["../src/react-three-fiber.tsx"],"names":[],"mappings":"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"}
|
package/dist/recipe.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AvatarExpression, AvatarOption, AvatarPaletteOption, AvatarRecipe, AvatarRecipeDocument, AvatarSeed } from "./types.js";
|
|
2
|
+
export declare const AVATAR_RECIPE_SCHEMA: "@pieai/swimmer-avatar-kit/recipe";
|
|
3
|
+
export declare const AVATAR_RECIPE_VERSION: 1;
|
|
4
|
+
export declare const SPECIES: readonly AvatarOption[];
|
|
5
|
+
export declare const PALETTES: readonly AvatarPaletteOption[];
|
|
6
|
+
export declare const PARTS: readonly AvatarOption[];
|
|
7
|
+
export declare const EXPRESSIONS: readonly AvatarExpression[];
|
|
8
|
+
export declare function cloneAvatarRecipe(recipe: AvatarRecipe): AvatarRecipe;
|
|
9
|
+
export declare function assertAvatarRecipe(value: unknown): asserts value is AvatarRecipe;
|
|
10
|
+
export declare function completeRecipe(recipe: AvatarRecipe): AvatarRecipe;
|
|
11
|
+
export declare const fillRecipe: typeof completeRecipe;
|
|
12
|
+
export declare function randomRecipe(seed?: AvatarSeed): AvatarRecipe;
|
|
13
|
+
export declare const createAvatarRecipe: typeof randomRecipe;
|
|
14
|
+
export declare function rerollPart(recipe: AvatarRecipe, partId: string): AvatarRecipe;
|
|
15
|
+
export declare function createAvatarRecipeDocument(recipe: AvatarRecipe): AvatarRecipeDocument;
|
|
16
|
+
export declare function parseAvatarRecipeDocument(value: unknown): AvatarRecipeDocument;
|
|
17
|
+
export declare function serializeAvatarRecipe(recipe: AvatarRecipe): string;
|
|
18
|
+
export declare function deserializeAvatarRecipe(serialized: string): AvatarRecipe;
|
package/dist/recipe.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { GLOSS_FACES } from "../vendor/kindergrimm/src/gloss/gface.js";
|
|
2
|
+
import { ensureGParams, GPARTS, newGRecipe, rerollGPart, } from "../vendor/kindergrimm/src/gloss/grig.js";
|
|
3
|
+
import { PALETTES as GLOSS_PALETTES } from "../vendor/kindergrimm/src/gloss/gpalette.js";
|
|
4
|
+
import { GSPECIES, GSPECIES_IDS } from "../vendor/kindergrimm/src/gloss/gspecies.js";
|
|
5
|
+
import { hashStr } from "../vendor/kindergrimm/src/rng.js";
|
|
6
|
+
export const AVATAR_RECIPE_SCHEMA = "@pieai/swimmer-avatar-kit/recipe";
|
|
7
|
+
export const AVATAR_RECIPE_VERSION = 1;
|
|
8
|
+
export const SPECIES = Object.freeze(GSPECIES_IDS.map((id) => ({ id, label: GSPECIES[id]?.label ?? id })));
|
|
9
|
+
export const PALETTES = Object.freeze(GLOSS_PALETTES.map((palette) => ({
|
|
10
|
+
id: palette.id,
|
|
11
|
+
label: palette.label,
|
|
12
|
+
colors: Object.freeze([...palette.colors]),
|
|
13
|
+
})));
|
|
14
|
+
export const PARTS = Object.freeze(GPARTS.map((part) => ({ id: part.id, label: part.label })));
|
|
15
|
+
export const EXPRESSIONS = Object.freeze([...GLOSS_FACES]);
|
|
16
|
+
const partIds = new Set(PARTS.map((part) => part.id));
|
|
17
|
+
function isRecord(value) {
|
|
18
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
function isNullableString(value) {
|
|
21
|
+
return typeof value === "string" || value === null;
|
|
22
|
+
}
|
|
23
|
+
function seedToNumber(seed) {
|
|
24
|
+
if (seed === undefined)
|
|
25
|
+
return (Math.random() * 1_000_000_000) | 0;
|
|
26
|
+
if (typeof seed === "number") {
|
|
27
|
+
if (!Number.isFinite(seed))
|
|
28
|
+
throw new TypeError("Avatar seed must be a finite number.");
|
|
29
|
+
return Math.trunc(seed) >>> 0;
|
|
30
|
+
}
|
|
31
|
+
if (/^\d+$/.test(seed))
|
|
32
|
+
return Number(BigInt(seed) & 0xffffffffn);
|
|
33
|
+
return hashStr(seed);
|
|
34
|
+
}
|
|
35
|
+
export function cloneAvatarRecipe(recipe) {
|
|
36
|
+
return structuredClone(recipe);
|
|
37
|
+
}
|
|
38
|
+
export function assertAvatarRecipe(value) {
|
|
39
|
+
if (!isRecord(value))
|
|
40
|
+
throw new TypeError("Avatar recipe must be an object.");
|
|
41
|
+
if (!Number.isInteger(value.seed) || Number(value.seed) < 0 || Number(value.seed) > 0xffff_ffff) {
|
|
42
|
+
throw new TypeError("Avatar recipe seed must be an unsigned 32-bit integer.");
|
|
43
|
+
}
|
|
44
|
+
for (const field of ["species", "body", "stance", "palette", "material"]) {
|
|
45
|
+
if (!isNullableString(value[field])) {
|
|
46
|
+
throw new TypeError(`Avatar recipe ${field} must be a string or null.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (value.colorIx !== null && (!Number.isInteger(value.colorIx) || Number(value.colorIx) < 0)) {
|
|
50
|
+
throw new TypeError("Avatar recipe colorIx must be a non-negative integer or null.");
|
|
51
|
+
}
|
|
52
|
+
if (!isRecord(value.parts))
|
|
53
|
+
throw new TypeError("Avatar recipe parts must be an object.");
|
|
54
|
+
for (const [partId, part] of Object.entries(value.parts)) {
|
|
55
|
+
if (!isRecord(part))
|
|
56
|
+
throw new TypeError(`Avatar recipe part ${partId} must be an object.`);
|
|
57
|
+
if (part.rr !== undefined && (!Number.isInteger(part.rr) || Number(part.rr) < 0)) {
|
|
58
|
+
throw new TypeError(`Avatar recipe part ${partId}.rr must be a non-negative integer.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export function completeRecipe(recipe) {
|
|
63
|
+
assertAvatarRecipe(recipe);
|
|
64
|
+
const next = cloneAvatarRecipe(recipe);
|
|
65
|
+
ensureGParams(next);
|
|
66
|
+
return next;
|
|
67
|
+
}
|
|
68
|
+
export const fillRecipe = completeRecipe;
|
|
69
|
+
export function randomRecipe(seed) {
|
|
70
|
+
const recipe = newGRecipe(seedToNumber(seed));
|
|
71
|
+
ensureGParams(recipe);
|
|
72
|
+
return cloneAvatarRecipe(recipe);
|
|
73
|
+
}
|
|
74
|
+
export const createAvatarRecipe = randomRecipe;
|
|
75
|
+
export function rerollPart(recipe, partId) {
|
|
76
|
+
if (!partIds.has(partId))
|
|
77
|
+
throw new RangeError(`Unknown avatar part: ${partId}`);
|
|
78
|
+
const next = completeRecipe(recipe);
|
|
79
|
+
rerollGPart(next, partId);
|
|
80
|
+
return next;
|
|
81
|
+
}
|
|
82
|
+
export function createAvatarRecipeDocument(recipe) {
|
|
83
|
+
return {
|
|
84
|
+
schema: AVATAR_RECIPE_SCHEMA,
|
|
85
|
+
version: AVATAR_RECIPE_VERSION,
|
|
86
|
+
renderer: "gloss",
|
|
87
|
+
recipe: completeRecipe(recipe),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function parseAvatarRecipeDocument(value) {
|
|
91
|
+
if (!isRecord(value))
|
|
92
|
+
throw new TypeError("Avatar recipe document must be an object.");
|
|
93
|
+
if (value.schema !== AVATAR_RECIPE_SCHEMA) {
|
|
94
|
+
throw new TypeError(`Unsupported avatar recipe schema: ${String(value.schema)}`);
|
|
95
|
+
}
|
|
96
|
+
if (value.version !== AVATAR_RECIPE_VERSION) {
|
|
97
|
+
throw new TypeError(`Unsupported avatar recipe version: ${String(value.version)}`);
|
|
98
|
+
}
|
|
99
|
+
if (value.renderer !== "gloss") {
|
|
100
|
+
throw new TypeError(`Unsupported avatar renderer: ${String(value.renderer)}`);
|
|
101
|
+
}
|
|
102
|
+
assertAvatarRecipe(value.recipe);
|
|
103
|
+
return createAvatarRecipeDocument(value.recipe);
|
|
104
|
+
}
|
|
105
|
+
export function serializeAvatarRecipe(recipe) {
|
|
106
|
+
return JSON.stringify(createAvatarRecipeDocument(recipe));
|
|
107
|
+
}
|
|
108
|
+
export function deserializeAvatarRecipe(serialized) {
|
|
109
|
+
let value;
|
|
110
|
+
try {
|
|
111
|
+
value = JSON.parse(serialized);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
throw new TypeError(`Avatar recipe document is not valid JSON: ${String(error)}`);
|
|
115
|
+
}
|
|
116
|
+
return parseAvatarRecipeDocument(value).recipe;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=recipe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipe.js","sourceRoot":"","sources":["../src/recipe.ts"],"names":[],"mappings":"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"}
|