@jgengine/shell 0.22.0 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +110 -0
- package/dist/GamePlayerShell.js +18 -3
- package/dist/GameUiPreview.js +2 -1
- package/dist/Shell3dPresentation.js +13 -8
- package/dist/ShellChrome.d.ts +1 -1
- package/dist/ShellChrome.js +4 -2
- package/dist/ShellHudPresentation.js +2 -1
- package/dist/ShellNotices.d.ts +26 -0
- package/dist/ShellNotices.js +51 -0
- package/dist/audio/AudioComponents.d.ts +3 -1
- package/dist/audio/AudioComponents.js +5 -2
- package/dist/audio/listenerAnchor.d.ts +15 -0
- package/dist/audio/listenerAnchor.js +17 -0
- package/dist/audio/playableAudio.d.ts +9 -0
- package/dist/audio/playableAudio.js +11 -0
- package/dist/commandSink.d.ts +1 -1
- package/dist/commandSink.js +8 -3
- package/dist/defineGame.js +3 -1
- package/dist/devtools/DevtoolsOverlay.js +1 -0
- package/dist/devtools/agentBridge.d.ts +21 -0
- package/dist/devtools/agentBridge.js +95 -3
- package/dist/devtools/devtoolsReports.d.ts +32 -0
- package/dist/devtools/devtoolsReports.js +26 -0
- package/dist/devtools/simStepper.d.ts +21 -0
- package/dist/devtools/simStepper.js +37 -0
- package/dist/drivers/FrameDriver.js +26 -14
- package/dist/drivers/HudOnlyDriver.js +16 -5
- package/dist/environment/EnvironmentScene.js +19 -7
- package/dist/environment/RoadJunctions.d.ts +3 -1
- package/dist/environment/RoadJunctions.js +9 -2
- package/dist/environment/RoadRibbons.d.ts +3 -1
- package/dist/environment/RoadRibbons.js +16 -3
- package/dist/environment/terrainGroundColor.js +3 -3
- package/dist/materialOverride.js +4 -2
- package/dist/playSurfaceFocus.d.ts +16 -0
- package/dist/playSurfaceFocus.js +42 -0
- package/dist/postfx/PostProcessing.d.ts +3 -1
- package/dist/postfx/PostProcessing.js +14 -6
- package/dist/registry.d.ts +5 -1
- package/dist/render/CascadedShadows.d.ts +2 -0
- package/dist/render/CascadedShadows.js +4 -0
- package/dist/render/EntityPreview.js +1 -1
- package/dist/render/EnvironmentLighting.d.ts +26 -7
- package/dist/render/EnvironmentLighting.js +40 -27
- package/dist/render/SceneLighting.js +2 -2
- package/dist/render/modelLoad.d.ts +7 -0
- package/dist/render/modelLoad.js +13 -0
- package/dist/render/modelRender.d.ts +1 -0
- package/dist/render/modelRender.js +3 -0
- package/dist/render/modelSurface.d.ts +39 -0
- package/dist/render/modelSurface.js +206 -0
- package/dist/render/useModelInstance.d.ts +1 -1
- package/dist/render/useModelInstance.js +2 -2
- package/dist/scatter/scatterModels.js +3 -1
- package/dist/scene/AuthoredScene.d.ts +11 -3
- package/dist/scene/AuthoredScene.js +17 -9
- package/dist/scene/CityRenderer.js +142 -25
- package/dist/scene/cityJunctionSurface.d.ts +7 -0
- package/dist/scene/cityJunctionSurface.js +34 -0
- package/dist/settings/settingsController.js +1 -1
- package/dist/terrain/groundEdgeFade.d.ts +67 -0
- package/dist/terrain/groundEdgeFade.js +234 -0
- package/dist/terrain/index.d.ts +5 -0
- package/dist/terrain/index.js +5 -0
- package/dist/terrain/surfaceRibbonMaterial.d.ts +62 -0
- package/dist/terrain/surfaceRibbonMaterial.js +227 -0
- package/dist/terrain/terrainDetailMaterial.d.ts +33 -10
- package/dist/terrain/terrainDetailMaterial.js +149 -63
- package/dist/terrain/terrainPaintColor.d.ts +12 -3
- package/dist/terrain/terrainPaintColor.js +46 -1
- package/dist/terrain/terrainQuality.d.ts +5 -0
- package/dist/terrain/terrainQuality.js +7 -0
- package/dist/terrain/terrainSurfaceShader.d.ts +16 -0
- package/dist/terrain/terrainSurfaceShader.js +266 -0
- package/dist/terrain/terrainSurfaces.d.ts +59 -0
- package/dist/terrain/terrainSurfaces.js +199 -0
- package/dist/world/StaticObjectModels.d.ts +1 -1
- package/dist/world/StaticObjectModels.js +1 -1
- package/dist/world/WorldScene.js +1 -1
- package/dist/world/staticObjectBatches.d.ts +5 -1
- package/dist/world/staticObjectBatches.js +9 -5
- package/dist/world/worldBarSamples.d.ts +13 -1
- package/dist/world/worldBarSamples.js +40 -8
- package/package.json +5 -5
- package/skills/jgengine/api.md +18 -18
- package/skills/jgengine-ui/SKILL.md +4 -0
- package/skills/jgengine-ui/api.md +254 -37
- package/skills/jgengine-ui/capabilities.md +58 -0
- package/skills/jgengine-ui/reference-primitives.md +4 -4
- package/skills/jgengine-world/api.md +27 -14
- package/skills/jgengine-world/capabilities.md +4 -0
- package/skills/jgengine-world/reference.md +27 -8
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
|
+
import type { GraphicsQuality } from "@jgengine/core/settings/settingsModel";
|
|
2
3
|
import type { ResolvedTerrainDetail } from "@jgengine/core/world/terrain";
|
|
4
|
+
import type { TerrainPaintTextures } from "./terrainSurfaces.js";
|
|
3
5
|
/** The built procedural detail terrain material, ready to mount on the ground mesh. */
|
|
4
6
|
export interface TerrainDetailMaterialHandle {
|
|
5
7
|
material: THREE.MeshStandardMaterial;
|
|
8
|
+
/** Editor relief-contour strength 0..1 (0 in play); write `.value` to retune without recompiling. */
|
|
9
|
+
contour: {
|
|
10
|
+
value: number;
|
|
11
|
+
};
|
|
6
12
|
}
|
|
7
13
|
/**
|
|
8
14
|
* Loaded PBR textures matching a resolved `ResolvedTerrainDetailMaterial.maps`' roles. The caller
|
|
@@ -15,17 +21,34 @@ export interface TerrainDetailMaterialTextures {
|
|
|
15
21
|
roughness?: THREE.Texture;
|
|
16
22
|
ao?: THREE.Texture;
|
|
17
23
|
}
|
|
24
|
+
/** Surface-pass inputs beyond the resolved detail config. */
|
|
25
|
+
export interface TerrainDetailMaterialOptions {
|
|
26
|
+
/** Authored paint coverage from {@link createTerrainPaintTextures}; omit for unpainted ground. */
|
|
27
|
+
paint?: TerrainPaintTextures;
|
|
28
|
+
/** Graphics tier. `low` draws one cheap mottle per surface and skips micro-normals. Default `high`. */
|
|
29
|
+
quality?: GraphicsQuality;
|
|
30
|
+
/**
|
|
31
|
+
* Surface classes to compile (bit `1 << classIndex`); defaults to the paint's own classes. An
|
|
32
|
+
* editor that can paint any surface passes {@link ALL_TERRAIN_SURFACE_CLASSES}.
|
|
33
|
+
*/
|
|
34
|
+
classMask?: number;
|
|
35
|
+
/** Initial editor relief-contour strength. Default 0. */
|
|
36
|
+
contour?: number;
|
|
37
|
+
}
|
|
38
|
+
/** Compile every surface response, for an editor viewport whose paint can change to any surface. */
|
|
39
|
+
export declare const ALL_TERRAIN_SURFACE_CLASSES = 511;
|
|
18
40
|
/**
|
|
19
|
-
* A `MeshStandardMaterial`
|
|
20
|
-
*
|
|
21
|
-
* sand
|
|
22
|
-
*
|
|
23
|
-
*
|
|
41
|
+
* A `MeshStandardMaterial` that renders terrain as ground rather than flat colour. Each painted
|
|
42
|
+
* layer draws its surface type's procedural response (grass clumps and blades, dirt clods and
|
|
43
|
+
* pebbles, gravel stones, cracked rock, rippled sand, wet mud, snow, road grain) in the layer's
|
|
44
|
+
* authored hue, with albedo, roughness and a micro-normal. Layers meet along noise-warped,
|
|
45
|
+
* height-aware edges instead of paint-cell blobs; steep slopes turn to rock; macro sweeps break up
|
|
46
|
+
* tiling. Unpainted ground keeps the biome-tinted vertex colour with procedural detail, plus the
|
|
47
|
+
* waterline sand and altitude snow bands. Full PBR: lit, shadowed and fogged like any standard material.
|
|
24
48
|
*
|
|
25
49
|
* When `detail.material` is set, pass the matching loaded `textures` (color/normal/roughness/ao):
|
|
26
50
|
* they tile by world position at `detail.material.repeat` world units per tile and blend over the
|
|
27
|
-
* procedural result at `detail.material.strength
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export declare function createTerrainDetailMaterial(detail: ResolvedTerrainDetail, textures?: TerrainDetailMaterialTextures): TerrainDetailMaterialHandle;
|
|
51
|
+
* procedural result at `detail.material.strength`.
|
|
52
|
+
* @internal
|
|
53
|
+
*/
|
|
54
|
+
export declare function createTerrainDetailMaterial(detail: ResolvedTerrainDetail, textures?: TerrainDetailMaterialTextures, options?: TerrainDetailMaterialOptions): TerrainDetailMaterialHandle;
|
|
@@ -1,49 +1,58 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
2
|
+
import { NOISE_GLSL, PAINT_PARS_GLSL, SURFACE_BUMP_NORMAL_GLSL, SURFACE_FUNCTIONS_GLSL } from "./terrainSurfaceShader.js";
|
|
3
|
+
const ROCK_CLASS_BIT = 1 << 4;
|
|
4
|
+
/** Compile every surface response, for an editor viewport whose paint can change to any surface. */
|
|
5
|
+
export const ALL_TERRAIN_SURFACE_CLASSES = 0x1ff;
|
|
6
|
+
// Slot 0 is the unpainted base, 1–8 the paint layers, 9 slope rock.
|
|
7
|
+
const SLOT_GLSL = /* glsl */ `
|
|
8
|
+
JgSurf jgSlot(int slot, vec3 baseTone, vec3 wp){
|
|
9
|
+
vec3 jgFlatC = baseTone;
|
|
10
|
+
JgSurf s;
|
|
11
|
+
if (slot == 9) {
|
|
12
|
+
jgFlatC = uRockColor;
|
|
13
|
+
s = jgSurface(4, uRockColor, roughness, 1.0, wp);
|
|
14
|
+
}
|
|
15
|
+
#ifdef JG_TERRAIN_PAINT
|
|
16
|
+
else if (slot > 0) {
|
|
17
|
+
int i = slot - 1;
|
|
18
|
+
jgFlatC = uLayerColor[i];
|
|
19
|
+
s = jgSurface(int(uLayerClass[i] + 0.5), uLayerColor[i], uLayerRough[i], uLayerScale[i], wp);
|
|
20
|
+
}
|
|
21
|
+
#endif
|
|
22
|
+
else {
|
|
23
|
+
s = jgSurface(0, baseTone, roughness, 1.0, wp);
|
|
24
|
+
}
|
|
25
|
+
s.albedo = mix(jgFlatC, s.albedo, uSurface);
|
|
26
|
+
s.height *= uSurface;
|
|
27
|
+
s.bump *= uSurface;
|
|
28
|
+
return s;
|
|
27
29
|
}
|
|
28
30
|
`;
|
|
29
31
|
/**
|
|
30
|
-
* A `MeshStandardMaterial`
|
|
31
|
-
*
|
|
32
|
-
* sand
|
|
33
|
-
*
|
|
34
|
-
*
|
|
32
|
+
* A `MeshStandardMaterial` that renders terrain as ground rather than flat colour. Each painted
|
|
33
|
+
* layer draws its surface type's procedural response (grass clumps and blades, dirt clods and
|
|
34
|
+
* pebbles, gravel stones, cracked rock, rippled sand, wet mud, snow, road grain) in the layer's
|
|
35
|
+
* authored hue, with albedo, roughness and a micro-normal. Layers meet along noise-warped,
|
|
36
|
+
* height-aware edges instead of paint-cell blobs; steep slopes turn to rock; macro sweeps break up
|
|
37
|
+
* tiling. Unpainted ground keeps the biome-tinted vertex colour with procedural detail, plus the
|
|
38
|
+
* waterline sand and altitude snow bands. Full PBR: lit, shadowed and fogged like any standard material.
|
|
35
39
|
*
|
|
36
40
|
* When `detail.material` is set, pass the matching loaded `textures` (color/normal/roughness/ao):
|
|
37
41
|
* they tile by world position at `detail.material.repeat` world units per tile and blend over the
|
|
38
|
-
* procedural result at `detail.material.strength
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
export function createTerrainDetailMaterial(detail, textures) {
|
|
42
|
+
* procedural result at `detail.material.strength`.
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
export function createTerrainDetailMaterial(detail, textures, options = {}) {
|
|
43
46
|
const hasTexture = textures !== undefined && detail.material !== undefined;
|
|
44
47
|
const repeat = detail.material?.repeat ?? 4;
|
|
45
48
|
const strength = detail.material?.strength ?? 1;
|
|
46
49
|
const tint = detail.material?.tint ?? 1;
|
|
50
|
+
const paint = options.paint;
|
|
51
|
+
const low = options.quality === "low";
|
|
52
|
+
const contour = { value: options.contour ?? 0 };
|
|
53
|
+
// Rock is always compiled for slope rock; each extra class costs shader time even where unused on
|
|
54
|
+
// GPUs that run both sides of a branch.
|
|
55
|
+
const classMask = (options.classMask ?? paint?.classMask ?? 0) | ROCK_CLASS_BIT;
|
|
47
56
|
const uniforms = {
|
|
48
57
|
uRockColor: { value: new THREE.Color(detail.rockColor) },
|
|
49
58
|
uSandColor: { value: new THREE.Color(detail.sandColor) },
|
|
@@ -56,6 +65,9 @@ export function createTerrainDetailMaterial(detail, textures) {
|
|
|
56
65
|
uDetailScale: { value: Math.max(0.5, detail.detailScale) },
|
|
57
66
|
uMacroScale: { value: Math.max(0.5, detail.macroScale) },
|
|
58
67
|
uStrength: { value: detail.strength },
|
|
68
|
+
uSurface: { value: detail.surfaceStrength },
|
|
69
|
+
uContour: contour,
|
|
70
|
+
...(paint === undefined ? {} : paint.uniforms),
|
|
59
71
|
...(hasTexture
|
|
60
72
|
? {
|
|
61
73
|
uMatColor: { value: textures.color },
|
|
@@ -74,6 +86,11 @@ export function createTerrainDetailMaterial(detail, textures) {
|
|
|
74
86
|
metalness: 0,
|
|
75
87
|
vertexColors: true,
|
|
76
88
|
});
|
|
89
|
+
material.defines = {
|
|
90
|
+
...(paint === undefined ? {} : { JG_TERRAIN_PAINT: "" }),
|
|
91
|
+
...(low ? { JG_SURFACE_LOW: "" } : {}),
|
|
92
|
+
JG_SURFACE_CLASSES: String(classMask),
|
|
93
|
+
};
|
|
77
94
|
material.onBeforeCompile = (shader) => {
|
|
78
95
|
Object.assign(shader.uniforms, uniforms);
|
|
79
96
|
shader.vertexShader = shader.vertexShader
|
|
@@ -109,12 +126,15 @@ jgCol = mix(jgCol, jgMatColor, uMatStrength * uMatTint);`
|
|
|
109
126
|
const materialRoughnessGlsl = hasTexture && textures.roughness !== undefined
|
|
110
127
|
? `
|
|
111
128
|
float jgMatRough = texture2D(uMatRoughness, jgMatUv).g;
|
|
112
|
-
|
|
129
|
+
// The map varies the authored roughness rather than replacing it: scanned ground roughness sits
|
|
130
|
+
// near 0.45, which turned matte terrain glossy enough to throw a wet sheen across dry sand.
|
|
131
|
+
roughnessFactor = clamp(roughnessFactor + (jgMatRough - 0.5) * uMatStrength * 0.6, 0.0, 1.0);`
|
|
113
132
|
: "";
|
|
133
|
+
// The map's tilt is world-space (U along +X, V along +Z on flat ground); `normal` is view-space.
|
|
114
134
|
const materialNormalGlsl = hasTexture
|
|
115
135
|
? `
|
|
116
136
|
vec2 jgMatNormalXy = texture2D(uMatNormal, jgMatUv).rg * 2.0 - 1.0;
|
|
117
|
-
normal = normalize(normal +
|
|
137
|
+
normal = normalize(normal + (viewMatrix * vec4(jgMatNormalXy.x, 0.0, jgMatNormalXy.y, 0.0)).xyz * uMatStrength * 0.6);`
|
|
118
138
|
: "";
|
|
119
139
|
const materialAoGlsl = hasTexture && textures.ao !== undefined
|
|
120
140
|
? `
|
|
@@ -136,48 +156,114 @@ uniform float uWaterLevel;
|
|
|
136
156
|
uniform float uDetailScale;
|
|
137
157
|
uniform float uMacroScale;
|
|
138
158
|
uniform float uStrength;
|
|
159
|
+
uniform float uSurface;
|
|
160
|
+
uniform float uContour;
|
|
139
161
|
${materialUniformsGlsl}
|
|
140
|
-
${NOISE_GLSL}
|
|
162
|
+
${NOISE_GLSL}
|
|
163
|
+
${SURFACE_FUNCTIONS_GLSL}
|
|
164
|
+
#ifdef JG_TERRAIN_PAINT
|
|
165
|
+
${PAINT_PARS_GLSL}
|
|
166
|
+
#endif
|
|
167
|
+
${SLOT_GLSL}`)
|
|
141
168
|
.replace("#include <color_fragment>", `#include <color_fragment>
|
|
142
169
|
vec2 jgWp = vJgWorldPos.xz;
|
|
143
|
-
|
|
170
|
+
jgFootprint = max(length(dFdx(vJgWorldPos)), length(dFdy(vJgWorldPos)));
|
|
144
171
|
float jgMacro = jgFbm(jgWp / uMacroScale);
|
|
145
|
-
float jgPatch = jgFbm(jgWp / (uMacroScale * 0.35) + vec2(37.2, 11.7));
|
|
146
172
|
float jgSlope = 1.0 - clamp(vJgWorldNormal.y, 0.0, 1.0);
|
|
147
173
|
float jgH = vJgWorldPos.y;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
174
|
+
float jgRockW = smoothstep(uRockSlopeStart, uRockSlopeStart + 0.18, jgSlope) * mix(uStrength, 1.0, uSurface);
|
|
175
|
+
|
|
176
|
+
// Only the two strongest of base, eight paint slots and slope rock are shaded; a third
|
|
177
|
+
// contribution is negligible once the coverage is smoothed, and shading every slot cost 10x.
|
|
178
|
+
vec4 jgPa = vec4(0.0);
|
|
179
|
+
vec4 jgPb = vec4(0.0);
|
|
180
|
+
float jgPaintTotal = 0.0;
|
|
181
|
+
#ifdef JG_TERRAIN_PAINT
|
|
182
|
+
vec2 jgPaintTexUv = jgPaintUv(jgWp);
|
|
183
|
+
jgPa = texture2D(uPaintA, jgPaintTexUv);
|
|
184
|
+
jgPb = texture2D(uPaintB, jgPaintTexUv);
|
|
185
|
+
jgPaintTotal = dot(jgPa, vec4(1.0)) + dot(jgPb, vec4(1.0));
|
|
186
|
+
#endif
|
|
187
|
+
float jgPaintNorm = (1.0 - jgRockW) / max(1.0, jgPaintTotal);
|
|
188
|
+
jgPaintTotal = min(jgPaintTotal, 1.0);
|
|
189
|
+
int jgI1 = 0;
|
|
190
|
+
int jgI2 = 0;
|
|
191
|
+
float jgW1 = (1.0 - jgPaintTotal) * (1.0 - jgRockW);
|
|
192
|
+
float jgW2 = 0.0;
|
|
193
|
+
#define JG_PICK(idx, w) { float jgPk = (w); if (jgPk > jgW1) { jgI2 = jgI1; jgW2 = jgW1; jgI1 = idx; jgW1 = jgPk; } else if (jgPk > jgW2) { jgI2 = idx; jgW2 = jgPk; } }
|
|
194
|
+
#ifdef JG_TERRAIN_PAINT
|
|
195
|
+
JG_PICK(1, jgPa.r * jgPaintNorm)
|
|
196
|
+
JG_PICK(2, jgPa.g * jgPaintNorm)
|
|
197
|
+
JG_PICK(3, jgPa.b * jgPaintNorm)
|
|
198
|
+
JG_PICK(4, jgPa.a * jgPaintNorm)
|
|
199
|
+
JG_PICK(5, jgPb.r * jgPaintNorm)
|
|
200
|
+
JG_PICK(6, jgPb.g * jgPaintNorm)
|
|
201
|
+
JG_PICK(7, jgPb.b * jgPaintNorm)
|
|
202
|
+
JG_PICK(8, jgPb.a * jgPaintNorm)
|
|
203
|
+
#endif
|
|
204
|
+
JG_PICK(9, jgRockW)
|
|
205
|
+
bool jgTwo = jgW2 > 0.004;
|
|
206
|
+
|
|
154
207
|
vec3 jgBase = diffuseColor.rgb;
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
jgBase *= 1.0
|
|
161
|
-
|
|
162
|
-
jgBase
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
208
|
+
if (jgI1 == 0 || (jgTwo && jgI2 == 0)) {
|
|
209
|
+
float jgFine = jgFbm(jgWp / uDetailScale);
|
|
210
|
+
float jgPatch = jgFbm(jgWp / (uMacroScale * 0.35) + vec2(37.2, 11.7));
|
|
211
|
+
float jgFleck = jgVNoise(jgWp / max(0.05, uDetailScale * 0.024));
|
|
212
|
+
float jgFleckMid = jgVNoise(jgWp / max(0.12, uDetailScale * 0.07) + vec2(51.3, 7.9));
|
|
213
|
+
jgBase *= mix(1.0 - 0.2 * uStrength, 1.0 + 0.16 * uStrength, jgFine);
|
|
214
|
+
// Biome patchwork: hue-shifted dry sweeps and cooler wet pockets, tinted per-world via detail.sweeps.
|
|
215
|
+
jgBase = mix(jgBase, jgBase * uSweepDry, smoothstep(0.5, 0.8, jgMacro) * 0.55 * uStrength);
|
|
216
|
+
jgBase = mix(jgBase, jgBase * uSweepWet, smoothstep(0.52, 0.85, jgPatch) * 0.5 * uStrength);
|
|
217
|
+
jgBase *= 1.0 + (jgFbm(jgWp / (uMacroScale * 2.3) + vec2(9.1, 73.4)) - 0.5) * 0.22 * uStrength;
|
|
218
|
+
jgBase *= 1.0 + (jgFleck - 0.5) * 0.48 * uStrength * jgBand(1.0 / max(0.05, uDetailScale * 0.024));
|
|
219
|
+
jgBase *= 1.0 + (jgFleckMid - 0.5) * 0.34 * uStrength * jgBand(1.0 / max(0.12, uDetailScale * 0.07));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
JgSurf jgS1 = jgSlot(jgI1, jgBase, vJgWorldPos);
|
|
223
|
+
JgSurf jgS2 = jgS1;
|
|
224
|
+
float jgA1 = 1.0;
|
|
225
|
+
float jgA2 = 0.0;
|
|
226
|
+
if (jgTwo) {
|
|
227
|
+
jgS2 = jgSlot(jgI2, jgBase, vJgWorldPos);
|
|
228
|
+
float jgB1 = jgW1 / (jgW1 + jgW2);
|
|
229
|
+
float jgB2 = 1.0 - jgB1;
|
|
230
|
+
// Height-aware blend: along an edge the layer standing taller (gravel stones, grass clumps) wins
|
|
231
|
+
// first, so transitions read as one material breaking through another, not a cross-fade.
|
|
232
|
+
float jgH1 = jgB1 + jgS1.height * 0.5;
|
|
233
|
+
float jgH2 = jgB2 + jgS2.height * 0.5;
|
|
234
|
+
float jgM = max(jgH1, jgH2) - 0.28;
|
|
235
|
+
jgA1 = mix(jgB1, max(0.0, jgH1 - jgM), uSurface);
|
|
236
|
+
jgA2 = mix(jgB2, max(0.0, jgH2 - jgM), uSurface);
|
|
237
|
+
float jgT = max(jgA1 + jgA2, 1e-4);
|
|
238
|
+
jgA1 /= jgT;
|
|
239
|
+
jgA2 /= jgT;
|
|
240
|
+
}
|
|
241
|
+
vec3 jgCol = jgS1.albedo * jgA1 + jgS2.albedo * jgA2;
|
|
242
|
+
float jgRough = jgS1.rough * jgA1 + jgS2.rough * jgA2;
|
|
243
|
+
float jgBumpH = jgS1.bump * jgA1 + jgS2.bump * jgA2;
|
|
244
|
+
|
|
245
|
+
// Large-scale light and sun-dried sweeps over painted ground, so a field painted one hue never tiles flat.
|
|
246
|
+
float jgPaintCover = jgPaintTotal * (1.0 - jgRockW) * uSurface;
|
|
247
|
+
jgCol *= 1.0 + (jgFbm2(jgWp / (uMacroScale * 1.3) + vec2(3.7, 61.2)) - 0.5) * 0.3 * jgPaintCover;
|
|
248
|
+
jgCol = mix(jgCol, jgCol * uSweepDry, smoothstep(0.52, 0.78, jgMacro) * 0.2 * jgPaintCover);
|
|
249
|
+
|
|
167
250
|
float jgSandW = smoothstep(uWaterLevel + 2.4, uWaterLevel - 0.4, jgH) * (1.0 - jgRockW) * uStrength;
|
|
168
|
-
jgCol = mix(jgCol,
|
|
169
|
-
vec3 jgSnow = uSnowColor * mix(0.94, 1.04, jgFine);
|
|
251
|
+
jgCol = mix(jgCol, uSandColor * (0.9 + 0.2 * jgMacro), jgSandW);
|
|
170
252
|
float jgSnowW = smoothstep(uSnowHeight, uSnowHeight + 8.0, jgH) * (1.0 - jgRockW * 0.6) * uStrength;
|
|
171
|
-
jgCol = mix(jgCol,
|
|
253
|
+
jgCol = mix(jgCol, uSnowColor * (0.94 + 0.1 * jgMacro), jgSnowW);
|
|
172
254
|
${materialColorGlsl}
|
|
255
|
+
float jgContour = 0.5 + 0.5 * cos(jgH / 1.5 * 6.2831853);
|
|
256
|
+
jgCol *= (1.0 - uContour * 0.4 * (1.0 - jgContour)) * (1.0 + clamp(jgH * 0.03, -0.12, 0.22) * uContour);
|
|
173
257
|
diffuseColor.rgb = jgCol;`)
|
|
174
258
|
.replace("#include <roughnessmap_fragment>", `#include <roughnessmap_fragment>
|
|
259
|
+
roughnessFactor = clamp(jgRough, 0.04, 1.0);
|
|
175
260
|
${materialRoughnessGlsl}`)
|
|
176
261
|
.replace("#include <normal_fragment_maps>", `#include <normal_fragment_maps>
|
|
262
|
+
${SURFACE_BUMP_NORMAL_GLSL}
|
|
177
263
|
${materialNormalGlsl}`)
|
|
178
264
|
.replace("#include <aomap_fragment>", `#include <aomap_fragment>
|
|
179
265
|
${materialAoGlsl}`);
|
|
180
266
|
};
|
|
181
|
-
material.customProgramCacheKey = () => `jgengine-terrain-detail-${
|
|
182
|
-
return { material };
|
|
267
|
+
material.customProgramCacheKey = () => `jgengine-terrain-detail-${hasTexture ? "tex" : "flat"}-${hasTexture && textures.roughness !== undefined ? "rough" : "scalar"}-${hasTexture && textures.ao !== undefined ? "ao" : "unoccluded"}-${paint === undefined ? "bare" : "paint"}-${low ? "low" : "full"}-${classMask}`;
|
|
268
|
+
return { material, contour };
|
|
183
269
|
}
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
import type { EditableTerrain } from "@jgengine/core/world/terraform";
|
|
3
|
-
/**
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
/**
|
|
4
|
+
* Live authored paint channels needed to sample a terrain's material stack without its heightfield.
|
|
5
|
+
* With the grid fields present, paint is filtered across neighbouring cells instead of snapping to each cell.
|
|
6
|
+
*/
|
|
7
|
+
export type TerrainPaintSource = Pick<EditableTerrain, "layers" | "weightsAt" | "surfaceAt"> & Partial<Pick<EditableTerrain, "gridBounds" | "cols" | "rows">>;
|
|
8
|
+
/**
|
|
9
|
+
* Blends authored paint over the base ground color already in `out`; returns false (leaving `out`
|
|
10
|
+
* untouched) where no painted cell contributes.
|
|
11
|
+
*/
|
|
6
12
|
export type TerrainPaintColorSampler = (x: number, z: number, out: THREE.Color) => boolean;
|
|
7
13
|
/**
|
|
8
14
|
* Shares layer tint, opacity and linear weighted-color blending between editor and runtime ground.
|
|
15
|
+
* Paint lives on a coarse cell grid; when the source exposes that grid, each sample smoothly blends
|
|
16
|
+
* the four surrounding cell centres (unpainted cells contribute the caller's base color in `out`),
|
|
17
|
+
* so painted paths and clearings get soft edges instead of grid-aligned stair steps.
|
|
9
18
|
* @capability terrain-paint-color sample authored terrain surfaces and blended layers for shared ground rendering
|
|
10
19
|
*/
|
|
11
20
|
export declare function createTerrainPaintColorSampler(terrain: TerrainPaintSource): TerrainPaintColorSampler;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
import { TERRAIN_MATERIAL_COLORS } from "@jgengine/core/editor/terrainMaterials";
|
|
3
|
+
const smooth = (t) => t * t * (3 - 2 * t);
|
|
3
4
|
/**
|
|
4
5
|
* Shares layer tint, opacity and linear weighted-color blending between editor and runtime ground.
|
|
6
|
+
* Paint lives on a coarse cell grid; when the source exposes that grid, each sample smoothly blends
|
|
7
|
+
* the four surrounding cell centres (unpainted cells contribute the caller's base color in `out`),
|
|
8
|
+
* so painted paths and clearings get soft edges instead of grid-aligned stair steps.
|
|
5
9
|
* @capability terrain-paint-color sample authored terrain surfaces and blended layers for shared ground rendering
|
|
6
10
|
*/
|
|
7
11
|
export function createTerrainPaintColorSampler(terrain) {
|
|
8
12
|
const layers = terrain.layers;
|
|
9
13
|
const colors = layers.map(layer => new THREE.Color(layer.tint ?? TERRAIN_MATERIAL_COLORS[layer.surface] ?? "#7c7f86"));
|
|
10
14
|
const defaults = new Map(Object.entries(TERRAIN_MATERIAL_COLORS).map(([id, color]) => [id, new THREE.Color(color)]));
|
|
11
|
-
|
|
15
|
+
const cellColor = (x, z, out) => {
|
|
12
16
|
const weights = terrain.weightsAt(x, z);
|
|
13
17
|
if (layers.length > 0 && weights.length === layers.length) {
|
|
14
18
|
let total = 0, r = 0, g = 0, b = 0;
|
|
@@ -44,4 +48,45 @@ export function createTerrainPaintColorSampler(terrain) {
|
|
|
44
48
|
out.copy(color);
|
|
45
49
|
return true;
|
|
46
50
|
};
|
|
51
|
+
const cols = terrain.cols;
|
|
52
|
+
const rows = terrain.rows;
|
|
53
|
+
if (terrain.gridBounds === undefined || cols === undefined || rows === undefined || cols < 2 || rows < 2)
|
|
54
|
+
return cellColor;
|
|
55
|
+
const grid = terrain.gridBounds;
|
|
56
|
+
const cellW = (grid.maxX - grid.minX) / cols;
|
|
57
|
+
const cellD = (grid.maxZ - grid.minZ) / rows;
|
|
58
|
+
const base = new THREE.Color();
|
|
59
|
+
const corner = new THREE.Color();
|
|
60
|
+
return (x, z, out) => {
|
|
61
|
+
if (x < grid.minX || x > grid.maxX || z < grid.minZ || z > grid.maxZ)
|
|
62
|
+
return false;
|
|
63
|
+
const fx = (x - grid.minX) / cellW - 0.5;
|
|
64
|
+
const fz = (z - grid.minZ) / cellD - 0.5;
|
|
65
|
+
const gx = Math.floor(fx);
|
|
66
|
+
const gz = Math.floor(fz);
|
|
67
|
+
const tx = smooth(fx - gx);
|
|
68
|
+
const tz = smooth(fz - gz);
|
|
69
|
+
base.copy(out);
|
|
70
|
+
let painted = false, r = 0, g = 0, b = 0;
|
|
71
|
+
for (let dz = 0; dz <= 1; dz++) {
|
|
72
|
+
const cz = Math.min(rows - 1, Math.max(0, gz + dz));
|
|
73
|
+
const wz = dz === 0 ? 1 - tz : tz;
|
|
74
|
+
for (let dx = 0; dx <= 1; dx++) {
|
|
75
|
+
const cx = Math.min(cols - 1, Math.max(0, gx + dx));
|
|
76
|
+
const w = (dx === 0 ? 1 - tx : tx) * wz;
|
|
77
|
+
if (w <= 0)
|
|
78
|
+
continue;
|
|
79
|
+
corner.copy(base);
|
|
80
|
+
if (cellColor(grid.minX + (cx + 0.5) * cellW, grid.minZ + (cz + 0.5) * cellD, corner))
|
|
81
|
+
painted = true;
|
|
82
|
+
r += corner.r * w;
|
|
83
|
+
g += corner.g * w;
|
|
84
|
+
b += corner.b * w;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (!painted)
|
|
88
|
+
return false;
|
|
89
|
+
out.setRGB(r, g, b);
|
|
90
|
+
return true;
|
|
91
|
+
};
|
|
47
92
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { GraphicsQuality } from "@jgengine/core/settings/settingsModel";
|
|
2
|
+
/** The player's graphics tier as terrain materials see it; the shell provides it, `high` outside one. */
|
|
3
|
+
export declare const TerrainQualityContext: import("react").Context<GraphicsQuality>;
|
|
4
|
+
/** Graphics tier for terrain surface shading (`low` takes the cheap path). */
|
|
5
|
+
export declare function useTerrainQuality(): GraphicsQuality;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
/** The player's graphics tier as terrain materials see it; the shell provides it, `high` outside one. */
|
|
3
|
+
export const TerrainQualityContext = createContext("high");
|
|
4
|
+
/** Graphics tier for terrain surface shading (`low` takes the cheap path). */
|
|
5
|
+
export function useTerrainQuality() {
|
|
6
|
+
return useContext(TerrainQualityContext);
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Integer-hash value noise (`jgHashUint`, `jgVNoise`, `jgFbm`) every surface chunk builds on. */
|
|
2
|
+
export declare const NOISE_GLSL = "\nuint jgHashUint(uvec2 v){\n uint x = v.x * 1664525u + v.y * 1013904223u;\n x = (x ^ (x >> 16u)) * 2246822519u;\n x = (x ^ (x >> 13u)) * 3266489917u;\n x = x ^ (x >> 16u);\n return x;\n}\nfloat jgHash(vec2 p){\n ivec2 i = ivec2(floor(p));\n return float(jgHashUint(uvec2(i))) * (1.0 / 4294967296.0);\n}\nfloat jgVNoise(vec2 p){\n vec2 i = floor(p); vec2 f = fract(p);\n vec2 u = f * f * (3.0 - 2.0 * f);\n float a = jgHash(i);\n float b = jgHash(i + vec2(1.0, 0.0));\n float c = jgHash(i + vec2(0.0, 1.0));\n float d = jgHash(i + vec2(1.0, 1.0));\n return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\nfloat jgFbm(vec2 p){\n float v = 0.0; float amp = 0.5;\n for (int i = 0; i < 4; i++){ v += amp * jgVNoise(p); p *= 2.0; amp *= 0.5; }\n return v;\n}\n";
|
|
3
|
+
/**
|
|
4
|
+
* GLSL for the terrain surface pass: per-surface procedural ground (grass, dirt, gravel, rock, sand,
|
|
5
|
+
* mud, snow, road) and the height-aware blend between painted layers. Needs `NOISE_GLSL` first.
|
|
6
|
+
* Every octave is gated by `jgBand(frequency)` so detail finer than a pixel fades to its mean
|
|
7
|
+
* instead of shimmering at distance.
|
|
8
|
+
*/
|
|
9
|
+
export declare const SURFACE_FUNCTIONS_GLSL = "\nfloat jgFootprint;\nfloat jgFbm2(vec2 p){ return jgVNoise(p) * 0.62 + jgVNoise(p * 2.03 + 17.3) * 0.38; }\nfloat jgFbm3o(vec2 p){ return (jgVNoise(p) * 0.5 + jgVNoise(p * 2.03 + 17.3) * 0.3 + jgVNoise(p * 4.1 + 5.9) * 0.2); }\nfloat jgBand(float freq){ return 1.0 - smoothstep(0.16, 0.42, freq * jgFootprint); }\nvec2 jgHash22(vec2 cell){\n uint h = jgHashUint(uvec2(ivec2(floor(cell + 0.5))));\n uint h2 = h * 747796405u + 2891336453u;\n h2 = (h2 ^ (h2 >> 15u)) * 2246822519u;\n return vec2(float(h), float(h2 ^ (h2 >> 13u))) * (1.0 / 4294967296.0);\n}\nfloat jgHash3(vec3 i){\n ivec3 c = ivec3(i);\n return float(jgHashUint(uvec2(uint(c.x) ^ (uint(c.z) * 2654435761u), uint(c.y) + uint(c.z) * 1013904223u))) * (1.0 / 4294967296.0);\n}\nfloat jgVNoise3(vec3 p){\n vec3 i = floor(p); vec3 f = fract(p);\n vec3 u = f * f * (3.0 - 2.0 * f);\n float a = jgHash3(i), b = jgHash3(i + vec3(1.0, 0.0, 0.0));\n float c = jgHash3(i + vec3(0.0, 1.0, 0.0)), d = jgHash3(i + vec3(1.0, 1.0, 0.0));\n float e = jgHash3(i + vec3(0.0, 0.0, 1.0)), g = jgHash3(i + vec3(1.0, 0.0, 1.0));\n float h = jgHash3(i + vec3(0.0, 1.0, 1.0)), k = jgHash3(i + vec3(1.0, 1.0, 1.0));\n return mix(mix(mix(a, b, u.x), mix(c, d, u.x), u.y), mix(mix(e, g, u.x), mix(h, k, u.x), u.y), u.z);\n}\nfloat jgFbm3(vec3 p){\n float v = 0.0; float amp = 0.5;\n for (int i = 0; i < 3; i++){ v += amp * jgVNoise3(p); p = p * 2.03 + vec3(1.7, 9.2, 4.1); amp *= 0.5; }\n return v / 0.875;\n}\n// x: distance to the nearest feature point, y: F2 - F1 (0 on a cell border), z: that cell's random id.\nvec3 jgVoronoi(vec2 p){\n vec2 n = floor(p); vec2 f = fract(p);\n float d1 = 8.0; float d2 = 8.0; float id = 0.0;\n for (int j = -1; j <= 1; j++){\n for (int i = -1; i <= 1; i++){\n vec2 g = vec2(float(i), float(j));\n vec2 o = jgHash22(n + g);\n vec2 r = g + 0.15 + o * 0.7 - f;\n float d = dot(r, r);\n if (d < d1){ d2 = d1; d1 = d; id = o.x; } else if (d < d2){ d2 = d; }\n }\n }\n d1 = sqrt(d1);\n return vec3(d1, sqrt(d2) - d1, id);\n}\n\nstruct JgSurf { vec3 albedo; float height; float rough; float bump; };\n\n// height: 0..1 relief used to decide which layer wins along a blend edge; bump: displacement in metres.\nJgSurf jgSurface(int cls, vec3 color, float rough, float scale, vec3 wp){\n vec2 p = wp.xz * scale;\n JgSurf s;\n s.albedo = color; s.rough = rough; s.height = 0.4; s.bump = 0.0;\n float m = jgFbm3o(p / 1.6 + float(cls) * 13.7);\n#ifdef JG_SURFACE_LOW\n s.albedo = color * (0.84 + 0.34 * m);\n s.height = m;\n return s;\n#else\n bool shaded = false;\n#if (JG_SURFACE_CLASSES & 2) != 0\n if (cls == 1) {\n float clump = jgFbm3o(p / 0.9 + 5.3);\n float dry = smoothstep(0.5, 0.75, jgFbm2(p / 7.0 + 17.0));\n float blade = jgVNoise(p * 11.0 + clump * 3.0);\n float fine = jgVNoise(p * 27.0 + 91.0);\n float bBlade = jgBand(11.0);\n s.albedo = mix(color, color * vec3(1.24, 1.12, 0.7), dry * 0.55);\n s.albedo *= 0.74 + 0.48 * clump;\n s.albedo = mix(s.albedo, color * vec3(0.78, 0.64, 0.46), smoothstep(0.42, 0.24, clump) * 0.25);\n s.albedo *= 1.0 + (blade - 0.5) * 0.5 * bBlade + (fine - 0.5) * 0.32 * jgBand(27.0);\n s.height = 0.2 + clump * 0.55 + blade * 0.25 * bBlade;\n s.bump = clump * 0.014 + blade * 0.008 * bBlade;\n shaded = true;\n }\n#endif\n#if (JG_SURFACE_CLASSES & 4) != 0\n if (cls == 2) {\n float clod = jgVNoise(p * 3.0 + m * 2.0);\n float crumb = jgVNoise(p * 14.0 + 7.0);\n vec3 v = jgVoronoi(p * 2.2 + 7.7);\n float peb = (1.0 - smoothstep(0.1, 0.2, v.x)) * step(0.7, v.z) * jgBand(7.0);\n s.albedo = color * (0.78 + 0.44 * m);\n s.albedo *= 1.0 + (clod - 0.5) * 0.32 * jgBand(3.0) + (crumb - 0.5) * 0.24 * jgBand(14.0);\n s.albedo = mix(s.albedo, color * vec3(1.34, 1.3, 1.24) + 0.02, peb * 0.75);\n s.height = 0.05 + m * 0.4 + clod * 0.2 + peb * 0.5;\n s.bump = clod * 0.012 * jgBand(3.0) + crumb * 0.003 * jgBand(14.0) + peb * 0.012;\n shaded = true;\n }\n#endif\n#if (JG_SURFACE_CLASSES & 8) != 0\n if (cls == 3) {\n // Round pebbles from F1 distance (not Voronoi cells, which read as flagstone paving), two sizes,\n // packed over darker grit.\n vec3 va = jgVoronoi(p * 5.5);\n float ra = 0.28 + 0.18 * fract(va.z * 13.1);\n float sa = 1.0 - smoothstep(ra * 0.72, ra, va.x);\n float da = sqrt(max(0.0, 1.0 - (va.x / ra) * (va.x / ra))) * sa;\n vec3 vb = jgVoronoi(p * 11.0 + 3.1);\n float rb = 0.32 + 0.14 * fract(vb.z * 7.7);\n float sb = (1.0 - smoothstep(rb * 0.66, rb, vb.x)) * (1.0 - sa);\n float db = sqrt(max(0.0, 1.0 - (vb.x / rb) * (vb.x / rb))) * sb;\n float grit = jgVNoise(p * 24.0);\n float bA = jgBand(5.5);\n float bB = jgBand(11.0);\n vec3 hueA = mix(vec3(1.08, 1.0, 0.9), vec3(0.92, 0.97, 1.08), fract(va.z * 7.13));\n vec3 hueB = mix(vec3(1.05, 1.0, 0.93), vec3(0.95, 0.98, 1.05), fract(vb.z * 5.71));\n vec3 near = color * (0.58 + 0.2 * grit);\n near = mix(near, color * hueB * (0.66 + 0.5 * vb.z) * (0.72 + 0.28 * db), sb * bB);\n near = mix(near, color * hueA * (0.64 + 0.62 * va.z) * (0.7 + 0.3 * da), sa);\n s.albedo = mix(color * (0.8 + 0.26 * m), near, bA);\n s.height = 0.3 + max(da, db * 0.6 * bB) * 0.6 * bA;\n s.bump = (da * 0.03 + db * 0.012 * bB) * bA;\n s.rough = rough - 0.08 * sa * bA;\n shaded = true;\n }\n#endif\n#if (JG_SURFACE_CLASSES & 16) != 0\n if (cls == 4) {\n vec3 q = wp * scale;\n float n = jgFbm3(q / 1.8);\n float n2 = jgFbm3(q * 1.7 + 5.0);\n float grain = jgVNoise3(q * 9.0);\n float strata = sin(q.y * 2.6 + n * 5.0) * 0.5 + 0.5;\n float pits = smoothstep(0.72, 0.9, jgVNoise3(q * 4.3 + 11.0)) * jgBand(4.3);\n float lichen = smoothstep(0.6, 0.78, jgFbm2(p / 3.0 + 41.0));\n s.albedo = color * (0.4 + 1.25 * n) * (0.8 + 0.4 * n2 * jgBand(3.4)) * (1.0 - pits * 0.35) * (0.94 + strata * 0.12);\n s.albedo *= 1.0 + (grain - 0.5) * 0.3 * jgBand(9.0);\n s.albedo = mix(s.albedo, s.albedo * vec3(0.92, 1.06, 0.82), lichen * 0.45);\n s.height = 0.35 + n * 0.65;\n s.bump = n * 0.1 + n2 * 0.02 * jgBand(3.4) - pits * 0.006 + grain * 0.004 * jgBand(9.0);\n s.rough = rough - 0.1 * n;\n shaded = true;\n }\n#endif\n#if (JG_SURFACE_CLASSES & 32) != 0\n if (cls == 5) {\n float warp = jgFbm2(p / 3.0 + 3.3);\n float ripple = sin((p.x * 0.83 + p.y * 0.56) * 6.0 + warp * 7.0) * 0.5 + 0.5;\n float grain = jgVNoise(p * 40.0);\n float bRipple = jgBand(1.0);\n s.albedo = color * (0.92 + 0.16 * m) * (1.0 + (ripple - 0.5) * 0.1 * bRipple);\n float speck = jgVNoise(p * 9.0 + 2.7);\n s.albedo *= 1.0 + (grain - 0.5) * 0.14 * jgBand(40.0) + (speck - 0.5) * 0.12 * jgBand(9.0);\n s.height = 0.05 + ripple * 0.25 + m * 0.2;\n s.bump = ripple * 0.012 * bRipple;\n shaded = true;\n }\n#endif\n#if (JG_SURFACE_CLASSES & 64) != 0\n if (cls == 6) {\n float puddle = smoothstep(0.56, 0.66, jgFbm3o(p / 3.2 + 31.0));\n float lumps = jgVNoise(p * 4.0 + m);\n s.albedo = color * (0.8 + 0.38 * m) * (1.0 + (lumps - 0.5) * 0.26 * jgBand(4.0));\n s.albedo = mix(s.albedo, s.albedo * 0.58, puddle);\n s.rough = mix(rough, 0.22, puddle);\n s.height = (0.1 + m * 0.4 + lumps * 0.2) * (1.0 - puddle * 0.8);\n s.bump = lumps * 0.012 * jgBand(4.0) * (1.0 - puddle);\n shaded = true;\n }\n#endif\n#if (JG_SURFACE_CLASSES & 128) != 0\n if (cls == 7) {\n float drift = jgFbm2(p / 4.0 + 9.0);\n s.albedo = color * (0.93 + 0.1 * m + 0.04 * drift);\n s.height = 0.3 + drift * 0.5;\n s.bump = drift * 0.03 + m * 0.01;\n shaded = true;\n }\n#endif\n#if (JG_SURFACE_CLASSES & 256) != 0\n if (cls == 8) {\n float grain = jgVNoise(p * 30.0);\n float stain = jgFbm2(p / 5.0 + 2.0);\n s.albedo = color * (0.88 + 0.22 * stain) * (1.0 + (grain - 0.5) * 0.2 * jgBand(30.0));\n s.height = 0.2 + stain * 0.2;\n s.bump = grain * 0.0015 * jgBand(30.0);\n shaded = true;\n }\n#endif\n if (!shaded) {\n float fine = jgVNoise(p * 6.0 + 3.0);\n s.albedo = color * (1.0 + (fine - 0.5) * 0.18 * jgBand(6.0));\n s.height = 0.25 + m * 0.4;\n s.bump = m * 0.02 + fine * 0.004 * jgBand(6.0);\n }\n return s;\n#endif\n}\n";
|
|
10
|
+
/** Paint-coverage uniforms and the warped, smoothed cell lookup. */
|
|
11
|
+
export declare const PAINT_PARS_GLSL = "\nuniform sampler2D uPaintA;\nuniform sampler2D uPaintB;\nuniform vec2 uPaintMin;\nuniform vec2 uPaintCell;\nuniform vec2 uPaintGrid;\nuniform float uPaintWarp;\nuniform vec3 uLayerColor[8];\nuniform float uLayerClass[8];\nuniform float uLayerRough[8];\nuniform float uLayerScale[8];\nfloat jgWarpNoise(vec2 p){ return (jgVNoise(p) * 0.65 + jgVNoise(p * 2.7 + 13.1) * 0.35) - 0.5; }\nvec2 jgPaintUv(vec2 wp){\n vec2 cell = (wp - uPaintMin) / uPaintCell;\n cell += vec2(jgWarpNoise(wp / 2.3 + 4.1), jgWarpNoise(wp / 2.3 + vec2(19.7, 3.3))) * uPaintWarp;\n vec2 c = cell - 0.5;\n vec2 i = floor(c); vec2 f = fract(c);\n f = f * f * (3.0 - 2.0 * f);\n return (i + f + 0.5) / uPaintGrid;\n}\n";
|
|
12
|
+
/**
|
|
13
|
+
* Micro-normal from a surface height `jgBumpH` (metres) via screen-space derivatives; goes after
|
|
14
|
+
* `normal_fragment_maps`. Skipped on the low tier.
|
|
15
|
+
*/
|
|
16
|
+
export declare const SURFACE_BUMP_NORMAL_GLSL = "\n#ifndef JG_SURFACE_LOW\n{\n // Bump from the blended surface height (metres) via screen-space derivatives, unnormalised so the\n // tilt is the true world slope of that height field at any distance.\n vec3 jgSigX = dFdx(-vViewPosition);\n vec3 jgSigY = dFdy(-vViewPosition);\n vec2 jgDh = vec2(dFdx(jgBumpH), dFdy(jgBumpH));\n vec3 jgR1 = cross(jgSigY, normal);\n vec3 jgR2 = cross(normal, jgSigX);\n float jgDet = dot(jgSigX, jgR1) * faceDirection;\n vec3 jgGrad = sign(jgDet) * (jgDh.x * jgR1 + jgDh.y * jgR2);\n normal = normalize(abs(jgDet) * normal - jgGrad);\n}\n#endif\n";
|