@jgengine/shell 0.10.0 → 0.12.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 +39 -2
- package/LICENSE +203 -661
- package/README.md +1 -1
- package/dist/CombatPresentation.d.ts +5 -0
- package/dist/CombatPresentation.js +8 -0
- package/dist/GameHost.d.ts +25 -1
- package/dist/GameHost.js +60 -4
- package/dist/GamePhaseStamp.d.ts +1 -0
- package/dist/GamePhaseStamp.js +9 -0
- package/dist/GamePlayer.d.ts +5 -0
- package/dist/GamePlayer.js +5 -0
- package/dist/GamePlayerShell.d.ts +5 -12
- package/dist/GamePlayerShell.js +67 -1332
- package/dist/GameUiPreview.js +3 -2
- package/dist/Shell3dPresentation.d.ts +63 -0
- package/dist/Shell3dPresentation.js +302 -0
- package/dist/ShellChrome.d.ts +51 -0
- package/dist/ShellChrome.js +51 -0
- package/dist/ShellHudPresentation.d.ts +41 -0
- package/dist/ShellHudPresentation.js +22 -0
- package/dist/audio/audioEngine.d.ts +4 -0
- package/dist/audio/audioEngine.js +128 -21
- package/dist/audio/audioWire.d.ts +40 -0
- package/dist/audio/audioWire.js +62 -0
- package/dist/audio/loopParams.d.ts +20 -0
- package/dist/audio/loopParams.js +28 -0
- package/dist/behaviour.d.ts +2 -1
- package/dist/behaviour.js +2 -1
- package/dist/behaviourAttach.d.ts +1 -0
- package/dist/behaviourAttach.js +1 -0
- package/dist/behaviourDriver.d.ts +1 -0
- package/dist/behaviourDriver.js +1 -0
- package/dist/boundActionDispatch.d.ts +13 -0
- package/dist/boundActionDispatch.js +37 -0
- package/dist/camera/GameCameraRig.d.ts +5 -2
- package/dist/camera/GameCameraRig.js +8 -3
- package/dist/camera/GameFirstPersonCamera.d.ts +9 -2
- package/dist/camera/GameFirstPersonCamera.js +8 -3
- package/dist/camera/GameInspectionCamera.d.ts +1 -1
- package/dist/camera/GameInspectionCamera.js +2 -2
- package/dist/camera/GameOrbitCamera.js +24 -9
- package/dist/camera/cameraBlendMath.d.ts +3 -0
- package/dist/camera/cameraBlendMath.js +3 -0
- package/dist/camera/cameraRigs.d.ts +3 -1
- package/dist/camera/cameraRigs.js +21 -3
- package/dist/camera/fovPreference.d.ts +6 -1
- package/dist/camera/fovPreference.js +6 -1
- package/dist/camera/index.d.ts +4 -7
- package/dist/camera/index.js +4 -7
- package/dist/camera/inspectionCameraMath.d.ts +7 -2
- package/dist/camera/inspectionCameraMath.js +7 -2
- package/dist/camera/orbitCameraMath.d.ts +36 -6
- package/dist/camera/orbitCameraMath.js +56 -6
- package/dist/camera/rigMath.d.ts +114 -24
- package/dist/camera/rigMath.js +129 -24
- package/dist/camera/rigResolve.d.ts +4 -2
- package/dist/camera/rigResolve.js +4 -2
- package/dist/camera/shakeChannel.d.ts +6 -2
- package/dist/camera/shakeChannel.js +6 -2
- package/dist/camera/shakeChannelMath.d.ts +1 -0
- package/dist/camera/shakeChannelMath.js +1 -0
- package/dist/commandSink.d.ts +8 -3
- package/dist/commandSink.js +8 -3
- package/dist/defineGame.d.ts +15 -1
- package/dist/defineGame.js +50 -9
- package/dist/devtools/ColPanel.d.ts +1 -0
- package/dist/devtools/ColPanel.js +14 -0
- package/dist/devtools/DevtoolsOverlay.d.ts +11 -7
- package/dist/devtools/DevtoolsOverlay.js +47 -462
- package/dist/devtools/KeysPanel.d.ts +4 -0
- package/dist/devtools/KeysPanel.js +31 -0
- package/dist/devtools/LogsPanel.d.ts +1 -0
- package/dist/devtools/LogsPanel.js +12 -0
- package/dist/devtools/NetPanel.d.ts +4 -0
- package/dist/devtools/NetPanel.js +10 -0
- package/dist/devtools/PerfPanel.d.ts +4 -0
- package/dist/devtools/PerfPanel.js +53 -0
- package/dist/devtools/TunePanel.d.ts +3 -0
- package/dist/devtools/TunePanel.js +309 -0
- package/dist/devtools/agentBridge.d.ts +41 -0
- package/dist/devtools/agentBridge.js +266 -0
- package/dist/devtools/collisionDebug.d.ts +6 -0
- package/dist/devtools/collisionDebug.js +6 -0
- package/dist/devtools/collisionDebugMath.d.ts +8 -2
- package/dist/devtools/collisionDebugMath.js +24 -5
- package/dist/devtools/devtoolsOverrides.d.ts +6 -0
- package/dist/devtools/devtoolsOverrides.js +48 -0
- package/dist/devtools/panelAtoms.d.ts +9 -0
- package/dist/devtools/panelAtoms.js +10 -0
- package/dist/devtools/perfDiagnose.d.ts +2 -0
- package/dist/devtools/perfDiagnose.js +43 -0
- package/dist/diagnostics/RuntimeDiagnostics.d.ts +36 -0
- package/dist/diagnostics/RuntimeDiagnostics.js +80 -0
- package/dist/drivers/FrameDriver.d.ts +37 -0
- package/dist/drivers/FrameDriver.js +232 -0
- package/dist/drivers/HudOnlyDriver.d.ts +16 -0
- package/dist/drivers/HudOnlyDriver.js +60 -0
- package/dist/environment/Daylight.d.ts +23 -7
- package/dist/environment/Daylight.js +106 -13
- package/dist/environment/EnvironmentScene.js +44 -9
- package/dist/environment/VolumetricClouds.d.ts +17 -0
- package/dist/environment/VolumetricClouds.js +202 -0
- package/dist/environment/daylightCycle.d.ts +3 -1
- package/dist/environment/daylightCycle.js +3 -1
- package/dist/environment/groundPadMath.d.ts +3 -0
- package/dist/environment/groundPadMath.js +3 -0
- package/dist/environment/index.d.ts +1 -0
- package/dist/environment/index.js +1 -0
- package/dist/environment/skyLightingPolicy.d.ts +2 -0
- package/dist/environment/skyLightingPolicy.js +2 -0
- package/dist/environment/terrainGroundColor.d.ts +9 -0
- package/dist/environment/terrainGroundColor.js +22 -0
- package/dist/gameKit.d.ts +27 -0
- package/dist/gameKit.js +29 -0
- package/dist/hotbarActions.d.ts +26 -0
- package/dist/hotbarActions.js +27 -0
- package/dist/inputSink.d.ts +16 -4
- package/dist/inputSink.js +57 -7
- package/dist/map/MapMarkerBeacons.d.ts +3 -2
- package/dist/materialOverride.d.ts +10 -0
- package/dist/materialOverride.js +15 -3
- package/dist/multiplayer.js +8 -2
- package/dist/postfx/PostProcessing.d.ts +9 -1
- package/dist/postfx/PostProcessing.js +56 -11
- package/dist/postfx/gradeShader.js +9 -1
- package/dist/presentationResolve.d.ts +35 -0
- package/dist/presentationResolve.js +51 -0
- package/dist/registry.d.ts +4 -2
- package/dist/render/EnvironmentLighting.d.ts +10 -0
- package/dist/render/EnvironmentLighting.js +34 -0
- package/dist/render/SceneLighting.d.ts +7 -0
- package/dist/render/SceneLighting.js +18 -0
- package/dist/render/SceneModels.d.ts +14 -0
- package/dist/render/SceneModels.js +337 -0
- package/dist/render/assetBase.d.ts +17 -0
- package/dist/render/assetBase.js +35 -0
- package/dist/render/defaultSurface.d.ts +29 -0
- package/dist/render/defaultSurface.js +101 -0
- package/dist/render/modelLoad.d.ts +24 -0
- package/dist/render/modelLoad.js +76 -0
- package/dist/render/modelRender.d.ts +11 -0
- package/dist/render/modelRender.js +15 -0
- package/dist/render/resolveModel.d.ts +54 -3
- package/dist/render/resolveModel.js +116 -4
- package/dist/render/useEntityRenderCues.d.ts +13 -0
- package/dist/render/useEntityRenderCues.js +62 -0
- package/dist/scatter/InstancedScatter.d.ts +24 -0
- package/dist/scatter/InstancedScatter.js +135 -0
- package/dist/scatter/scatterModels.d.ts +25 -0
- package/dist/scatter/scatterModels.js +61 -0
- package/dist/scatter/scatterProxies.d.ts +6 -0
- package/dist/scatter/scatterProxies.js +118 -0
- package/dist/scatter.d.ts +1 -0
- package/dist/scatter.js +1 -0
- package/dist/scene/AuthoredScene.d.ts +84 -0
- package/dist/scene/AuthoredScene.js +178 -0
- package/dist/scene/CityRenderer.d.ts +2 -0
- package/dist/scene/CityRenderer.js +1031 -0
- package/dist/scene/GeneratedAssetRenderer.d.ts +14 -0
- package/dist/scene/GeneratedAssetRenderer.js +59 -0
- package/dist/scene/GrassFieldRenderer.d.ts +2 -0
- package/dist/scene/GrassFieldRenderer.js +73 -0
- package/dist/scene/PoleLineRenderer.d.ts +2 -0
- package/dist/scene/PoleLineRenderer.js +287 -0
- package/dist/scene/SoilPatchRenderer.d.ts +2 -0
- package/dist/scene/SoilPatchRenderer.js +84 -0
- package/dist/scene/StudioStage.d.ts +37 -0
- package/dist/scene/StudioStage.js +42 -0
- package/dist/scene/WaterRenderer.d.ts +2 -0
- package/dist/scene/WaterRenderer.js +61 -0
- package/dist/scene/builtinSceneKindRenderers.d.ts +2 -0
- package/dist/scene/builtinSceneKindRenderers.js +26 -0
- package/dist/scene/cityWindowMaterial.d.ts +16 -0
- package/dist/scene/cityWindowMaterial.js +81 -0
- package/dist/scene/sceneKindRenderers.d.ts +30 -0
- package/dist/scene/sceneKindRenderers.js +17 -0
- package/dist/scene.d.ts +1 -0
- package/dist/scene.js +1 -0
- package/dist/settings/appliedSettings.d.ts +7 -1
- package/dist/settings/appliedSettings.js +13 -2
- package/dist/settings/settingsController.d.ts +2 -0
- package/dist/settings/settingsController.js +22 -1
- package/dist/shellConstants.d.ts +4 -0
- package/dist/shellConstants.js +4 -0
- package/dist/shellMovement.d.ts +8 -0
- package/dist/shellMovement.js +11 -0
- package/dist/shellPointer.d.ts +20 -0
- package/dist/shellPointer.js +28 -0
- package/dist/structures/PlacementGhost.d.ts +4 -0
- package/dist/structures/PlacementGhost.js +4 -0
- package/dist/structures/TransformGizmo.d.ts +53 -0
- package/dist/structures/TransformGizmo.js +59 -0
- package/dist/structures/index.d.ts +1 -0
- package/dist/structures/index.js +1 -0
- package/dist/terrain/GrassField.d.ts +11 -3
- package/dist/terrain/GrassField.js +16 -6
- package/dist/terrain/TerraformBrushCursor.js +3 -0
- package/dist/terrain/grassBudget.d.ts +11 -2
- package/dist/terrain/grassBudget.js +20 -4
- package/dist/terrain/grassGeometry.d.ts +35 -0
- package/dist/terrain/grassGeometry.js +85 -20
- package/dist/terrain/grassMaterial.d.ts +20 -0
- package/dist/terrain/grassMaterial.js +68 -13
- package/dist/terrain/heightfieldGeometry.d.ts +34 -0
- package/dist/terrain/heightfieldGeometry.js +109 -0
- package/dist/terrain/index.d.ts +1 -0
- package/dist/terrain/index.js +1 -0
- package/dist/terrain/random.d.ts +3 -0
- package/dist/terrain/random.js +3 -0
- package/dist/terrain/soilPatchMaterial.d.ts +23 -0
- package/dist/terrain/soilPatchMaterial.js +147 -0
- package/dist/terrain/terrainDetailMaterial.d.ts +19 -2
- package/dist/terrain/terrainDetailMaterial.js +87 -7
- package/dist/terrain/terrainMath.d.ts +8 -1
- package/dist/terrain/terrainMath.js +8 -1
- package/dist/touch/TouchControlsOverlay.d.ts +9 -5
- package/dist/touch/TouchControlsOverlay.js +201 -22
- package/dist/useShellMultiplayerSync.d.ts +9 -0
- package/dist/useShellMultiplayerSync.js +98 -0
- package/dist/visibility/CullingProvider.js +1 -1
- package/dist/water/Ocean.d.ts +7 -1
- package/dist/water/Ocean.js +16 -3
- package/dist/water/OceanConfig.d.ts +11 -0
- package/dist/water/OceanConfig.js +8 -0
- package/dist/water/OceanMaterial.d.ts +12 -0
- package/dist/water/OceanMaterial.js +9 -0
- package/dist/water/OceanShader.d.ts +2 -2
- package/dist/water/OceanShader.js +78 -10
- package/dist/weather/fireSpreadPose.d.ts +1 -0
- package/dist/weather/fireSpreadPose.js +1 -0
- package/dist/weather/weatherGeometry.d.ts +1 -0
- package/dist/weather/weatherGeometry.js +1 -0
- package/dist/weather/weatherMath.d.ts +3 -0
- package/dist/weather/weatherMath.js +3 -0
- package/dist/weather/weatherUniforms.d.ts +3 -0
- package/dist/weather/weatherUniforms.js +3 -0
- package/dist/world/PlaceScene.d.ts +11 -0
- package/dist/world/PlaceScene.js +27 -0
- package/dist/world/RetainedVfx.d.ts +10 -0
- package/dist/world/RetainedVfx.js +136 -0
- package/dist/world/WorldHud.d.ts +49 -2
- package/dist/world/WorldHud.js +87 -2
- package/dist/world/WorldScene.d.ts +21 -0
- package/dist/world/WorldScene.js +186 -0
- package/dist/world/WorldVfx.d.ts +9 -0
- package/dist/world/WorldVfx.js +184 -0
- package/dist/world/entityPose.d.ts +2 -0
- package/dist/world/entityPose.js +2 -0
- package/dist/world/floatTextStyle.d.ts +1 -0
- package/dist/world/floatTextStyle.js +1 -0
- package/dist/world/telegraphPulse.d.ts +1 -0
- package/dist/world/telegraphPulse.js +1 -0
- package/dist/world/worldBarSamples.d.ts +25 -0
- package/dist/world/worldBarSamples.js +38 -0
- package/dist/worldSky.d.ts +11 -0
- package/dist/worldSky.js +21 -0
- package/dist/worldSync.d.ts +2 -1
- package/dist/worldSync.js +2 -1
- package/package.json +6 -6
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +54 -0
- package/skills/jgengine/api.md +527 -0
- package/skills/jgengine/capabilities.md +131 -0
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-ui/SKILL.md +41 -0
- package/skills/jgengine-ui/api.md +2007 -0
- package/skills/jgengine-ui/capabilities.md +211 -0
- package/skills/jgengine-ui/recipes/portable-minimap.md +150 -0
- package/skills/jgengine-ui/reference.md +513 -0
- package/skills/jgengine-ui/references/visual-scorecard.md +73 -0
- package/skills/jgengine-world/SKILL.md +51 -0
- package/skills/jgengine-world/api.md +3056 -0
- package/skills/jgengine-world/capabilities.md +476 -0
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +261 -0
- package/skills/level-design/SKILL.md +54 -0
- package/skills/level-design/agents/openai.yaml +4 -0
- package/skills/level-design/references/application-playbook.md +192 -0
- package/skills/level-design/references/field-guide.md +337 -0
- package/skills/level-design/references/live-editor-loop.md +179 -0
- package/dist/cartridge.d.ts +0 -145
- package/dist/cartridge.js +0 -246
- package/llms.txt +0 -2761
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
const EXTENT_KEY = "jgHeightfieldExtent";
|
|
3
|
+
/**
|
|
4
|
+
* Re-samples a `PlaneGeometry(width, depth, segments, segments).rotateX(-π/2)` heightfield mesh
|
|
5
|
+
* from a `sampleHeight` field in place: vertex Y, optional vertex color, grid central-difference
|
|
6
|
+
* normals, and an analytically maintained bounding sphere. With a dirty `region`, the work is
|
|
7
|
+
* O(region vertices) — heights/colors update inside the covering vertex window, normals inside the
|
|
8
|
+
* window plus a one-vertex ring — never a whole-mesh `computeVertexNormals`/`computeBoundingSphere`
|
|
9
|
+
* pass, which is what keeps per-frame brush stamps inside the editor's frame budget. The bounding
|
|
10
|
+
* sphere derives from the fixed plane extents plus a running height range kept on
|
|
11
|
+
* `geometry.userData`; partial passes only expand it, a full pass resets it exactly.
|
|
12
|
+
* @capability heightfield-mesh-update in-place partial update of a displaced ground plane mesh
|
|
13
|
+
*/
|
|
14
|
+
export function displaceHeightfieldGeometry(geometry, sampleHeight, options) {
|
|
15
|
+
const position = geometry.attributes.position;
|
|
16
|
+
const normals = geometry.attributes.normal;
|
|
17
|
+
const { bounds } = options;
|
|
18
|
+
const cx = (bounds.minX + bounds.maxX) / 2;
|
|
19
|
+
const cz = (bounds.minZ + bounds.maxZ) / 2;
|
|
20
|
+
const width = Math.max(1, bounds.maxX - bounds.minX);
|
|
21
|
+
const depth = Math.max(1, bounds.maxZ - bounds.minZ);
|
|
22
|
+
// PlaneGeometry(w, d, s, s) rotated -π/2 about X is a (s+1)² row-major grid: x fastest ascending,
|
|
23
|
+
// z ascending per row (verified by heightfieldGeometry.test.ts against the three.js constructor).
|
|
24
|
+
const side = Math.round(Math.sqrt(position.count));
|
|
25
|
+
const segments = Math.max(1, side - 1);
|
|
26
|
+
const stepX = width / segments;
|
|
27
|
+
const stepZ = depth / segments;
|
|
28
|
+
let colors = geometry.attributes.color;
|
|
29
|
+
const wantColors = options.color !== undefined;
|
|
30
|
+
const colorsMissing = wantColors && (colors === undefined || colors.count !== position.count);
|
|
31
|
+
const region = options.region ?? null;
|
|
32
|
+
const full = region === null || colorsMissing;
|
|
33
|
+
if (colorsMissing) {
|
|
34
|
+
colors = new THREE.BufferAttribute(new Float32Array(position.count * 3), 3);
|
|
35
|
+
geometry.setAttribute("color", colors);
|
|
36
|
+
}
|
|
37
|
+
const clampCell = (value) => (value < 0 ? 0 : value > segments ? segments : value);
|
|
38
|
+
const col0 = full ? 0 : clampCell(Math.floor(((region.minX - bounds.minX) / width) * segments));
|
|
39
|
+
const col1 = full ? segments : clampCell(Math.ceil(((region.maxX - bounds.minX) / width) * segments));
|
|
40
|
+
const row0 = full ? 0 : clampCell(Math.floor(((region.minZ - bounds.minZ) / depth) * segments));
|
|
41
|
+
const row1 = full ? segments : clampCell(Math.ceil(((region.maxZ - bounds.minZ) / depth) * segments));
|
|
42
|
+
const tone = new THREE.Color();
|
|
43
|
+
let windowMin = Infinity;
|
|
44
|
+
let windowMax = -Infinity;
|
|
45
|
+
for (let row = row0; row <= row1; row += 1) {
|
|
46
|
+
const base = row * side;
|
|
47
|
+
for (let col = col0; col <= col1; col += 1) {
|
|
48
|
+
const index = base + col;
|
|
49
|
+
const x = position.getX(index) + cx;
|
|
50
|
+
const z = position.getZ(index) + cz;
|
|
51
|
+
const height = sampleHeight(x, z);
|
|
52
|
+
position.setY(index, height);
|
|
53
|
+
if (height < windowMin)
|
|
54
|
+
windowMin = height;
|
|
55
|
+
if (height > windowMax)
|
|
56
|
+
windowMax = height;
|
|
57
|
+
if (options.color !== undefined) {
|
|
58
|
+
options.color(x, z, height, tone);
|
|
59
|
+
colors.setXYZ(index, tone.r, tone.g, tone.b);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Grid central-difference normals over the window plus a one-vertex ring — a height change moves
|
|
64
|
+
// its neighbors' normals too. Matches smooth shading without a whole-mesh triangle pass.
|
|
65
|
+
const nc0 = Math.max(0, col0 - 1);
|
|
66
|
+
const nc1 = Math.min(segments, col1 + 1);
|
|
67
|
+
const nr0 = Math.max(0, row0 - 1);
|
|
68
|
+
const nr1 = Math.min(segments, row1 + 1);
|
|
69
|
+
for (let row = nr0; row <= nr1; row += 1) {
|
|
70
|
+
const rowD = Math.max(0, row - 1);
|
|
71
|
+
const rowU = Math.min(segments, row + 1);
|
|
72
|
+
for (let col = nc0; col <= nc1; col += 1) {
|
|
73
|
+
const colL = Math.max(0, col - 1);
|
|
74
|
+
const colR = Math.min(segments, col + 1);
|
|
75
|
+
const hL = position.getY(row * side + colL);
|
|
76
|
+
const hR = position.getY(row * side + colR);
|
|
77
|
+
const hD = position.getY(rowD * side + col);
|
|
78
|
+
const hU = position.getY(rowU * side + col);
|
|
79
|
+
const nx = -(hR - hL) / Math.max(1e-6, (colR - colL) * stepX);
|
|
80
|
+
const nz = -(hU - hD) / Math.max(1e-6, (rowU - rowD) * stepZ);
|
|
81
|
+
const inv = 1 / Math.sqrt(nx * nx + 1 + nz * nz);
|
|
82
|
+
normals.setXYZ(row * side + col, nx * inv, inv, nz * inv);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const extent = geometry.userData[EXTENT_KEY] ?? {
|
|
86
|
+
minY: Infinity,
|
|
87
|
+
maxY: -Infinity,
|
|
88
|
+
};
|
|
89
|
+
if (full) {
|
|
90
|
+
extent.minY = windowMin;
|
|
91
|
+
extent.maxY = windowMax;
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
extent.minY = Math.min(extent.minY, windowMin);
|
|
95
|
+
extent.maxY = Math.max(extent.maxY, windowMax);
|
|
96
|
+
}
|
|
97
|
+
geometry.userData[EXTENT_KEY] = extent;
|
|
98
|
+
const hasExtent = extent.minY <= extent.maxY;
|
|
99
|
+
const midY = hasExtent ? (extent.minY + extent.maxY) / 2 : 0;
|
|
100
|
+
const halfY = hasExtent ? (extent.maxY - extent.minY) / 2 : 0;
|
|
101
|
+
if (geometry.boundingSphere === null)
|
|
102
|
+
geometry.boundingSphere = new THREE.Sphere();
|
|
103
|
+
geometry.boundingSphere.center.set(0, midY, 0);
|
|
104
|
+
geometry.boundingSphere.radius = Math.sqrt((width / 2) ** 2 + (depth / 2) ** 2 + halfY ** 2);
|
|
105
|
+
position.needsUpdate = true;
|
|
106
|
+
if (colors !== undefined && wantColors)
|
|
107
|
+
colors.needsUpdate = true;
|
|
108
|
+
normals.needsUpdate = true;
|
|
109
|
+
}
|
package/dist/terrain/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { CarvedTerrain, type CarvedTerrainProps } from "./CarvedTerrain.js";
|
|
|
2
2
|
export { GrassField, type GrassFieldProps } from "./GrassField.js";
|
|
3
3
|
export { ProceduralGround, type ProceduralGroundProps } from "./ProceduralGround.js";
|
|
4
4
|
export { EditableGround, type EditableGroundProps } from "./EditableGround.js";
|
|
5
|
+
export { displaceHeightfieldGeometry, type HeightfieldColorFn, type HeightfieldDisplaceOptions, type HeightfieldRect, } from "./heightfieldGeometry.js";
|
|
5
6
|
export { TerraformBrushCursor, type TerraformBrushCursorProps } from "./TerraformBrushCursor.js";
|
|
6
7
|
export { createGrassBladeGeometry, resolveGrassBladeGeometryOptions, resolveGrassRange, type GrassBladeGeometryOptions, type GrassRange, type ResolvedGrassBladeGeometryOptions, } from "./grassGeometry.js";
|
|
7
8
|
export { createGrassMaterial, DEFAULT_GRASS_WIND, resolveGrassWind, type GrassMaterialHandle, type GrassMaterialOptions, type GrassShaderUniforms, type GrassWindOptions, } from "./grassMaterial.js";
|
package/dist/terrain/index.js
CHANGED
|
@@ -2,6 +2,7 @@ export { CarvedTerrain } from "./CarvedTerrain.js";
|
|
|
2
2
|
export { GrassField } from "./GrassField.js";
|
|
3
3
|
export { ProceduralGround } from "./ProceduralGround.js";
|
|
4
4
|
export { EditableGround } from "./EditableGround.js";
|
|
5
|
+
export { displaceHeightfieldGeometry, } from "./heightfieldGeometry.js";
|
|
5
6
|
export { TerraformBrushCursor } from "./TerraformBrushCursor.js";
|
|
6
7
|
export { createGrassBladeGeometry, resolveGrassBladeGeometryOptions, resolveGrassRange, } from "./grassGeometry.js";
|
|
7
8
|
export { createGrassMaterial, DEFAULT_GRASS_WIND, resolveGrassWind, } from "./grassMaterial.js";
|
package/dist/terrain/random.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export type TerrainSeed = number | string;
|
|
2
|
+
/** @internal */
|
|
2
3
|
export declare function seedToUint32(seed?: TerrainSeed): number;
|
|
4
|
+
/** @internal */
|
|
3
5
|
export declare function createSeededRandom(seed?: TerrainSeed): () => number;
|
|
6
|
+
/** @internal */
|
|
4
7
|
export declare function hashNoise2(x: number, z: number, seed?: TerrainSeed): number;
|
package/dist/terrain/random.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/** @internal */
|
|
1
2
|
export function seedToUint32(seed = 1) {
|
|
2
3
|
if (typeof seed === "number")
|
|
3
4
|
return seed >>> 0;
|
|
@@ -8,6 +9,7 @@ export function seedToUint32(seed = 1) {
|
|
|
8
9
|
}
|
|
9
10
|
return hash >>> 0;
|
|
10
11
|
}
|
|
12
|
+
/** @internal */
|
|
11
13
|
export function createSeededRandom(seed = 1) {
|
|
12
14
|
let state = seedToUint32(seed);
|
|
13
15
|
return () => {
|
|
@@ -18,6 +20,7 @@ export function createSeededRandom(seed = 1) {
|
|
|
18
20
|
return ((value ^ (value >>> 14)) >>> 0) / 4294967296;
|
|
19
21
|
};
|
|
20
22
|
}
|
|
23
|
+
/** @internal */
|
|
21
24
|
export function hashNoise2(x, z, seed = 1) {
|
|
22
25
|
let hash = seedToUint32(seed);
|
|
23
26
|
hash ^= Math.imul(x | 0, 374761393);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
import type { SoilRules } from "@jgengine/core/world/soilKind";
|
|
3
|
+
/** Optional noise-eroded edge-fade config for {@link createSoilPatchMaterial} — the patch dissolves into the ground at its border. */
|
|
4
|
+
export interface SoilPatchEdge {
|
|
5
|
+
/** Patch center on XZ, world units — with `halfSize`, drives the noise-eroded edge fade. */
|
|
6
|
+
center: readonly [number, number];
|
|
7
|
+
/** Patch half extents on XZ, world units. */
|
|
8
|
+
halfSize: readonly [number, number];
|
|
9
|
+
/** Fade band width in meters from the patch border inward. Default 1.6. */
|
|
10
|
+
fade?: number;
|
|
11
|
+
/** Terrain color under the patch: the border blends its *color* toward the ground it meets, not just its alpha. Defaults to the soil base color (color blend off). */
|
|
12
|
+
groundColor?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* A `MeshStandardMaterial` for one authored `soil` volume: a base dirt tint with a Worley crack-line
|
|
16
|
+
* network and an FBM moss-patch overlay injected via `onBeforeCompile`, seeded from `rules.seed`. Full
|
|
17
|
+
* PBR — lit, shadowed, fogged — like the terrain detail material it borrows its noise machinery from.
|
|
18
|
+
* Pass `edge` to add a noise-eroded alpha fade at the patch border (the draped scene-kind renderer
|
|
19
|
+
* does) so the patch blends into the surrounding ground. A game never calls this directly — author a
|
|
20
|
+
* soil patch via the editor's `soil` studio kind.
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export declare function createSoilPatchMaterial(rules: SoilRules, edge?: SoilPatchEdge): THREE.MeshStandardMaterial;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import * as THREE from "three";
|
|
2
|
+
/**
|
|
3
|
+
* Worley (F1/F2 cellular) crack mask + FBM moss mask — the same noise-driven-shader technique as
|
|
4
|
+
* `terrainDetailMaterial.ts`'s rock/sand/snow blend, injected here via `onBeforeCompile` into a
|
|
5
|
+
* dedicated soil-patch material instead of the whole-ground detail material. `F2 - F1` (the gap
|
|
6
|
+
* between nearest and second-nearest Worley feature distances) goes to zero right at a cell boundary,
|
|
7
|
+
* which reads as a network of cracked-earth veins once thresholded — the standard "cracked ground"
|
|
8
|
+
* shader trick.
|
|
9
|
+
*/
|
|
10
|
+
const SOIL_NOISE_GLSL = /* glsl */ `
|
|
11
|
+
uint jgSoilHashUint(uvec2 v){
|
|
12
|
+
uint x = v.x * 1664525u + v.y * 1013904223u;
|
|
13
|
+
x = (x ^ (x >> 16u)) * 2246822519u;
|
|
14
|
+
x = (x ^ (x >> 13u)) * 3266489917u;
|
|
15
|
+
x = x ^ (x >> 16u);
|
|
16
|
+
return x;
|
|
17
|
+
}
|
|
18
|
+
float jgSoilHash1(vec2 p){
|
|
19
|
+
return float(jgSoilHashUint(uvec2(floor(p)))) * (1.0 / 4294967296.0);
|
|
20
|
+
}
|
|
21
|
+
vec2 jgSoilHash2(vec2 p){
|
|
22
|
+
uvec2 base = uvec2(floor(p));
|
|
23
|
+
float a = float(jgSoilHashUint(base)) * (1.0 / 4294967296.0);
|
|
24
|
+
float b = float(jgSoilHashUint(base + uvec2(19u, 47u))) * (1.0 / 4294967296.0);
|
|
25
|
+
return vec2(a, b);
|
|
26
|
+
}
|
|
27
|
+
float jgSoilVNoise(vec2 p){
|
|
28
|
+
vec2 i = floor(p); vec2 f = fract(p);
|
|
29
|
+
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
30
|
+
float a = jgSoilHash1(i);
|
|
31
|
+
float b = jgSoilHash1(i + vec2(1.0, 0.0));
|
|
32
|
+
float c = jgSoilHash1(i + vec2(0.0, 1.0));
|
|
33
|
+
float d = jgSoilHash1(i + vec2(1.0, 1.0));
|
|
34
|
+
return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
|
|
35
|
+
}
|
|
36
|
+
float jgSoilFbm(vec2 p){
|
|
37
|
+
float v = 0.0; float amp = 0.5;
|
|
38
|
+
for (int i = 0; i < 4; i++){ v += amp * jgSoilVNoise(p); p *= 2.0; amp *= 0.5; }
|
|
39
|
+
return v;
|
|
40
|
+
}
|
|
41
|
+
vec2 jgSoilWorleyF1F2(vec2 p){
|
|
42
|
+
vec2 ip = floor(p);
|
|
43
|
+
vec2 fp = fract(p);
|
|
44
|
+
float f1 = 8.0; float f2 = 8.0;
|
|
45
|
+
for (int y = -1; y <= 1; y++){
|
|
46
|
+
for (int x = -1; x <= 1; x++){
|
|
47
|
+
vec2 neighbor = vec2(float(x), float(y));
|
|
48
|
+
vec2 point = jgSoilHash2(ip + neighbor);
|
|
49
|
+
float dist = length(neighbor + point - fp);
|
|
50
|
+
if (dist < f1) { f2 = f1; f1 = dist; }
|
|
51
|
+
else if (dist < f2) { f2 = dist; }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return vec2(f1, f2);
|
|
55
|
+
}
|
|
56
|
+
float jgSoilCrackMask(vec2 p, float scale){
|
|
57
|
+
vec2 f = jgSoilWorleyF1F2(p / scale);
|
|
58
|
+
return 1.0 - smoothstep(0.0, 0.065, f.y - f.x);
|
|
59
|
+
}
|
|
60
|
+
`;
|
|
61
|
+
function hashSeed(text) {
|
|
62
|
+
let h = 2166136261 >>> 0;
|
|
63
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
64
|
+
h ^= text.charCodeAt(i);
|
|
65
|
+
h = Math.imul(h, 16777619);
|
|
66
|
+
}
|
|
67
|
+
return (h >>> 0) % 100000;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A `MeshStandardMaterial` for one authored `soil` volume: a base dirt tint with a Worley crack-line
|
|
71
|
+
* network and an FBM moss-patch overlay injected via `onBeforeCompile`, seeded from `rules.seed`. Full
|
|
72
|
+
* PBR — lit, shadowed, fogged — like the terrain detail material it borrows its noise machinery from.
|
|
73
|
+
* Pass `edge` to add a noise-eroded alpha fade at the patch border (the draped scene-kind renderer
|
|
74
|
+
* does) so the patch blends into the surrounding ground. A game never calls this directly — author a
|
|
75
|
+
* soil patch via the editor's `soil` studio kind.
|
|
76
|
+
* @internal
|
|
77
|
+
*/
|
|
78
|
+
export function createSoilPatchMaterial(rules, edge) {
|
|
79
|
+
const seedOffset = hashSeed(rules.seed);
|
|
80
|
+
const material = new THREE.MeshStandardMaterial({
|
|
81
|
+
color: rules.baseColor,
|
|
82
|
+
roughness: 0.92,
|
|
83
|
+
metalness: 0,
|
|
84
|
+
...(edge === undefined ? {} : { transparent: true, polygonOffset: true, polygonOffsetFactor: -2, polygonOffsetUnits: -2 }),
|
|
85
|
+
});
|
|
86
|
+
material.onBeforeCompile = (shader) => {
|
|
87
|
+
shader.uniforms.uSoilCrackColor = { value: new THREE.Color(rules.crackColor) };
|
|
88
|
+
shader.uniforms.uSoilCrackScale = { value: Math.max(0.1, rules.crackScale) };
|
|
89
|
+
shader.uniforms.uSoilCrackIntensity = { value: rules.crackIntensity };
|
|
90
|
+
shader.uniforms.uSoilMossColor = { value: new THREE.Color(rules.mossColor) };
|
|
91
|
+
shader.uniforms.uSoilMossCoverage = { value: rules.mossCoverage };
|
|
92
|
+
shader.uniforms.uSoilSeed = { value: seedOffset };
|
|
93
|
+
if (edge !== undefined) {
|
|
94
|
+
shader.uniforms.uSoilCenter = { value: new THREE.Vector2(edge.center[0], edge.center[1]) };
|
|
95
|
+
shader.uniforms.uSoilHalfSize = { value: new THREE.Vector2(edge.halfSize[0], edge.halfSize[1]) };
|
|
96
|
+
shader.uniforms.uSoilEdgeFade = { value: edge.fade ?? 1.6 };
|
|
97
|
+
shader.uniforms.uSoilGroundColor = { value: new THREE.Color(edge.groundColor ?? rules.baseColor) };
|
|
98
|
+
}
|
|
99
|
+
shader.vertexShader = shader.vertexShader.replace("#include <common>", `#include <common>
|
|
100
|
+
varying vec3 vJgSoilWorldPos;`).replace("#include <begin_vertex>", `#include <begin_vertex>
|
|
101
|
+
vJgSoilWorldPos = (modelMatrix * vec4(transformed, 1.0)).xyz;`);
|
|
102
|
+
shader.fragmentShader = shader.fragmentShader.replace("#include <common>", `#include <common>
|
|
103
|
+
varying vec3 vJgSoilWorldPos;
|
|
104
|
+
uniform vec3 uSoilCrackColor;
|
|
105
|
+
uniform float uSoilCrackScale;
|
|
106
|
+
uniform float uSoilCrackIntensity;
|
|
107
|
+
uniform vec3 uSoilMossColor;
|
|
108
|
+
uniform float uSoilMossCoverage;
|
|
109
|
+
uniform float uSoilSeed;
|
|
110
|
+
${edge === undefined ? "" : "uniform vec2 uSoilCenter;\nuniform vec2 uSoilHalfSize;\nuniform float uSoilEdgeFade;\nuniform vec3 uSoilGroundColor;\n"}${SOIL_NOISE_GLSL}`).replace("#include <color_fragment>", `#include <color_fragment>
|
|
111
|
+
vec2 jgSoilP = vJgSoilWorldPos.xz + vec2(uSoilSeed * 0.37, uSoilSeed * 0.71);
|
|
112
|
+
float jgSoilCrack = jgSoilCrackMask(jgSoilP, uSoilCrackScale);
|
|
113
|
+
vec3 jgSoilCol = mix(diffuseColor.rgb, uSoilCrackColor, jgSoilCrack * uSoilCrackIntensity);
|
|
114
|
+
// Crack floors go darker still — paired with the relief normals they read as recessed, not painted.
|
|
115
|
+
jgSoilCol *= 1.0 - jgSoilCrack * uSoilCrackIntensity * 0.4;
|
|
116
|
+
// Sun-baked plate mottling so the dirt between cracks isn't one flat swatch.
|
|
117
|
+
jgSoilCol *= mix(0.92, 1.08, jgSoilFbm(jgSoilP / 1.1));
|
|
118
|
+
float jgSoilMossN = jgSoilFbm(jgSoilP / 2.4);
|
|
119
|
+
float jgSoilMossMask = smoothstep(1.0 - uSoilMossCoverage, 1.0 - uSoilMossCoverage + 0.18, jgSoilMossN);
|
|
120
|
+
// Fine clump break-up: moss creeps in speckled colonies (and pools into the cracks), not airbrushed blobs.
|
|
121
|
+
jgSoilMossMask *= smoothstep(0.3, 0.75, jgSoilFbm(jgSoilP / 0.55));
|
|
122
|
+
jgSoilMossMask = min(1.0, jgSoilMossMask + jgSoilMossMask * jgSoilCrack * 0.8);
|
|
123
|
+
jgSoilCol = mix(jgSoilCol, uSoilMossColor * mix(0.85, 1.1, jgSoilFbm(jgSoilP / 0.9)), jgSoilMossMask);${edge === undefined
|
|
124
|
+
? "\ndiffuseColor.rgb = jgSoilCol;"
|
|
125
|
+
: `
|
|
126
|
+
// Border: color walks toward the terrain tint across the fade band (alpha alone left a decal
|
|
127
|
+
// seam — the dirt must *become* the ground it meets), then a noise-eaten alpha finishes it.
|
|
128
|
+
vec2 jgSoilEdgeD = uSoilHalfSize - abs(vJgSoilWorldPos.xz - uSoilCenter);
|
|
129
|
+
float jgSoilEdgeDist = min(jgSoilEdgeD.x, jgSoilEdgeD.y);
|
|
130
|
+
float jgSoilEdgeNoise = jgSoilFbm(jgSoilP * 0.9) * uSoilEdgeFade;
|
|
131
|
+
float jgSoilInside = clamp((jgSoilEdgeDist - jgSoilEdgeNoise) / max(uSoilEdgeFade, 0.001) + 0.55, 0.0, 1.0);
|
|
132
|
+
jgSoilCol = mix(uSoilGroundColor, jgSoilCol, smoothstep(0.0, 0.85, jgSoilInside));
|
|
133
|
+
diffuseColor.rgb = jgSoilCol;
|
|
134
|
+
diffuseColor.a *= clamp(jgSoilInside * 1.5, 0.0, 1.0);`}`).replace("#include <normal_fragment_maps>", `#include <normal_fragment_maps>
|
|
135
|
+
// Crack relief: tilt normals along the Worley gradient so crack walls catch and lose the light
|
|
136
|
+
// (finite differences over the same field that colors the cracks — they self-shadow in sync).
|
|
137
|
+
vec2 jgSoilNp = vJgSoilWorldPos.xz + vec2(uSoilSeed * 0.37, uSoilSeed * 0.71);
|
|
138
|
+
float jgSoilNe = 0.05;
|
|
139
|
+
vec2 jgSoilGrad = vec2(
|
|
140
|
+
jgSoilCrackMask(jgSoilNp + vec2(jgSoilNe, 0.0), uSoilCrackScale) - jgSoilCrackMask(jgSoilNp - vec2(jgSoilNe, 0.0), uSoilCrackScale),
|
|
141
|
+
jgSoilCrackMask(jgSoilNp + vec2(0.0, jgSoilNe), uSoilCrackScale) - jgSoilCrackMask(jgSoilNp - vec2(0.0, jgSoilNe), uSoilCrackScale)
|
|
142
|
+
) / (2.0 * jgSoilNe);
|
|
143
|
+
normal = normalize(normal + vec3(jgSoilGrad.x, 0.0, jgSoilGrad.y) * uSoilCrackIntensity * 0.2);`);
|
|
144
|
+
};
|
|
145
|
+
material.customProgramCacheKey = () => `jgengine-soil-${rules.crackScale}-${rules.crackIntensity}-${rules.mossCoverage}-${seedOffset}-${edge === undefined ? "solid" : "faded"}`;
|
|
146
|
+
return material;
|
|
147
|
+
}
|
|
@@ -4,11 +4,28 @@ import type { ResolvedTerrainDetail } from "@jgengine/core/world/terrain";
|
|
|
4
4
|
export interface TerrainDetailMaterialHandle {
|
|
5
5
|
material: THREE.MeshStandardMaterial;
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Loaded PBR textures matching a resolved `ResolvedTerrainDetailMaterial.maps`' roles. The caller
|
|
9
|
+
* (a React component, via `useTexture`/`useLoader`) owns loading and disposal; `core` never touches
|
|
10
|
+
* `THREE.Texture` and this shell function never fetches a URL itself.
|
|
11
|
+
*/
|
|
12
|
+
export interface TerrainDetailMaterialTextures {
|
|
13
|
+
color: THREE.Texture;
|
|
14
|
+
normal: THREE.Texture;
|
|
15
|
+
roughness: THREE.Texture;
|
|
16
|
+
ao: THREE.Texture;
|
|
17
|
+
}
|
|
7
18
|
/**
|
|
8
19
|
* A `MeshStandardMaterial` whose fragment shader keeps the biome-tinted vertex
|
|
9
20
|
* colour as the base ground and blends procedural, noise-broken rock (by slope),
|
|
10
21
|
* sand (by waterline), and snow (by height) over it — textured-reading terrain
|
|
11
22
|
* with no image assets. Full PBR: lit, shadowed, and fogged like any standard
|
|
12
23
|
* material, so it composes with the post-processing chain.
|
|
13
|
-
|
|
14
|
-
|
|
24
|
+
*
|
|
25
|
+
* When `detail.material` is set, pass the matching loaded `textures` (color/normal/roughness/ao):
|
|
26
|
+
* they tile by world position at `detail.material.repeat` world units per tile and blend over the
|
|
27
|
+
* procedural result at `detail.material.strength` — the real-texture seam layers onto the existing
|
|
28
|
+
* look instead of replacing it.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
export declare function createTerrainDetailMaterial(detail: ResolvedTerrainDetail, textures?: TerrainDetailMaterialTextures): TerrainDetailMaterialHandle;
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
const NOISE_GLSL = /* glsl */ `
|
|
3
|
-
|
|
3
|
+
uint jgHashUint(uvec2 v){
|
|
4
|
+
uint x = v.x * 1664525u + v.y * 1013904223u;
|
|
5
|
+
x = (x ^ (x >> 16u)) * 2246822519u;
|
|
6
|
+
x = (x ^ (x >> 13u)) * 3266489917u;
|
|
7
|
+
x = x ^ (x >> 16u);
|
|
8
|
+
return x;
|
|
9
|
+
}
|
|
10
|
+
float jgHash(vec2 p){
|
|
11
|
+
ivec2 i = ivec2(floor(p));
|
|
12
|
+
return float(jgHashUint(uvec2(i))) * (1.0 / 4294967296.0);
|
|
13
|
+
}
|
|
4
14
|
float jgVNoise(vec2 p){
|
|
5
15
|
vec2 i = floor(p); vec2 f = fract(p);
|
|
6
16
|
vec2 u = f * f * (3.0 - 2.0 * f);
|
|
@@ -22,8 +32,17 @@ float jgFbm(vec2 p){
|
|
|
22
32
|
* sand (by waterline), and snow (by height) over it — textured-reading terrain
|
|
23
33
|
* with no image assets. Full PBR: lit, shadowed, and fogged like any standard
|
|
24
34
|
* material, so it composes with the post-processing chain.
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
*
|
|
36
|
+
* When `detail.material` is set, pass the matching loaded `textures` (color/normal/roughness/ao):
|
|
37
|
+
* they tile by world position at `detail.material.repeat` world units per tile and blend over the
|
|
38
|
+
* procedural result at `detail.material.strength` — the real-texture seam layers onto the existing
|
|
39
|
+
* look instead of replacing it.
|
|
40
|
+
* @internal
|
|
41
|
+
*/
|
|
42
|
+
export function createTerrainDetailMaterial(detail, textures) {
|
|
43
|
+
const hasTexture = textures !== undefined && detail.material !== undefined;
|
|
44
|
+
const repeat = detail.material?.repeat ?? 4;
|
|
45
|
+
const strength = detail.material?.strength ?? 1;
|
|
27
46
|
const uniforms = {
|
|
28
47
|
uRockColor: { value: new THREE.Color(detail.rockColor) },
|
|
29
48
|
uSandColor: { value: new THREE.Color(detail.sandColor) },
|
|
@@ -34,6 +53,16 @@ export function createTerrainDetailMaterial(detail) {
|
|
|
34
53
|
uDetailScale: { value: Math.max(0.5, detail.detailScale) },
|
|
35
54
|
uMacroScale: { value: Math.max(0.5, detail.macroScale) },
|
|
36
55
|
uStrength: { value: detail.strength },
|
|
56
|
+
...(hasTexture
|
|
57
|
+
? {
|
|
58
|
+
uMatColor: { value: textures.color },
|
|
59
|
+
uMatNormal: { value: textures.normal },
|
|
60
|
+
uMatRoughness: { value: textures.roughness },
|
|
61
|
+
uMatAo: { value: textures.ao },
|
|
62
|
+
uMatRepeat: { value: Math.max(0.01, repeat) },
|
|
63
|
+
uMatStrength: { value: strength },
|
|
64
|
+
}
|
|
65
|
+
: {}),
|
|
37
66
|
};
|
|
38
67
|
const material = new THREE.MeshStandardMaterial({
|
|
39
68
|
color: "#ffffff",
|
|
@@ -51,6 +80,36 @@ varying vec3 vJgWorldNormal;`)
|
|
|
51
80
|
vJgWorldNormal = normalize(mat3(modelMatrix) * objectNormal);`)
|
|
52
81
|
.replace("#include <begin_vertex>", `#include <begin_vertex>
|
|
53
82
|
vJgWorldPos = (modelMatrix * vec4(transformed, 1.0)).xyz;`);
|
|
83
|
+
const materialUniformsGlsl = hasTexture
|
|
84
|
+
? `
|
|
85
|
+
uniform sampler2D uMatColor;
|
|
86
|
+
uniform sampler2D uMatNormal;
|
|
87
|
+
uniform sampler2D uMatRoughness;
|
|
88
|
+
uniform sampler2D uMatAo;
|
|
89
|
+
uniform float uMatRepeat;
|
|
90
|
+
uniform float uMatStrength;`
|
|
91
|
+
: "";
|
|
92
|
+
const materialColorGlsl = hasTexture
|
|
93
|
+
? `
|
|
94
|
+
vec2 jgMatUv = jgWp / uMatRepeat;
|
|
95
|
+
vec3 jgMatColor = texture2D(uMatColor, jgMatUv).rgb;
|
|
96
|
+
jgCol = mix(jgCol, jgMatColor, uMatStrength);`
|
|
97
|
+
: "";
|
|
98
|
+
const materialRoughnessGlsl = hasTexture
|
|
99
|
+
? `
|
|
100
|
+
float jgMatRough = texture2D(uMatRoughness, jgMatUv).g;
|
|
101
|
+
roughnessFactor = mix(roughnessFactor, jgMatRough, uMatStrength);`
|
|
102
|
+
: "";
|
|
103
|
+
const materialNormalGlsl = hasTexture
|
|
104
|
+
? `
|
|
105
|
+
vec2 jgMatNormalXy = texture2D(uMatNormal, jgMatUv).rg * 2.0 - 1.0;
|
|
106
|
+
normal = normalize(normal + vec3(jgMatNormalXy.x, 0.0, jgMatNormalXy.y) * uMatStrength * 0.6);`
|
|
107
|
+
: "";
|
|
108
|
+
const materialAoGlsl = hasTexture
|
|
109
|
+
? `
|
|
110
|
+
float jgMatAo = texture2D(uMatAo, jgMatUv).r;
|
|
111
|
+
reflectedLight.indirectDiffuse *= mix(1.0, jgMatAo, uMatStrength);`
|
|
112
|
+
: "";
|
|
54
113
|
shader.fragmentShader = shader.fragmentShader
|
|
55
114
|
.replace("#include <common>", `#include <common>
|
|
56
115
|
varying vec3 vJgWorldPos;
|
|
@@ -64,16 +123,30 @@ uniform float uWaterLevel;
|
|
|
64
123
|
uniform float uDetailScale;
|
|
65
124
|
uniform float uMacroScale;
|
|
66
125
|
uniform float uStrength;
|
|
126
|
+
${materialUniformsGlsl}
|
|
67
127
|
${NOISE_GLSL}`)
|
|
68
128
|
.replace("#include <color_fragment>", `#include <color_fragment>
|
|
69
129
|
vec2 jgWp = vJgWorldPos.xz;
|
|
70
130
|
float jgFine = jgFbm(jgWp / uDetailScale);
|
|
71
131
|
float jgMacro = jgFbm(jgWp / uMacroScale);
|
|
132
|
+
float jgPatch = jgFbm(jgWp / (uMacroScale * 0.35) + vec2(37.2, 11.7));
|
|
72
133
|
float jgSlope = 1.0 - clamp(vJgWorldNormal.y, 0.0, 1.0);
|
|
73
134
|
float jgH = vJgWorldPos.y;
|
|
135
|
+
// Blade-scale flecks fade with view distance so close ground reads as turf without
|
|
136
|
+
// shimmering into noise on the horizon.
|
|
137
|
+
float jgViewDist = length(vJgWorldPos - cameraPosition);
|
|
138
|
+
float jgNear = 1.0 - smoothstep(14.0, 70.0, jgViewDist);
|
|
139
|
+
float jgFleck = jgVNoise(jgWp / max(0.05, uDetailScale * 0.024));
|
|
140
|
+
float jgFleckMid = jgVNoise(jgWp / max(0.12, uDetailScale * 0.07) + vec2(51.3, 7.9));
|
|
74
141
|
vec3 jgBase = diffuseColor.rgb;
|
|
75
|
-
jgBase *= mix(1.0 - 0.
|
|
76
|
-
|
|
142
|
+
jgBase *= mix(1.0 - 0.2 * uStrength, 1.0 + 0.16 * uStrength, jgFine);
|
|
143
|
+
// Meadow patchwork: hue-shifted sun-dried sweeps and cooler lush pockets, not just darker spots.
|
|
144
|
+
jgBase = mix(jgBase, jgBase * vec3(1.14, 1.05, 0.76), smoothstep(0.5, 0.8, jgMacro) * 0.55 * uStrength);
|
|
145
|
+
jgBase = mix(jgBase, jgBase * vec3(0.78, 1.0, 0.86), smoothstep(0.52, 0.85, jgPatch) * 0.5 * uStrength);
|
|
146
|
+
// Rolling large-scale light/dark sweeps keep far hills from flattening into one tone.
|
|
147
|
+
jgBase *= 1.0 + (jgFbm(jgWp / (uMacroScale * 2.3) + vec2(9.1, 73.4)) - 0.5) * 0.22 * uStrength;
|
|
148
|
+
jgBase *= 1.0 + (jgFleck - 0.5) * 0.48 * uStrength * jgNear;
|
|
149
|
+
jgBase *= 1.0 + (jgFleckMid - 0.5) * 0.34 * uStrength;
|
|
77
150
|
vec3 jgRock = uRockColor * mix(0.72, 1.15, jgFbm(jgWp / (uDetailScale * 1.7)));
|
|
78
151
|
float jgRockW = smoothstep(uRockSlopeStart, uRockSlopeStart + 0.18, jgSlope) * uStrength;
|
|
79
152
|
vec3 jgCol = mix(jgBase, jgRock, jgRockW);
|
|
@@ -83,8 +156,15 @@ jgCol = mix(jgCol, jgSand, jgSandW);
|
|
|
83
156
|
vec3 jgSnow = uSnowColor * mix(0.94, 1.04, jgFine);
|
|
84
157
|
float jgSnowW = smoothstep(uSnowHeight, uSnowHeight + 8.0, jgH) * (1.0 - jgRockW * 0.6) * uStrength;
|
|
85
158
|
jgCol = mix(jgCol, jgSnow, jgSnowW);
|
|
86
|
-
|
|
159
|
+
${materialColorGlsl}
|
|
160
|
+
diffuseColor.rgb = jgCol;`)
|
|
161
|
+
.replace("#include <roughnessmap_fragment>", `#include <roughnessmap_fragment>
|
|
162
|
+
${materialRoughnessGlsl}`)
|
|
163
|
+
.replace("#include <normal_fragment_maps>", `#include <normal_fragment_maps>
|
|
164
|
+
${materialNormalGlsl}`)
|
|
165
|
+
.replace("#include <aomap_fragment>", `#include <aomap_fragment>
|
|
166
|
+
${materialAoGlsl}`);
|
|
87
167
|
};
|
|
88
|
-
material.customProgramCacheKey = () => `jgengine-terrain-detail-${detail.rockSlopeStart}-${detail.snowHeight}`;
|
|
168
|
+
material.customProgramCacheKey = () => `jgengine-terrain-detail-${detail.rockSlopeStart}-${detail.snowHeight}-${hasTexture ? "tex" : "flat"}`;
|
|
89
169
|
return { material };
|
|
90
170
|
}
|
|
@@ -27,10 +27,15 @@ export interface TerrainVertexColorOptions {
|
|
|
27
27
|
waterline?: THREE.ColorRepresentation;
|
|
28
28
|
waterlineHeight?: number;
|
|
29
29
|
}
|
|
30
|
+
/** @internal */
|
|
30
31
|
export declare function normalizeHeightBlend(height: number, minHeight: number, maxHeight: number): number;
|
|
32
|
+
/** @internal */
|
|
31
33
|
export declare function resolveTerrainSize(size?: TerrainArea): ResolvedTerrainSize;
|
|
34
|
+
/** @internal */
|
|
32
35
|
export declare function resolveTerrainSegments(segments?: ProceduralTerrainConfig["segments"]): ResolvedTerrainSegments;
|
|
36
|
+
/** @internal */
|
|
33
37
|
export declare function toNoiseFieldConfig(config?: ProceduralTerrainConfig): NoiseFieldConfig;
|
|
38
|
+
/** @internal */
|
|
34
39
|
export declare function createProceduralTerrainSampler(config?: ProceduralTerrainConfig): TerrainHeightSampler;
|
|
35
40
|
/** Per-position palette override for multi-biome ground coloring — `createTerrainPaletteSampler` from `@jgengine/core/world/terrain` returns exactly this shape. */
|
|
36
41
|
export type TerrainPaletteSampler = (x: number, z: number) => {
|
|
@@ -51,6 +56,8 @@ export interface FieldGroundOptions {
|
|
|
51
56
|
* Mesh any `TerrainField` — including a `CarvableField` with craters/mounds written into it — into a
|
|
52
57
|
* vertex-coloured ground geometry. `sampleHeight` drives the vertices, so runtime carves show up as
|
|
53
58
|
* real depressions the moment the field is re-sampled (bump the caller's rebuild key after a carve).
|
|
54
|
-
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
55
61
|
export declare function createFieldGroundGeometry(field: TerrainField, options?: FieldGroundOptions): THREE.BufferGeometry;
|
|
62
|
+
/** @internal */
|
|
56
63
|
export declare function createProceduralGroundGeometry(config?: ProceduralTerrainConfig, colors?: TerrainVertexColorOptions): THREE.BufferGeometry;
|
|
@@ -1,21 +1,25 @@
|
|
|
1
1
|
import { noiseField } from "@jgengine/core/world/terrain";
|
|
2
2
|
import * as THREE from "three";
|
|
3
3
|
import {} from "./random.js";
|
|
4
|
+
/** @internal */
|
|
4
5
|
export function normalizeHeightBlend(height, minHeight, maxHeight) {
|
|
5
6
|
const range = maxHeight - minHeight;
|
|
6
7
|
if (range <= 1e-6)
|
|
7
8
|
return 0.5;
|
|
8
9
|
return THREE.MathUtils.clamp((height - minHeight) / range, 0, 1);
|
|
9
10
|
}
|
|
11
|
+
/** @internal */
|
|
10
12
|
export function resolveTerrainSize(size = 40) {
|
|
11
13
|
return typeof size === "number" ? { width: size, depth: size } : { width: size[0], depth: size[1] };
|
|
12
14
|
}
|
|
15
|
+
/** @internal */
|
|
13
16
|
export function resolveTerrainSegments(segments = 96) {
|
|
14
17
|
if (typeof segments !== "number")
|
|
15
18
|
return { x: Math.max(1, Math.floor(segments[0])), z: Math.max(1, Math.floor(segments[1])) };
|
|
16
19
|
const resolved = Math.max(1, Math.floor(segments));
|
|
17
20
|
return { x: resolved, z: resolved };
|
|
18
21
|
}
|
|
22
|
+
/** @internal */
|
|
19
23
|
export function toNoiseFieldConfig(config = {}) {
|
|
20
24
|
return {
|
|
21
25
|
seed: config.seed,
|
|
@@ -26,6 +30,7 @@ export function toNoiseFieldConfig(config = {}) {
|
|
|
26
30
|
baseHeight: config.baseOffset ?? 0,
|
|
27
31
|
};
|
|
28
32
|
}
|
|
33
|
+
/** @internal */
|
|
29
34
|
export function createProceduralTerrainSampler(config = {}) {
|
|
30
35
|
return noiseField(toNoiseFieldConfig(config)).sampleHeight;
|
|
31
36
|
}
|
|
@@ -33,7 +38,8 @@ export function createProceduralTerrainSampler(config = {}) {
|
|
|
33
38
|
* Mesh any `TerrainField` — including a `CarvableField` with craters/mounds written into it — into a
|
|
34
39
|
* vertex-coloured ground geometry. `sampleHeight` drives the vertices, so runtime carves show up as
|
|
35
40
|
* real depressions the moment the field is re-sampled (bump the caller's rebuild key after a carve).
|
|
36
|
-
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
37
43
|
export function createFieldGroundGeometry(field, options = {}) {
|
|
38
44
|
const size = resolveTerrainSize(options.size);
|
|
39
45
|
const segments = resolveTerrainSegments(options.segments);
|
|
@@ -118,6 +124,7 @@ function buildGroundGeometry(size, segments, sampler, opts) {
|
|
|
118
124
|
geometry.computeBoundingSphere();
|
|
119
125
|
return geometry;
|
|
120
126
|
}
|
|
127
|
+
/** @internal */
|
|
121
128
|
export function createProceduralGroundGeometry(config = {}, colors = {}) {
|
|
122
129
|
const size = resolveTerrainSize(config.size);
|
|
123
130
|
const segments = resolveTerrainSegments(config.segments);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type MutableRefObject } from "react";
|
|
2
|
-
import { type TouchScheme } from "@jgengine/core/input/touchScheme";
|
|
2
|
+
import { type TouchScheme, type TouchStyle } from "@jgengine/core/input/touchScheme";
|
|
3
3
|
export interface TouchCodeSink {
|
|
4
4
|
onCodeDown(code: string): void;
|
|
5
5
|
onCodeUp(code: string): void;
|
|
@@ -14,9 +14,11 @@ export declare function primaryButtonOffsets(count: number, scale?: number): {
|
|
|
14
14
|
bottom: number;
|
|
15
15
|
}[] | null;
|
|
16
16
|
/**
|
|
17
|
-
* Vertical space (px, excluding device safe areas)
|
|
18
|
-
* the bottom edge. The shell publishes it as
|
|
19
|
-
* `HudCanvas` regions never collide with touch
|
|
17
|
+
* Vertical space (px, excluding device safe areas) that *bottom-docked* clusters
|
|
18
|
+
* occupy above the bottom edge. The shell publishes it as
|
|
19
|
+
* `--jg-hud-dock-clearance` so `HudCanvas` regions never collide with touch
|
|
20
|
+
* controls. Side rails and top clusters reserve their own rectangles through the
|
|
21
|
+
* layout registry instead of this scalar.
|
|
20
22
|
*/
|
|
21
23
|
export declare function touchDockClearance(scheme: TouchScheme | null, scale?: number): number;
|
|
22
24
|
export declare function TouchPlaySurface({ scheme, sink, yawRef, pitchRef, maxPitch, onPrimaryTap, }: {
|
|
@@ -27,8 +29,10 @@ export declare function TouchPlaySurface({ scheme, sink, yawRef, pitchRef, maxPi
|
|
|
27
29
|
maxPitch: number;
|
|
28
30
|
onPrimaryTap: () => void;
|
|
29
31
|
}): import("react").JSX.Element;
|
|
30
|
-
export declare function TouchControlsDock({ scheme, sink, scale }: {
|
|
32
|
+
export declare function TouchControlsDock({ scheme, sink, style, scale, }: {
|
|
31
33
|
scheme: TouchScheme;
|
|
32
34
|
sink: TouchCodeSink;
|
|
35
|
+
/** Player-selected skin; falls back to the scheme's game default. */
|
|
36
|
+
style?: TouchStyle;
|
|
33
37
|
scale?: number;
|
|
34
38
|
}): import("react").JSX.Element;
|