Skip to content

feat(tools): render FGFoliage + data-driven foliage/grass colour on the base map (#246) - #284

Merged
StuartMeeks merged 5 commits into
mainfrom
feature/246-fgfoliage-trees
Aug 6, 2026
Merged

feat(tools): render FGFoliage + data-driven foliage/grass colour on the base map (#246)#284
StuartMeeks merged 5 commits into
mainfrom
feature/246-fgfoliage-trees

Conversation

@StuartMeeks

Copy link
Copy Markdown
Owner

🤖 AI-generated PR

Renders Satisfactory's full world foliage on the #246 base map and makes the terrain/cover colours data-driven. The whole "other trees" + foliage-colour arc for the base-map renderer (host-only tool tools/sf-map-renderer).

What's in it

  • FGFoliage decode — read Satisfactory's custom FGFoliageInstancedSMC (~3.3M instances: trees, bushes, grass, coral) via ObjectTypeRegistry + the cell-offset transform (offset lives on the owning InstancedFoliageActor, not TranslatedInstanceSpaceOrigin). Spec: docs/base-map-foliage-decode.md.
  • All foliage by default, HLOD proxy double-render fixed, per-mesh textures confirmed.
  • Baked Landscape-Grass overlayFLandscapeComponentGrassData (per-vertex, per-grass-type density) drives the ground grass colour, so vegetated biomes read correctly (Grass Fields/forests green, Red Bamboo red, Blue Crater blue-grey).
  • Data-driven colours — grass colour = grass mesh albedo × material Color Tint; MaterialColourSampler now reads each mesh's real albedo (PM_Diffuse/Grass Albedo) instead of a shared placeholder texture (map-wide fidelity fix).
  • Grass on the topmost object — the stacked rock-mesh spire tops (Jungle Spires) read as their grass cover, not bare rock.

Coral keeps its stylized palette colour by choice (its real emissive is orange/cyan per species, not the recognisable purple).

Verify

  • Build clean (TWAE), tests 54/54 (host-only — CUE4Parse ProjectReference).
  • Interactive layered artifact eyeballed across biomes.

Refs #246

StuartMeeks and others added 5 commits July 13, 2026 10:41
Satisfactory stores the bulk of world foliage (~71k components, ~3.3M
instances — most trees plus all small foliage) in a custom
FGFoliageInstancedSMC component that the renderer's scene collector never
reads, so those trees are invisible on the map. This is the "other tree
species" follow-up called out in base-map-renderer.md §6.

Capture the reverse-engineered decode as an agreed spec before coding:

- Register FGFoliageInstancedSMC as a HISM (CUE4Parse.UE4.Assets.
  ObjectTypeRegistry) so its per-instance transform buffer deserialises.
- Use the FGFoliage-specific world-position formula: the km-scale cell
  offset lives on the owning InstancedFoliageActor's RootComponent
  (via AttachParent), NOT in TranslatedInstanceSpaceOrigin (which is a
  red herring equal to instance[0] and double-counts if added).
- Render trees + large foliage only (drop grass/ground-cover) via the
  existing --flora include-filter and flora rasterisation.

Slice plan and risks (perf, double-render, "large" threshold) included.
Cross-referenced from CLAUDE.md and base-map-renderer.md §6.

Refs #246

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Satisfactory keeps ~3.3M foliage instances (most trees + all small
foliage) in a custom FGFoliageInstancedSMC component the scene collector
never read, so the map was near-treeless. Decode and render it:

- Register FGFoliageInstancedSMC as a HierarchicalInstancedSMC
  (CUE4Parse.UE4.Assets.ObjectTypeRegistry) so CUE4Parse deserialises its
  per-instance transform buffer.
- Read it in SceneCollector.TryAddMesh: widen the component-name guard to
  "…InstancedSMC", and use the FGFoliage-specific world formula — the
  km-scale cell offset lives on the owning InstancedFoliageActor's
  RootComponent (via AttachParent), NOT TranslatedInstanceSpaceOrigin
  (which equals instance[0] and double-counts). Stock foliage keeps the
  origin+translation formula.
- Trees & large only: the existing --flora include-filter (/Foliage/Trees/
  + /Coral/) already classifies these; grass/ferns/small foliage fall
  through to null and are dropped, so the 3.3M never all rasterise.
- Log placed-mesh + flora-instance counts (no silent truncation).
- Tidy the `treeat` probe to resolve FGFoliage with the same formula.

A downsample-4 render now places 150,786 flora instances (up from the
sparse stock-only set); full forest canopy + coral fields appear. Tests
green (54/54); build clean (TWAE).

Design & decode rationale: docs/base-map-foliage-decode.md.

Refs #246

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nder (#246)

Slice 3 of the FGFoliage work — bushes, textures, double-render, and the
default flora scope:

- Default flora is now the FULL set: DefaultFloraFolders =
  ["/Environment/Foliage/", "/Environment/Bush/"] — Coral, all Trees,
  Grass, Flowers, /SmallFoliage/ (ferns/bushes/roots) plus the large
  bushes. A full-map render is ~2.6M instances in ~114s at full res
  (downsample 2); the FGFoliage decode is what makes that affordable.
  Small foliage reads as ground cover, not clutter. Narrow via --flora
  (e.g. "/Foliage/Trees/,/Environment/Bush/") for a trees-only map.
- Fix HLOD double-render: skip components whose type contains "HLOD" in
  TryAddMesh. `probe florasrc` (new audit) showed FGFoliage is disjoint
  from stock foliage (no new double-plant), but HLOD proxies were 1:1
  duplicates of 13 tree/rock meshes (e.g. SM_TitanTree_01: individual=73
  AND HLOD=73) — planted twice. The real instances remain.
- Per-mesh textures: confirmed already wired (MeshGeometryCache samples
  each section's material albedo via MaterialColourSampler; rasteriser
  uses it, palette fallback only for coral + DesertRock). A full render
  albedo-samples 192 of 248 unique meshes. Log now reports the count.

Tests 54/54; build clean (TWAE). Docs updated (base-map-foliage-decode.md
outcomes + base-map-renderer.md §6 filter/HLOD).

Refs #246

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…246)

Two new pieces for the dense-foliage half of the base map, committed on
their own so they are tracked; the call sites that wire them in are still
in progress.

`GroundCover` accumulates dense ground-cover foliage (grass, ferns, small
plants) as a per-cell colour and blends it into the terrain, so vegetated
ground reads as its canopy colour from above. Grass geometry is far too
thin to fill a map cell if z-buffered as canopy, so each instance instead
stamps a small disc — a radius of 2 cells, since the game's grass carpet
spreads between plants and a plant every few metres reads as continuous
cover. Overlap drives the blend: three overlapping instances tint fully,
so dense cover (Jungle Spires) greens completely while a stray desert
plant barely shifts the sand. Void and unpainted cells are left alone.

`GrassDataProbe` reads the baked FLandscapeComponentGrassData for the
component covering a coordinate, reporting per grass type (Grass, Forest,
Sand, …) the average, maximum and at-point density, then correlating the
dominant type against vertex height. That is the game's real runtime-grass
spawn, so it shows where green grass actually grows even where the
weightmap says Sand.

Refs #246. See docs/base-map-foliage-decode.md.
…ss + real albedo (#246)

Colour the terrain and its cover from game data instead of guessed
palettes, fixing the Jungle Spires (sandy tops that are grass in-game),
Crater Lakes and Blue Crater biomes:

- Baked Landscape-Grass overlay: read FLandscapeComponentGrassData
  (per-vertex, per-grass-type density baked into each LandscapeComponent)
  and tint the terrain with the grass carpet's colour where the game
  grows it. This is the authoritative ground-vegetation signal — it owns
  the landscape colour (Grass Fields/Northern Forest green, Red Bamboo
  red, Blue Crater blue-grey), no weightmap guessing.
- Grass colour derived, not hardcoded: each grass type's colour = its
  primary grass mesh albedo x the material "Color Tint" param.
- Real per-species albedo: MaterialColourSampler now prefers the
  material's PM_Diffuse / "Grass Albedo" param over the shared first
  texture (TX_Grass_01_BC), so blue crater grass reads blue, pink grass
  pink, etc. — a map-wide colour-fidelity fix.
- Grass grows on the topmost object: the ground-cover foliage tint now
  colours grass-carpeted rock-mesh tops (the stacked-mesh spires) too,
  so Jungle Spires tops read green; and it no longer over-paints the
  landscape ground (the baked overlay owns that), fixing Crater Lakes.
- Diagnostics: probe grassdata (baked grass density + height correlation)
  and richer terrainmat/matcolour material dumps.

Coral stays its stylized palette colour by choice (its albedo is a dull
green/brown; its real per-species emissive is orange/cyan, not the
recognisable purple). Build clean (TWAE); tests 54/54.

Refs #246

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@StuartMeeks
StuartMeeks merged commit 28ed4a2 into main Aug 6, 2026
2 of 5 checks passed
@StuartMeeks
StuartMeeks deleted the feature/246-fgfoliage-trees branch August 6, 2026 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant