@call-me-sensei/toonlab 0.1.1
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/ATTRIBUTION.md +40 -0
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/package.json +97 -0
- package/src/character/characterRig.js +353 -0
- package/src/character/freestyleSwimClip.js +541 -0
- package/src/character/index.js +4 -0
- package/src/character/modelLoader.js +433 -0
- package/src/core/materialRoles.js +438 -0
- package/src/core/presetDocuments.js +110 -0
- package/src/core/shaderBackend.js +24 -0
- package/src/debrisgen/debrisFields.js +544 -0
- package/src/debrisgen/debrisGenerator.js +1929 -0
- package/src/debrisgen/debrisPalettes.js +71 -0
- package/src/debrisgen/debrisPhysics.js +198 -0
- package/src/debrisgen/debrisPresets.js +155 -0
- package/src/debrisgen/debrisSettings.js +333 -0
- package/src/debrisgen/debrisTextures.js +380 -0
- package/src/debrisgen/index.js +5 -0
- package/src/debug/fieldValues.js +95 -0
- package/src/debug/index.js +7 -0
- package/src/debug/settingsPanel.js +170 -0
- package/src/environment/environmentAmbientProbe.js +160 -0
- package/src/environment/environmentMaterialAdapter.js +336 -0
- package/src/environment/environmentMaterialClassifier.js +178 -0
- package/src/environment/environmentPlanarReflection.js +201 -0
- package/src/environment/environmentPresets.js +443 -0
- package/src/environment/environmentRigs.js +684 -0
- package/src/environment/environmentSettings.js +504 -0
- package/src/environment/environmentShaderMaterials.js +318 -0
- package/src/environment/environmentSunShadowPass.js +268 -0
- package/src/environment/environmentTextureResolver.js +161 -0
- package/src/environment/environmentTimeOfDay.js +167 -0
- package/src/environment/environmentVertexAo.js +188 -0
- package/src/environment/index.js +8 -0
- package/src/environment/scanAssetStylize.js +103 -0
- package/src/index.js +21 -0
- package/src/loaders/index.js +1 -0
- package/src/post/index.js +2 -0
- package/src/post/postProcessing.js +1087 -0
- package/src/rockgen/export/glbExport.js +177 -0
- package/src/rockgen/heightfield/heightfieldErosion.js +7 -0
- package/src/rockgen/heightfield/heightfieldPatch.js +183 -0
- package/src/rockgen/heightfield/stylizedErosionSim.js +356 -0
- package/src/rockgen/index.js +14 -0
- package/src/rockgen/mesh/meshAttributes.js +374 -0
- package/src/rockgen/mesh/meshDocument.js +158 -0
- package/src/rockgen/mesh/surfaceNets.js +329 -0
- package/src/rockgen/noise/cellularNoise3.js +92 -0
- package/src/rockgen/noise/prng.js +62 -0
- package/src/rockgen/noise/simplexNoise3.js +89 -0
- package/src/rockgen/noise/valueNoise3.js +87 -0
- package/src/rockgen/rockDocument.js +255 -0
- package/src/rockgen/rockHelpers.js +8 -0
- package/src/rockgen/rockgenPresets.js +559 -0
- package/src/rockgen/rockgenSettings.js +756 -0
- package/src/rockgen/sdf/fieldCompiler.js +544 -0
- package/src/rockgen/sdf/sculptEdits.js +66 -0
- package/src/rockgen/sdf/sdfModifiers.js +45 -0
- package/src/rockgen/sdf/sdfOps.js +37 -0
- package/src/rockgen/sdf/sdfPrimitives.js +88 -0
- package/src/shaders-tsl/anime.js +963 -0
- package/src/shaders-tsl/chunks/character-color.js +64 -0
- package/src/shaders-tsl/chunks/character-highlights.js +174 -0
- package/src/shaders-tsl/chunks/character-lighting.js +309 -0
- package/src/shaders-tsl/chunks/character-material-maps.js +146 -0
- package/src/shaders-tsl/chunks/character-roles.js +52 -0
- package/src/shaders-tsl/chunks/character-scene-lights.js +270 -0
- package/src/shaders-tsl/chunks/character-shadow-color.js +61 -0
- package/src/shaders-tsl/chunks/character-skinning.js +140 -0
- package/src/shaders-tsl/chunks/environment-color.js +47 -0
- package/src/shaders-tsl/chunks/environment-debug.js +79 -0
- package/src/shaders-tsl/chunks/environment-lighting.js +260 -0
- package/src/shaders-tsl/chunks/environment-sun-shadow.js +87 -0
- package/src/shaders-tsl/chunks/foliage-fog.js +60 -0
- package/src/shaders-tsl/chunks/pass-depth-color.js +89 -0
- package/src/shaders-tsl/chunks/stylized-cloud-shadow.js +70 -0
- package/src/shaders-tsl/chunks/water-color.js +190 -0
- package/src/shaders-tsl/chunks/water-common.js +124 -0
- package/src/shaders-tsl/chunks/water-foam.js +93 -0
- package/src/shaders-tsl/chunks/water-lighting.js +116 -0
- package/src/shaders-tsl/chunks/water-ripple.js +64 -0
- package/src/shaders-tsl/chunks/water-waves.js +100 -0
- package/src/shaders-tsl/environment-ao-overlay.js +60 -0
- package/src/shaders-tsl/environment.js +680 -0
- package/src/shaders-tsl/flower.js +267 -0
- package/src/shaders-tsl/grass.js +193 -0
- package/src/shaders-tsl/post-composite.js +461 -0
- package/src/shaders-tsl/sky.js +175 -0
- package/src/shaders-tsl/tree-leaf.js +264 -0
- package/src/shaders-tsl/water-breaker.js +423 -0
- package/src/shaders-tsl/water-kelp.js +171 -0
- package/src/shaders-tsl/water-rain.js +107 -0
- package/src/shaders-tsl/water-simulation.js +140 -0
- package/src/shaders-tsl/water-splash.js +298 -0
- package/src/shaders-tsl/water.js +509 -0
- package/src/sky/index.js +2 -0
- package/src/sky/stylizedSky.js +341 -0
- package/src/toon/characterRenderPasses.js +646 -0
- package/src/toon/index.js +4 -0
- package/src/toon/settings/alphaSettings.js +170 -0
- package/src/toon/settings/averageShadowSettings.js +181 -0
- package/src/toon/settings/baseTextureSettings.js +133 -0
- package/src/toon/settings/celShadeSettings.js +96 -0
- package/src/toon/settings/contactShadowSettings.js +114 -0
- package/src/toon/settings/eyeHighlightSettings.js +187 -0
- package/src/toon/settings/faceLightingSettings.js +135 -0
- package/src/toon/settings/furSettings.js +112 -0
- package/src/toon/settings/glitterSettings.js +102 -0
- package/src/toon/settings/hairHighlightSettings.js +325 -0
- package/src/toon/settings/indirectLightSettings.js +244 -0
- package/src/toon/settings/localLightSettings.js +176 -0
- package/src/toon/settings/materialMapSettings.js +299 -0
- package/src/toon/settings/outlineSettings.js +263 -0
- package/src/toon/settings/perspectiveRemovalSettings.js +60 -0
- package/src/toon/settings/rimLightSettings.js +268 -0
- package/src/toon/settings/sceneShadowSettings.js +147 -0
- package/src/toon/settings/selfShadowSettings.js +221 -0
- package/src/toon/settings/shadowColorSettings.js +238 -0
- package/src/toon/settings/skinToneSettings.js +150 -0
- package/src/toon/settings/specularSettings.js +314 -0
- package/src/toon/settings/stickerSettings.js +102 -0
- package/src/toon/toonMaterialAdapter.js +2235 -0
- package/src/toon/toonSettings.js +860 -0
- package/src/vegetation/flowerSpecies.js +260 -0
- package/src/vegetation/index.js +10 -0
- package/src/vegetation/stylizedBush.js +136 -0
- package/src/vegetation/stylizedFlower.js +213 -0
- package/src/vegetation/stylizedFlowers.js +297 -0
- package/src/vegetation/stylizedGrass.js +491 -0
- package/src/vegetation/stylizedTree.js +2449 -0
- package/src/vegetation/stylizedTreeFoliage.js +691 -0
- package/src/vegetation/treeExport.js +300 -0
- package/src/vegetation/treeRecipe.js +647 -0
- package/src/water/index.js +2 -0
- package/src/water/water.js +20 -0
- package/src/water/waterBreakerSystem.js +591 -0
- package/src/water/waterInteraction.js +193 -0
- package/src/water/waterMaterial.js +263 -0
- package/src/water/waterRain.js +136 -0
- package/src/water/waterRippleSimulation.js +227 -0
- package/src/water/waterScenePasses.js +415 -0
- package/src/water/waterSettings.js +1138 -0
- package/src/water/waterSplashSystem.js +251 -0
- package/src/water/waterSurface.js +475 -0
- package/src/water/waterVegetation.js +83 -0
package/ATTRIBUTION.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Asset attribution
|
|
2
|
+
|
|
3
|
+
Code is MIT-licensed by Hyperbond Studio PTE. LTD. (see LICENSE). The assets bundled in this repository:
|
|
4
|
+
|
|
5
|
+
## Bundled assets
|
|
6
|
+
|
|
7
|
+
| Asset | Location | License | Source |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| Mannequin character (45 embedded animation clips) | `public/characters/mannequin.glb` | CC0 1.0 | [Quaternius — Universal Animation Library](https://quaternius.com/packs/universalanimationlibrary.html), recolored to neutral grays. See `public/characters/LICENSE.md`. |
|
|
10
|
+
| Landscape textures (grassy-land, land, mountain, rock, sand, tree-trunk) | `labs/shared/textures/` | CC0 1.0 | First-party (created by the toonlab authors), dedicated to the public domain. |
|
|
11
|
+
|
|
12
|
+
Everything else that renders — water, sky, grass, flowers, trees, foliage,
|
|
13
|
+
splashes, post-processing — is procedural code, no asset files.
|
|
14
|
+
|
|
15
|
+
## Not bundled (bring your own)
|
|
16
|
+
|
|
17
|
+
The labs can additionally load content from a gitignored `assets-local/`
|
|
18
|
+
folder that is **not** part of this repository:
|
|
19
|
+
|
|
20
|
+
- **Mixamo animation clips** (`assets-local/animations/*.fbx`) — the retarget
|
|
21
|
+
pipeline plays Adobe Mixamo clips on any humanoid model. Download clips
|
|
22
|
+
with your own Adobe account from [mixamo.com](https://www.mixamo.com) and
|
|
23
|
+
drop them in; Adobe's terms do not permit us to redistribute the files.
|
|
24
|
+
Without them, models fall back to their own embedded clips (the bundled
|
|
25
|
+
mannequin covers idle/walk/run/jump/swim/tread natively).
|
|
26
|
+
- **Your models** (`assets-local/models/`) — PMX/VRM/GLB/FBX/OBJ characters
|
|
27
|
+
for testing; or pass any hosted URL via the HUD's Model URL field /
|
|
28
|
+
`?model=`. Make sure you have the rights to any model you load.
|
|
29
|
+
- **Scanned props/environments** (`assets-local/environments/`) — licensed
|
|
30
|
+
packs (Fab/Megascans etc.) can be dropped in for the demo scenes;
|
|
31
|
+
the scenes render procedural stand-ins when they're absent.
|
|
32
|
+
|
|
33
|
+
## Recommended free sources
|
|
34
|
+
|
|
35
|
+
- [Quaternius](https://quaternius.com) — CC0 models + the Universal
|
|
36
|
+
Animation Library (v1 and v2 free tiers).
|
|
37
|
+
- [Poly Haven](https://polyhaven.com) — CC0 textures/HDRIs/models.
|
|
38
|
+
- [Kenney](https://kenney.nl) — CC0 game assets.
|
|
39
|
+
- [OpenGameArt](https://opengameart.org) — filter by CC0/CC-BY.
|
|
40
|
+
- [VRoid Hub](https://hub.vroid.com) — VRM avatars (check per-model terms).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hyperbond Studio PTE. LTD.
|
|
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/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# ToonLab by Call Me Sensei
|
|
2
|
+
|
|
3
|
+
ToonLab by Call Me Sensei is a stylized anime-style game starter kit and
|
|
4
|
+
runtime library for [Three.js](https://threejs.org/). Its goal is to simplify
|
|
5
|
+
Three.js game development: you get a better-looking anime/stylized game
|
|
6
|
+
without having to develop shaders yourself, and procedurally generated assets
|
|
7
|
+
(trees, rocks, grass, water, sky) reduce content complexity so you can focus
|
|
8
|
+
on your core game logic instead. Clone it and a toon-shaded character is
|
|
9
|
+
walking and swimming through a fully stylized world: cel-shaded characters,
|
|
10
|
+
painterly environments, interactive water, procedural vegetation and sky,
|
|
11
|
+
post-processing, and a schema-driven tuning panel. Use it as a starter kit,
|
|
12
|
+
or import the pieces you need as a library (`@call-me-sensei/toonlab` on npm,
|
|
13
|
+
subpath exports per cluster).
|
|
14
|
+
|
|
15
|
+
## Quickstart
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/call-me-sensei/toonlab.git && cd toonlab
|
|
19
|
+
npm install
|
|
20
|
+
npm run dev
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Vite serves the labs at `http://localhost:5175`. See
|
|
24
|
+
[Getting started](docs/getting-started.md) for a tour.
|
|
25
|
+
|
|
26
|
+
## The labs
|
|
27
|
+
|
|
28
|
+
Runnable demos in `labs/` (not published to npm). Switch between them with
|
|
29
|
+
the HUD Scene select:
|
|
30
|
+
|
|
31
|
+
- **Shader Lab** (`/`) — character + environment shader tuning: every toon
|
|
32
|
+
and environment setting as a live control, preset export/import, debug
|
|
33
|
+
views.
|
|
34
|
+
- **Playground** (`/playground/`) — third-person character controller
|
|
35
|
+
in gameplay scenes: calm lake, river crossing, ocean beach with breaking
|
|
36
|
+
waves, and an indoor room. Walk, jump, swim, splash.
|
|
37
|
+
- **Rock Lab** (`/rock-lab/`) — procedural stylized rocks, cliffs,
|
|
38
|
+
heightfields, sculpt edits, and GLB export.
|
|
39
|
+
- **Tree Lab** (`/tree-lab/`) — procedural stylized trees,
|
|
40
|
+
flowers, sketches, recipes, and GLB export.
|
|
41
|
+
|
|
42
|
+
Every URL parameter has a HUD control, lab state persists per lab in
|
|
43
|
+
`localStorage`, and **Reset Lab** clears it. Point any lab at your own model
|
|
44
|
+
with the Model URL input or `?model=` — see
|
|
45
|
+
[Characters](docs/characters.md).
|
|
46
|
+
|
|
47
|
+
## What's inside
|
|
48
|
+
|
|
49
|
+
| Cluster | Import | What you get |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| Toon character shading | `@call-me-sensei/toonlab/toon` | Modern anime character shader: cel bands with art-directed face lighting, skin-tone shadow management, shadow-color HSV control, scene/self/contact shadows, average-shadow smoothing, rim light (fresnel or screen-space depth), stylized + anisotropic hair highlights, eye catchlights, role-aware specular, source map routing (normal/AO/emissive/MatCap/ramp/detail), inverted-hull outlines, glitter, stickers, perspective removal, shell fur, dither fades — 23 settings groups, all preset-serializable. [Docs](docs/toon-shading.md) |
|
|
52
|
+
| Environment shading | `@call-me-sensei/toonlab/environment` | Modern anime-style scene shader for texture packs, standard glTF, and untextured scenes: material-role classification, wrapped lighting, packed-map hints, window cutouts, sun/lamp rigs, time-of-day, six-direction ambient probe, planar floor reflections, BVH vertex-AO baking, height fog, cloud shadows. [Docs](docs/environment.md) |
|
|
53
|
+
| Water | `@call-me-sensei/toonlab/water` | Fully procedural interactive water: Gerstner wave stack with a calm→storm dial, wave sets, plunging breakers you can surf, three-stop absorption color, refraction/caustics/foam, GPU ripple sim, splashes, wakes, rain, kelp, underwater view — with a CPU mirror of the whole spectrum for buoyancy. [Docs](docs/water.md) |
|
|
54
|
+
| Vegetation | `@call-me-sensei/toonlab/vegetation` | Instanced grass and flower fields (wind, push-away, scene + cloud shadows, backlit translucency) and procedural stylized trees with a serializable recipe system. [Docs](docs/vegetation-sky.md) |
|
|
55
|
+
| Sky | `@call-me-sensei/toonlab/sky` | Procedural gradient/sun/painterly-cloud/star dome that also shows up in water reflections. [Docs](docs/vegetation-sky.md) |
|
|
56
|
+
| Post-processing | `@call-me-sensei/toonlab/post` | Optional single-pipeline compositor: character-aware bloom, color grade, LUT, vignette, screen outline, depth cue — schema-driven, preset-serializable. [Docs](docs/post-processing.md) |
|
|
57
|
+
| Character pipeline | `@call-me-sensei/toonlab/character` | Bone-role adapters for VRM/MMD/Mixamo/Rigify rigs, native-clip conventions, Mixamo retarget helpers, and a procedural freestyle swim clip. [Docs](docs/characters.md) |
|
|
58
|
+
| Model loaders | `@call-me-sensei/toonlab/loaders` | Optional GLB/glTF, VRM 0+1, PMX/PMD, FBX, OBJ, and text-USDZ loading helpers. Kept off the root import so apps that do not load models avoid loader dependencies. [Docs](docs/characters.md) |
|
|
59
|
+
| Debug panel | `@call-me-sensei/toonlab/debug` | One-line schema-driven tuning GUI for any settings module — the same panel the labs use. [Docs](docs/debug-panel.md) |
|
|
60
|
+
|
|
61
|
+
Zero texture assets in the library: water, sky, grass, flowers, trees, and
|
|
62
|
+
splashes are all procedural. The one bundled model is a CC0 mannequin with 45
|
|
63
|
+
embedded animation clips.
|
|
64
|
+
|
|
65
|
+
## Library usage
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
import { applyToonShader, createToonSettings } from '@call-me-sensei/toonlab/toon';
|
|
69
|
+
|
|
70
|
+
const settings = createToonSettings({
|
|
71
|
+
preset: 'default',
|
|
72
|
+
skinTone: {
|
|
73
|
+
skinShadowBrightness: 0.94,
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
applyToonShader(characterRoot, { settings });
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```js
|
|
81
|
+
import { WaterSurface } from '@call-me-sensei/toonlab/water';
|
|
82
|
+
import { StylizedSky } from '@call-me-sensei/toonlab/sky';
|
|
83
|
+
|
|
84
|
+
const water = new WaterSurface({ width: 200, depth: 200, preset: 'lake' });
|
|
85
|
+
scene.add(water);
|
|
86
|
+
const sky = new StylizedSky();
|
|
87
|
+
scene.add(sky);
|
|
88
|
+
|
|
89
|
+
// per frame, before renderer.render(scene, camera):
|
|
90
|
+
water.update(renderer, scene, camera, delta);
|
|
91
|
+
sky.update(delta, camera);
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Inside this repo the labs import from `../../src/...`; the `@call-me-sensei/toonlab/...`
|
|
95
|
+
specifiers are what you use once the package is installed from npm. The
|
|
96
|
+
shader clusters are TSL/NodeMaterial modules for Three's WebGPU renderer
|
|
97
|
+
stack, with WebGL2 fallback through the same TSL path.
|
|
98
|
+
|
|
99
|
+
## Documentation
|
|
100
|
+
|
|
101
|
+
- [Getting started](docs/getting-started.md) — clone, run, tour the labs,
|
|
102
|
+
load your own models.
|
|
103
|
+
- [Toon character shading](docs/toon-shading.md)
|
|
104
|
+
- [Environment shading](docs/environment.md)
|
|
105
|
+
- [Water](docs/water.md)
|
|
106
|
+
- [Vegetation and sky](docs/vegetation-sky.md)
|
|
107
|
+
- [Post-processing](docs/post-processing.md)
|
|
108
|
+
- [Characters and animation](docs/characters.md)
|
|
109
|
+
- [Debug panel](docs/debug-panel.md)
|
|
110
|
+
- [Settings reference](docs/settings-reference.md) — every tunable field,
|
|
111
|
+
generated from the schemas (`node scripts/generate-settings-reference.mjs`).
|
|
112
|
+
- [Shader constants](docs/shader-constants.md) — the deliberately unexposed
|
|
113
|
+
constants and where they live.
|
|
114
|
+
|
|
115
|
+
## AI coding agents
|
|
116
|
+
|
|
117
|
+
The GitHub repo includes downloadable runtime-usage guidance for Codex, Claude
|
|
118
|
+
Code, Cursor, and other coding agents under `agents/`. These files help
|
|
119
|
+
developers use ToonLab in their own apps and are not part of the npm package.
|
|
120
|
+
|
|
121
|
+
## License
|
|
122
|
+
|
|
123
|
+
Code is [MIT](LICENSE), copyright Hyperbond Studio PTE. LTD. Bundled assets are CC0 — see
|
|
124
|
+
[ATTRIBUTION.md](ATTRIBUTION.md) for credits and for the bring-your-own
|
|
125
|
+
conventions (Mixamo clips, your own models, licensed scan packs).
|
package/package.json
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@call-me-sensei/toonlab",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Stylized (anime/toon) game starter kit for Three.js: character toon shading, environment shading, water, vegetation, sky, and post-processing",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Hyperbond Studio PTE. LTD. (Call Me Sensei)",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/call-me-sensei/toonlab.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/call-me-sensei/toonlab#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/call-me-sensei/toonlab/issues"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"three",
|
|
17
|
+
"threejs",
|
|
18
|
+
"toon",
|
|
19
|
+
"cel-shading",
|
|
20
|
+
"anime",
|
|
21
|
+
"shader",
|
|
22
|
+
"water",
|
|
23
|
+
"game-starter"
|
|
24
|
+
],
|
|
25
|
+
"type": "module",
|
|
26
|
+
"main": "./src/index.js",
|
|
27
|
+
"module": "./src/index.js",
|
|
28
|
+
"files": [
|
|
29
|
+
"src",
|
|
30
|
+
"README.md",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"ATTRIBUTION.md"
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
".": "./src/index.js",
|
|
36
|
+
"./toon": "./src/toon/index.js",
|
|
37
|
+
"./environment": "./src/environment/index.js",
|
|
38
|
+
"./water": "./src/water/index.js",
|
|
39
|
+
"./vegetation": "./src/vegetation/index.js",
|
|
40
|
+
"./sky": "./src/sky/index.js",
|
|
41
|
+
"./post": "./src/post/index.js",
|
|
42
|
+
"./rockgen": "./src/rockgen/index.js",
|
|
43
|
+
"./debrisgen": "./src/debrisgen/index.js",
|
|
44
|
+
"./character": "./src/character/index.js",
|
|
45
|
+
"./loaders": "./src/loaders/index.js",
|
|
46
|
+
"./debug": "./src/debug/index.js",
|
|
47
|
+
"./toon-settings": "./src/toon/toonSettings.js",
|
|
48
|
+
"./water-settings": "./src/water/waterSettings.js",
|
|
49
|
+
"./grass": "./src/vegetation/stylizedGrass.js",
|
|
50
|
+
"./post-processing": "./src/post/postProcessing.js"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"assets:characters": "node scripts/generate-local-character-assets.mjs",
|
|
54
|
+
"assets:environments": "node scripts/generate-local-environment-assets.mjs",
|
|
55
|
+
"assets:local": "npm run assets:characters && npm run assets:environments",
|
|
56
|
+
"dev": "vite",
|
|
57
|
+
"build": "vite build",
|
|
58
|
+
"baseline:compare": "node scripts/compare-baselines.mjs",
|
|
59
|
+
"preview": "vite preview",
|
|
60
|
+
"baseline:capture": "node scripts/baseline-capture.mjs",
|
|
61
|
+
"verify:rockgen": "node scripts/verify-rockgen.mjs",
|
|
62
|
+
"visual:check": "node scripts/visual-check.mjs"
|
|
63
|
+
},
|
|
64
|
+
"peerDependencies": {
|
|
65
|
+
"@pixiv/three-vrm": "^3.5.4",
|
|
66
|
+
"three-mesh-bvh": "^0.8.3",
|
|
67
|
+
"three-stdlib": "^2.36.1",
|
|
68
|
+
"three": "^0.185.1"
|
|
69
|
+
},
|
|
70
|
+
"peerDependenciesMeta": {
|
|
71
|
+
"@pixiv/three-vrm": {
|
|
72
|
+
"optional": true
|
|
73
|
+
},
|
|
74
|
+
"three-mesh-bvh": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"three-stdlib": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"devDependencies": {
|
|
82
|
+
"@pixiv/three-vrm": "^3.5.4",
|
|
83
|
+
"@react-three/drei": "^10.7.7",
|
|
84
|
+
"@react-three/fiber": "^9.6.1",
|
|
85
|
+
"@react-three/rapier": "^2.2.0",
|
|
86
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
87
|
+
"ecctrl": "^1.0.97",
|
|
88
|
+
"lucide-react": "^1.23.0",
|
|
89
|
+
"playwright": "^1.60.0",
|
|
90
|
+
"react": "^19.2.7",
|
|
91
|
+
"react-dom": "^19.2.7",
|
|
92
|
+
"three": "^0.185.1",
|
|
93
|
+
"three-mesh-bvh": "^0.8.3",
|
|
94
|
+
"three-stdlib": "^2.36.1",
|
|
95
|
+
"vite": "^5.0.0"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
// Canonical humanoid bone roles and per-convention name adapters.
|
|
2
|
+
//
|
|
3
|
+
// The retarget pipeline in ecctrlMain.jsx does a world-space bake that only
|
|
4
|
+
// needs to know WHICH target bone plays WHICH humanoid role — everything else
|
|
5
|
+
// (rest-pose differences, hierarchy mismatches) is solved numerically. This
|
|
6
|
+
// module owns that role knowledge so the pipeline itself stays rig-agnostic:
|
|
7
|
+
// roles follow the VRM humanoid naming (the only spec'd humanoid standard in
|
|
8
|
+
// the glTF world), and each supported convention (VRM, MMD/PMX, Mixamo-named
|
|
9
|
+
// skeletons) contributes a small name table mapping onto those roles.
|
|
10
|
+
//
|
|
11
|
+
// Animation sources are Mixamo FBX clips, so each role also carries its
|
|
12
|
+
// Mixamo bone name; a resolved rig is ultimately a pair of maps between
|
|
13
|
+
// target bone names and Mixamo source names.
|
|
14
|
+
|
|
15
|
+
// Mixamo bone per VRM humanoid role. Also the source-side chain map used for
|
|
16
|
+
// rest-pose direction corrections lives on these names (MIXAMO_CHAIN_CHILD).
|
|
17
|
+
const MIXAMO_BONE_BY_ROLE = Object.freeze({
|
|
18
|
+
hips: 'mixamorigHips',
|
|
19
|
+
spine: 'mixamorigSpine',
|
|
20
|
+
chest: 'mixamorigSpine1',
|
|
21
|
+
upperChest: 'mixamorigSpine2',
|
|
22
|
+
neck: 'mixamorigNeck',
|
|
23
|
+
head: 'mixamorigHead',
|
|
24
|
+
|
|
25
|
+
leftShoulder: 'mixamorigLeftShoulder',
|
|
26
|
+
leftUpperArm: 'mixamorigLeftArm',
|
|
27
|
+
leftLowerArm: 'mixamorigLeftForeArm',
|
|
28
|
+
leftHand: 'mixamorigLeftHand',
|
|
29
|
+
rightShoulder: 'mixamorigRightShoulder',
|
|
30
|
+
rightUpperArm: 'mixamorigRightArm',
|
|
31
|
+
rightLowerArm: 'mixamorigRightForeArm',
|
|
32
|
+
rightHand: 'mixamorigRightHand',
|
|
33
|
+
|
|
34
|
+
leftThumbMetacarpal: 'mixamorigLeftHandThumb1',
|
|
35
|
+
leftThumbProximal: 'mixamorigLeftHandThumb2',
|
|
36
|
+
leftThumbDistal: 'mixamorigLeftHandThumb3',
|
|
37
|
+
leftIndexProximal: 'mixamorigLeftHandIndex1',
|
|
38
|
+
leftIndexIntermediate: 'mixamorigLeftHandIndex2',
|
|
39
|
+
leftIndexDistal: 'mixamorigLeftHandIndex3',
|
|
40
|
+
leftMiddleProximal: 'mixamorigLeftHandMiddle1',
|
|
41
|
+
leftMiddleIntermediate: 'mixamorigLeftHandMiddle2',
|
|
42
|
+
leftMiddleDistal: 'mixamorigLeftHandMiddle3',
|
|
43
|
+
leftRingProximal: 'mixamorigLeftHandRing1',
|
|
44
|
+
leftRingIntermediate: 'mixamorigLeftHandRing2',
|
|
45
|
+
leftRingDistal: 'mixamorigLeftHandRing3',
|
|
46
|
+
leftLittleProximal: 'mixamorigLeftHandPinky1',
|
|
47
|
+
leftLittleIntermediate: 'mixamorigLeftHandPinky2',
|
|
48
|
+
leftLittleDistal: 'mixamorigLeftHandPinky3',
|
|
49
|
+
|
|
50
|
+
rightThumbMetacarpal: 'mixamorigRightHandThumb1',
|
|
51
|
+
rightThumbProximal: 'mixamorigRightHandThumb2',
|
|
52
|
+
rightThumbDistal: 'mixamorigRightHandThumb3',
|
|
53
|
+
rightIndexProximal: 'mixamorigRightHandIndex1',
|
|
54
|
+
rightIndexIntermediate: 'mixamorigRightHandIndex2',
|
|
55
|
+
rightIndexDistal: 'mixamorigRightHandIndex3',
|
|
56
|
+
rightMiddleProximal: 'mixamorigRightHandMiddle1',
|
|
57
|
+
rightMiddleIntermediate: 'mixamorigRightHandMiddle2',
|
|
58
|
+
rightMiddleDistal: 'mixamorigRightHandMiddle3',
|
|
59
|
+
rightRingProximal: 'mixamorigRightHandRing1',
|
|
60
|
+
rightRingIntermediate: 'mixamorigRightHandRing2',
|
|
61
|
+
rightRingDistal: 'mixamorigRightHandRing3',
|
|
62
|
+
rightLittleProximal: 'mixamorigRightHandPinky1',
|
|
63
|
+
rightLittleIntermediate: 'mixamorigRightHandPinky2',
|
|
64
|
+
rightLittleDistal: 'mixamorigRightHandPinky3',
|
|
65
|
+
|
|
66
|
+
leftUpperLeg: 'mixamorigLeftUpLeg',
|
|
67
|
+
leftLowerLeg: 'mixamorigLeftLeg',
|
|
68
|
+
leftFoot: 'mixamorigLeftFoot',
|
|
69
|
+
leftToes: 'mixamorigLeftToeBase',
|
|
70
|
+
rightUpperLeg: 'mixamorigRightUpLeg',
|
|
71
|
+
rightLowerLeg: 'mixamorigRightLeg',
|
|
72
|
+
rightFoot: 'mixamorigRightFoot',
|
|
73
|
+
rightToes: 'mixamorigRightToeBase',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export const HUMANOID_ROLES = Object.freeze(Object.keys(MIXAMO_BONE_BY_ROLE));
|
|
77
|
+
|
|
78
|
+
// Source-side chain children for rest-pose direction corrections (Mixamo rigs
|
|
79
|
+
// are T-pose; targets are often A-pose). Keyed by Mixamo names because the
|
|
80
|
+
// correction compares source bone directions against target bone directions.
|
|
81
|
+
export const MIXAMO_CHAIN_CHILD = Object.freeze({
|
|
82
|
+
mixamorigLeftShoulder: 'mixamorigLeftArm',
|
|
83
|
+
mixamorigLeftArm: 'mixamorigLeftForeArm',
|
|
84
|
+
mixamorigLeftForeArm: 'mixamorigLeftHand',
|
|
85
|
+
mixamorigLeftHand: 'mixamorigLeftHandMiddle1',
|
|
86
|
+
mixamorigRightShoulder: 'mixamorigRightArm',
|
|
87
|
+
mixamorigRightArm: 'mixamorigRightForeArm',
|
|
88
|
+
mixamorigRightForeArm: 'mixamorigRightHand',
|
|
89
|
+
mixamorigRightHand: 'mixamorigRightHandMiddle1',
|
|
90
|
+
mixamorigLeftUpLeg: 'mixamorigLeftLeg',
|
|
91
|
+
mixamorigLeftLeg: 'mixamorigLeftFoot',
|
|
92
|
+
mixamorigLeftFoot: 'mixamorigLeftToeBase',
|
|
93
|
+
mixamorigRightUpLeg: 'mixamorigRightLeg',
|
|
94
|
+
mixamorigRightLeg: 'mixamorigRightFoot',
|
|
95
|
+
mixamorigRightFoot: 'mixamorigRightToeBase',
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// MMD/PMX bone names per role. Multiple entries per role are all animated:
|
|
99
|
+
// many rigs (incl. many anime-game models) bind the leg skin weights to the "D"
|
|
100
|
+
// deform bones, which only follow the FK bones through MMD's grant system
|
|
101
|
+
// (needs MMDAnimationHelper) — animating both moves every variant. The LAST
|
|
102
|
+
// existing entry is the preferred one for chain/inverse lookups, matching the
|
|
103
|
+
// deform-bone preference the old hardcoded tables had.
|
|
104
|
+
const MMD_BONES_BY_ROLE = Object.freeze({
|
|
105
|
+
hips: ['下半身'],
|
|
106
|
+
spine: ['上半身'],
|
|
107
|
+
chest: ['上半身3'],
|
|
108
|
+
upperChest: ['上半身2'],
|
|
109
|
+
neck: ['首'],
|
|
110
|
+
head: ['頭'],
|
|
111
|
+
|
|
112
|
+
leftShoulder: ['左肩'],
|
|
113
|
+
leftUpperArm: ['左腕'],
|
|
114
|
+
leftLowerArm: ['左ひじ'],
|
|
115
|
+
leftHand: ['左手首'],
|
|
116
|
+
rightShoulder: ['右肩'],
|
|
117
|
+
rightUpperArm: ['右腕'],
|
|
118
|
+
rightLowerArm: ['右ひじ'],
|
|
119
|
+
rightHand: ['右手首'],
|
|
120
|
+
|
|
121
|
+
leftThumbMetacarpal: ['左親指0'],
|
|
122
|
+
leftThumbProximal: ['左親指1'],
|
|
123
|
+
leftThumbDistal: ['左親指2'],
|
|
124
|
+
leftIndexProximal: ['左人指1'],
|
|
125
|
+
leftIndexIntermediate: ['左人指2'],
|
|
126
|
+
leftIndexDistal: ['左人指3'],
|
|
127
|
+
leftMiddleProximal: ['左中指1'],
|
|
128
|
+
leftMiddleIntermediate: ['左中指2'],
|
|
129
|
+
leftMiddleDistal: ['左中指3'],
|
|
130
|
+
leftRingProximal: ['左薬指1'],
|
|
131
|
+
leftRingIntermediate: ['左薬指2'],
|
|
132
|
+
leftRingDistal: ['左薬指3'],
|
|
133
|
+
leftLittleProximal: ['左小指1'],
|
|
134
|
+
leftLittleIntermediate: ['左小指2'],
|
|
135
|
+
leftLittleDistal: ['左小指3'],
|
|
136
|
+
|
|
137
|
+
rightThumbMetacarpal: ['右親指0'],
|
|
138
|
+
rightThumbProximal: ['右親指1'],
|
|
139
|
+
rightThumbDistal: ['右親指2'],
|
|
140
|
+
rightIndexProximal: ['右人指1'],
|
|
141
|
+
rightIndexIntermediate: ['右人指2'],
|
|
142
|
+
rightIndexDistal: ['右人指3'],
|
|
143
|
+
rightMiddleProximal: ['右中指1'],
|
|
144
|
+
rightMiddleIntermediate: ['右中指2'],
|
|
145
|
+
rightMiddleDistal: ['右中指3'],
|
|
146
|
+
rightRingProximal: ['右薬指1'],
|
|
147
|
+
rightRingIntermediate: ['右薬指2'],
|
|
148
|
+
rightRingDistal: ['右薬指3'],
|
|
149
|
+
rightLittleProximal: ['右小指1'],
|
|
150
|
+
rightLittleIntermediate: ['右小指2'],
|
|
151
|
+
rightLittleDistal: ['右小指3'],
|
|
152
|
+
|
|
153
|
+
leftUpperLeg: ['左足', '左足D'],
|
|
154
|
+
leftLowerLeg: ['左ひざ', '左ひざD'],
|
|
155
|
+
leftFoot: ['左足首', '左足首D'],
|
|
156
|
+
leftToes: ['左つま先', '左足先EX'],
|
|
157
|
+
rightUpperLeg: ['右足', '右足D'],
|
|
158
|
+
rightLowerLeg: ['右ひざ', '右ひざD'],
|
|
159
|
+
rightFoot: ['右足首', '右足首D'],
|
|
160
|
+
rightToes: ['右つま先', '右足先EX'],
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// Blender Rigify deform-bone names (the bundled mannequin and other
|
|
164
|
+
// Rigify-exported GLBs). GLTFLoader sanitizes node names on load (dots and
|
|
165
|
+
// other reserved characters are stripped: "DEF-upper_arm.L" arrives as
|
|
166
|
+
// "DEF-upper_armL"), so this table is matched loosely — see looseBoneName().
|
|
167
|
+
const RIGIFY_BONES_BY_ROLE = Object.freeze({
|
|
168
|
+
hips: ['DEF-hips'],
|
|
169
|
+
spine: ['DEF-spine.001'],
|
|
170
|
+
chest: ['DEF-spine.002'],
|
|
171
|
+
upperChest: ['DEF-spine.003'],
|
|
172
|
+
neck: ['DEF-neck'],
|
|
173
|
+
head: ['DEF-head'],
|
|
174
|
+
|
|
175
|
+
leftShoulder: ['DEF-shoulder.L'],
|
|
176
|
+
leftUpperArm: ['DEF-upper_arm.L'],
|
|
177
|
+
leftLowerArm: ['DEF-forearm.L'],
|
|
178
|
+
leftHand: ['DEF-hand.L'],
|
|
179
|
+
rightShoulder: ['DEF-shoulder.R'],
|
|
180
|
+
rightUpperArm: ['DEF-upper_arm.R'],
|
|
181
|
+
rightLowerArm: ['DEF-forearm.R'],
|
|
182
|
+
rightHand: ['DEF-hand.R'],
|
|
183
|
+
|
|
184
|
+
leftThumbMetacarpal: ['DEF-thumb.01.L'],
|
|
185
|
+
leftThumbProximal: ['DEF-thumb.02.L'],
|
|
186
|
+
leftThumbDistal: ['DEF-thumb.03.L'],
|
|
187
|
+
leftIndexProximal: ['DEF-f_index.01.L'],
|
|
188
|
+
leftIndexIntermediate: ['DEF-f_index.02.L'],
|
|
189
|
+
leftIndexDistal: ['DEF-f_index.03.L'],
|
|
190
|
+
leftMiddleProximal: ['DEF-f_middle.01.L'],
|
|
191
|
+
leftMiddleIntermediate: ['DEF-f_middle.02.L'],
|
|
192
|
+
leftMiddleDistal: ['DEF-f_middle.03.L'],
|
|
193
|
+
leftRingProximal: ['DEF-f_ring.01.L'],
|
|
194
|
+
leftRingIntermediate: ['DEF-f_ring.02.L'],
|
|
195
|
+
leftRingDistal: ['DEF-f_ring.03.L'],
|
|
196
|
+
leftLittleProximal: ['DEF-f_pinky.01.L'],
|
|
197
|
+
leftLittleIntermediate: ['DEF-f_pinky.02.L'],
|
|
198
|
+
leftLittleDistal: ['DEF-f_pinky.03.L'],
|
|
199
|
+
|
|
200
|
+
rightThumbMetacarpal: ['DEF-thumb.01.R'],
|
|
201
|
+
rightThumbProximal: ['DEF-thumb.02.R'],
|
|
202
|
+
rightThumbDistal: ['DEF-thumb.03.R'],
|
|
203
|
+
rightIndexProximal: ['DEF-f_index.01.R'],
|
|
204
|
+
rightIndexIntermediate: ['DEF-f_index.02.R'],
|
|
205
|
+
rightIndexDistal: ['DEF-f_index.03.R'],
|
|
206
|
+
rightMiddleProximal: ['DEF-f_middle.01.R'],
|
|
207
|
+
rightMiddleIntermediate: ['DEF-f_middle.02.R'],
|
|
208
|
+
rightMiddleDistal: ['DEF-f_middle.03.R'],
|
|
209
|
+
rightRingProximal: ['DEF-f_ring.01.R'],
|
|
210
|
+
rightRingIntermediate: ['DEF-f_ring.02.R'],
|
|
211
|
+
rightRingDistal: ['DEF-f_ring.03.R'],
|
|
212
|
+
rightLittleProximal: ['DEF-f_pinky.01.R'],
|
|
213
|
+
rightLittleIntermediate: ['DEF-f_pinky.02.R'],
|
|
214
|
+
rightLittleDistal: ['DEF-f_pinky.03.R'],
|
|
215
|
+
|
|
216
|
+
leftUpperLeg: ['DEF-thigh.L'],
|
|
217
|
+
leftLowerLeg: ['DEF-shin.L'],
|
|
218
|
+
leftFoot: ['DEF-foot.L'],
|
|
219
|
+
leftToes: ['DEF-toe.L'],
|
|
220
|
+
rightUpperLeg: ['DEF-thigh.R'],
|
|
221
|
+
rightLowerLeg: ['DEF-shin.R'],
|
|
222
|
+
rightFoot: ['DEF-foot.R'],
|
|
223
|
+
rightToes: ['DEF-toe.R'],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// MMD's translation carrier: hip sway/bob is baked onto センター so any stance
|
|
227
|
+
// stays balanced over the capsule. Non-MMD rigs carry it on the hips bone.
|
|
228
|
+
const MMD_CENTER_BONE = 'センター';
|
|
229
|
+
|
|
230
|
+
function looseBoneName(name) {
|
|
231
|
+
return String(name || '').replace(/[^0-9A-Za-z_\--ヿ一-鿿!-⦆]/g, '');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export function normalizeMixamoBoneName(name) {
|
|
235
|
+
return String(name || '')
|
|
236
|
+
.replace(/^mixamorig:/, 'mixamorig')
|
|
237
|
+
.replace(/:/g, '');
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function buildRig(type, entries, { hipCarrierName = null } = {}) {
|
|
241
|
+
// entries: [{ targetName, mixamoName }] in preference order per mixamo name
|
|
242
|
+
// (later entries override earlier ones in the inverse map).
|
|
243
|
+
const targetToMixamo = new Map();
|
|
244
|
+
const mixamoToTarget = new Map();
|
|
245
|
+
for (const { targetName, mixamoName } of entries) {
|
|
246
|
+
if (!targetToMixamo.has(targetName)) targetToMixamo.set(targetName, mixamoName);
|
|
247
|
+
mixamoToTarget.set(mixamoName, targetName);
|
|
248
|
+
}
|
|
249
|
+
if (targetToMixamo.size === 0) return null;
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
type,
|
|
253
|
+
targetToMixamo,
|
|
254
|
+
mixamoToTarget,
|
|
255
|
+
hipCarrierName: hipCarrierName || mixamoToTarget.get(MIXAMO_BONE_BY_ROLE.hips) || null,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function resolveVrmRig(vrm) {
|
|
260
|
+
const humanoid = vrm?.humanoid;
|
|
261
|
+
if (!humanoid?.getRawBoneNode) return null;
|
|
262
|
+
|
|
263
|
+
const entries = [];
|
|
264
|
+
for (const [role, mixamoName] of Object.entries(MIXAMO_BONE_BY_ROLE)) {
|
|
265
|
+
const node = humanoid.getRawBoneNode(role);
|
|
266
|
+
if (node?.name) entries.push({ targetName: node.name, mixamoName });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return buildRig('vrm', entries);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function resolveMmdRig(boneNames) {
|
|
273
|
+
const entries = [];
|
|
274
|
+
for (const [role, targetNames] of Object.entries(MMD_BONES_BY_ROLE)) {
|
|
275
|
+
const mixamoName = MIXAMO_BONE_BY_ROLE[role];
|
|
276
|
+
for (const targetName of targetNames) {
|
|
277
|
+
if (boneNames.has(targetName)) entries.push({ targetName, mixamoName });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return buildRig('mmd', entries, {
|
|
282
|
+
hipCarrierName: boneNames.has(MMD_CENTER_BONE) ? MMD_CENTER_BONE : null,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function resolveRigifyRig(boneNames) {
|
|
287
|
+
const actualByLooseName = new Map();
|
|
288
|
+
for (const boneName of boneNames) actualByLooseName.set(looseBoneName(boneName), boneName);
|
|
289
|
+
|
|
290
|
+
const entries = [];
|
|
291
|
+
for (const [role, candidates] of Object.entries(RIGIFY_BONES_BY_ROLE)) {
|
|
292
|
+
const mixamoName = MIXAMO_BONE_BY_ROLE[role];
|
|
293
|
+
for (const candidate of candidates) {
|
|
294
|
+
const targetName = actualByLooseName.get(looseBoneName(candidate));
|
|
295
|
+
if (targetName) entries.push({ targetName, mixamoName });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return buildRig('rigify', entries);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function resolveMixamoNamedRig(boneNames) {
|
|
303
|
+
const mixamoNames = new Set(Object.values(MIXAMO_BONE_BY_ROLE));
|
|
304
|
+
const entries = [];
|
|
305
|
+
for (const boneName of boneNames) {
|
|
306
|
+
const normalized = normalizeMixamoBoneName(boneName);
|
|
307
|
+
if (mixamoNames.has(normalized)) entries.push({ targetName: boneName, mixamoName: normalized });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return buildRig('mixamo', entries);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Resolves how a loaded character's bones map onto the canonical roles.
|
|
314
|
+
// Returns { type, targetToMixamo, mixamoToTarget, hipCarrierName } or null
|
|
315
|
+
// when no known convention matches (retargeting is impossible then).
|
|
316
|
+
export function resolveCharacterRig(targetMesh, { vrm = null } = {}) {
|
|
317
|
+
if (vrm) {
|
|
318
|
+
const rig = resolveVrmRig(vrm);
|
|
319
|
+
if (rig) return rig;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const bones = targetMesh?.skeleton?.bones || [];
|
|
323
|
+
const boneNames = new Set(bones.map((bone) => bone.name));
|
|
324
|
+
if (boneNames.size === 0) return null;
|
|
325
|
+
|
|
326
|
+
const mmdRig = boneNames.has('下半身') || boneNames.has(MMD_CENTER_BONE)
|
|
327
|
+
? resolveMmdRig(boneNames)
|
|
328
|
+
: null;
|
|
329
|
+
if (mmdRig) return mmdRig;
|
|
330
|
+
|
|
331
|
+
return resolveMixamoNamedRig(boneNames) || resolveRigifyRig(boneNames);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Preferred target bone name for a canonical role (the skin-weighted variant
|
|
335
|
+
// on rigs that split FK/deform bones), or null when the model lacks it.
|
|
336
|
+
export function targetBoneNameForRole(rig, role) {
|
|
337
|
+
const mixamoName = MIXAMO_BONE_BY_ROLE[role];
|
|
338
|
+
return (mixamoName && rig?.mixamoToTarget.get(mixamoName)) || null;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Every target bone playing a canonical role — MMD leg roles return both the
|
|
342
|
+
// FK bone and its "D" deform twin, so procedural clips can drive all variants
|
|
343
|
+
// the way the retarget bake does.
|
|
344
|
+
export function targetBoneNamesForRole(rig, role) {
|
|
345
|
+
const mixamoName = MIXAMO_BONE_BY_ROLE[role];
|
|
346
|
+
if (!mixamoName || !rig) return [];
|
|
347
|
+
|
|
348
|
+
const names = [];
|
|
349
|
+
for (const [targetName, candidateMixamoName] of rig.targetToMixamo) {
|
|
350
|
+
if (candidateMixamoName === mixamoName) names.push(targetName);
|
|
351
|
+
}
|
|
352
|
+
return names;
|
|
353
|
+
}
|