@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
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
// Surface nets over a dense sampled grid: one vertex per sign-changing
|
|
2
|
+
// cell, one quad per sign-changing grid edge — the even, quad-dominant
|
|
3
|
+
// topology that cel-shades cleanly at low resolutions.
|
|
4
|
+
//
|
|
5
|
+
// Vertex placement has two modes:
|
|
6
|
+
// - mass point (classic naive nets): the mean of the cell's edge
|
|
7
|
+
// crossings. Chamfers sharp creases by ~1 voxel.
|
|
8
|
+
// - DUAL CONTOURING (pass `evaluate`): solve the QEF — the point that
|
|
9
|
+
// best satisfies every crossing's tangent plane (normals from field
|
|
10
|
+
// gradients) — so planar-cut edges and heightfield ridgelines stay
|
|
11
|
+
// knife-sharp. Tikhonov-regularized toward the mass point and clamped
|
|
12
|
+
// into the cell, so smooth surfaces degrade gracefully to classic
|
|
13
|
+
// nets instead of growing spikes.
|
|
14
|
+
|
|
15
|
+
// Cube corner offsets in (x, y, z) bit order.
|
|
16
|
+
const CORNER_OFFSETS = [
|
|
17
|
+
[0, 0, 0], [1, 0, 0], [0, 1, 0], [1, 1, 0],
|
|
18
|
+
[0, 0, 1], [1, 0, 1], [0, 1, 1], [1, 1, 1],
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// The 12 cube edges as corner index pairs.
|
|
22
|
+
const CUBE_EDGES = [
|
|
23
|
+
[0, 1], [2, 3], [4, 5], [6, 7],
|
|
24
|
+
[0, 2], [1, 3], [4, 6], [5, 7],
|
|
25
|
+
[0, 4], [1, 5], [2, 6], [3, 7],
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Samples `evaluate` over a uniform grid covering `bounds`. `resolution` is
|
|
30
|
+
* the cell count along the longest axis; other axes derive from aspect so
|
|
31
|
+
* cells stay cubic.
|
|
32
|
+
*
|
|
33
|
+
* Returns { values, dims, origin, cellSize } where `values` holds
|
|
34
|
+
* (nx+1)*(ny+1)*(nz+1) corner samples (x fastest, then y, then z).
|
|
35
|
+
*/
|
|
36
|
+
export function sampleGrid(evaluate, bounds, resolution) {
|
|
37
|
+
const sizeX = bounds.max[0] - bounds.min[0];
|
|
38
|
+
const sizeY = bounds.max[1] - bounds.min[1];
|
|
39
|
+
const sizeZ = bounds.max[2] - bounds.min[2];
|
|
40
|
+
const longest = Math.max(sizeX, sizeY, sizeZ);
|
|
41
|
+
if (!(longest > 0)) throw new Error('Rockgen grid bounds are empty.');
|
|
42
|
+
const cellSize = longest / resolution;
|
|
43
|
+
const nx = Math.max(Math.ceil(sizeX / cellSize), 1);
|
|
44
|
+
const ny = Math.max(Math.ceil(sizeY / cellSize), 1);
|
|
45
|
+
const nz = Math.max(Math.ceil(sizeZ / cellSize), 1);
|
|
46
|
+
|
|
47
|
+
const values = new Float32Array((nx + 1) * (ny + 1) * (nz + 1));
|
|
48
|
+
const origin = [bounds.min[0], bounds.min[1], bounds.min[2]];
|
|
49
|
+
let write = 0;
|
|
50
|
+
for (let z = 0; z <= nz; z += 1) {
|
|
51
|
+
const wz = origin[2] + z * cellSize;
|
|
52
|
+
for (let y = 0; y <= ny; y += 1) {
|
|
53
|
+
const wy = origin[1] + y * cellSize;
|
|
54
|
+
for (let x = 0; x <= nx; x += 1) {
|
|
55
|
+
values[write] = evaluate(origin[0] + x * cellSize, wy, wz);
|
|
56
|
+
write += 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return { cellSize, dims: [nx, ny, nz], origin, values };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Solves the 3x3 symmetric system (A + λI) x = b via the adjugate;
|
|
64
|
+
// returns false when the matrix is too ill-conditioned to trust.
|
|
65
|
+
function solveSymmetric3(a00, a01, a02, a11, a12, a22, b0, b1, b2, out) {
|
|
66
|
+
const c00 = a11 * a22 - a12 * a12;
|
|
67
|
+
const c01 = a02 * a12 - a01 * a22;
|
|
68
|
+
const c02 = a01 * a12 - a02 * a11;
|
|
69
|
+
const det = a00 * c00 + a01 * c01 + a02 * c02;
|
|
70
|
+
if (Math.abs(det) < 1e-10) return false;
|
|
71
|
+
const inv = 1 / det;
|
|
72
|
+
out[0] = (c00 * b0 + c01 * b1 + c02 * b2) * inv;
|
|
73
|
+
out[1] = (c01 * b0 + (a00 * a22 - a02 * a02) * b1 + (a02 * a01 - a00 * a12) * b2) * inv;
|
|
74
|
+
out[2] = (c02 * b0 + (a01 * a02 - a00 * a12) * b1 + (a00 * a11 - a01 * a01) * b2) * inv;
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Extracts the zero isosurface from a sampled grid. Returns
|
|
80
|
+
* { positions: Float32Array, indices: Uint32Array } in world space.
|
|
81
|
+
*
|
|
82
|
+
* @param {object} grid From sampleGrid().
|
|
83
|
+
* @param {object} [options]
|
|
84
|
+
* @param {(x,y,z)=>number} [options.evaluate] Field for gradient normals —
|
|
85
|
+
* enables dual-contouring (QEF) vertex placement; omit for mass points.
|
|
86
|
+
*/
|
|
87
|
+
export function surfaceNets(grid, { evaluate = null } = {}) {
|
|
88
|
+
const [nx, ny, nz] = grid.dims;
|
|
89
|
+
const { cellSize, origin, values } = grid;
|
|
90
|
+
const rowStride = nx + 1;
|
|
91
|
+
const sliceStride = (nx + 1) * (ny + 1);
|
|
92
|
+
|
|
93
|
+
// Vertex index per cell, -1 where the cell has no crossing.
|
|
94
|
+
const cellVertex = new Int32Array(nx * ny * nz).fill(-1);
|
|
95
|
+
const positions = [];
|
|
96
|
+
|
|
97
|
+
const cornerValues = new Float32Array(8);
|
|
98
|
+
const crossX = new Float32Array(12);
|
|
99
|
+
const crossY = new Float32Array(12);
|
|
100
|
+
const crossZ = new Float32Array(12);
|
|
101
|
+
const normX = new Float32Array(12);
|
|
102
|
+
const normY = new Float32Array(12);
|
|
103
|
+
const normZ = new Float32Array(12);
|
|
104
|
+
const solved = new Float32Array(3);
|
|
105
|
+
const gradEps = cellSize * 0.5;
|
|
106
|
+
// Regularization weight: high enough that near-flat cells stay at the
|
|
107
|
+
// mass point, low enough that two crossing cut planes pin the crease.
|
|
108
|
+
const LAMBDA = 0.12;
|
|
109
|
+
|
|
110
|
+
for (let z = 0; z < nz; z += 1) {
|
|
111
|
+
for (let y = 0; y < ny; y += 1) {
|
|
112
|
+
for (let x = 0; x < nx; x += 1) {
|
|
113
|
+
let mask = 0;
|
|
114
|
+
for (let corner = 0; corner < 8; corner += 1) {
|
|
115
|
+
const [ox, oy, oz] = CORNER_OFFSETS[corner];
|
|
116
|
+
const value = values[(x + ox) + (y + oy) * rowStride + (z + oz) * sliceStride];
|
|
117
|
+
cornerValues[corner] = value;
|
|
118
|
+
if (value < 0) mask |= 1 << corner;
|
|
119
|
+
}
|
|
120
|
+
if (mask === 0 || mask === 0xff) continue;
|
|
121
|
+
|
|
122
|
+
// Edge zero-crossings (cell-local coordinates in [0, 1]).
|
|
123
|
+
let px = 0;
|
|
124
|
+
let py = 0;
|
|
125
|
+
let pz = 0;
|
|
126
|
+
let crossings = 0;
|
|
127
|
+
for (const [c0, c1] of CUBE_EDGES) {
|
|
128
|
+
const v0 = cornerValues[c0];
|
|
129
|
+
const v1 = cornerValues[c1];
|
|
130
|
+
if ((v0 < 0) === (v1 < 0)) continue;
|
|
131
|
+
const t = v0 / (v0 - v1);
|
|
132
|
+
const [ax, ay, az] = CORNER_OFFSETS[c0];
|
|
133
|
+
const [bx, by, bz] = CORNER_OFFSETS[c1];
|
|
134
|
+
crossX[crossings] = ax + (bx - ax) * t;
|
|
135
|
+
crossY[crossings] = ay + (by - ay) * t;
|
|
136
|
+
crossZ[crossings] = az + (bz - az) * t;
|
|
137
|
+
px += crossX[crossings];
|
|
138
|
+
py += crossY[crossings];
|
|
139
|
+
pz += crossZ[crossings];
|
|
140
|
+
crossings += 1;
|
|
141
|
+
}
|
|
142
|
+
const inv = 1 / crossings;
|
|
143
|
+
px *= inv;
|
|
144
|
+
py *= inv;
|
|
145
|
+
pz *= inv;
|
|
146
|
+
|
|
147
|
+
if (evaluate) {
|
|
148
|
+
// QEF: minimize Σ (n_i · (v - p_i))² + λ|v - masspoint|² over the
|
|
149
|
+
// crossing tangent planes (normals = field gradients).
|
|
150
|
+
let a00 = LAMBDA;
|
|
151
|
+
let a01 = 0;
|
|
152
|
+
let a02 = 0;
|
|
153
|
+
let a11 = LAMBDA;
|
|
154
|
+
let a12 = 0;
|
|
155
|
+
let a22 = LAMBDA;
|
|
156
|
+
let b0 = LAMBDA * px;
|
|
157
|
+
let b1 = LAMBDA * py;
|
|
158
|
+
let b2 = LAMBDA * pz;
|
|
159
|
+
for (let i = 0; i < crossings; i += 1) {
|
|
160
|
+
const wx = origin[0] + (x + crossX[i]) * cellSize;
|
|
161
|
+
const wy = origin[1] + (y + crossY[i]) * cellSize;
|
|
162
|
+
const wz = origin[2] + (z + crossZ[i]) * cellSize;
|
|
163
|
+
let gx = evaluate(wx + gradEps, wy, wz) - evaluate(wx - gradEps, wy, wz);
|
|
164
|
+
let gy = evaluate(wx, wy + gradEps, wz) - evaluate(wx, wy - gradEps, wz);
|
|
165
|
+
let gz = evaluate(wx, wy, wz + gradEps) - evaluate(wx, wy, wz - gradEps);
|
|
166
|
+
const len = Math.sqrt(gx * gx + gy * gy + gz * gz);
|
|
167
|
+
if (len < 1e-9) continue;
|
|
168
|
+
gx /= len;
|
|
169
|
+
gy /= len;
|
|
170
|
+
gz /= len;
|
|
171
|
+
const dot = gx * crossX[i] + gy * crossY[i] + gz * crossZ[i];
|
|
172
|
+
a00 += gx * gx;
|
|
173
|
+
a01 += gx * gy;
|
|
174
|
+
a02 += gx * gz;
|
|
175
|
+
a11 += gy * gy;
|
|
176
|
+
a12 += gy * gz;
|
|
177
|
+
a22 += gz * gz;
|
|
178
|
+
b0 += gx * dot;
|
|
179
|
+
b1 += gy * dot;
|
|
180
|
+
b2 += gz * dot;
|
|
181
|
+
}
|
|
182
|
+
if (solveSymmetric3(a00, a01, a02, a11, a12, a22, b0, b1, b2, solved)) {
|
|
183
|
+
// Clamp into the cell: an out-of-cell QEF solution means the
|
|
184
|
+
// planes meet elsewhere — trust the topology, not the spike.
|
|
185
|
+
px = Math.min(Math.max(solved[0], 0), 1);
|
|
186
|
+
py = Math.min(Math.max(solved[1], 0), 1);
|
|
187
|
+
pz = Math.min(Math.max(solved[2], 0), 1);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
cellVertex[x + y * nx + z * nx * ny] = positions.length / 3;
|
|
192
|
+
positions.push(
|
|
193
|
+
origin[0] + (x + px) * cellSize,
|
|
194
|
+
origin[1] + (y + py) * cellSize,
|
|
195
|
+
origin[2] + (z + pz) * cellSize,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// One quad (two triangles) per sign-changing interior grid edge, joining
|
|
202
|
+
// the four cells around that edge. Axis loops are unrolled per direction
|
|
203
|
+
// so the winding stays consistent with the sign of the near corner.
|
|
204
|
+
const indices = [];
|
|
205
|
+
const pushQuad = (a, b, c, d, flip) => {
|
|
206
|
+
if (a < 0 || b < 0 || c < 0 || d < 0) return;
|
|
207
|
+
if (flip) {
|
|
208
|
+
indices.push(a, b, c, c, b, d);
|
|
209
|
+
} else {
|
|
210
|
+
indices.push(a, c, b, b, c, d);
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
for (let z = 0; z < nz; z += 1) {
|
|
215
|
+
for (let y = 0; y < ny; y += 1) {
|
|
216
|
+
for (let x = 0; x < nx; x += 1) {
|
|
217
|
+
const corner = values[x + y * rowStride + z * sliceStride];
|
|
218
|
+
const inside = corner < 0;
|
|
219
|
+
|
|
220
|
+
// Edge along +X: shared by cells (x, y-1..y, z-1..z).
|
|
221
|
+
if (y > 0 && z > 0 && x < nx) {
|
|
222
|
+
const next = values[(x + 1) + y * rowStride + z * sliceStride];
|
|
223
|
+
if ((next < 0) !== inside) {
|
|
224
|
+
pushQuad(
|
|
225
|
+
cellVertex[x + (y - 1) * nx + (z - 1) * nx * ny],
|
|
226
|
+
cellVertex[x + y * nx + (z - 1) * nx * ny],
|
|
227
|
+
cellVertex[x + (y - 1) * nx + z * nx * ny],
|
|
228
|
+
cellVertex[x + y * nx + z * nx * ny],
|
|
229
|
+
inside,
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
// Edge along +Y: shared by cells (x-1..x, y, z-1..z).
|
|
234
|
+
if (x > 0 && z > 0 && y < ny) {
|
|
235
|
+
const next = values[x + (y + 1) * rowStride + z * sliceStride];
|
|
236
|
+
if ((next < 0) !== inside) {
|
|
237
|
+
pushQuad(
|
|
238
|
+
cellVertex[(x - 1) + y * nx + (z - 1) * nx * ny],
|
|
239
|
+
cellVertex[(x - 1) + y * nx + z * nx * ny],
|
|
240
|
+
cellVertex[x + y * nx + (z - 1) * nx * ny],
|
|
241
|
+
cellVertex[x + y * nx + z * nx * ny],
|
|
242
|
+
inside,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
// Edge along +Z: shared by cells (x-1..x, y-1..y, z).
|
|
247
|
+
if (x > 0 && y > 0 && z < nz) {
|
|
248
|
+
const next = values[x + y * rowStride + (z + 1) * sliceStride];
|
|
249
|
+
if ((next < 0) !== inside) {
|
|
250
|
+
pushQuad(
|
|
251
|
+
cellVertex[(x - 1) + (y - 1) * nx + z * nx * ny],
|
|
252
|
+
cellVertex[x + (y - 1) * nx + z * nx * ny],
|
|
253
|
+
cellVertex[(x - 1) + y * nx + z * nx * ny],
|
|
254
|
+
cellVertex[x + y * nx + z * nx * ny],
|
|
255
|
+
inside,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
indices: new Uint32Array(indices),
|
|
265
|
+
positions: new Float32Array(positions),
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Drops disconnected surface fragments smaller than `minRatio` of the
|
|
271
|
+
* largest component's triangle count, compacting the vertex list. Strong
|
|
272
|
+
* domain warp / high noise amplitudes routinely pinch off small floating
|
|
273
|
+
* islands; they read as artifacts, so meshDocument filters them by default.
|
|
274
|
+
*/
|
|
275
|
+
export function filterSmallIslands({ indices, positions }, minRatio = 0.02) {
|
|
276
|
+
const vertexCount = positions.length / 3;
|
|
277
|
+
const parent = new Int32Array(vertexCount);
|
|
278
|
+
for (let v = 0; v < vertexCount; v += 1) parent[v] = v;
|
|
279
|
+
const find = (v) => {
|
|
280
|
+
let root = v;
|
|
281
|
+
while (parent[root] !== root) root = parent[root];
|
|
282
|
+
while (parent[v] !== root) {
|
|
283
|
+
const next = parent[v];
|
|
284
|
+
parent[v] = root;
|
|
285
|
+
v = next;
|
|
286
|
+
}
|
|
287
|
+
return root;
|
|
288
|
+
};
|
|
289
|
+
for (let t = 0; t < indices.length; t += 3) {
|
|
290
|
+
const a = find(indices[t]);
|
|
291
|
+
const b = find(indices[t + 1]);
|
|
292
|
+
const c = find(indices[t + 2]);
|
|
293
|
+
if (b !== a) parent[b] = a;
|
|
294
|
+
if (c !== a) parent[c] = a;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const triangleCounts = new Map();
|
|
298
|
+
let largest = 0;
|
|
299
|
+
for (let t = 0; t < indices.length; t += 3) {
|
|
300
|
+
const root = find(indices[t]);
|
|
301
|
+
const count = (triangleCounts.get(root) ?? 0) + 1;
|
|
302
|
+
triangleCounts.set(root, count);
|
|
303
|
+
if (count > largest) largest = count;
|
|
304
|
+
}
|
|
305
|
+
const threshold = Math.max(largest * minRatio, 1);
|
|
306
|
+
if ([...triangleCounts.values()].every((count) => count >= threshold)) {
|
|
307
|
+
return { indices, positions };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Compact kept triangles and remap their vertices.
|
|
311
|
+
const remap = new Int32Array(vertexCount).fill(-1);
|
|
312
|
+
const keptPositions = [];
|
|
313
|
+
const keptIndices = [];
|
|
314
|
+
for (let t = 0; t < indices.length; t += 3) {
|
|
315
|
+
if (triangleCounts.get(find(indices[t])) < threshold) continue;
|
|
316
|
+
for (let corner = 0; corner < 3; corner += 1) {
|
|
317
|
+
const source = indices[t + corner];
|
|
318
|
+
if (remap[source] === -1) {
|
|
319
|
+
remap[source] = keptPositions.length / 3;
|
|
320
|
+
keptPositions.push(positions[source * 3], positions[source * 3 + 1], positions[source * 3 + 2]);
|
|
321
|
+
}
|
|
322
|
+
keptIndices.push(remap[source]);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
indices: new Uint32Array(keptIndices),
|
|
327
|
+
positions: new Float32Array(keptPositions),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Seeded 3D cellular (Voronoi/Worley) noise. F2 - F1 approaches zero along
|
|
2
|
+
// cell borders, which rockgen carves into the field as facet creases — the
|
|
3
|
+
// hard-planed, fractured look that sells stylized rock without any texture.
|
|
4
|
+
|
|
5
|
+
import { hash3u } from './prng.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns the two nearest feature-point distances { f1, f2 } for a point.
|
|
9
|
+
* `jitter` in [0, 1] moves feature points from cell centers (0) to fully
|
|
10
|
+
* random positions (1). Distances are Euclidean, in noise-space units.
|
|
11
|
+
*/
|
|
12
|
+
export function cellular3(seed, x, y, z, jitter = 1) {
|
|
13
|
+
const xi = Math.floor(x);
|
|
14
|
+
const yi = Math.floor(y);
|
|
15
|
+
const zi = Math.floor(z);
|
|
16
|
+
|
|
17
|
+
let f1 = Infinity;
|
|
18
|
+
let f2 = Infinity;
|
|
19
|
+
for (let dz = -1; dz <= 1; dz += 1) {
|
|
20
|
+
for (let dy = -1; dy <= 1; dy += 1) {
|
|
21
|
+
for (let dx = -1; dx <= 1; dx += 1) {
|
|
22
|
+
const cx = xi + dx;
|
|
23
|
+
const cy = yi + dy;
|
|
24
|
+
const cz = zi + dz;
|
|
25
|
+
const h = hash3u(seed, cx, cy, cz);
|
|
26
|
+
// Derive three decorrelated offsets from one hash (11/11/10 bits).
|
|
27
|
+
const ox = 0.5 + jitter * (((h & 0x7ff) / 0x7ff) - 0.5);
|
|
28
|
+
const oy = 0.5 + jitter * ((((h >>> 11) & 0x7ff) / 0x7ff) - 0.5);
|
|
29
|
+
const oz = 0.5 + jitter * ((((h >>> 22) & 0x3ff) / 0x3ff) - 0.5);
|
|
30
|
+
const px = cx + ox - x;
|
|
31
|
+
const py = cy + oy - y;
|
|
32
|
+
const pz = cz + oz - z;
|
|
33
|
+
const d = px * px + py * py + pz * pz;
|
|
34
|
+
if (d < f1) {
|
|
35
|
+
f2 = f1;
|
|
36
|
+
f1 = d;
|
|
37
|
+
} else if (d < f2) {
|
|
38
|
+
f2 = d;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return { f1: Math.sqrt(f1), f2: Math.sqrt(f2) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 2D cellular noise in the XZ plane with a stable per-cell id — the basis
|
|
48
|
+
* of the columnar-jointing stage (each Voronoi cell = one rock column).
|
|
49
|
+
* Returns { f1, f2, id } where `id` is the nearest cell's hash in [0, 1).
|
|
50
|
+
*/
|
|
51
|
+
export function cellular2(seed, x, z, jitter = 1) {
|
|
52
|
+
const xi = Math.floor(x);
|
|
53
|
+
const zi = Math.floor(z);
|
|
54
|
+
|
|
55
|
+
let f1 = Infinity;
|
|
56
|
+
let f2 = Infinity;
|
|
57
|
+
let id = 0;
|
|
58
|
+
for (let dz = -1; dz <= 1; dz += 1) {
|
|
59
|
+
for (let dx = -1; dx <= 1; dx += 1) {
|
|
60
|
+
const cx = xi + dx;
|
|
61
|
+
const cz = zi + dz;
|
|
62
|
+
const h = hash3u(seed, cx, 0, cz);
|
|
63
|
+
const ox = 0.5 + jitter * (((h & 0xffff) / 0xffff) - 0.5);
|
|
64
|
+
const oz = 0.5 + jitter * ((((h >>> 16) & 0xffff) / 0xffff) - 0.5);
|
|
65
|
+
const px = cx + ox - x;
|
|
66
|
+
const pz = cz + oz - z;
|
|
67
|
+
const d = px * px + pz * pz;
|
|
68
|
+
if (d < f1) {
|
|
69
|
+
f2 = f1;
|
|
70
|
+
f1 = d;
|
|
71
|
+
id = h / 4294967296;
|
|
72
|
+
} else if (d < f2) {
|
|
73
|
+
f2 = d;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { f1: Math.sqrt(f1), f2: Math.sqrt(f2), id };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Facet crease profile: 1 at cell borders (F2 ≈ F1), falling to 0 inside
|
|
82
|
+
* cells. `width` controls how far the crease reaches into the cell.
|
|
83
|
+
*/
|
|
84
|
+
export function cellularCrease3(seed, x, y, z, jitter = 1, width = 0.35) {
|
|
85
|
+
const { f1, f2 } = cellular3(seed, x, y, z, jitter);
|
|
86
|
+
const edge = (f2 - f1) / width;
|
|
87
|
+
if (edge >= 1) return 0;
|
|
88
|
+
if (edge <= 0) return 1;
|
|
89
|
+
// Smoothstep falloff keeps the crease shoulder soft at mesh resolution.
|
|
90
|
+
const t = 1 - edge;
|
|
91
|
+
return t * t * (3 - 2 * t);
|
|
92
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// Deterministic integer hashing for all rockgen randomness. Every
|
|
2
|
+
// generation-time random value flows through these functions — no
|
|
3
|
+
// Math.random, and nothing here calls transcendental Math functions whose
|
|
4
|
+
// rounding may vary between JS engines — so the same document produces
|
|
5
|
+
// bit-identical geometry on every run (verified by scripts/verify-rockgen.mjs).
|
|
6
|
+
|
|
7
|
+
/** Chris Wellons' lowbias32: uint32 -> well-mixed uint32. */
|
|
8
|
+
export function lowbias32(x) {
|
|
9
|
+
let h = x >>> 0;
|
|
10
|
+
h = Math.imul(h ^ (h >>> 16), 0x7feb352d) >>> 0;
|
|
11
|
+
h = Math.imul(h ^ (h >>> 15), 0x846ca68b) >>> 0;
|
|
12
|
+
return (h ^ (h >>> 16)) >>> 0;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Mixes two uint32 seeds into one (order-sensitive). */
|
|
16
|
+
export function hashCombine(a, b) {
|
|
17
|
+
return lowbias32((a >>> 0) ^ (Math.imul(b >>> 0, 0x9e3779b9) >>> 0));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Hashes a 3D integer lattice coordinate under a seed -> uint32. */
|
|
21
|
+
export function hash3u(seed, xi, yi, zi) {
|
|
22
|
+
let h = seed >>> 0;
|
|
23
|
+
h = (h ^ (Math.imul(xi | 0, 0x8da6b343) >>> 0)) >>> 0;
|
|
24
|
+
h = (h ^ (Math.imul(yi | 0, 0xd8163841) >>> 0)) >>> 0;
|
|
25
|
+
h = (h ^ (Math.imul(zi | 0, 0xcb1ab31f) >>> 0)) >>> 0;
|
|
26
|
+
return lowbias32(h);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Hashes a 3D integer lattice coordinate under a seed -> [0, 1). */
|
|
30
|
+
export function hash3f(seed, xi, yi, zi) {
|
|
31
|
+
return hash3u(seed, xi, yi, zi) / 4294967296;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* sfc32 PRNG for sequential draws (piece placement jitter, preset scatter).
|
|
36
|
+
* Returns a function yielding floats in [0, 1). Seed all four words for a
|
|
37
|
+
* healthy state; hashSeeds(seed) below does that from one uint32.
|
|
38
|
+
*/
|
|
39
|
+
export function sfc32(a, b, c, d) {
|
|
40
|
+
let sa = a >>> 0;
|
|
41
|
+
let sb = b >>> 0;
|
|
42
|
+
let sc = c >>> 0;
|
|
43
|
+
let sd = d >>> 0;
|
|
44
|
+
return function next() {
|
|
45
|
+
const t = (sa + sb + sd) >>> 0;
|
|
46
|
+
sd = (sd + 1) >>> 0;
|
|
47
|
+
sa = sb ^ (sb >>> 9);
|
|
48
|
+
sb = (sc + (sc << 3)) >>> 0;
|
|
49
|
+
sc = ((sc << 21) | (sc >>> 11)) >>> 0;
|
|
50
|
+
sc = (sc + t) >>> 0;
|
|
51
|
+
return t / 4294967296;
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Builds an sfc32 generator from a single uint32 seed. */
|
|
56
|
+
export function createRandom(seed) {
|
|
57
|
+
const s = seed >>> 0;
|
|
58
|
+
const random = sfc32(lowbias32(s), lowbias32(s + 1), lowbias32(s + 2), lowbias32(s + 3));
|
|
59
|
+
// sfc32 needs a few warm-up rounds before the state is well mixed.
|
|
60
|
+
for (let i = 0; i < 8; i += 1) random();
|
|
61
|
+
return random;
|
|
62
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Seeded 3D simplex noise (Gustavson's reference algorithm with the
|
|
2
|
+
// permutation table replaced by the rockgen lattice hash, so it is seedable
|
|
3
|
+
// without table setup). Used for domain warp, where value noise's lattice
|
|
4
|
+
// alignment would leave visible grid bias in the warped shapes.
|
|
5
|
+
|
|
6
|
+
import { hash3u } from './prng.js';
|
|
7
|
+
|
|
8
|
+
const F3 = 1 / 3;
|
|
9
|
+
const G3 = 1 / 6;
|
|
10
|
+
|
|
11
|
+
// 12 cube-edge gradient directions.
|
|
12
|
+
const GRAD3 = new Float32Array([
|
|
13
|
+
1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1, 0,
|
|
14
|
+
1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, -1,
|
|
15
|
+
0, 1, 1, 0, -1, 1, 0, 1, -1, 0, -1, -1,
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
function gradDot(seed, xi, yi, zi, x, y, z) {
|
|
19
|
+
const g = (hash3u(seed, xi, yi, zi) % 12) * 3;
|
|
20
|
+
return GRAD3[g] * x + GRAD3[g + 1] * y + GRAD3[g + 2] * z;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Seeded 3D simplex noise -> approximately [-1, 1]. */
|
|
24
|
+
export function simplexNoise3(seed, x, y, z) {
|
|
25
|
+
const s = (x + y + z) * F3;
|
|
26
|
+
const i = Math.floor(x + s);
|
|
27
|
+
const j = Math.floor(y + s);
|
|
28
|
+
const k = Math.floor(z + s);
|
|
29
|
+
const t = (i + j + k) * G3;
|
|
30
|
+
const x0 = x - (i - t);
|
|
31
|
+
const y0 = y - (j - t);
|
|
32
|
+
const z0 = z - (k - t);
|
|
33
|
+
|
|
34
|
+
// Rank the simplex corner offsets by descending coordinate.
|
|
35
|
+
let i1;
|
|
36
|
+
let j1;
|
|
37
|
+
let k1;
|
|
38
|
+
let i2;
|
|
39
|
+
let j2;
|
|
40
|
+
let k2;
|
|
41
|
+
if (x0 >= y0) {
|
|
42
|
+
if (y0 >= z0) {
|
|
43
|
+
i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 1; k2 = 0;
|
|
44
|
+
} else if (x0 >= z0) {
|
|
45
|
+
i1 = 1; j1 = 0; k1 = 0; i2 = 1; j2 = 0; k2 = 1;
|
|
46
|
+
} else {
|
|
47
|
+
i1 = 0; j1 = 0; k1 = 1; i2 = 1; j2 = 0; k2 = 1;
|
|
48
|
+
}
|
|
49
|
+
} else if (y0 < z0) {
|
|
50
|
+
i1 = 0; j1 = 0; k1 = 1; i2 = 0; j2 = 1; k2 = 1;
|
|
51
|
+
} else if (x0 < z0) {
|
|
52
|
+
i1 = 0; j1 = 1; k1 = 0; i2 = 0; j2 = 1; k2 = 1;
|
|
53
|
+
} else {
|
|
54
|
+
i1 = 0; j1 = 1; k1 = 0; i2 = 1; j2 = 1; k2 = 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const x1 = x0 - i1 + G3;
|
|
58
|
+
const y1 = y0 - j1 + G3;
|
|
59
|
+
const z1 = z0 - k1 + G3;
|
|
60
|
+
const x2 = x0 - i2 + 2 * G3;
|
|
61
|
+
const y2 = y0 - j2 + 2 * G3;
|
|
62
|
+
const z2 = z0 - k2 + 2 * G3;
|
|
63
|
+
const x3 = x0 - 1 + 3 * G3;
|
|
64
|
+
const y3 = y0 - 1 + 3 * G3;
|
|
65
|
+
const z3 = z0 - 1 + 3 * G3;
|
|
66
|
+
|
|
67
|
+
let n = 0;
|
|
68
|
+
let f = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
|
|
69
|
+
if (f > 0) {
|
|
70
|
+
f *= f;
|
|
71
|
+
n += f * f * gradDot(seed, i, j, k, x0, y0, z0);
|
|
72
|
+
}
|
|
73
|
+
f = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
|
|
74
|
+
if (f > 0) {
|
|
75
|
+
f *= f;
|
|
76
|
+
n += f * f * gradDot(seed, i + i1, j + j1, k + k1, x1, y1, z1);
|
|
77
|
+
}
|
|
78
|
+
f = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
|
|
79
|
+
if (f > 0) {
|
|
80
|
+
f *= f;
|
|
81
|
+
n += f * f * gradDot(seed, i + i2, j + j2, k + k2, x2, y2, z2);
|
|
82
|
+
}
|
|
83
|
+
f = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
|
|
84
|
+
if (f > 0) {
|
|
85
|
+
f *= f;
|
|
86
|
+
n += f * f * gradDot(seed, i + 1, j + 1, k + 1, x3, y3, z3);
|
|
87
|
+
}
|
|
88
|
+
return 32 * n;
|
|
89
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Seeded 3D value noise + FBM stacks. Value noise (hashed lattice values,
|
|
2
|
+
// quintic interpolation) is the workhorse for rock displacement: cheaper than
|
|
3
|
+
// simplex per sample and its slightly blocky character reads well on stylized
|
|
4
|
+
// rock at the amplitudes rockgen uses.
|
|
5
|
+
|
|
6
|
+
import { hash3f } from './prng.js';
|
|
7
|
+
|
|
8
|
+
function fade(t) {
|
|
9
|
+
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function lerp(a, b, t) {
|
|
13
|
+
return a + (b - a) * t;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Seeded 3D value noise -> [-1, 1]. */
|
|
17
|
+
export function valueNoise3(seed, x, y, z) {
|
|
18
|
+
const xi = Math.floor(x);
|
|
19
|
+
const yi = Math.floor(y);
|
|
20
|
+
const zi = Math.floor(z);
|
|
21
|
+
const tx = fade(x - xi);
|
|
22
|
+
const ty = fade(y - yi);
|
|
23
|
+
const tz = fade(z - zi);
|
|
24
|
+
|
|
25
|
+
const c000 = hash3f(seed, xi, yi, zi);
|
|
26
|
+
const c100 = hash3f(seed, xi + 1, yi, zi);
|
|
27
|
+
const c010 = hash3f(seed, xi, yi + 1, zi);
|
|
28
|
+
const c110 = hash3f(seed, xi + 1, yi + 1, zi);
|
|
29
|
+
const c001 = hash3f(seed, xi, yi, zi + 1);
|
|
30
|
+
const c101 = hash3f(seed, xi + 1, yi, zi + 1);
|
|
31
|
+
const c011 = hash3f(seed, xi, yi + 1, zi + 1);
|
|
32
|
+
const c111 = hash3f(seed, xi + 1, yi + 1, zi + 1);
|
|
33
|
+
|
|
34
|
+
const x00 = lerp(c000, c100, tx);
|
|
35
|
+
const x10 = lerp(c010, c110, tx);
|
|
36
|
+
const x01 = lerp(c001, c101, tx);
|
|
37
|
+
const x11 = lerp(c011, c111, tx);
|
|
38
|
+
const y0 = lerp(x00, x10, ty);
|
|
39
|
+
const y1 = lerp(x01, x11, ty);
|
|
40
|
+
return lerp(y0, y1, tz) * 2 - 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Fractional Brownian motion over valueNoise3, normalized by the octave
|
|
45
|
+
* amplitude sum so the result stays in roughly [-1, 1] for any octave count.
|
|
46
|
+
*/
|
|
47
|
+
export function fbm3(seed, x, y, z, octaves = 4, lacunarity = 2, gain = 0.5) {
|
|
48
|
+
let sum = 0;
|
|
49
|
+
let amplitude = 1;
|
|
50
|
+
let amplitudeSum = 0;
|
|
51
|
+
let fx = x;
|
|
52
|
+
let fy = y;
|
|
53
|
+
let fz = z;
|
|
54
|
+
for (let i = 0; i < octaves; i += 1) {
|
|
55
|
+
sum += amplitude * valueNoise3(seed + i, fx, fy, fz);
|
|
56
|
+
amplitudeSum += amplitude;
|
|
57
|
+
amplitude *= gain;
|
|
58
|
+
fx *= lacunarity;
|
|
59
|
+
fy *= lacunarity;
|
|
60
|
+
fz *= lacunarity;
|
|
61
|
+
}
|
|
62
|
+
return sum / amplitudeSum;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Ridged FBM: sharp creases where the underlying noise crosses zero.
|
|
67
|
+
* Returns [0, 1] with 1 at the ridge lines.
|
|
68
|
+
*/
|
|
69
|
+
export function ridgedFbm3(seed, x, y, z, octaves = 4, lacunarity = 2, gain = 0.5) {
|
|
70
|
+
let sum = 0;
|
|
71
|
+
let amplitude = 1;
|
|
72
|
+
let amplitudeSum = 0;
|
|
73
|
+
let fx = x;
|
|
74
|
+
let fy = y;
|
|
75
|
+
let fz = z;
|
|
76
|
+
for (let i = 0; i < octaves; i += 1) {
|
|
77
|
+
const n = valueNoise3(seed + i, fx, fy, fz);
|
|
78
|
+
const ridge = 1 - Math.abs(n);
|
|
79
|
+
sum += amplitude * ridge * ridge;
|
|
80
|
+
amplitudeSum += amplitude;
|
|
81
|
+
amplitude *= gain;
|
|
82
|
+
fx *= lacunarity;
|
|
83
|
+
fy *= lacunarity;
|
|
84
|
+
fz *= lacunarity;
|
|
85
|
+
}
|
|
86
|
+
return sum / amplitudeSum;
|
|
87
|
+
}
|