@call-me-sensei/toonlab 0.4.21 → 0.4.23
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 +87 -27
- package/README.md +11 -10
- package/agents/PROMPTS.md +17 -3
- package/agents/README.md +13 -3
- package/agents/claude/CLAUDE.md +3 -0
- package/agents/codex/AGENTS.md +112 -159
- package/agents/cursor/toonlab.mdc +7 -3
- package/agents/examples/game-foundation.mjs +104 -0
- package/agents/references/game-lifecycle.md +57 -0
- package/agents/references/runtime-entry-points.md +26 -0
- package/agents/references/style-bundles.md +4 -1
- package/agents/skills/claude/asset-sourcing/SKILL.md +7 -4
- package/agents/skills/claude/environment/SKILL.md +4 -0
- package/agents/skills/claude/game-dev/SKILL.md +63 -87
- package/agents/skills/claude/lighting/SKILL.md +36 -0
- package/agents/skills/claude/post-processing/SKILL.md +8 -3
- package/agents/skills/claude/rock-ground-shaders/SKILL.md +10 -15
- package/agents/skills/claude/rockgen/SKILL.md +52 -21
- package/agents/skills/claude/scene-style-application/SKILL.md +4 -0
- package/agents/skills/claude/style-presets/SKILL.md +11 -8
- package/agents/skills/claude/toon-shading/SKILL.md +4 -0
- package/agents/skills/claude/vegetation-sky/SKILL.md +13 -7
- package/agents/skills/claude/visual-verification/SKILL.md +45 -119
- package/agents/skills/claude/water/SKILL.md +6 -2
- package/agents/skills/codex/asset-sourcing/SKILL.md +7 -4
- package/agents/skills/codex/environment/SKILL.md +4 -0
- package/agents/skills/codex/game-dev/SKILL.md +63 -87
- package/agents/skills/codex/lighting/SKILL.md +36 -0
- package/agents/skills/codex/post-processing/SKILL.md +8 -3
- package/agents/skills/codex/rock-ground-shaders/SKILL.md +10 -15
- package/agents/skills/codex/rockgen/SKILL.md +52 -21
- package/agents/skills/codex/scene-style-application/SKILL.md +4 -0
- package/agents/skills/codex/style-presets/SKILL.md +11 -8
- package/agents/skills/codex/toon-shading/SKILL.md +4 -0
- package/agents/skills/codex/vegetation-sky/SKILL.md +13 -7
- package/agents/skills/codex/visual-verification/SKILL.md +45 -119
- package/agents/skills/codex/water/SKILL.md +6 -2
- package/database/apply-one-catalog-seed.mjs +63 -0
- package/database/providers.mjs +1 -0
- package/database/seeds/catalog/0005_2026-08-c8-first12.sql +1101 -0
- package/database/seeds/catalog/0006_2026-09-c8-first100.sql +14245 -0
- package/database/seeds/catalog/0007_2026-09-c8-first100-primary-model.sql +14845 -0
- package/mcp/lab-management.mjs +11 -8
- package/mcp/public-catalog.mjs +0 -16
- package/mcp/tree-lab-contract.mjs +2381 -0
- package/mcp/vite-plugin.mjs +5 -1
- package/package.json +29 -18
- package/src/catalog/officialCatalog.js +1 -0
- package/src/catalog/officialCatalogAssetRuntime.js +98 -1
- package/src/catalog/officialCatalogLod.js +160 -16
- package/src/catalog/officialCatalogPlacement.js +52 -12
- package/src/catalog/officialCatalogProvider.js +79 -8
- package/src/catalog/officialCatalogRockPackage.js +172 -0
- package/src/cloud/cloudReprojection.js +40 -7
- package/src/cloud/cloudVolume.js +44 -7
- package/src/cloud/noise/baseShapeVolume.js +8 -6
- package/src/cloud/noise/cirrusMap.js +2 -1
- package/src/cloud/noise/curlNoise.js +2 -1
- package/src/cloud/noise/erosionVolume.js +2 -1
- package/src/cloud/noise/textureCache.js +77 -0
- package/src/cloud/noise/weatherMap.js +2 -1
- package/src/core/sha256.js +83 -0
- package/src/ground-shader/groundShaderMaterial.js +12 -3
- package/src/rock-shader/rockMaterial.js +205 -28
- package/src/rock-shader/rockShaderRuntime.js +17 -1
- package/src/rock-shader/rockShaderSettings.js +16 -0
- package/src/rockgen/index.js +9 -0
- package/src/rockgen/lod/index.js +1 -0
- package/src/rockgen/lod/rockDenseFieldPolicy.js +143 -0
- package/src/rockgen/rockDocument.js +899 -35
- package/src/rockgen/surface/c7GeologySurface.js +39 -9
- package/src/rockgen/surface/naturalRockSurface.js +1948 -0
- package/src/shaders-tsl/chunks/projected-water-caustics.js +14 -2
- package/src/shaders-tsl/chunks/water-foam.js +6 -6
- package/src/shaders-tsl/chunks/water-lighting.js +28 -5
- package/src/shaders-tsl/chunks/water-shore-state.js +5 -2
- package/src/shaders-tsl/chunks/water-waves.js +10 -5
- package/src/shaders-tsl/flower.js +7 -4
- package/src/shaders-tsl/grass.js +4 -3
- package/src/shaders-tsl/water-shore-state-simulation.js +3 -2
- package/src/shaders-tsl/water.js +209 -41
- package/src/sky/skySystem.js +30 -7
- package/src/toon/settings/perspectiveRemovalSettings.js +1 -1
- package/src/toon/toonSettings.js +74 -0
- package/src/vegetation/stylizedFlower.js +6 -0
- package/src/version.js +1 -1
- package/src/water/waterDetailSpectrum.js +91 -0
- package/src/water/waterDynamics.js +422 -0
- package/src/water/waterFoamParticles.js +196 -0
- package/src/water/waterFoamTexture.js +57 -0
- package/src/water/waterHydrodynamics.js +209 -0
- package/src/water/waterMaterial.js +1 -0
- package/src/water/waterRenderExtension.js +107 -0
- package/src/water/waterScenePasses.js +14 -6
- package/src/water/waterSettings.js +53 -26
- package/src/water/waterShoreMaterial.js +38 -14
- package/src/water/waterSpectralOcean.js +137 -0
- package/src/water/waterStageSettings.js +19 -1
- package/src/water/waterSurface.js +62 -18
- package/src/water/waterUnderwaterAtmosphere.js +30 -3
- package/types/catalog/officialCatalog.d.ts +1 -0
- package/types/catalog/officialCatalogPlacement.d.ts +4 -0
- package/types/catalog/officialCatalogRockPackage.d.ts +68 -0
- package/types/cloud/noise/baseShapeVolume.d.ts +1 -1
- package/types/cloud/noise/textureCache.d.ts +12 -0
- package/types/core/sha256.d.ts +2 -0
- package/types/index.d.ts +30 -0
- package/types/rock-shader/rockMaterial.d.ts +3 -0
- package/types/rock-shader/rockShaderSettings.d.ts +2 -0
- package/types/rockgen/index.d.ts +1 -0
- package/types/rockgen/lod/index.d.ts +1 -0
- package/types/rockgen/lod/rockDenseFieldPolicy.d.ts +181 -0
- package/types/rockgen/rockDocument.d.ts +654 -71
- package/types/rockgen/surface/naturalRockSurface.d.ts +100 -0
- package/types/shaders-tsl/chunks/projected-water-caustics.d.ts +7 -1
- package/types/shaders-tsl/chunks/water-lighting.d.ts +4 -3
- package/types/shaders-tsl/water.d.ts +2 -1
- package/types/version.d.ts +1 -1
- package/types/water/waterDetailSpectrum.d.ts +13 -0
- package/types/water/waterDynamics.d.ts +89 -0
- package/types/water/waterFoamParticles.d.ts +32 -0
- package/types/water/waterFoamTexture.d.ts +1 -0
- package/types/water/waterHydrodynamics.d.ts +49 -0
- package/types/water/waterRenderExtension.d.ts +16 -0
- package/types/water/waterScenePasses.d.ts +1 -1
- package/types/water/waterSettings.d.ts +1 -0
- package/types/water/waterSpectralOcean.d.ts +17 -0
- package/types/water/waterStageSettings.d.ts +1 -1
- package/types/water/waterUnderwaterAtmosphere.d.ts +19 -4
package/src/cloud/cloudVolume.js
CHANGED
|
@@ -1003,6 +1003,7 @@ export function marchCloud({
|
|
|
1003
1003
|
moonRadiance,
|
|
1004
1004
|
jitter,
|
|
1005
1005
|
stepConeAngle = float(0.003),
|
|
1006
|
+
surfaceLighting = float(1),
|
|
1006
1007
|
steps = CLOUD_PRIMARY_MARCH_STEPS,
|
|
1007
1008
|
}) {
|
|
1008
1009
|
const scattered = vec3(0).toVar();
|
|
@@ -1027,7 +1028,8 @@ export function marchCloud({
|
|
|
1027
1028
|
weight,
|
|
1028
1029
|
}) => {
|
|
1029
1030
|
If(
|
|
1030
|
-
|
|
1031
|
+
float(surfaceLighting).greaterThan(0).and(surfaceLightSet.lessThan(0.5))
|
|
1032
|
+
.and(weight.greaterThan(CLOUD_SURFACE_LIGHT_MIN_WEIGHT)),
|
|
1031
1033
|
() => {
|
|
1032
1034
|
const radius = CLOUD_SURFACE_NORMAL_RADIUS;
|
|
1033
1035
|
// Use the broad envelope for the lighting normal. Including the full
|
|
@@ -1136,11 +1138,11 @@ export function marchCloud({
|
|
|
1136
1138
|
CLOUD_INTERIOR_COARSEN_FULL,
|
|
1137
1139
|
opticalDepthAccum,
|
|
1138
1140
|
).toVar();
|
|
1139
|
-
const coreStep = mix(
|
|
1141
|
+
const coreStep = min(shell.tEnd.sub(t), mix(
|
|
1140
1142
|
surfaceCoreStep,
|
|
1141
1143
|
effectiveBaseStep.mul(CLOUD_INTERIOR_STEP_SCALE),
|
|
1142
1144
|
interiorOpenness,
|
|
1143
|
-
).toVar();
|
|
1145
|
+
)).toVar();
|
|
1144
1146
|
stepSize.assign(coreStep);
|
|
1145
1147
|
const cheapLight = step(0.3, transmittance).oneMinus().toVar();
|
|
1146
1148
|
const lightOpticalDepth = lightDepthNode(
|
|
@@ -1231,11 +1233,11 @@ export function marchCloud({
|
|
|
1231
1233
|
CLOUD_INTERIOR_COARSEN_FULL,
|
|
1232
1234
|
opticalDepthAccum,
|
|
1233
1235
|
).toVar();
|
|
1234
|
-
const coreStep = mix(
|
|
1236
|
+
const coreStep = min(shell.tEnd.sub(t), mix(
|
|
1235
1237
|
surfaceCoreStep,
|
|
1236
1238
|
effectiveBaseStep.mul(CLOUD_INTERIOR_STEP_SCALE),
|
|
1237
1239
|
interiorOpenness,
|
|
1238
|
-
).toVar();
|
|
1240
|
+
)).toVar();
|
|
1239
1241
|
stepSize.assign(coreStep);
|
|
1240
1242
|
const cheapLight = step(0.3, transmittance).oneMinus().toVar();
|
|
1241
1243
|
const lightOpticalDepth = lightDepthNode(
|
|
@@ -1403,6 +1405,11 @@ export function createCloudVolumeMaterial({
|
|
|
1403
1405
|
const stepConeAngle = uniform(0.003);
|
|
1404
1406
|
const baseShapeResolution = uniform(resolved.baseShape?.image?.width ?? 64);
|
|
1405
1407
|
const rayHitDistance = property('float', 'toonlabCloudRayHitDistance');
|
|
1408
|
+
const opaqueViewDistance = property('float', 'toonlabCloudOpaqueViewDistance');
|
|
1409
|
+
const sceneDepthFallback = new THREE.DataTexture(new Float32Array([65000]), 1, 1, THREE.RedFormat, THREE.FloatType);
|
|
1410
|
+
sceneDepthFallback.needsUpdate = true;
|
|
1411
|
+
const sceneDepthNode = texture(sceneDepthFallback);
|
|
1412
|
+
const sceneDepthEnabled = uniform(0);
|
|
1406
1413
|
// A generated cirrus mask ships with the field; the 1x1 stand-in keeps the
|
|
1407
1414
|
// graph stable when a host explicitly clears it. `cirrusEnabled` is the gate,
|
|
1408
1415
|
// so setCirrusTexture(null) still removes the deck exactly.
|
|
@@ -1482,6 +1489,7 @@ export function createCloudVolumeMaterial({
|
|
|
1482
1489
|
pixelJitter,
|
|
1483
1490
|
rayBasis,
|
|
1484
1491
|
stepConeAngle,
|
|
1492
|
+
sceneDepthEnabled,
|
|
1485
1493
|
};
|
|
1486
1494
|
|
|
1487
1495
|
const material = new NodeMaterial();
|
|
@@ -1502,6 +1510,16 @@ export function createCloudVolumeMaterial({
|
|
|
1502
1510
|
screenUV.y.mul(-2).add(1),
|
|
1503
1511
|
).add(pixelJitter).toVar();
|
|
1504
1512
|
const viewDir = normalize(rayBasis.mul(vec3(ndc, 1))).toVar();
|
|
1513
|
+
opaqueViewDistance.assign(65000);
|
|
1514
|
+
If(sceneDepthEnabled.greaterThan(0.5), () => {
|
|
1515
|
+
const depthUv = vec2(ndc.x.mul(0.5).add(0.5), ndc.y.mul(-0.5).add(0.5));
|
|
1516
|
+
opaqueViewDistance.assign(min(max(sceneDepthNode.sample(depthUv).r, 0), 65000));
|
|
1517
|
+
});
|
|
1518
|
+
const opaqueRayDistance = select(
|
|
1519
|
+
sceneDepthEnabled.greaterThan(0.5).and(opaqueViewDistance.lessThan(64000)),
|
|
1520
|
+
opaqueViewDistance.mul(length(rayBasis.mul(vec3(ndc, 1)))),
|
|
1521
|
+
float(1e20),
|
|
1522
|
+
).toVar();
|
|
1505
1523
|
const cameraAltitude = max(cameraPositionUniform.y.sub(groundLevelUniform), 0.5).toVar();
|
|
1506
1524
|
|
|
1507
1525
|
const baseAltitude = max(shape.altitude, 0).toVar();
|
|
@@ -1509,7 +1527,7 @@ export function createCloudVolumeMaterial({
|
|
|
1509
1527
|
const shell = intersectCloudShell({
|
|
1510
1528
|
baseAltitude,
|
|
1511
1529
|
cameraAltitude,
|
|
1512
|
-
maxDistance: fade.maxMarchDist,
|
|
1530
|
+
maxDistance: min(fade.maxMarchDist, opaqueRayDistance),
|
|
1513
1531
|
topAltitude,
|
|
1514
1532
|
viewDir,
|
|
1515
1533
|
});
|
|
@@ -1530,6 +1548,9 @@ export function createCloudVolumeMaterial({
|
|
|
1530
1548
|
|
|
1531
1549
|
// --- the volumetric march ---------------------------------------------
|
|
1532
1550
|
const march = marchCloud({
|
|
1551
|
+
surfaceLighting: style?.surfaceLight
|
|
1552
|
+
? style.enabled.mul(style.amount).mul(style.surfaceLight.enabled).mul(style.surfaceLight.amount)
|
|
1553
|
+
: float(0),
|
|
1533
1554
|
cameraPosition: cameraPositionUniform,
|
|
1534
1555
|
coarseDensityNode: field.sampleCoarseDensityNode,
|
|
1535
1556
|
cosMoon,
|
|
@@ -1739,6 +1760,7 @@ export function createCloudVolumeMaterial({
|
|
|
1739
1760
|
).rgb.toVar();
|
|
1740
1761
|
|
|
1741
1762
|
// Composite the cirrus/haze deck behind the volumetric layer.
|
|
1763
|
+
deckOpacity.mulAssign(step(deckDistance, opaqueRayDistance));
|
|
1742
1764
|
const deckTerm = deckRadiance.mul(deckOpacity).toVar();
|
|
1743
1765
|
scattered.addAssign(deckTerm.mul(transmittance));
|
|
1744
1766
|
transmittance.mulAssign(deckOpacity.oneMinus());
|
|
@@ -1754,7 +1776,7 @@ export function createCloudVolumeMaterial({
|
|
|
1754
1776
|
// are matched by cloudReprojection's MRT render target.
|
|
1755
1777
|
material.fragmentNode = mrt({
|
|
1756
1778
|
output: cloudColorNode,
|
|
1757
|
-
rayHitDist: vec4(rayHitDistance,
|
|
1779
|
+
rayHitDist: vec4(rayHitDistance, opaqueViewDistance, 0, 1),
|
|
1758
1780
|
});
|
|
1759
1781
|
|
|
1760
1782
|
const rightScratch = new THREE.Vector3();
|
|
@@ -1762,6 +1784,20 @@ export function createCloudVolumeMaterial({
|
|
|
1762
1784
|
const forwardScratch = new THREE.Vector3();
|
|
1763
1785
|
|
|
1764
1786
|
const api = {
|
|
1787
|
+
sceneDepthNode,
|
|
1788
|
+
/** Positive view-space depth (metres) in R; clear to >=65000. Host-owned. */
|
|
1789
|
+
setSceneDepthTexture(map = null) {
|
|
1790
|
+
if (map !== null && (!map?.isTexture || map.isDepthTexture)) {
|
|
1791
|
+
throw new TypeError('Cloud scene depth must be a linear view-distance color texture, not hardware depth.');
|
|
1792
|
+
}
|
|
1793
|
+
if (map && (map.colorSpace !== THREE.NoColorSpace
|
|
1794
|
+
|| ![THREE.FloatType, THREE.HalfFloatType].includes(map.type)
|
|
1795
|
+
|| map.minFilter !== THREE.NearestFilter || map.magFilter !== THREE.NearestFilter)) {
|
|
1796
|
+
throw new TypeError('Cloud scene depth requires a nearest-filtered float texture with NoColorSpace.');
|
|
1797
|
+
}
|
|
1798
|
+
sceneDepthNode.value = map ?? sceneDepthFallback;
|
|
1799
|
+
sceneDepthEnabled.value = map ? 1 : 0;
|
|
1800
|
+
},
|
|
1765
1801
|
material,
|
|
1766
1802
|
uniforms,
|
|
1767
1803
|
marchBudget: budget,
|
|
@@ -1893,6 +1929,7 @@ export function createCloudVolumeMaterial({
|
|
|
1893
1929
|
},
|
|
1894
1930
|
|
|
1895
1931
|
dispose() {
|
|
1932
|
+
sceneDepthFallback.dispose();
|
|
1896
1933
|
cirrusFallback.dispose();
|
|
1897
1934
|
material.dispose();
|
|
1898
1935
|
},
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createCloudTextureCache } from './textureCache.js';
|
|
1
2
|
// Cloud base-shape volume — packed inverted-Worley FBMs.
|
|
2
3
|
//
|
|
3
4
|
// R inverted Worley FBM at 4 / 8 / 16 cells
|
|
@@ -10,8 +11,9 @@
|
|
|
10
11
|
// the Perlin-Worley + separate erosion-volume path used by the discarded lab.
|
|
11
12
|
//
|
|
12
13
|
// The hash has no author seed, so it produces one canonical field. The public
|
|
13
|
-
// seed remains in metadata
|
|
14
|
-
// perturb these bytes.
|
|
14
|
+
// seed remains in uncached bake metadata for document compatibility, but does
|
|
15
|
+
// not perturb these bytes. Cached textures use canonical seed 0 and share one
|
|
16
|
+
// texture and mip chain per resolution, independent of the authored seed.
|
|
15
17
|
//
|
|
16
18
|
// Every band is periodic over the unit tile and the texture wraps on all three
|
|
17
19
|
// axes, so the field continues exactly across a repeat. Sampling is at texel
|
|
@@ -77,7 +79,7 @@ export const CLOUD_BASE_SHAPE_PLAN = Object.freeze({
|
|
|
77
79
|
worleyTexelsPerCell: 1,
|
|
78
80
|
});
|
|
79
81
|
|
|
80
|
-
const volumeCache =
|
|
82
|
+
const volumeCache = createCloudTextureCache('base-shape');
|
|
81
83
|
|
|
82
84
|
// Fixed u32 hash and cellular sampler. These stay local rather than using the
|
|
83
85
|
// seeded noise utilities because the canonical base-shape bake must remain
|
|
@@ -307,7 +309,7 @@ export function createCloudBaseShapeVolume({
|
|
|
307
309
|
}
|
|
308
310
|
|
|
309
311
|
/**
|
|
310
|
-
* Cached base-shape volume, keyed by resolution
|
|
312
|
+
* Cached canonical base-shape volume, keyed by resolution. A tier switch that
|
|
311
313
|
* returns to a resolution already baked pays nothing, and the shadow/env-map
|
|
312
314
|
* bakes share the runtime's volumes.
|
|
313
315
|
*/
|
|
@@ -316,10 +318,10 @@ export function getCloudBaseShapeVolume({
|
|
|
316
318
|
seed = 1,
|
|
317
319
|
} = {}) {
|
|
318
320
|
const size = resolveNoiseDims(dims, CLOUD_BASE_SHAPE_MASTER_DIM);
|
|
319
|
-
const key = noiseVolumeKey(size,
|
|
321
|
+
const key = noiseVolumeKey(size, 0);
|
|
320
322
|
const cached = volumeCache.get(key);
|
|
321
323
|
if (cached) return cached;
|
|
322
|
-
const texture = createCloudBaseShapeVolume({ dims: size, seed });
|
|
324
|
+
const texture = createCloudBaseShapeVolume({ dims: size, seed: 0 });
|
|
323
325
|
volumeCache.set(key, texture);
|
|
324
326
|
return texture;
|
|
325
327
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createCloudTextureCache } from './textureCache.js';
|
|
1
2
|
// Procedural high-cloud mask for the thin cirrus deck.
|
|
2
3
|
//
|
|
3
4
|
// Cirrus is not a shallower version of the lower volumetric layer. Ice-crystal
|
|
@@ -17,7 +18,7 @@ export const CLOUD_CIRRUS_MAP_WIDTH = 1024;
|
|
|
17
18
|
export const CLOUD_CIRRUS_MAP_HEIGHT = 512;
|
|
18
19
|
export const CLOUD_CIRRUS_SEED_NAMESPACE = 'cloud-cirrus';
|
|
19
20
|
|
|
20
|
-
const mapCache =
|
|
21
|
+
const mapCache = createCloudTextureCache('cirrus');
|
|
21
22
|
|
|
22
23
|
function smoothstep01(value) {
|
|
23
24
|
const x = value < 0 ? 0 : value > 1 ? 1 : value;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createCloudTextureCache } from './textureCache.js';
|
|
1
2
|
// Curl noise — the divergence-free vector field that advects cloud edges into
|
|
2
3
|
// wisps. Displacing a sample position by an arbitrary noise vector pumps
|
|
3
4
|
// density in and out of the cloud (the field has sources and sinks), so edges
|
|
@@ -68,7 +69,7 @@ const NORMALIZATION_PROBE_DIMS = 32;
|
|
|
68
69
|
// 15%, resolveNoiseDims' 256³ ceiling included.
|
|
69
70
|
const NORMALIZATION_HEADROOM = 1.15;
|
|
70
71
|
|
|
71
|
-
const volumeCache =
|
|
72
|
+
const volumeCache = createCloudTextureCache('curl');
|
|
72
73
|
|
|
73
74
|
/**
|
|
74
75
|
* Builds the divergence-free field.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createCloudTextureCache } from './textureCache.js';
|
|
1
2
|
// Cloud erosion volume — the high-frequency Worley detail field that carves
|
|
2
3
|
// the cloud silhouette after the base shape has decided where cloud exists.
|
|
3
4
|
//
|
|
@@ -66,7 +67,7 @@ export const CLOUD_EROSION_PLAN = Object.freeze({
|
|
|
66
67
|
texelsPerCell: 2,
|
|
67
68
|
});
|
|
68
69
|
|
|
69
|
-
const volumeCache =
|
|
70
|
+
const volumeCache = createCloudTextureCache('erosion');
|
|
70
71
|
|
|
71
72
|
/**
|
|
72
73
|
* Bakes the RGBA8 erosion field. Returned separately from the texture so
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// Bound cache-owned retention without disposing textures still used by a
|
|
2
|
+
// renderer or host. Weak entries preserve identity for live consumers; the
|
|
3
|
+
// browser can reclaim unreferenced textures after they leave the recent set.
|
|
4
|
+
const recent = new Map();
|
|
5
|
+
const entries = new Map();
|
|
6
|
+
const MAX_BYTES = 64 * 1024 * 1024;
|
|
7
|
+
const MAX_ENTRIES = 32;
|
|
8
|
+
let retainedBytes = 0;
|
|
9
|
+
const finalized = new FinalizationRegistry(({ key, ref }) => {
|
|
10
|
+
if (entries.get(key)?.ref === ref) entries.delete(key);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
function forget(key) {
|
|
14
|
+
const item = recent.get(key);
|
|
15
|
+
if (item) retainedBytes -= item.bytes;
|
|
16
|
+
recent.delete(key);
|
|
17
|
+
const entry = entries.get(key);
|
|
18
|
+
if (entry) finalized.unregister(entry.ref);
|
|
19
|
+
entries.delete(key);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function retain(key, texture, bytes) {
|
|
23
|
+
const previous = recent.get(key);
|
|
24
|
+
if (previous) retainedBytes -= previous.bytes;
|
|
25
|
+
recent.delete(key);
|
|
26
|
+
recent.set(key, { texture, bytes });
|
|
27
|
+
retainedBytes += bytes;
|
|
28
|
+
while (retainedBytes > MAX_BYTES || recent.size > MAX_ENTRIES) {
|
|
29
|
+
const oldest = recent.keys().next().value;
|
|
30
|
+
retainedBytes -= recent.get(oldest).bytes;
|
|
31
|
+
recent.delete(oldest);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function getCloudTextureCacheStats() {
|
|
36
|
+
return { retainedBytes, retainedEntries: recent.size, maxBytes: MAX_BYTES, maxEntries: MAX_ENTRIES };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function createCloudTextureCache(namespace) {
|
|
40
|
+
const prefix = `${namespace}:`;
|
|
41
|
+
return {
|
|
42
|
+
get(key) {
|
|
43
|
+
const id = prefix + key;
|
|
44
|
+
const entry = entries.get(id);
|
|
45
|
+
const texture = entry?.ref.deref();
|
|
46
|
+
if (!texture) { forget(id); return undefined; }
|
|
47
|
+
retain(id, texture, entry.bytes);
|
|
48
|
+
return texture;
|
|
49
|
+
},
|
|
50
|
+
set(key, texture) {
|
|
51
|
+
const id = prefix + key;
|
|
52
|
+
forget(id);
|
|
53
|
+
const levels = texture.userData?.toonlabVolumeMipChain?.levels;
|
|
54
|
+
const bytes = levels
|
|
55
|
+
? levels.reduce((sum, data) => sum + data.byteLength, 0)
|
|
56
|
+
: (texture.image?.data?.byteLength ?? 0);
|
|
57
|
+
const ref = new WeakRef(texture);
|
|
58
|
+
entries.set(id, { ref, bytes });
|
|
59
|
+
finalized.register(texture, { key: id, ref }, ref);
|
|
60
|
+
texture.addEventListener('dispose', () => {
|
|
61
|
+
if (entries.get(id)?.ref === ref) forget(id);
|
|
62
|
+
});
|
|
63
|
+
retain(id, texture, bytes);
|
|
64
|
+
},
|
|
65
|
+
*values() {
|
|
66
|
+
for (const [key, entry] of entries) {
|
|
67
|
+
if (key.startsWith(prefix)) {
|
|
68
|
+
const texture = entry.ref.deref();
|
|
69
|
+
if (texture) yield texture;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
clear() {
|
|
74
|
+
for (const key of entries.keys()) if (key.startsWith(prefix)) forget(key);
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createCloudTextureCache } from './textureCache.js';
|
|
1
2
|
// Weather map — the 2D field that decides *where* cloud exists, before any of
|
|
2
3
|
// the 3D noise decides what shape it takes. CPU FBM, deterministic from a
|
|
3
4
|
// seed, tiling in both axes, regenerated whenever the sky-cloud lab changes the
|
|
@@ -151,7 +152,7 @@ export const WEATHER_MAP_PROFILE_DEFAULTS = Object.freeze(
|
|
|
151
152
|
),
|
|
152
153
|
);
|
|
153
154
|
|
|
154
|
-
const mapCache =
|
|
155
|
+
const mapCache = createCloudTextureCache('weather');
|
|
155
156
|
|
|
156
157
|
/**
|
|
157
158
|
* Normalizes any partial input into a complete WeatherMapProfile: every field
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const SHA256_INITIAL = Object.freeze([
|
|
2
|
+
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
|
3
|
+
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
|
|
4
|
+
]);
|
|
5
|
+
|
|
6
|
+
const SHA256_ROUND = Object.freeze([
|
|
7
|
+
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
|
|
8
|
+
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
|
|
9
|
+
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
|
|
10
|
+
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
|
|
11
|
+
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
|
|
12
|
+
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
|
|
13
|
+
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
|
|
14
|
+
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
function rotateRight(value, amount) {
|
|
18
|
+
return (value >>> amount) | (value << (32 - amount));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function inputBytes(value) {
|
|
22
|
+
if (typeof value === 'string') return new TextEncoder().encode(value);
|
|
23
|
+
if (value instanceof Uint8Array) return value;
|
|
24
|
+
if (ArrayBuffer.isView(value)) {
|
|
25
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
26
|
+
}
|
|
27
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value);
|
|
28
|
+
throw new TypeError('sha256Hex requires a string, ArrayBuffer, or typed-array input.');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Synchronous browser-safe SHA-256 used for portable document identities. */
|
|
32
|
+
export function sha256Hex(value) {
|
|
33
|
+
const source = inputBytes(value);
|
|
34
|
+
const bitLength = BigInt(source.byteLength) * 8n;
|
|
35
|
+
const paddedLength = Math.ceil((source.byteLength + 9) / 64) * 64;
|
|
36
|
+
const bytes = new Uint8Array(paddedLength);
|
|
37
|
+
bytes.set(source);
|
|
38
|
+
bytes[source.byteLength] = 0x80;
|
|
39
|
+
const view = new DataView(bytes.buffer);
|
|
40
|
+
view.setUint32(paddedLength - 8, Number((bitLength >> 32n) & 0xffffffffn), false);
|
|
41
|
+
view.setUint32(paddedLength - 4, Number(bitLength & 0xffffffffn), false);
|
|
42
|
+
|
|
43
|
+
const state = [...SHA256_INITIAL];
|
|
44
|
+
const words = new Uint32Array(64);
|
|
45
|
+
for (let offset = 0; offset < paddedLength; offset += 64) {
|
|
46
|
+
for (let index = 0; index < 16; index += 1) {
|
|
47
|
+
words[index] = view.getUint32(offset + (index * 4), false);
|
|
48
|
+
}
|
|
49
|
+
for (let index = 16; index < 64; index += 1) {
|
|
50
|
+
const left = words[index - 15];
|
|
51
|
+
const right = words[index - 2];
|
|
52
|
+
const sigma0 = rotateRight(left, 7) ^ rotateRight(left, 18) ^ (left >>> 3);
|
|
53
|
+
const sigma1 = rotateRight(right, 17) ^ rotateRight(right, 19) ^ (right >>> 10);
|
|
54
|
+
words[index] = (words[index - 16] + sigma0 + words[index - 7] + sigma1) >>> 0;
|
|
55
|
+
}
|
|
56
|
+
let [a, b, c, d, e, f, g, h] = state;
|
|
57
|
+
for (let index = 0; index < 64; index += 1) {
|
|
58
|
+
const sum1 = rotateRight(e, 6) ^ rotateRight(e, 11) ^ rotateRight(e, 25);
|
|
59
|
+
const choose = (e & f) ^ (~e & g);
|
|
60
|
+
const temporary1 = (h + sum1 + choose + SHA256_ROUND[index] + words[index]) >>> 0;
|
|
61
|
+
const sum0 = rotateRight(a, 2) ^ rotateRight(a, 13) ^ rotateRight(a, 22);
|
|
62
|
+
const majority = (a & b) ^ (a & c) ^ (b & c);
|
|
63
|
+
const temporary2 = (sum0 + majority) >>> 0;
|
|
64
|
+
h = g;
|
|
65
|
+
g = f;
|
|
66
|
+
f = e;
|
|
67
|
+
e = (d + temporary1) >>> 0;
|
|
68
|
+
d = c;
|
|
69
|
+
c = b;
|
|
70
|
+
b = a;
|
|
71
|
+
a = (temporary1 + temporary2) >>> 0;
|
|
72
|
+
}
|
|
73
|
+
state[0] = (state[0] + a) >>> 0;
|
|
74
|
+
state[1] = (state[1] + b) >>> 0;
|
|
75
|
+
state[2] = (state[2] + c) >>> 0;
|
|
76
|
+
state[3] = (state[3] + d) >>> 0;
|
|
77
|
+
state[4] = (state[4] + e) >>> 0;
|
|
78
|
+
state[5] = (state[5] + f) >>> 0;
|
|
79
|
+
state[6] = (state[6] + g) >>> 0;
|
|
80
|
+
state[7] = (state[7] + h) >>> 0;
|
|
81
|
+
}
|
|
82
|
+
return state.map((word) => word.toString(16).padStart(8, '0')).join('');
|
|
83
|
+
}
|
|
@@ -239,6 +239,8 @@ export function createGroundShaderMaterial({
|
|
|
239
239
|
uSceneGroundSnowDepth: uniform(0),
|
|
240
240
|
uSceneGroundShadowDebug: uniform(0),
|
|
241
241
|
uSceneGroundSunColor: uniform(new THREE.Color(0xfff0d2)),
|
|
242
|
+
uSceneGroundSunIntensity: uniform(1),
|
|
243
|
+
uSceneGroundSkyIntensity: uniform(1),
|
|
242
244
|
uSceneGroundSunDirection: uniform(new THREE.Vector3(0.45, 0.75, 0.5).normalize()),
|
|
243
245
|
uSceneGroundSkyColor: uniform(new THREE.Color(0xc7dcff)),
|
|
244
246
|
uSceneGroundWaterLevel: uniform(-10000),
|
|
@@ -572,9 +574,12 @@ export function createGroundShaderMaterial({
|
|
|
572
574
|
u('material', 'metalness'),
|
|
573
575
|
);
|
|
574
576
|
groundColor.addAssign(specularColor.mul(specularAmount));
|
|
575
|
-
groundColor.
|
|
576
|
-
|
|
577
|
-
|
|
577
|
+
const emission = groundColor.mul(u('material', 'emissiveStrength')).toVar();
|
|
578
|
+
// Defaults of one preserve the authored look. Scene intensity remains
|
|
579
|
+
// transient, and can reach zero without erasing intentional emission.
|
|
580
|
+
groundColor.mulAssign(mix(sceneUniforms.uSceneGroundSkyIntensity,
|
|
581
|
+
sceneUniforms.uSceneGroundSunIntensity, smoothstep(0.06, 0.72, direct)));
|
|
582
|
+
groundColor.addAssign(emission);
|
|
578
583
|
|
|
579
584
|
groundColor.assign(mix(
|
|
580
585
|
groundColor,
|
|
@@ -617,7 +622,9 @@ export function createGroundShaderMaterial({
|
|
|
617
622
|
snowDepth,
|
|
618
623
|
sunColor,
|
|
619
624
|
sunDirection,
|
|
625
|
+
sunIntensity,
|
|
620
626
|
skyColor,
|
|
627
|
+
skyIntensity,
|
|
621
628
|
shadowDebug,
|
|
622
629
|
waterLevel,
|
|
623
630
|
wetness,
|
|
@@ -639,6 +646,8 @@ export function createGroundShaderMaterial({
|
|
|
639
646
|
sceneUniforms.uSceneGroundShadowDebug.value = shadowDebug ? 1 : 0;
|
|
640
647
|
}
|
|
641
648
|
if (sunColor !== undefined) sceneUniforms.uSceneGroundSunColor.value.set(sunColor);
|
|
649
|
+
if (Number.isFinite(sunIntensity)) sceneUniforms.uSceneGroundSunIntensity.value = Math.max(0, sunIntensity);
|
|
650
|
+
if (Number.isFinite(skyIntensity)) sceneUniforms.uSceneGroundSkyIntensity.value = Math.max(0, skyIntensity);
|
|
642
651
|
if (Array.isArray(sunDirection) && sunDirection.length >= 3) {
|
|
643
652
|
sceneUniforms.uSceneGroundSunDirection.value.set(...sunDirection).normalize();
|
|
644
653
|
} else if (sunDirection?.isVector3) {
|