@call-me-sensei/toonlab 0.4.19 → 0.4.21
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/NPM-LIBRARY.md +230 -0
- package/README.md +78 -36
- package/agents/PROMPTS.md +91 -0
- package/agents/README.md +9 -14
- package/agents/claude/CLAUDE.md +8 -0
- package/agents/codex/AGENTS.md +161 -0
- package/agents/cursor/toonlab.mdc +37 -0
- package/agents/references/mcp-asset-discovery.md +12 -9
- package/agents/references/runtime-entry-points.md +5 -4
- package/agents/skills/claude/asset-sourcing/SKILL.md +9 -6
- package/agents/skills/claude/rock-ground-shaders/SKILL.md +16 -0
- package/agents/skills/claude/vegetation-sky/SKILL.md +7 -4
- package/agents/skills/codex/asset-sourcing/SKILL.md +9 -6
- package/agents/skills/codex/rock-ground-shaders/SKILL.md +16 -0
- package/agents/skills/codex/vegetation-sky/SKILL.md +7 -4
- package/cli/toonlab.mjs +3 -2
- package/database/fal-patina.mjs +131 -0
- package/database/generation-service.mjs +108 -7
- package/database/migrations/0007_creation_revisions.sql +80 -0
- package/database/migrations/0008_creation_revision_hardening.sql +98 -0
- package/database/migrations/0009_creation_revision_delete_integrity.sql +11 -0
- package/database/migrations/0010_external_catalog_assets.sql +28 -0
- package/database/providers.mjs +195 -16
- package/database/repository.mjs +518 -70
- package/database/seeds/catalog/0003_2026-08-open-assets.sql +159086 -0
- package/database/seeds/catalog/0004_2026-08-c7-rocks.sql +39405 -0
- package/mcp/public-catalog.mjs +4 -52
- package/mcp/server.mjs +175 -12
- package/mcp/vite-plugin.mjs +78 -2
- package/package.json +32 -12
- package/scripts/generate-catalog-seed.mjs +34 -7
- package/src/asset-policy/catalogLicenses.js +64 -6
- package/src/assetlib/loadImported.js +15 -1
- package/src/catalog/officialCatalogAssetRuntime.js +13 -2
- package/src/catalog/officialCatalogLod.js +34 -1
- package/src/catalog/officialCatalogPlacement.js +29 -14
- package/src/character/animationRetarget.js +2 -0
- package/src/character/characterRig.js +6 -1
- package/src/character/characterRuntime.js +175 -7
- package/src/environment/environmentMaterialAdapter.js +30 -0
- package/src/environment/environmentPresets.js +90 -0
- package/src/environment/environmentSunShadowPass.js +42 -0
- package/src/environment/toonLabSurfaceLighting.js +108 -10
- package/src/environment/urbanPropMaterial.js +104 -2
- package/src/lighting/lightingSystem.js +65 -10
- package/src/renderer/index.js +7 -0
- package/src/renderer/styleComparison.js +985 -0
- package/src/rock-shader/index.js +5 -0
- package/src/rock-shader/rockGeometryDetail.js +701 -0
- package/src/rock-shader/rockMaterial.js +782 -39
- package/src/rock-shader/rockRegionRuntime.js +185 -0
- package/src/rock-shader/rockSemanticMaterialRuntime.js +337 -0
- package/src/rock-shader/rockShaderRuntime.js +354 -67
- package/src/rock-shader/rockShaderSettings.js +246 -16
- package/src/rock-shader/rockTangentIntegrity.js +136 -0
- package/src/rock-shader/rockTextureIntegrity.js +115 -0
- package/src/rockgen/index.js +1 -0
- package/src/rockgen/rockDocument.js +27 -3
- package/src/rockgen/surface/c7GeologySurface.js +372 -0
- package/src/shaders-tsl/water.js +8 -0
- package/src/sky/cloudShadow.js +25 -1
- package/src/sky/skySystem.js +9 -0
- package/src/styles/index.js +23 -0
- package/src/styles/neutralStylePresets.js +712 -0
- package/src/styles/sceneStyleRuntime.js +51 -4
- package/src/styles/styleAdapters.js +13 -2
- package/src/styles/styleBundle.js +1 -1
- package/src/texgen/evaluateTexture.js +30 -3
- package/src/texgen/textureGenerators.js +63 -31
- package/src/texgen/textureSettings.js +25 -8
- package/src/toon/toonSettings.js +96 -2
- package/src/vegetation/branchTree.js +327 -42
- package/src/vegetation/index.js +24 -2
- package/src/vegetation/scatter.js +372 -1
- package/src/vegetation/stylizedTree.js +312 -126
- package/src/vegetation/stylizedTreeFoliage.js +229 -7
- package/src/vegetation/treeSurfaceTextures.js +328 -0
- package/src/version.js +1 -1
- package/src/water/waterSettings.js +15 -7
- package/types/asset-policy/catalogLicenses.d.ts +2 -0
- package/types/asset-policy/index.d.ts +2 -0
- package/types/assetlib/loadImported.d.ts +3 -1
- package/types/catalog/officialCatalogLod.d.ts +63 -5
- package/types/character/characterRig.d.ts +59 -0
- package/types/character/characterRuntime.d.ts +1 -0
- package/types/environment/environmentMaterialAdapter.d.ts +3 -1
- package/types/environment/toonLabSurfaceLighting.d.ts +20 -3
- package/types/index.d.ts +89 -7
- package/types/lighting/lightingSystem.d.ts +7 -0
- package/types/react/index.d.ts +20 -0
- package/types/renderer/index.d.ts +1 -0
- package/types/renderer/styleComparison.d.ts +409 -0
- package/types/rock-shader/index.d.ts +5 -0
- package/types/rock-shader/rockGeometryDetail.d.ts +300 -0
- package/types/rock-shader/rockMaterial.d.ts +3 -0
- package/types/rock-shader/rockRegionRuntime.d.ts +86 -0
- package/types/rock-shader/rockSemanticMaterialRuntime.d.ts +29 -0
- package/types/rock-shader/rockShaderRuntime.d.ts +2 -0
- package/types/rock-shader/rockShaderSettings.d.ts +19 -5
- package/types/rock-shader/rockTangentIntegrity.d.ts +51 -0
- package/types/rock-shader/rockTextureIntegrity.d.ts +61 -0
- package/types/rockgen/index.d.ts +1 -0
- package/types/rockgen/rockDocument.d.ts +29 -1
- package/types/rockgen/surface/c7GeologySurface.d.ts +8 -0
- package/types/sky/cloudShadow.d.ts +1 -0
- package/types/styles/index.d.ts +1 -0
- package/types/styles/neutralStylePresets.d.ts +944 -0
- package/types/styles/sceneStyleRuntime.d.ts +20 -0
- package/types/vegetation/branchTree.d.ts +4 -0
- package/types/vegetation/index.d.ts +9 -7
- package/types/vegetation/scatter.d.ts +1 -0
- package/types/vegetation/stylizedTree.d.ts +1 -2
- package/types/vegetation/stylizedTreeFoliage.d.ts +1 -0
- package/types/version.d.ts +1 -1
- package/agents/references/geology-playbook.md +0 -118
- package/agents/skills/claude/karst-cliff-construction/SKILL.md +0 -258
- package/agents/skills/claude/outdoor-world/SKILL.md +0 -492
- package/agents/skills/claude/outdoor-world/references/coastal-landform-formation.md +0 -100
- package/agents/skills/codex/karst-cliff-construction/SKILL.md +0 -258
- package/agents/skills/codex/outdoor-world/SKILL.md +0 -492
- package/agents/skills/codex/outdoor-world/references/coastal-landform-formation.md +0 -100
- package/src/vegetation/legacyTreePresets.js +0 -120
- package/types/vegetation/legacyTreePresets.d.ts +0 -22
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@call-me-sensei/toonlab",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.21",
|
|
4
4
|
"description": "Anime-style game toolkit for Three.js with cel shading, vegetation, rocks, water, sky, clouds, procedural textures, and portable Lab artifacts",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Hyperbond Studio PTE. LTD. (Call Me Sensei)",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "git+https://github.com/call-me-sensei/toonlab.git"
|
|
10
10
|
},
|
|
11
|
-
"homepage": "https://
|
|
11
|
+
"homepage": "https://toonlab.io/docs",
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/call-me-sensei/toonlab/issues"
|
|
14
14
|
},
|
|
@@ -83,14 +83,22 @@
|
|
|
83
83
|
"src/collisionMetadata.js",
|
|
84
84
|
"types",
|
|
85
85
|
"mcp",
|
|
86
|
-
"cli",
|
|
86
|
+
"cli/toonlab.mjs",
|
|
87
87
|
"database",
|
|
88
|
-
"agents/references",
|
|
88
|
+
"agents/references/anime-art-direction.md",
|
|
89
|
+
"agents/references/asset-sourcing-policy.md",
|
|
90
|
+
"agents/references/custom-gap-report.md",
|
|
91
|
+
"agents/references/mcp-asset-discovery.md",
|
|
92
|
+
"agents/references/runtime-entry-points.md",
|
|
93
|
+
"agents/references/style-bundles.md",
|
|
94
|
+
"agents/README.md",
|
|
95
|
+
"agents/PROMPTS.md",
|
|
96
|
+
"agents/claude/CLAUDE.md",
|
|
97
|
+
"agents/codex/AGENTS.md",
|
|
98
|
+
"agents/cursor/toonlab.mdc",
|
|
89
99
|
"agents/skills/claude/asset-sourcing",
|
|
90
100
|
"agents/skills/claude/environment",
|
|
91
101
|
"agents/skills/claude/game-dev",
|
|
92
|
-
"agents/skills/claude/karst-cliff-construction",
|
|
93
|
-
"agents/skills/claude/outdoor-world",
|
|
94
102
|
"agents/skills/claude/post-processing",
|
|
95
103
|
"agents/skills/claude/rock-ground-shaders",
|
|
96
104
|
"agents/skills/claude/rockgen",
|
|
@@ -103,8 +111,6 @@
|
|
|
103
111
|
"agents/skills/codex/asset-sourcing",
|
|
104
112
|
"agents/skills/codex/environment",
|
|
105
113
|
"agents/skills/codex/game-dev",
|
|
106
|
-
"agents/skills/codex/karst-cliff-construction",
|
|
107
|
-
"agents/skills/codex/outdoor-world",
|
|
108
114
|
"agents/skills/codex/post-processing",
|
|
109
115
|
"agents/skills/codex/rock-ground-shaders",
|
|
110
116
|
"agents/skills/codex/rockgen",
|
|
@@ -117,6 +123,7 @@
|
|
|
117
123
|
"scripts/setup-local.mjs",
|
|
118
124
|
"scripts/generate-catalog-seed.mjs",
|
|
119
125
|
"compose.yaml",
|
|
126
|
+
"NPM-LIBRARY.md",
|
|
120
127
|
"README.md",
|
|
121
128
|
"LICENSE"
|
|
122
129
|
],
|
|
@@ -272,8 +279,20 @@
|
|
|
272
279
|
"golden:compare": "node scripts/compare-golden-captures.mjs",
|
|
273
280
|
"verify:rockgen": "node scripts/verify-rockgen.mjs",
|
|
274
281
|
"verify:rock-generation-lab": "node scripts/verify-rock-generation-lab.mjs",
|
|
282
|
+
"verify:rock-c7-library": "node scripts/verify-rock-c7-surface-library.mjs",
|
|
275
283
|
"verify:rock-lab-lifecycle": "node scripts/verify-rock-lab-lifecycle.mjs",
|
|
276
284
|
"verify:rock-shader": "node scripts/verify-rock-shader.mjs",
|
|
285
|
+
"verify:rock-regions": "node scripts/verify-rock-regions.mjs",
|
|
286
|
+
"verify:rock-region-compiler": "node scripts/verify-rock-region-compiler.mjs",
|
|
287
|
+
"verify:rock-region-migrations": "node scripts/verify-rock-region-schema-migrations.mjs",
|
|
288
|
+
"verify:rock-regions-package": "node scripts/verify-packaged-rock-regions.mjs",
|
|
289
|
+
"verify:rock-regions-clean-consumer": "node scripts/verify-rock-region-clean-consumer.mjs",
|
|
290
|
+
"verify:rock-semantic-materials": "node scripts/verify-rock-semantic-materials.mjs",
|
|
291
|
+
"verify:rock-geology-v2-ontology": "node scripts/verify-rock-geology-v2-ontology.mjs",
|
|
292
|
+
"verify:rock-geology-v2-morphology": "node scripts/verify-rock-geology-v2-morphology.mjs",
|
|
293
|
+
"verify:rock-geology-v2-inspirations": "node scripts/verify-rock-geology-v2-inspirations.mjs",
|
|
294
|
+
"verify:rock-geology-v2-canonical-pilot": "node scripts/verify-rock-geology-v2-canonical-pilot.mjs",
|
|
295
|
+
"capture:rock-geology-v2-canonical-pilot": "node scripts/capture-rock-geology-v2-canonical-pilot.mjs",
|
|
277
296
|
"verify:ground-shader": "node scripts/verify-ground-shader.mjs",
|
|
278
297
|
"verify:pathgen": "node scripts/verify-pathgen.mjs",
|
|
279
298
|
"visual:check": "node scripts/visual-check.mjs",
|
|
@@ -295,6 +314,7 @@
|
|
|
295
314
|
"verify:localization": "node scripts/verify-localization.mjs",
|
|
296
315
|
"verify:lab-asset-provenance": "node scripts/verify-lab-asset-provenance.mjs",
|
|
297
316
|
"verify:lab-preview": "node scripts/verify-lab-preview.mjs",
|
|
317
|
+
"verify:library-preview": "node scripts/verify-library-preview-coverage.mjs",
|
|
298
318
|
"verify:public-labs": "node scripts/verify-public-lab-authoring.mjs",
|
|
299
319
|
"verify:vegetation-labs": "node scripts/verify-vegetation-lab-readiness.mjs",
|
|
300
320
|
"verify:water-lab": "node scripts/verify-water-lab-contract.mjs",
|
|
@@ -327,7 +347,6 @@
|
|
|
327
347
|
"verify:quality-integration": "node scripts/verify-quality-budget-integration.mjs && node scripts/verify-packaged-quality-budget-integration.mjs",
|
|
328
348
|
"verify:vegetation-shader": "node scripts/verify-vegetation-shader.mjs",
|
|
329
349
|
"verify:lab-styles": "node scripts/verify-lab-style-boundaries.mjs",
|
|
330
|
-
"verify:legacy-trees": "node scripts/verify-legacy-trees.mjs",
|
|
331
350
|
"verify:branch-tree": "node scripts/verify-branch-tree.mjs",
|
|
332
351
|
"verify:tree-doodle": "node scripts/verify-tree-doodle.mjs",
|
|
333
352
|
"verify:tree-woody": "node scripts/verify-tree-woody-engine.mjs",
|
|
@@ -345,7 +364,7 @@
|
|
|
345
364
|
"verify:collision-package": "node scripts/verify-packaged-world-collision.mjs",
|
|
346
365
|
"verify:official-catalog": "node scripts/verify-official-catalog-provider.mjs && node scripts/verify-official-catalog-runtime.mjs && node scripts/verify-official-catalog-lod.mjs && node scripts/verify-official-catalog-placement.mjs && node scripts/verify-playground-official-catalog.mjs && node scripts/verify-packaged-official-catalog.mjs",
|
|
347
366
|
"verify:system-style": "node scripts/verify-system-style-policy.mjs",
|
|
348
|
-
"verify:release": "npm run verify:system-style && npm run verify:style-contract && npm run verify:style-labels-package && npm run verify:collision-package && npm run verify:scene-collision && npm run verify:official-catalog && npm run verify:public-labs && npm run verify:live-labs && npm run verify:rock-shader && npm run verify:rock-generation-lab && npm run verify:rock-lab-lifecycle && npm run verify:ground-shader && npm run verify:manufactured-materials && npm run verify:material-labeling:agent && npm run verify:material-labeling:mcp && npm run verify:water-lab && npm run verify:water && npm run verify:vegetation-labs && npm run verify:texgen && npm run verify:cloud-shader && npm run verify:sky-cloud-lab && npm run verify:world-quality && npm run verify:scatter && npm run verify:
|
|
367
|
+
"verify:release": "npm run verify:system-style && npm run verify:style-contract && npm run verify:style-labels-package && npm run verify:collision-package && npm run verify:scene-collision && npm run verify:official-catalog && npm run verify:public-labs && npm run verify:live-labs && npm run verify:rock-shader && npm run verify:rock-generation-lab && npm run verify:rock-lab-lifecycle && npm run verify:ground-shader && npm run verify:manufactured-materials && npm run verify:material-labeling:agent && npm run verify:material-labeling:mcp && npm run verify:water-lab && npm run verify:water && npm run verify:vegetation-labs && npm run verify:texgen && npm run verify:cloud-shader && npm run verify:sky-cloud-lab && npm run verify:world-quality && npm run verify:scatter && npm run verify:branch-tree && npm run verify:grass-package && npm run verify:character-package && npm run verify:locomotion-state && npm run verify:ground-stabilizer && npm run verify:water-interaction && npm run verify:walkable-character && npm run verify:playground-walkable && npm run verify:renderer-configuration && npm run verify:scene-scheduler && npm run verify:scene-surface && npm run verify:shared-shadow-receivers && npm run verify:scene-quality && npm run verify:quality-integration && npm run verify:lab-asset-provenance && npm run verify:api && npm run verify:types && npm run verify:mcp && npm run verify:skills && npm run verify:docs && npm run verify:local-database && npm run verify:package",
|
|
349
368
|
"verify:generation": "node scripts/verify-generation.mjs",
|
|
350
369
|
"verify:post": "node scripts/verify-post.mjs",
|
|
351
370
|
"verify:camera": "node scripts/verify-camera.mjs",
|
|
@@ -354,13 +373,14 @@
|
|
|
354
373
|
"verify:soundscape": "node scripts/verify-soundscape.mjs",
|
|
355
374
|
"verify:game-feel": "node scripts/verify-game-feel.mjs",
|
|
356
375
|
"verify:world-quality": "node scripts/verify-world-quality.mjs",
|
|
357
|
-
"verify:all": "npm run verify:system-style && npm run verify:product-namespace && npm run verify:generation && npm run verify:post && npm run verify:camera && npm run verify:motion && npm run verify:biome && npm run verify:soundscape && npm run verify:game-feel && npm run verify:world-quality && npm run verify:rock-shader && npm run verify:ground-shader && npm run verify:rockgen && npm run verify:rock-generation-lab && npm run verify:pathgen && npm run verify:propgen && npm run verify:buildinggen && npm run verify:villagegen && npm run verify:fauna && npm run verify:ambientfx && npm run verify:vfxgen && npm run verify:water && npm run verify:landscape && npm run verify:lighting && npm run verify:lighting-lab && npm run verify:live-labs && npm run verify:lab-preview && npm run verify:weather && npm run verify:climate && npm run verify:atmospheric-condition-lab && npm run verify:sky && npm run verify:cloud-shader && npm run verify:sky-cloud-lab && npm run verify:environment-cycle && npm run verify:manufactured-materials && npm run verify:ground-field && npm run verify:grass && npm run verify:grass-package && npm run verify:character-package && npm run verify:vegetation-shader && npm run verify:lab-styles && npm run verify:scatter && npm run verify:
|
|
376
|
+
"verify:all": "npm run verify:system-style && npm run verify:product-namespace && npm run verify:generation && npm run verify:post && npm run verify:camera && npm run verify:motion && npm run verify:biome && npm run verify:soundscape && npm run verify:game-feel && npm run verify:world-quality && npm run verify:rock-shader && npm run verify:ground-shader && npm run verify:rockgen && npm run verify:rock-generation-lab && npm run verify:pathgen && npm run verify:propgen && npm run verify:buildinggen && npm run verify:villagegen && npm run verify:fauna && npm run verify:ambientfx && npm run verify:vfxgen && npm run verify:water && npm run verify:landscape && npm run verify:lighting && npm run verify:lighting-lab && npm run verify:live-labs && npm run verify:lab-preview && npm run verify:weather && npm run verify:climate && npm run verify:atmospheric-condition-lab && npm run verify:sky && npm run verify:cloud-shader && npm run verify:sky-cloud-lab && npm run verify:environment-cycle && npm run verify:manufactured-materials && npm run verify:ground-field && npm run verify:grass && npm run verify:grass-package && npm run verify:character-package && npm run verify:vegetation-shader && npm run verify:lab-styles && npm run verify:scatter && npm run verify:branch-tree && npm run verify:collision-package && npm run verify:scene-collision && npm run verify:official-catalog && npm run verify:api && npm run verify:package && npm run verify:catalog && npm run verify:assetlib && npm run verify:mcp && npm run verify:local-generation",
|
|
358
377
|
"verify:catalog": "node scripts/verify-catalog.mjs",
|
|
359
378
|
"verify:assetlib": "node scripts/verify-assetlib.mjs",
|
|
360
379
|
"verify:mcp": "node scripts/verify-mcp.mjs",
|
|
361
380
|
"verify:local-database": "node scripts/verify-local-database.mjs",
|
|
381
|
+
"verify:creation-revisions": "node scripts/verify-creation-revisions.mjs",
|
|
362
382
|
"verify:gallery-database": "node scripts/verify-official-gallery-database.mjs",
|
|
363
|
-
"verify:local-generation": "node scripts/verify-local-generation.mjs",
|
|
383
|
+
"verify:local-generation": "node scripts/verify-local-generation.mjs && node scripts/verify-fal-patina.mjs",
|
|
364
384
|
"thumbs:prop": "node scripts/generate-prop-thumbs.mjs",
|
|
365
385
|
"thumbs:building": "node scripts/generate-building-thumbs.mjs",
|
|
366
386
|
"thumbs:catalog": "node scripts/generate-catalog-thumbs.mjs",
|
|
@@ -73,6 +73,22 @@ function assertContentType(value, label) {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
function assertPublicHttpsUrl(value, label, { optional = false } = {}) {
|
|
77
|
+
if (optional && !value) return;
|
|
78
|
+
let url;
|
|
79
|
+
try {
|
|
80
|
+
url = new URL(String(value ?? ''));
|
|
81
|
+
} catch {
|
|
82
|
+
throw new Error(`${label}: must be public HTTPS`);
|
|
83
|
+
}
|
|
84
|
+
if (
|
|
85
|
+
url.protocol !== 'https:'
|
|
86
|
+
|| url.username
|
|
87
|
+
|| url.password
|
|
88
|
+
|| /[?&](?:sign|signature|token|X-Amz-Credential|X-Amz-Signature)=/i.test(url.search)
|
|
89
|
+
) throw new Error(`${label}: must be public HTTPS`);
|
|
90
|
+
}
|
|
91
|
+
|
|
76
92
|
function assertReview(asset, policy) {
|
|
77
93
|
const review = asset.licenseReview;
|
|
78
94
|
if (!review || typeof review !== 'object') throw new Error(`${asset.id}: licenseReview is required`);
|
|
@@ -110,7 +126,7 @@ function assertRockDimensions(asset) {
|
|
|
110
126
|
function assertAsset(asset, release, baseUrl) {
|
|
111
127
|
const required = [
|
|
112
128
|
'id', 'source', 'kind', 'name', 'license', 'licenseUrl', 'downloadUrl',
|
|
113
|
-
'
|
|
129
|
+
'redistributionScope', 'reviewedAt',
|
|
114
130
|
];
|
|
115
131
|
for (const field of required) {
|
|
116
132
|
if (asset[field] == null || asset[field] === '') throw new Error(`${asset.id ?? 'asset'}: ${field} is required`);
|
|
@@ -130,13 +146,24 @@ function assertAsset(asset, release, baseUrl) {
|
|
|
130
146
|
if (asset.attributionRequired && !String(asset.attribution ?? '').trim()) {
|
|
131
147
|
throw new Error(`${asset.id}: attribution text is required`);
|
|
132
148
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
149
|
+
const externalOnly = asset.redistributionScope === 'external-only';
|
|
150
|
+
if (!externalOnly) {
|
|
151
|
+
assertSha(asset.sha256, asset.id);
|
|
152
|
+
assertBytes(asset.byteSize, asset.id);
|
|
153
|
+
assertContentType(asset.contentType, asset.id);
|
|
154
|
+
} else if (asset.sha256 != null || asset.byteSize != null || asset.contentType != null) {
|
|
155
|
+
throw new Error(`${asset.id}: external-only assets must not claim release-owned integrity metadata`);
|
|
156
|
+
}
|
|
136
157
|
assertRockDimensions(asset);
|
|
158
|
+
assertPublicHttpsUrl(asset.sourceUrl, `${asset.id}: sourceUrl`);
|
|
137
159
|
const prefix = immutablePrefix(baseUrl, release, asset.id);
|
|
138
|
-
|
|
139
|
-
|
|
160
|
+
if (externalOnly) {
|
|
161
|
+
assertPublicHttpsUrl(asset.downloadUrl, `${asset.id}: external-only downloadUrl`);
|
|
162
|
+
assertPublicHttpsUrl(asset.thumbnailUrl, `${asset.id}: external-only thumbnailUrl`, { optional: true });
|
|
163
|
+
} else {
|
|
164
|
+
assertUrl(asset.downloadUrl, prefix, `${asset.id}: downloadUrl`);
|
|
165
|
+
assertUrl(asset.thumbnailUrl, prefix, `${asset.id}: thumbnailUrl`, { optional: true });
|
|
166
|
+
}
|
|
140
167
|
const paths = new Set();
|
|
141
168
|
const files = asset.files ?? [];
|
|
142
169
|
if (!Array.isArray(files)) throw new Error(`${asset.id}: files must be an array`);
|
|
@@ -209,7 +236,7 @@ const rows = manifest.assets.map((asset) => `(
|
|
|
209
236
|
${sqlString(asset.sourceId)}, ${sqlString(asset.kind)}, ${sqlString(asset.name)},
|
|
210
237
|
${sqlString(asset.description)}, ${sqlString(asset.license)}, ${sqlString(asset.attribution)},
|
|
211
238
|
${sqlString(asset.sourceUrl)}, ${sqlString(asset.downloadUrl)}, ${sqlString(asset.thumbnailUrl)},
|
|
212
|
-
${sqlString(asset.sha256
|
|
239
|
+
${sqlString(asset.sha256?.toLowerCase())}, ${asset.byteSize == null ? 'null' : Number(asset.byteSize)}, ${sqlString(asset.contentType)},
|
|
213
240
|
${sqlArray(asset.tags)}, ${sqlString(JSON.stringify(asset.metadata ?? {}))}::jsonb,
|
|
214
241
|
${sqlString(asset.licenseUrl)}, ${asset.attributionRequired === true},
|
|
215
242
|
${sqlString(asset.redistributionScope)}, ${sqlString(asset.reviewedAt)}, 'active'
|
|
@@ -7,10 +7,35 @@ export const APPROVED_CATALOG_SPDX_LICENSES = Object.freeze([
|
|
|
7
7
|
'MIT',
|
|
8
8
|
]);
|
|
9
9
|
|
|
10
|
-
// Custom terms are deny-by-default.
|
|
11
|
-
//
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
// Custom terms are deny-by-default. Every shipped entry needs a checked-in,
|
|
11
|
+
// authoritative approval URL and explicit redistribution scopes.
|
|
12
|
+
export const REVIEWED_CATALOG_LICENSES = Object.freeze({
|
|
13
|
+
'LicenseRef-PLATEAU-PDL-1.0': Object.freeze({
|
|
14
|
+
approvalEvidence: 'https://www.mlit.go.jp/plateau/site-policy/',
|
|
15
|
+
approved: true,
|
|
16
|
+
canonicalUrl: 'https://www.mlit.go.jp/plateau/site-policy/',
|
|
17
|
+
extractedFileRedistribution: true,
|
|
18
|
+
id: 'LicenseRef-PLATEAU-PDL-1.0',
|
|
19
|
+
originalArchiveRedistribution: true,
|
|
20
|
+
spdx: false,
|
|
21
|
+
status: 'approved',
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Classification for a license field that carries no value.
|
|
27
|
+
*
|
|
28
|
+
* First-party ToonLab catalog records ship `license: null` — there is no
|
|
29
|
+
* third-party grant to record because the asset is owned. That is a different
|
|
30
|
+
* situation from a third-party asset whose license is unknown, and collapsing
|
|
31
|
+
* the two produced a misleading failure: every first-party rock record hit
|
|
32
|
+
* "(missing license) is not in the reviewed-license registry", which reads as
|
|
33
|
+
* an unreviewed third-party license rather than an absent field.
|
|
34
|
+
*
|
|
35
|
+
* Absence is still never an approval. It is reported as its own state so a
|
|
36
|
+
* caller can decide, and so the error text points at the real problem.
|
|
37
|
+
*/
|
|
38
|
+
export const CATALOG_LICENSE_UNSPECIFIED = 'unspecified';
|
|
14
39
|
|
|
15
40
|
export function resolveCatalogLicense(id) {
|
|
16
41
|
const licenseId = String(id ?? '').trim();
|
|
@@ -27,9 +52,42 @@ export function resolveCatalogLicense(id) {
|
|
|
27
52
|
return REVIEWED_CATALOG_LICENSES[licenseId] ?? null;
|
|
28
53
|
}
|
|
29
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Classifies a license id without throwing, so callers can branch on the
|
|
57
|
+
* outcome instead of using exceptions for control flow.
|
|
58
|
+
*
|
|
59
|
+
* `not-approved` is a distinct state from `unrecognized`: the registry exists to
|
|
60
|
+
* hold rejected policies too (see the `!policy.approved` branch below), so
|
|
61
|
+
* collapsing "reviewed and refused" into "approved" would let a caller that
|
|
62
|
+
* branches on the state ship an asset the review explicitly rejected.
|
|
63
|
+
*
|
|
64
|
+
* @returns {{state: 'approved'|'not-approved'|'unspecified'|'unrecognized', id: string|null, policy: object|null}}
|
|
65
|
+
*/
|
|
66
|
+
export function describeCatalogLicense(id) {
|
|
67
|
+
const licenseId = String(id ?? '').trim();
|
|
68
|
+
if (!licenseId) {
|
|
69
|
+
return { id: null, policy: null, state: CATALOG_LICENSE_UNSPECIFIED };
|
|
70
|
+
}
|
|
71
|
+
const policy = resolveCatalogLicense(licenseId);
|
|
72
|
+
if (!policy) return { id: licenseId, policy: null, state: 'unrecognized' };
|
|
73
|
+
return {
|
|
74
|
+
id: licenseId,
|
|
75
|
+
policy,
|
|
76
|
+
state: policy.approved ? 'approved' : 'not-approved',
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
30
80
|
export function assertCatalogLicenseRelease({ id, redistributionScope, evidence } = {}) {
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
81
|
+
const described = describeCatalogLicense(id);
|
|
82
|
+
if (described.state === CATALOG_LICENSE_UNSPECIFIED) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
'catalog license is unspecified: the record carries no license value. '
|
|
85
|
+
+ 'First-party assets still need an explicit license id before release — '
|
|
86
|
+
+ 'absence is not an approval.',
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const policy = described.policy;
|
|
90
|
+
if (!policy) throw new Error(`${id} is not in the reviewed-license registry`);
|
|
33
91
|
if (!policy.approved) throw new Error(`${id}: reviewed-license policy is ${policy.status ?? 'not approved'}`);
|
|
34
92
|
if (redistributionScope !== 'external-only' && !policy.originalArchiveRedistribution) {
|
|
35
93
|
throw new Error(`${id}: original-archive redistribution is not approved`);
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import * as THREE from 'three';
|
|
8
8
|
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
|
|
9
9
|
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
10
|
+
import { KTX2Loader } from 'three/examples/jsm/loaders/KTX2Loader.js';
|
|
10
11
|
|
|
11
12
|
import { readZipEntries } from './zip.js';
|
|
12
13
|
|
|
@@ -17,7 +18,13 @@ import { readZipEntries } from './zip.js';
|
|
|
17
18
|
* LoadingManager URL modifier rewrites each request by suffix match, which
|
|
18
19
|
* also covers URL-encoded variants.
|
|
19
20
|
*/
|
|
20
|
-
export async function loadImportedModel({
|
|
21
|
+
export async function loadImportedModel({
|
|
22
|
+
url,
|
|
23
|
+
resources = {},
|
|
24
|
+
dracoDecoderPath = '/draco/gltf/',
|
|
25
|
+
ktx2TranscoderPath = '/basis/',
|
|
26
|
+
renderer = null,
|
|
27
|
+
}) {
|
|
21
28
|
const manager = new THREE.LoadingManager();
|
|
22
29
|
const byPath = Object.entries(resources);
|
|
23
30
|
manager.setURLModifier((requested) => {
|
|
@@ -32,11 +39,18 @@ export async function loadImportedModel({ url, resources = {}, dracoDecoderPath
|
|
|
32
39
|
const dracoLoader = new DRACOLoader(manager);
|
|
33
40
|
dracoLoader.setDecoderPath(dracoDecoderPath);
|
|
34
41
|
loader.setDRACOLoader(dracoLoader);
|
|
42
|
+
const ktx2Loader = renderer
|
|
43
|
+
? new KTX2Loader(manager)
|
|
44
|
+
.setTranscoderPath(ktx2TranscoderPath)
|
|
45
|
+
.detectSupport(renderer)
|
|
46
|
+
: null;
|
|
47
|
+
if (ktx2Loader) loader.setKTX2Loader(ktx2Loader);
|
|
35
48
|
let gltf;
|
|
36
49
|
try {
|
|
37
50
|
gltf = await loader.loadAsync(url);
|
|
38
51
|
} finally {
|
|
39
52
|
dracoLoader.dispose();
|
|
53
|
+
ktx2Loader?.dispose();
|
|
40
54
|
}
|
|
41
55
|
const object = gltf.scene ?? gltf.scenes?.[0];
|
|
42
56
|
if (!object) throw new Error('loadImportedModel: glTF contained no scene.');
|
|
@@ -8,6 +8,11 @@ import {
|
|
|
8
8
|
setObjectTextureColorSpaces,
|
|
9
9
|
waitForObjectTextures,
|
|
10
10
|
} from '../toon/toonMaterialAdapter.js';
|
|
11
|
+
import {
|
|
12
|
+
copyRockSemanticMaterialBinding,
|
|
13
|
+
prepareRockSemanticMaterialBindings,
|
|
14
|
+
rockSemanticMaterialTextures,
|
|
15
|
+
} from '../rock-shader/rockSemanticMaterialRuntime.js';
|
|
11
16
|
import { OFFICIAL_CATALOG_ASSET_VERSION } from './officialCatalogProvider.js';
|
|
12
17
|
|
|
13
18
|
const rendererPools = new WeakMap();
|
|
@@ -29,6 +34,9 @@ function collectModelResources(root) {
|
|
|
29
34
|
Object.values(material).forEach((value) => {
|
|
30
35
|
if (value?.isTexture) textures.add(value);
|
|
31
36
|
});
|
|
37
|
+
for (const value of Object.values(rockSemanticMaterialTextures(material) ?? {})) {
|
|
38
|
+
if (value?.isTexture) textures.add(value);
|
|
39
|
+
}
|
|
32
40
|
});
|
|
33
41
|
});
|
|
34
42
|
return { geometries, materials, textures };
|
|
@@ -86,7 +94,9 @@ function cloneModelRoot(sourceRoot) {
|
|
|
86
94
|
root.traverse((object) => {
|
|
87
95
|
if (!object.isMesh || !object.material) return;
|
|
88
96
|
const cloneMaterial = (material) => {
|
|
89
|
-
if (!materialClones.has(material))
|
|
97
|
+
if (!materialClones.has(material)) {
|
|
98
|
+
materialClones.set(material, copyRockSemanticMaterialBinding(material, material.clone()));
|
|
99
|
+
}
|
|
90
100
|
return materialClones.get(material);
|
|
91
101
|
};
|
|
92
102
|
object.material = Array.isArray(object.material)
|
|
@@ -115,9 +125,10 @@ function releaseInstanceRoot(root, sourceResources) {
|
|
|
115
125
|
});
|
|
116
126
|
}
|
|
117
127
|
|
|
118
|
-
async function defaultPrepareTextures(root) {
|
|
128
|
+
async function defaultPrepareTextures(root, asset) {
|
|
119
129
|
await waitForObjectTextures(root);
|
|
120
130
|
setObjectTextureColorSpaces(root);
|
|
131
|
+
await prepareRockSemanticMaterialBindings(root, { modelUrl: asset?.modelUrl });
|
|
121
132
|
}
|
|
122
133
|
|
|
123
134
|
/**
|
|
@@ -57,10 +57,16 @@ export function selectCatalogLodLevel({
|
|
|
57
57
|
distance,
|
|
58
58
|
distances,
|
|
59
59
|
maxLevel = Number.POSITIVE_INFINITY,
|
|
60
|
+
currentLevel = null,
|
|
61
|
+
hysteresis = 0,
|
|
60
62
|
} = {}) {
|
|
61
63
|
const available = [...new Set((availableLevels ?? []).map(finiteLevel).filter((v) => v !== null))]
|
|
62
64
|
.sort((left, right) => left - right);
|
|
63
65
|
if (available.length === 0) return null;
|
|
66
|
+
const margin = Number(hysteresis);
|
|
67
|
+
if (!Number.isFinite(margin) || margin < 0 || margin >= 0.5) {
|
|
68
|
+
throw new RangeError('Catalog LOD hysteresis must be a finite ratio in [0, 0.5).');
|
|
69
|
+
}
|
|
64
70
|
const thresholds = normalizeCatalogLodDistances(distances, available.at(-1) + 1);
|
|
65
71
|
const finiteDistance = Math.max(Number(distance) || 0, 0);
|
|
66
72
|
let requested = 0;
|
|
@@ -69,7 +75,26 @@ export function selectCatalogLodLevel({
|
|
|
69
75
|
}
|
|
70
76
|
requested = Math.min(requested, Math.max(Number(maxLevel) || 0, 0));
|
|
71
77
|
const lower = available.filter((level) => level <= requested).at(-1);
|
|
72
|
-
|
|
78
|
+
const selected = lower ?? available[0];
|
|
79
|
+
|
|
80
|
+
const current = finiteLevel(currentLevel);
|
|
81
|
+
if (current === null || !available.includes(current) || margin === 0 || selected === current) {
|
|
82
|
+
return selected;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (selected > current) {
|
|
86
|
+
// Do not leave the current LOD until the first available coarser level's
|
|
87
|
+
// threshold plus its margin has been crossed. This also handles authored
|
|
88
|
+
// groups with deliberately missing intermediate levels.
|
|
89
|
+
const next = available.find((level) => level > current && level <= selected);
|
|
90
|
+
if (next !== undefined && finiteDistance < thresholds[next] * (1 + margin)) return current;
|
|
91
|
+
} else {
|
|
92
|
+
// Hold the current LOD while the camera jitters inside the lower half of
|
|
93
|
+
// the transition band. Crossing it permits the normal deterministic
|
|
94
|
+
// selector to choose one or several finer levels.
|
|
95
|
+
if (finiteDistance >= thresholds[current] * (1 - margin)) return current;
|
|
96
|
+
}
|
|
97
|
+
return selected;
|
|
73
98
|
}
|
|
74
99
|
|
|
75
100
|
/**
|
|
@@ -79,6 +104,7 @@ export function selectCatalogLodLevel({
|
|
|
79
104
|
*/
|
|
80
105
|
export function createCatalogLodRuntime(root, {
|
|
81
106
|
distances = [0, 45, 120],
|
|
107
|
+
hysteresis = 0,
|
|
82
108
|
maxLevel = Number.POSITIVE_INFINITY,
|
|
83
109
|
namePattern = DEFAULT_LOD_NAME_PATTERN,
|
|
84
110
|
} = {}) {
|
|
@@ -87,6 +113,10 @@ export function createCatalogLodRuntime(root, {
|
|
|
87
113
|
[...new Set(bindings.map((binding) => binding.level))].sort((a, b) => a - b),
|
|
88
114
|
);
|
|
89
115
|
const thresholds = normalizeCatalogLodDistances(distances, availableLevels.at(-1) + 1);
|
|
116
|
+
const hysteresisRatio = Number(hysteresis);
|
|
117
|
+
if (!Number.isFinite(hysteresisRatio) || hysteresisRatio < 0 || hysteresisRatio >= 0.5) {
|
|
118
|
+
throw new RangeError('Catalog LOD hysteresis must be a finite ratio in [0, 0.5).');
|
|
119
|
+
}
|
|
90
120
|
const worldPosition = new Vector3();
|
|
91
121
|
const worldScale = new Vector3();
|
|
92
122
|
let currentLevel = null;
|
|
@@ -124,6 +154,8 @@ export function createCatalogLodRuntime(root, {
|
|
|
124
154
|
availableLevels,
|
|
125
155
|
distance,
|
|
126
156
|
distances: thresholds,
|
|
157
|
+
currentLevel,
|
|
158
|
+
hysteresis: hysteresisRatio,
|
|
127
159
|
maxLevel,
|
|
128
160
|
});
|
|
129
161
|
const changed = setLevel(level);
|
|
@@ -145,6 +177,7 @@ export function createCatalogLodRuntime(root, {
|
|
|
145
177
|
get level() { return currentLevel; },
|
|
146
178
|
setLevel,
|
|
147
179
|
thresholds,
|
|
180
|
+
hysteresis: hysteresisRatio,
|
|
148
181
|
update,
|
|
149
182
|
});
|
|
150
183
|
}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
validateCollisionMetadata,
|
|
8
8
|
} from '../collisionMetadata.js';
|
|
9
9
|
import { applyRockShader } from '../rock-shader/rockShaderRuntime.js';
|
|
10
|
+
import { withoutDegenerateDetailMaps } from '../rock-shader/rockTextureIntegrity.js';
|
|
10
11
|
import {
|
|
11
12
|
applyStyleBundle,
|
|
12
13
|
} from '../styles/styleApplication.js';
|
|
@@ -38,28 +39,37 @@ function rotation3(value) {
|
|
|
38
39
|
return [0, Number(value) || 0, 0];
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Validates caller-supplied style textures without reclassifying the asset's
|
|
44
|
+
* own UV-space maps as triplanar detail maps. applyRockShader reads the full
|
|
45
|
+
* original material directly (albedo, metallic/roughness, normal, occlusion,
|
|
46
|
+
* specular, and emissive); harvesting its normal into `rockNormal` would apply
|
|
47
|
+
* that same map twice and with the wrong coordinate system.
|
|
48
|
+
*/
|
|
49
|
+
function catalogRockTextures(overrides = {}) {
|
|
50
|
+
const usable = withoutDegenerateDetailMaps(overrides);
|
|
51
|
+
return {
|
|
52
|
+
rejected: usable.rejected,
|
|
53
|
+
textures: usable.textures,
|
|
54
|
+
};
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
function styleAdapterFor(asset, root) {
|
|
57
|
+
function styleAdapterFor(asset, root, { textures: overrides, variation }) {
|
|
53
58
|
if (asset.domain !== 'natural.rock') return null;
|
|
54
|
-
const
|
|
59
|
+
const resolved = catalogRockTextures(overrides);
|
|
60
|
+
const report = { rejectedTextures: resolved.rejected };
|
|
55
61
|
return Object.freeze({
|
|
56
62
|
apply(subject, settings) {
|
|
57
|
-
|
|
63
|
+
const result = applyRockShader(subject, settings, {
|
|
58
64
|
name: `ToonLab · ${asset.label}`,
|
|
59
|
-
textures,
|
|
65
|
+
textures: resolved.textures,
|
|
66
|
+
variation,
|
|
60
67
|
});
|
|
68
|
+
report.rejectedTextures = [...resolved.rejected, ...(result.rejectedTextures ?? [])];
|
|
69
|
+
return result;
|
|
61
70
|
},
|
|
62
71
|
id: 'toonlab-official-catalog-rock',
|
|
72
|
+
report,
|
|
63
73
|
});
|
|
64
74
|
}
|
|
65
75
|
|
|
@@ -113,6 +123,8 @@ export async function loadOfficialCatalogAsset({
|
|
|
113
123
|
scale = [1, 1, 1],
|
|
114
124
|
styleBundle,
|
|
115
125
|
targetId = null,
|
|
126
|
+
textures = {},
|
|
127
|
+
variation = 0,
|
|
116
128
|
} = {}) {
|
|
117
129
|
if (!assetRuntime?.acquireAsset) {
|
|
118
130
|
throw new TypeError('loadOfficialCatalogAsset requires an official catalog asset runtime.');
|
|
@@ -136,6 +148,7 @@ export async function loadOfficialCatalogAsset({
|
|
|
136
148
|
container.updateWorldMatrix(true, true);
|
|
137
149
|
|
|
138
150
|
let metadata;
|
|
151
|
+
let adapter = null;
|
|
139
152
|
let style = null;
|
|
140
153
|
let lod = null;
|
|
141
154
|
let collisionRegistration = null;
|
|
@@ -147,7 +160,7 @@ export async function loadOfficialCatalogAsset({
|
|
|
147
160
|
collision: metadata,
|
|
148
161
|
targetId: id,
|
|
149
162
|
}));
|
|
150
|
-
|
|
163
|
+
adapter = styleAdapterFor(asset, root, { textures, variation });
|
|
151
164
|
style = await applyStyleBundle(styleBundle, {
|
|
152
165
|
targets: [createStyleTarget(id, asset.domain, root, { adapter })],
|
|
153
166
|
});
|
|
@@ -209,6 +222,8 @@ export async function loadOfficialCatalogAsset({
|
|
|
209
222
|
normalization,
|
|
210
223
|
object: root,
|
|
211
224
|
quality: qualityProfile,
|
|
225
|
+
/** Detail maps the artifact shipped that were too small to be usable. */
|
|
226
|
+
get rejectedTextures() { return adapter?.report?.rejectedTextures ?? []; },
|
|
212
227
|
async release() {
|
|
213
228
|
if (released) return false;
|
|
214
229
|
released = true;
|
|
@@ -838,6 +838,7 @@ async function loadPackagedLocomotionClipsForTarget(targetMesh, rig, {
|
|
|
838
838
|
retargetMode = 'world',
|
|
839
839
|
rootMotion = false,
|
|
840
840
|
sourceUrl = TOONLAB_MANNEQUIN_ASSET_URL,
|
|
841
|
+
trackNameStyle = 'skeleton',
|
|
841
842
|
} = {}) {
|
|
842
843
|
const sourceAsset = await loadModelAsset(sourceUrl);
|
|
843
844
|
let sourceMesh = null;
|
|
@@ -942,6 +943,7 @@ async function loadPackagedLocomotionClipsForTarget(targetMesh, rig, {
|
|
|
942
943
|
mode: retargetMode,
|
|
943
944
|
rootMotion,
|
|
944
945
|
sourceRig,
|
|
946
|
+
trackNameStyle,
|
|
945
947
|
},
|
|
946
948
|
);
|
|
947
949
|
const clip = spec.filter(retargeted);
|
|
@@ -14,7 +14,12 @@
|
|
|
14
14
|
|
|
15
15
|
// Mixamo bone per VRM humanoid role. Also the source-side chain map used for
|
|
16
16
|
// rest-pose direction corrections lives on these names (MIXAMO_CHAIN_CHILD).
|
|
17
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The humanoid contract itself: role -> canonical Mixamo bone name. Exported so
|
|
19
|
+
* a host can audit a rig against it (which roles resolved, which are missing)
|
|
20
|
+
* rather than inferring the answer from a bone count.
|
|
21
|
+
*/
|
|
22
|
+
export const MIXAMO_BONE_BY_ROLE = Object.freeze({
|
|
18
23
|
hips: 'mixamorigHips',
|
|
19
24
|
spine: 'mixamorigSpine',
|
|
20
25
|
chest: 'mixamorigSpine1',
|