@genex-ai/cli-demo 0.93.0-dev.247 → 0.94.0-dev.249
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/dist/index.js +25 -4
- package/package.json +1 -1
- package/templates/skills/genex-ai-model/SKILL.md +3 -2
- package/templates/skills/genex-ai-skybox/SKILL.md +3 -3
- package/templates/skills/genex-game-director/SKILL.md +321 -411
- package/templates/skills/genex-game-director/references/design-contract.md +130 -179
- package/templates/skills/genex-game-director/references/routing-map.md +242 -362
- package/templates/skills/genex-threejs-exposure-color-grading/SKILL.md +3 -7
- package/templates/skills/genex-threejs-procedural-assets/SKILL.md +112 -0
- package/templates/skills/genex-threejs-procedural-materials/SKILL.md +3 -7
- package/templates/skills/genex-threejs-procedural-vfx/SKILL.md +6 -11
- package/templates/skills/genex-threejs-shadow-systems/SKILL.md +2 -3
- package/templates/skills/genex-threejs-visual-validation/SKILL.md +0 -4
- package/templates/skills/genex-threejs-atmosphere-aerial-perspective/SKILL.md +0 -46
- package/templates/skills/genex-threejs-atmosphere-aerial-perspective/references/atmosphere.md +0 -213
- package/templates/skills/genex-threejs-bloom/SKILL.md +0 -44
- package/templates/skills/genex-threejs-bloom/references/bloom.md +0 -187
- package/templates/skills/genex-threejs-exposure-color-grading/references/exposure-grading.md +0 -205
- package/templates/skills/genex-threejs-game-content/SKILL.md +0 -198
- package/templates/skills/genex-threejs-game-content/references/content-tables.md +0 -269
- package/templates/skills/genex-threejs-image-pipeline/SKILL.md +0 -56
- package/templates/skills/genex-threejs-image-pipeline/references/image-pipeline.md +0 -197
- package/templates/skills/genex-threejs-lighting-design/SKILL.md +0 -144
- package/templates/skills/genex-threejs-lighting-design/references/light-recipes.md +0 -137
- package/templates/skills/genex-threejs-open-world/SKILL.md +0 -149
- package/templates/skills/genex-threejs-open-world/references/terrain-streaming.md +0 -215
- package/templates/skills/genex-threejs-precipitation-surfaces/SKILL.md +0 -59
- package/templates/skills/genex-threejs-precipitation-surfaces/references/precipitation-surfaces.md +0 -181
- package/templates/skills/genex-threejs-procedural-architecture/SKILL.md +0 -50
- package/templates/skills/genex-threejs-procedural-architecture/references/architecture-systems.md +0 -509
- package/templates/skills/genex-threejs-procedural-fields/SKILL.md +0 -61
- package/templates/skills/genex-threejs-procedural-fields/references/field-systems.md +0 -232
- package/templates/skills/genex-threejs-procedural-geometry/SKILL.md +0 -39
- package/templates/skills/genex-threejs-procedural-geometry/references/mesh-systems.md +0 -202
- package/templates/skills/genex-threejs-procedural-materials/references/material-systems.md +0 -198
- package/templates/skills/genex-threejs-procedural-planets/SKILL.md +0 -48
- package/templates/skills/genex-threejs-procedural-planets/references/planet-systems.md +0 -498
- package/templates/skills/genex-threejs-procedural-vegetation/SKILL.md +0 -52
- package/templates/skills/genex-threejs-procedural-vegetation/references/vegetation-systems.md +0 -314
- package/templates/skills/genex-threejs-procedural-vfx/references/vfx-systems.md +0 -218
- package/templates/skills/genex-threejs-raymarched-space-effects/SKILL.md +0 -37
- package/templates/skills/genex-threejs-raymarched-space-effects/references/space-effects.md +0 -194
- package/templates/skills/genex-threejs-screen-space-ambient-occlusion/SKILL.md +0 -35
- package/templates/skills/genex-threejs-screen-space-ambient-occlusion/references/ambient-occlusion.md +0 -441
- package/templates/skills/genex-threejs-spectral-ocean/SKILL.md +0 -52
- package/templates/skills/genex-threejs-spectral-ocean/references/spectral-ocean.md +0 -473
- package/templates/skills/genex-threejs-temporal-surfaces/SKILL.md +0 -41
- package/templates/skills/genex-threejs-temporal-surfaces/references/temporal-surfaces.md +0 -207
- package/templates/skills/genex-threejs-visual-validation/references/visual-validation.md +0 -427
- package/templates/skills/genex-threejs-volumetric-clouds/SKILL.md +0 -49
- package/templates/skills/genex-threejs-volumetric-clouds/references/volumetric-clouds.md +0 -579
- package/templates/skills/genex-threejs-water-optics/SKILL.md +0 -57
- package/templates/skills/genex-threejs-water-optics/references/water-optics.md +0 -195
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# Light Recipes
|
|
2
|
-
|
|
3
|
-
Contents: rig baselines by environment, the practical couple, shafts and
|
|
4
|
-
visible air, light as information, budgets and lifecycle, the kill-switch
|
|
5
|
-
diagnostic.
|
|
6
|
-
|
|
7
|
-
Numbers below are relationships, not mandates — validate them against the
|
|
8
|
-
project's renderer baseline (`$genex-threejs-exposure-color-grading`), because
|
|
9
|
-
tone mapping and exposure change what every intensity means.
|
|
10
|
-
|
|
11
|
-
## Rig baselines by environment
|
|
12
|
-
|
|
13
|
-
- **Outdoor day** — a warm sun `DirectionalLight` key plus cool sky fill
|
|
14
|
-
(`HemisphereLight`, or the skybox IBL when one is set). Fill sits well below
|
|
15
|
-
the key: when fill approaches key strength, shadow shapes die and the scene
|
|
16
|
-
flattens. The sun owns shadows: size the shadow camera's ortho bounds to the
|
|
17
|
-
playfield edges, fix acne at contact points with `bias`/`normalBias`, and
|
|
18
|
-
bump `mapSize` before loosening the frustum. A roaming world (racer, open
|
|
19
|
-
map) outgrows one frustum — that cascade question is
|
|
20
|
-
`$genex-threejs-shadow-systems`.
|
|
21
|
-
- **Outdoor night** — the moon is a dim, cool, hard key, not a gray day.
|
|
22
|
-
Practicals carry visibility; the moon's job is silhouette and geography.
|
|
23
|
-
Push the fiction's emitters up the ladder before brightening the moon. No
|
|
24
|
-
moon (a city street)? The settlement's own skyglow is the fill — a dim,
|
|
25
|
-
warm-tinted hemisphere — and the emitters carry the rest.
|
|
26
|
-
- **Interior** — the openings and fixtures ARE the rig: a window is a
|
|
27
|
-
`SpotLight` aimed the way the sun outside would aim (or a shaft, below);
|
|
28
|
-
fixtures are coupled practicals. IBL still fills, well below the windows.
|
|
29
|
-
When the hard sun-rectangle on the floor IS the shot (a big window, a long
|
|
30
|
-
room), swap the spot for a shadow-casting `DirectionalLight` through the
|
|
31
|
-
opening — parallel light keeps the patch's edges straight where a spot's
|
|
32
|
-
cone diverges.
|
|
33
|
-
- **Cave / underground** — no sky, no free fill. The brightest practical or
|
|
34
|
-
the crack shaft is the key; darkness is part of the palette, and the
|
|
35
|
-
player's own torch is a gameplay object. Ration the black, don't erase it.
|
|
36
|
-
- **Space** — one hard star key, black fill, bounce only from IBL or
|
|
37
|
-
planetshine. The harsh terminator IS the look; softening it reads as a
|
|
38
|
-
studio shoot.
|
|
39
|
-
|
|
40
|
-
## The practical couple
|
|
41
|
-
|
|
42
|
-
- Contain the light: set `distance` so the falloff dies inside the space the
|
|
43
|
-
source serves, and keep `decay` at the physical `2`. An uncontained point
|
|
44
|
-
light climbs walls three rooms away, and the scene creeps toward flat.
|
|
45
|
-
- Position the light inside the emitter, slightly above the visible flame or
|
|
46
|
-
tube — the pool it throws must sit centered under the thing that explains it.
|
|
47
|
-
- An area emitter — neon tube, screen, softbox — is by default a tinted
|
|
48
|
-
`PointLight` or `SpotLight` sunk into it. Reach for `RectAreaLight` only
|
|
49
|
-
when the rectangular wash on a nearby wall IS the shot, and know its terms:
|
|
50
|
-
`RectAreaLightUniformsLib.init()` first, no shadows, standard/physical
|
|
51
|
-
materials only.
|
|
52
|
-
- When the player roams, the budget follows them — but never add or remove a
|
|
53
|
-
light mid-play: changing the light count recompiles every shader that sees
|
|
54
|
-
it, a visible hitch. Allocate a small fixed pool up front, reassign members'
|
|
55
|
-
position and color to the nearest sources, and retire one by driving its
|
|
56
|
-
intensity to 0.
|
|
57
|
-
|
|
58
|
-
## Shafts and visible air
|
|
59
|
-
|
|
60
|
-
The default shaft is geometry, not a render pass — an open cone or tapered
|
|
61
|
-
cylinder from the aperture, additive, fading along its length:
|
|
62
|
-
|
|
63
|
-
```ts
|
|
64
|
-
function beamGradient(): THREE.Texture {
|
|
65
|
-
const c = document.createElement("canvas");
|
|
66
|
-
c.width = 1; c.height = 64;
|
|
67
|
-
const ctx = c.getContext("2d")!;
|
|
68
|
-
const g = ctx.createLinearGradient(0, 0, 0, 64);
|
|
69
|
-
g.addColorStop(0, "rgba(255,255,255,0.9)");
|
|
70
|
-
g.addColorStop(1, "rgba(255,255,255,0)");
|
|
71
|
-
ctx.fillStyle = g;
|
|
72
|
-
ctx.fillRect(0, 0, 1, 64);
|
|
73
|
-
const t = new THREE.Texture(c);
|
|
74
|
-
t.needsUpdate = true;
|
|
75
|
-
return t;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const shaft = new THREE.Mesh(
|
|
79
|
-
new THREE.CylinderGeometry(0.25, 1.6, 7, 24, 1, true), // narrow at the crack, wide at the floor
|
|
80
|
-
new THREE.MeshBasicMaterial({
|
|
81
|
-
map: beamGradient(), // flip the gradient if the bright end lands on the floor
|
|
82
|
-
transparent: true,
|
|
83
|
-
opacity: 0.35,
|
|
84
|
-
blending: THREE.AdditiveBlending,
|
|
85
|
-
depthWrite: false,
|
|
86
|
-
side: THREE.DoubleSide,
|
|
87
|
-
}),
|
|
88
|
-
);
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
- Pair the shaft with a real `SpotLight` from the same aperture: the bright
|
|
92
|
-
pool at its foot is what sells the beam. A shaft with nothing at its foot
|
|
93
|
-
reads as a hologram.
|
|
94
|
-
- If the scene earns its one ambient-particle layer (the moment gate's
|
|
95
|
-
budget), inside the beam is where it pays — slow dust motes, the
|
|
96
|
-
`$genex-threejs-procedural-vfx` dot-texture recipe.
|
|
97
|
-
- `scene.fog` (or `FogExp2`) is a scene-wide mood decision: every light pool
|
|
98
|
-
becomes a cone and contrast falls everywhere — choose it for the whole look.
|
|
99
|
-
- A full-screen shaft hanging off the sun disc rides the atmosphere pass
|
|
100
|
-
(`$genex-threejs-atmosphere-aerial-perspective`); this skill's shafts are
|
|
101
|
-
per-aperture beams built as geometry. A real raymarched volumetric pass is
|
|
102
|
-
for scenes whose identity is shafts.
|
|
103
|
-
|
|
104
|
-
## Light as information
|
|
105
|
-
|
|
106
|
-
- Reserve a signal hue: pickups and objectives get a color no environment
|
|
107
|
-
light uses, and it never changes meaning mid-game.
|
|
108
|
-
- Aim the player with brightness before UI: the lit doorway beats the arrow.
|
|
109
|
-
The eye lands on the highest contrast in frame — put it where the player
|
|
110
|
-
should go.
|
|
111
|
-
- Telegraph danger with the light's shape — a red cone where the boss will
|
|
112
|
-
sweep, a pulsing ring under the falling crate — timed to the same event
|
|
113
|
-
feedback the moment gate placed.
|
|
114
|
-
- A vertical beacon (shaft recipe above) reads across the whole map and over
|
|
115
|
-
occluding walls — the map's own "you are here".
|
|
116
|
-
|
|
117
|
-
## Budgets and lifecycle
|
|
118
|
-
|
|
119
|
-
- The whole rig of most shipped scenes: one shadow-casting key, IBL or
|
|
120
|
-
hemisphere fill, and a handful of coupled practicals. Each real light
|
|
121
|
-
beyond that carries a stated reason.
|
|
122
|
-
- A shadow-casting point light renders the scene six more times; a
|
|
123
|
-
shadow-casting spot, once. Prefer the spot — and prefer no shadow at all on
|
|
124
|
-
a flickering source, because the moving pool already sells it.
|
|
125
|
-
- `scene.remove(light)` does not free it: call `light.dispose()` when the
|
|
126
|
-
level unloads, and the shadow map goes with it.
|
|
127
|
-
|
|
128
|
-
## The kill-switch diagnostic
|
|
129
|
-
|
|
130
|
-
Wire a debug toggle per light, the same way the other systems expose debug
|
|
131
|
-
views. Turn each light off alone and name what died — "the campfire pool",
|
|
132
|
-
"the corridor signal" — watching the frame time as well as the frame: a light
|
|
133
|
-
whose absence changes nothing is dead weight to delete, and a light that costs
|
|
134
|
-
more milliseconds than the mood it adds is over budget. That observation is
|
|
135
|
-
the budget — there is no magic count. Then take two screenshots with post
|
|
136
|
-
off — the darkest playable corner and the brightest — and check both: if the
|
|
137
|
-
subject and the way forward read in each, the rig holds.
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: genex-threejs-open-world
|
|
3
|
-
description: Build a big explorable world that stays fast — seeded heightfield terrain with chunk streaming, biomes, points of interest, instanced scatter, and honest world bounds. Use when the ask says big or open world, multiple regions or locations, exploration, or any map beyond one arena — before the first terrain code, instead of one flat plane with fog.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Genex Three.js Open World
|
|
7
|
-
|
|
8
|
-
"Big world" in a request is a size class, not a mood. A 300 m plane with fog
|
|
9
|
-
pulled in to hide the walls is an arena wearing a costume — the player finds
|
|
10
|
-
the invisible wall in the first two minutes, and no amount of dressing
|
|
11
|
-
survives that. This skill is the recipe for a world that is actually big —
|
|
12
|
-
kilometers-class, streamed, varied — at a cost one session can afford.
|
|
13
|
-
|
|
14
|
-
## Decide the scale first, as a number
|
|
15
|
-
|
|
16
|
-
Put the world's size in the plan (and in the content contract's `world:` line
|
|
17
|
-
when `$genex-threejs-game-content` is loaded — a big world exists to hold
|
|
18
|
-
content, so the two skills almost always load together):
|
|
19
|
-
|
|
20
|
-
- **One arena** (~100–300 m): only when the ask says so (a shooter map, a
|
|
21
|
-
sports pitch, a boss rush). Never the silent fallback for "big world".
|
|
22
|
-
- **A district** (~500 m – 1 km): a town + surroundings; streaming optional.
|
|
23
|
-
- **Open world** (2–4 km bounded): the default meaning of "big/open world" —
|
|
24
|
-
a dozen locations, real travel time between them, streamed terrain. Beyond
|
|
25
|
-
~4 km you're spending budget on emptiness; density beats acreage.
|
|
26
|
-
|
|
27
|
-
Bound the world honestly: an edge-mountain ring, a coastline, or a cliff
|
|
28
|
-
reads as "the world ends here" — an invisible wall on a flat horizon reads as
|
|
29
|
-
a bug. Fog is atmosphere and draw-distance management
|
|
30
|
-
(`$genex-threejs-atmosphere-aerial-perspective` for the real thing), never a
|
|
31
|
-
wall to hide how small the map is.
|
|
32
|
-
|
|
33
|
-
## The scaling law: procedural fabric, generated landmarks
|
|
34
|
-
|
|
35
|
-
The single decision that determines whether a big world is affordable:
|
|
36
|
-
|
|
37
|
-
- **The world's FABRIC is procedural** — a seeded heightfield, instanced
|
|
38
|
-
vegetation, scattered rocks and props built from primitives and the
|
|
39
|
-
procedural skills (`$genex-threejs-procedural-vegetation`,
|
|
40
|
-
`$genex-threejs-procedural-geometry`). Procedural fabric costs the same at
|
|
41
|
-
4 km as at 300 m; that is what makes the size class reachable.
|
|
42
|
-
- **Generated assets are LANDMARKS** — `npx genex model` set pieces placed at
|
|
43
|
-
points of interest (the village well, the boss lair gate, the shrine), a
|
|
44
|
-
ground texture (`npx genex texture --terrain`, UVs from `worldUV` — never a
|
|
45
|
-
hand-picked repeat), a skybox. Hero assets decorate the world's landmarks;
|
|
46
|
-
they never decide the world's size, because a world assembled from
|
|
47
|
-
hand-placed generated meshes caps out at a diorama.
|
|
48
|
-
|
|
49
|
-
Wire generated pieces in as upgrades over procedural placeholders (the
|
|
50
|
-
standard swap discipline), so the world is walkable at full size from the
|
|
51
|
-
first hour.
|
|
52
|
-
|
|
53
|
-
## One height function to rule everything
|
|
54
|
-
|
|
55
|
-
Terrain is a seeded fBm heightfield (`$genex-threejs-procedural-fields` owns
|
|
56
|
-
the noise craft) with ridges for drama, an edge-mountain ring for the bound,
|
|
57
|
-
and **flattened pads blended in around each location** so structures sit on
|
|
58
|
-
level ground. The load-bearing rule: **exactly one canonical
|
|
59
|
-
`getHeightAt(x, z)`** that chunk meshing, physics grounding, placement
|
|
60
|
-
scatter, NPC spawns, and the minimap all share. The moment a second height
|
|
61
|
-
formula exists, trees float and feet sink — every large-world bug report
|
|
62
|
-
starts there.
|
|
63
|
-
|
|
64
|
-
Full copy-paste module — noise, fBm, location flattening, chunk manager,
|
|
65
|
-
instanced scatter — in
|
|
66
|
-
[references/terrain-streaming.md](references/terrain-streaming.md).
|
|
67
|
-
|
|
68
|
-
## Stream chunks, budget the frame
|
|
69
|
-
|
|
70
|
-
Build the terrain as fixed-size chunks (128 m is a good default) around the
|
|
71
|
-
player: load radius ~5 chunks (~600 m view with fog), unload behind, and
|
|
72
|
-
build queued chunks inside a **per-frame time budget** (a few ms) so streaming
|
|
73
|
-
never hitches the game. Seam rule: compute normals with a one-vertex apron
|
|
74
|
-
into the neighbor chunk, or every chunk border shows as a lighting crease.
|
|
75
|
-
Far distance is fog + the skybox — a low-res far ring is an upgrade, not a
|
|
76
|
-
requirement.
|
|
77
|
-
|
|
78
|
-
Physics grounding: keep the character/vehicle on the ground via the canonical
|
|
79
|
-
`getHeightAt` (cheap, always loaded) or per-chunk Rapier heightfield
|
|
80
|
-
colliders (`$genex-threejs-physics-rapier`) when projectiles and ragdolls
|
|
81
|
-
need real collision — but never build colliders for chunks the player isn't
|
|
82
|
-
near.
|
|
83
|
-
|
|
84
|
-
## Biomes: variety from two noise fields
|
|
85
|
-
|
|
86
|
-
Two low-frequency noise fields (temperature, moisture) + altitude + slope
|
|
87
|
-
give 4–7 biomes from one lookup: meadow, forest, marsh, desert, alpine, snow.
|
|
88
|
-
The biome function drives everything downstream from ONE place — vertex
|
|
89
|
-
colors or texture blend, vegetation species and density
|
|
90
|
-
(`$genex-threejs-procedural-vegetation`, instanced), enemy spawn tables and
|
|
91
|
-
ambient audio per biome (that's the content hookup). "Different locations" in
|
|
92
|
-
an ask is only half-answered by placed structures; the other half is the
|
|
93
|
-
ground itself changing as you travel.
|
|
94
|
-
|
|
95
|
-
## Points of interest: the world's content skeleton
|
|
96
|
-
|
|
97
|
-
A data table of locations — id, name, position, radius, builder function —
|
|
98
|
-
is the spine the content contract hangs from (quest givers live somewhere;
|
|
99
|
-
"locations: 8" means eight entries here):
|
|
100
|
-
|
|
101
|
-
- **Density beats acreage:** a point of interest every ~300–500 m of travel
|
|
102
|
-
on the natural routes. A 4 km world with three POIs is emptier than a 1 km
|
|
103
|
-
world with eight.
|
|
104
|
-
- Each location: terrain pad flattened (blend into the heightfield — see the
|
|
105
|
-
reference), a hand-authored builder (walls, tents, standing stones — merged
|
|
106
|
-
primitive geometry + generated landmark pieces), spawns, loot, and its
|
|
107
|
-
compass/minimap marker.
|
|
108
|
-
- Light budget: point lights per location, hard cap world-wide (~6 active) —
|
|
109
|
-
swap distant ones for emissive materials (`$genex-threejs-lighting-design`
|
|
110
|
-
owns the walk).
|
|
111
|
-
- Roads or worn paths between major POIs guide travel and double as the
|
|
112
|
-
navigation answer ("follow the road north beats a quest arrow").
|
|
113
|
-
|
|
114
|
-
## Performance floors (the size class depends on them)
|
|
115
|
-
|
|
116
|
-
- **Instancing for everything repeated**: trees, grass, rocks are
|
|
117
|
-
`InstancedMesh` per chunk — thousands of draw calls is the classic
|
|
118
|
-
big-world death; hundreds is the target.
|
|
119
|
-
- **Merge static location geometry** per material into a handful of meshes.
|
|
120
|
-
- Shadows at scale need a strategy, not defaults: a tight shadow camera
|
|
121
|
-
following the player, or cascades — `$genex-threejs-shadow-systems`.
|
|
122
|
-
- Keep per-frame allocation out of the loop (reuse vectors), and keep chunk
|
|
123
|
-
building inside its time budget.
|
|
124
|
-
- Phone-survivable stays the bar: pixel ratio cap, texture ≤ 2048², and the
|
|
125
|
-
chunk radius is the quality knob to shrink first.
|
|
126
|
-
|
|
127
|
-
## Day/night: cheap and almost expected
|
|
128
|
-
|
|
129
|
-
A big explorable world reads twice as alive with a sun cycle: one animated
|
|
130
|
-
`DirectionalLight` angle + a palette lerp (sky, fog, ambient) + practical
|
|
131
|
-
lights that matter at night. It's ~50 lines against the lighting rig
|
|
132
|
-
(`$genex-threejs-lighting-design`), and it makes travel time feel like time.
|
|
133
|
-
Optional — but when the ask says "like the big RPGs", this is one of the
|
|
134
|
-
three things they mean.
|
|
135
|
-
|
|
136
|
-
## Failure modes to catch
|
|
137
|
-
|
|
138
|
-
- A flat plane with invisible walls and close fog shipped as "big world" —
|
|
139
|
-
the defining failure this skill exists to prevent.
|
|
140
|
-
- Two height functions (mesh vs physics vs placement) drifting apart —
|
|
141
|
-
floating trees, buried chests.
|
|
142
|
-
- Hand-placing every tree at world scale — placement must be seeded scatter
|
|
143
|
-
with biome rules, or the world stays empty.
|
|
144
|
-
- Per-chunk geometry that never unloads or shares materials — memory climbs,
|
|
145
|
-
draw calls explode.
|
|
146
|
-
- POIs as map dots only — a named location with nothing to do fails the
|
|
147
|
-
content contract it was meant to serve (`$genex-threejs-game-content`).
|
|
148
|
-
- Seams: normal creases at every chunk border (missing apron), or texture
|
|
149
|
-
tiling picked by eye instead of `worldUV` (`$genex-ai-texture`).
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
# Genex open-world terrain — copy-paste streaming heightfield
|
|
2
|
-
|
|
3
|
-
One module: seeded noise → fBm heightfield with location pads → chunked,
|
|
4
|
-
time-budgeted streaming with seam-free normals → instanced scatter. Plain
|
|
5
|
-
vanilla-ts, three.js only. Copy it, then tune the named constants at the top —
|
|
6
|
-
they are the whole difficulty/size surface.
|
|
7
|
-
|
|
8
|
-
## Constants + the canonical height function
|
|
9
|
-
|
|
10
|
-
```ts
|
|
11
|
-
// world-terrain.ts
|
|
12
|
-
import * as THREE from "three";
|
|
13
|
-
|
|
14
|
-
export const WORLD_SEED = 1337; // one seed → same world on every machine
|
|
15
|
-
export const WORLD_BOUND = 2048; // half-size: playable area is 4096×4096 m
|
|
16
|
-
export const CHUNK = 128; // chunk side in meters
|
|
17
|
-
export const CHUNK_RES = 32; // vertices per side (33×33 grid)
|
|
18
|
-
export const VIEW_CHUNKS = 5; // load radius → ~600 m view with fog
|
|
19
|
-
|
|
20
|
-
// --- seeded value noise + fBm (swap in $genex-threejs-procedural-fields'
|
|
21
|
-
// simplex if it's already loaded; the shape below is what matters) ---
|
|
22
|
-
const hash2 = (x: number, y: number): number => {
|
|
23
|
-
let h = (Math.imul(x, 374761393) + Math.imul(y, 668265263) + WORLD_SEED) | 0;
|
|
24
|
-
h = Math.imul(h ^ (h >>> 13), 1274126177);
|
|
25
|
-
return ((h ^ (h >>> 16)) >>> 0) / 0xffffffff;
|
|
26
|
-
};
|
|
27
|
-
const smooth = (t: number): number => t * t * (3 - 2 * t);
|
|
28
|
-
function valueNoise(x: number, y: number): number {
|
|
29
|
-
const xi = Math.floor(x), yi = Math.floor(y);
|
|
30
|
-
const tx = smooth(x - xi), ty = smooth(y - yi);
|
|
31
|
-
const a = hash2(xi, yi), b = hash2(xi + 1, yi);
|
|
32
|
-
const c = hash2(xi, yi + 1), d = hash2(xi + 1, yi + 1);
|
|
33
|
-
return (a + (b - a) * tx) * (1 - ty) + (c + (d - c) * tx) * ty; // 0..1
|
|
34
|
-
}
|
|
35
|
-
function fbm(x: number, y: number, octaves = 5): number {
|
|
36
|
-
let sum = 0, amp = 0.5, freq = 1;
|
|
37
|
-
for (let i = 0; i < octaves; i++) {
|
|
38
|
-
sum += amp * (valueNoise(x * freq, y * freq) * 2 - 1);
|
|
39
|
-
amp *= 0.5; freq *= 2;
|
|
40
|
-
}
|
|
41
|
-
return sum; // ~-1..1
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// --- location pads: level ground for structures, BLENDED into the field ---
|
|
45
|
-
type Pad = { x: number; z: number; radius: number; height: number };
|
|
46
|
-
export const PADS: Pad[] = []; // fill from your POI table before first chunk build
|
|
47
|
-
|
|
48
|
-
function baseHeight(x: number, z: number): number {
|
|
49
|
-
const n = fbm(x * 0.0016, z * 0.0016); // rolling base, ~600 m features
|
|
50
|
-
const ridge = 1 - Math.abs(fbm(x * 0.004, z * 0.004)); // sharp ridge lines
|
|
51
|
-
let h = n * 26 + ridge * ridge * 18;
|
|
52
|
-
// edge-mountain ring: the honest world bound the player can SEE
|
|
53
|
-
const edge = Math.max(Math.abs(x), Math.abs(z)) / WORLD_BOUND; // 0..1
|
|
54
|
-
if (edge > 0.85) h += ((edge - 0.85) / 0.15) ** 2 * 90;
|
|
55
|
-
return h;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* THE canonical height. Chunk meshing, physics grounding, scatter, spawns,
|
|
60
|
-
* and the minimap all call THIS — a second height formula anywhere is how
|
|
61
|
-
* trees float and feet sink.
|
|
62
|
-
*/
|
|
63
|
-
export function getHeightAt(x: number, z: number): number {
|
|
64
|
-
let h = baseHeight(x, z);
|
|
65
|
-
for (const p of PADS) {
|
|
66
|
-
const d = Math.hypot(x - p.x, z - p.z);
|
|
67
|
-
if (d < p.radius) {
|
|
68
|
-
const t = smooth(1 - d / p.radius); // 1 at center → 0 at rim
|
|
69
|
-
h = h * (1 - t) + p.height * t; // flat pad blended into the hills
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
return h;
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Chunk manager — streamed, time-budgeted, seam-free
|
|
77
|
-
|
|
78
|
-
```ts
|
|
79
|
-
// world-chunks.ts
|
|
80
|
-
const chunkKey = (cx: number, cz: number): string => `${cx},${cz}`;
|
|
81
|
-
const live = new Map<string, THREE.Mesh>();
|
|
82
|
-
const queue: Array<{ cx: number; cz: number }> = [];
|
|
83
|
-
const material = new THREE.MeshStandardMaterial({ vertexColors: true });
|
|
84
|
-
|
|
85
|
-
function buildChunk(cx: number, cz: number): THREE.Mesh {
|
|
86
|
-
const geo = new THREE.PlaneGeometry(CHUNK, CHUNK, CHUNK_RES, CHUNK_RES);
|
|
87
|
-
geo.rotateX(-Math.PI / 2);
|
|
88
|
-
const pos = geo.attributes.position;
|
|
89
|
-
const colors = new Float32Array(pos.count * 3);
|
|
90
|
-
const x0 = cx * CHUNK, z0 = cz * CHUNK;
|
|
91
|
-
for (let i = 0; i < pos.count; i++) {
|
|
92
|
-
const wx = x0 + pos.getX(i), wz = z0 + pos.getZ(i);
|
|
93
|
-
const h = getHeightAt(wx, wz);
|
|
94
|
-
pos.setY(i, h);
|
|
95
|
-
const c = biomeColor(wx, wz, h); // your biome lookup (see below)
|
|
96
|
-
colors.set([c.r, c.g, c.b], i * 3);
|
|
97
|
-
}
|
|
98
|
-
geo.setAttribute("color", new THREE.BufferAttribute(colors, 3));
|
|
99
|
-
// Seam rule: analytic normals from the SAME height field (an implicit
|
|
100
|
-
// one-vertex apron) — computeVertexNormals() per chunk creases every border.
|
|
101
|
-
const nrm = geo.attributes.normal;
|
|
102
|
-
const eps = CHUNK / CHUNK_RES;
|
|
103
|
-
for (let i = 0; i < pos.count; i++) {
|
|
104
|
-
const wx = x0 + pos.getX(i), wz = z0 + pos.getZ(i);
|
|
105
|
-
const hx = getHeightAt(wx + eps, wz) - getHeightAt(wx - eps, wz);
|
|
106
|
-
const hz = getHeightAt(wx, wz + eps) - getHeightAt(wx, wz - eps);
|
|
107
|
-
const n = new THREE.Vector3(-hx, 2 * eps, -hz).normalize();
|
|
108
|
-
nrm.setXYZ(i, n.x, n.y, n.z);
|
|
109
|
-
}
|
|
110
|
-
const mesh = new THREE.Mesh(geo, material);
|
|
111
|
-
mesh.position.set(x0, 0, z0);
|
|
112
|
-
mesh.receiveShadow = true;
|
|
113
|
-
return mesh;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/** Call every frame. Streams chunks around the player inside a ms budget. */
|
|
117
|
-
export function updateChunks(scene: THREE.Scene, px: number, pz: number, budgetMs = 5): void {
|
|
118
|
-
const ccx = Math.round(px / CHUNK), ccz = Math.round(pz / CHUNK);
|
|
119
|
-
for (let dz = -VIEW_CHUNKS; dz <= VIEW_CHUNKS; dz++) {
|
|
120
|
-
for (let dx = -VIEW_CHUNKS; dx <= VIEW_CHUNKS; dx++) {
|
|
121
|
-
const key = chunkKey(ccx + dx, ccz + dz);
|
|
122
|
-
if (!live.has(key) && !queue.some((q) => chunkKey(q.cx, q.cz) === key)) {
|
|
123
|
-
queue.push({ cx: ccx + dx, cz: ccz + dz });
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
const start = performance.now();
|
|
128
|
-
while (queue.length && performance.now() - start < budgetMs) {
|
|
129
|
-
const { cx, cz } = queue.shift()!;
|
|
130
|
-
const mesh = buildChunk(cx, cz);
|
|
131
|
-
live.set(chunkKey(cx, cz), mesh);
|
|
132
|
-
scene.add(mesh);
|
|
133
|
-
// scatterChunk(scene, cx, cz) — instanced vegetation, below
|
|
134
|
-
}
|
|
135
|
-
for (const [key, mesh] of live) {
|
|
136
|
-
const [cx, cz] = key.split(",").map(Number);
|
|
137
|
-
if (Math.max(Math.abs(cx - ccx), Math.abs(cz - ccz)) > VIEW_CHUNKS + 1) {
|
|
138
|
-
scene.remove(mesh);
|
|
139
|
-
mesh.geometry.dispose(); // material is shared — never dispose it here
|
|
140
|
-
live.delete(key);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
## Biomes — one lookup drives color, scatter, and spawns
|
|
147
|
-
|
|
148
|
-
```ts
|
|
149
|
-
type Biome = "meadow" | "forest" | "marsh" | "desert" | "alpine";
|
|
150
|
-
export function biomeAt(x: number, z: number, h: number): Biome {
|
|
151
|
-
if (h > 55) return "alpine";
|
|
152
|
-
const temp = valueNoise(x * 0.0006 + 100, z * 0.0006); // slow fields:
|
|
153
|
-
const moist = valueNoise(x * 0.0006, z * 0.0006 + 100); // ~1.5 km regions
|
|
154
|
-
if (temp > 0.62 && moist < 0.4) return "desert";
|
|
155
|
-
if (moist > 0.62 && h < 12) return "marsh";
|
|
156
|
-
if (moist > 0.45) return "forest";
|
|
157
|
-
return "meadow";
|
|
158
|
-
}
|
|
159
|
-
const BIOME_TINT: Record<Biome, THREE.Color> = {
|
|
160
|
-
meadow: new THREE.Color(0x5d7f3a), forest: new THREE.Color(0x3f5f31),
|
|
161
|
-
marsh: new THREE.Color(0x4a5a3c), desert: new THREE.Color(0xb59a63),
|
|
162
|
-
alpine: new THREE.Color(0x8d8d94),
|
|
163
|
-
};
|
|
164
|
-
function biomeColor(x: number, z: number, h: number): THREE.Color {
|
|
165
|
-
const c = BIOME_TINT[biomeAt(x, z, h)].clone();
|
|
166
|
-
// slope → rock, shoreline → sand: cheap reads that sell the terrain
|
|
167
|
-
return c;
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Spawn tables and ambient audio key off `biomeAt` too — that is the content
|
|
172
|
-
hookup (`$genex-threejs-game-content`'s enemy line usually reads "wolves in
|
|
173
|
-
the forest, bandits on the roads": this function is where that sentence
|
|
174
|
-
becomes true).
|
|
175
|
-
|
|
176
|
-
## Instanced scatter — a forest for one draw call
|
|
177
|
-
|
|
178
|
-
```ts
|
|
179
|
-
export function scatterChunk(scene: THREE.Scene, cx: number, cz: number): void {
|
|
180
|
-
const rng = seededRng((cx * 73856093) ^ (cz * 19349663) ^ WORLD_SEED);
|
|
181
|
-
const spots: THREE.Matrix4[] = [];
|
|
182
|
-
for (let i = 0; i < 40; i++) {
|
|
183
|
-
const x = (cx + rng()) * CHUNK, z = (cz + rng()) * CHUNK;
|
|
184
|
-
const h = getHeightAt(x, z);
|
|
185
|
-
if (biomeAt(x, z, h) !== "forest") continue;
|
|
186
|
-
if (PADS.some((p) => Math.hypot(x - p.x, z - p.z) < p.radius + 6)) continue;
|
|
187
|
-
const s = 0.8 + rng() * 0.7;
|
|
188
|
-
spots.push(new THREE.Matrix4().compose(
|
|
189
|
-
new THREE.Vector3(x, h, z),
|
|
190
|
-
new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), rng() * Math.PI * 2),
|
|
191
|
-
new THREE.Vector3(s, s, s),
|
|
192
|
-
));
|
|
193
|
-
}
|
|
194
|
-
if (!spots.length) return;
|
|
195
|
-
const tree = new THREE.InstancedMesh(sharedTreeGeometry, sharedTreeMaterial, spots.length);
|
|
196
|
-
spots.forEach((m, i) => tree.setMatrixAt(i, m));
|
|
197
|
-
tree.castShadow = true;
|
|
198
|
-
scene.add(tree); // track per chunk-key and remove alongside the chunk
|
|
199
|
-
}
|
|
200
|
-
```
|
|
201
|
-
|
|
202
|
-
(`seededRng` is the one from `$genex-threejs-game-content`'s reference —
|
|
203
|
-
share it. `sharedTreeGeometry`/`sharedTreeMaterial` are module-level ONE-time
|
|
204
|
-
allocations: per-chunk geometry allocation is the classic memory leak here.
|
|
205
|
-
Real trees come from `$genex-threejs-procedural-vegetation`; a cone-on-
|
|
206
|
-
cylinder placeholder is fine until it loads.)
|
|
207
|
-
|
|
208
|
-
## Grounding the player
|
|
209
|
-
|
|
210
|
-
For the character/vehicle, the cheap path is the canonical function itself —
|
|
211
|
-
`y = getHeightAt(x, z)` plus a small offset for walking, with slopes from the
|
|
212
|
-
same finite differences as the normals. Move to per-chunk Rapier heightfield
|
|
213
|
-
colliders (`$genex-threejs-physics-rapier`) when projectiles, ragdolls, or
|
|
214
|
-
rolling props need true collision — build them only for the chunks around the
|
|
215
|
-
player, and from the SAME `getHeightAt`, never a copy.
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: genex-threejs-precipitation-surfaces
|
|
3
|
-
description: Build coupled precipitation and weather-affected surfaces for Genex Three.js games. Use for falling snow, snow accumulation, model snow caps, rain, wet asphalt puddles, procedural ripple normals, splash flipbooks, rain streaks, shared weather envelopes, and surface wetness or coverage transitions.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Genex Three.js Precipitation Surfaces
|
|
7
|
-
|
|
8
|
-
Treat weather as a coupled event, particle, and surface-response system. Do not
|
|
9
|
-
add rain or snow particles that are visually disconnected from the ground.
|
|
10
|
-
|
|
11
|
-
## Build order
|
|
12
|
-
|
|
13
|
-
```text
|
|
14
|
-
weather envelope
|
|
15
|
-
-> falling precipitation volume
|
|
16
|
-
-> world/object surface mask
|
|
17
|
-
-> displaced or optical surface response
|
|
18
|
-
-> impact residue and splashes
|
|
19
|
-
-> shared lighting/post presentation
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
Read [references/precipitation-surfaces.md](references/precipitation-surfaces.md)
|
|
23
|
-
for snow accumulation, object capping, wrapped precipitation volumes, wet
|
|
24
|
-
puddle masks, procedural ripple normals, splash placement, and debug outputs.
|
|
25
|
-
|
|
26
|
-
For the base ground surface under the wetness or snow (asphalt, dirt, stone),
|
|
27
|
-
generate a real texture with `npx genex texture` and load it via
|
|
28
|
-
`$genex-ai-texture`, then build the precipitation response on top of it.
|
|
29
|
-
Splash flipbook atlases and ripple normals stay procedural (for example a
|
|
30
|
-
canvas-drawn expanding-ring atlas) — they are not generated assets.
|
|
31
|
-
|
|
32
|
-
## Required controls
|
|
33
|
-
|
|
34
|
-
- precipitation density and speed;
|
|
35
|
-
- wind direction and strength;
|
|
36
|
-
- shared weather progress or coverage;
|
|
37
|
-
- wetness, snow, or puddle mask threshold and softness;
|
|
38
|
-
- ripple or drift normal strength;
|
|
39
|
-
- surface roughness response;
|
|
40
|
-
- particle/splash opacity;
|
|
41
|
-
- debug modes for masks, normals, particles, and event progress.
|
|
42
|
-
|
|
43
|
-
## Failure conditions
|
|
44
|
-
|
|
45
|
-
- falling precipitation ignores the wind or timing used by surface response;
|
|
46
|
-
- snow height and snow normals come from different fields;
|
|
47
|
-
- model snow sticks to vertical faces without an upward-facing filter;
|
|
48
|
-
- puddles only lower roughness without a mask, normal response, or ripples;
|
|
49
|
-
- splashes appear on downward or hidden faces;
|
|
50
|
-
- rain streaks allocate per drop or fail to wrap around the camera;
|
|
51
|
-
- temporal wetness is faked with unrelated time noise.
|
|
52
|
-
|
|
53
|
-
## Routing boundary
|
|
54
|
-
|
|
55
|
-
Use `$genex-threejs-water-optics` for bounded pool simulation, caustics, Fresnel,
|
|
56
|
-
refraction, and Beer-Lambert water volumes. Use `$genex-threejs-procedural-vfx` for
|
|
57
|
-
general sparks, plasma, trails, and non-weather particles. Use
|
|
58
|
-
`$genex-threejs-temporal-surfaces` for screen-space touch history or frost clearing.
|
|
59
|
-
This skill owns precipitation events and the surfaces they visibly alter.
|