Compare commits

...

89 Commits

Author SHA1 Message Date
admin d6520c426a feat: resource consumption rate display in village panel
Village panel now shows daily consumption rates alongside stockpiles (e.g. Food: 15 (-2/d), Wood: 7 (-1/d)). Rates are computed over a rolling 200-tick (~1 day) window by summing item_consumed economic events. The rate display only appears when non-zero, keeping the panel clean until actual consumption occurs. Closes the feedback loop: events show individual transfers, rates show aggregate trends.
2026-07-09 01:18:33 +02:00
admin 6981c7e9bc feat: simulation speed control with bracket keys
Press [ to slow down, ] to speed up through 6 levels: 0.25x, 0.5x, 1x, 2x, 4x, 10x. Clock delta is multiplied by the current speed, so time_of_day advances proportionally and the visual day-night cycle stays synced. Current speed shown in the village panel header. 10x makes a full day cycle watchable in ~24 seconds. Speed changes are non-destructive to simulation determinism.
2026-07-09 00:25:57 +02:00
admin 4b5fc858f8 feat: NPC mourning behavior on housemate death
When an NPC dies from starvation, their highest-familiarity companion enters mourning for 100 ticks (~half a day). Mourning NPCs skip work: they rest if energy is low, otherwise wander near home. Mourning_ticks counts down each tick through ActionExecutionSystem and is serialized for save/load continuity with backward-compatible default of 0. This gives the familiarity graph its first behavioral consequence.
2026-07-09 00:23:00 +02:00
admin 8a955f3c29 feat: profession-based staggered daily schedules
ProfessionDefinition gains schedule_offset (-0.12 to +0.12 fraction of day). ActionSelectionSystem shifts each NPC's effective time_of_day by their profession offset before determining sleep/meal/work periods. Farmers wake earliest (-0.04h = ~2:38am), woodcutters slightly early (-0.02h), guards slightly late (+0.02h), scholars latest (+0.05h = ~5:45am). Wanderers have no offset. The village now has naturally staggered rhythms instead of everyone doing the same thing at the same time.
2026-07-09 00:19:14 +02:00
admin 2c3890502a feat: wood consumption for patrol and study
Patrol and study now consume 1 wood from the village woodpile on completion, recording an item_consumed economic event. This gives wood a purpose and creates resource pressure between gathering and work-support activities. Familiarity serialization switched from raw Dictionary to sorted Array of {id, score} pairs for deterministic JSON checksum output across save/load.
2026-07-09 00:16:52 +02:00
admin 595d67724f feat: runtime familiarity from shared work and utility exposure
NPCs who target the same resource or site concurrently gain mutual familiarity +0.1 per encounter, capped at 1.0. Action selection now receives the full NPC array and adds a +0.3 utility bonus for actions that familiar NPCs are currently performing - NPCs gravitate toward their housemates' professions. Inspector shows the highest-familiarity NPC with a percentage score under the resource lines.
2026-07-09 00:09:50 +02:00
admin fce5c533fe feat: seed household familiarity from shared home proximity
NPCs whose home positions are within 4m of each other gain mutual familiarity at baseline 0.5. Stored per NPC as a Dictionary keyed by other npc_id, serialized through NPCStateRecord with backward-compatible fallback for old saves. Household pairs form naturally from the home_positions configuration in main.tscn. Includes save/load round-trip test verifying familiarity persists.
2026-07-09 00:03:20 +02:00
admin 8094975094 feat: resource depletion events and village event feed
Record resource_depleted narrative events when NPC or player extraction exhausts a ResourceNode. Add get_recent_events() for cross-NPC event queries. Village panel now shows the last 3 village-wide events as a readable feed below the resource counters, making shortages and depletion visible without opening the inspector.
2026-07-08 21:22:43 +02:00
admin 8a1913c5ae feat: structured narrative event feed with per-NPC inspector timeline
Add narrative event types (task_started, npc_slept, npc_died, resource_depleted) alongside economic transfers. EconomicEventRecord gains create_narrative factory and human-readable description method. SimulationManager records task-start and sleep/death events, exposes get_npc_events() for per-NPC history queries. Inspector now shows a Recent timeline of the last 5 events for the selected NPC, plus displays carried wood count. Relax EconomicEventRecord validation to accept non-economic events. Update roadmap with completed milestones.
2026-07-08 19:02:11 +02:00
admin c61d286005 feat: wood inventory, carrying, and woodpile storage
Wood now follows the same gather-carry-deposit pattern as food. NPCs put harvested wood into inventory, walk to the VillageWoodpile StorageNode, and deposit it. Village wood syncs from storage. Player harvesting also routes wood to the woodpile. ActiveWorldAdapter exposes woodpile routing for DEPOSIT_WOOD action targeting. WorldViewManager recognizes wood inventory changes. Tests verify woodpile node existence, storage ID, navigation reachability, and adapter routing.
2026-07-08 18:55:51 +02:00
admin 5ea83b06ed feat: directional wind breeze and painted-terrain Ghibli styling
- Wind strokes now flow in a single prevailing direction (left-to-right breeze)
  with gentle flatness, narrow spread, and subtle tangential sway
- Added floating white specks (WindSpecks) as sunlight pollen/dust motes
- Two of each emitter (valley + ridge) for layered atmosphere
- Terrain: disabled auto_base_texture for flatter painted look, enabled
  overlay texture, bolder macro-variation (yellow-green 0.78/0.90/0.42
  and emerald 0.55/0.75/0.38), sharper material blends
2026-07-08 18:49:23 +02:00
admin ae0b3dbf89 chore: add wind stroke shader uid sidecar 2026-07-08 18:47:47 +02:00
admin 7aa5c1c979 feat: starvation balance rework and Ghibli wind-stroke visuals
- Reduce hunger rate to 0.125/tick (~25/day) for ~6-7 day death timeline
- Slow energy drain: idle 0.125, travel 0.5, work 0.5, complete 0.0625/tick
- Use exact binary fractions for all rates to preserve float precision in save/load
- Starving/critically-hungry NPCs always override sleep to seek food first
- Extend starvation death threshold from 20 to 600 ticks (~3 days of starvation)
- Add calligraphy-style wind stroke particles (WindStrokes scene + shader)
- Two wind stroke emitters in valley and ridge for Ghibli atmosphere
- Rework terrain macro-variation colors for vibrant warm-green palette
- Enhance environment: stronger glow/bloom, higher saturation, warmer sky/fog/ambient
2026-07-08 18:44:03 +02:00
admin 6008b1023e chore: update terrain assets with Godot UIDs and foliage mesh 2026-07-08 18:35:17 +02:00
admin adedf28417 feat: assign NPC home positions near village houses
Add home_positions export to SimulationManager. When configured, NPCs get homes assigned from the array wrapping around for more NPCs than homes. main.tscn now sets 6 home positions near the three village houses, so each NPC has a real home to walk to at night. Empty home_positions leaves the spawn-point-as-home backward-compatible behavior.
2026-07-08 18:32:08 +02:00
admin cab732a977 feat: sync visual day-night cycle with simulation clock
DayNightCycle now reads time_of_day from SimulationManager group node, falling back to self-tracked elapsed for lookdev scenes. Simulation starts at 06:00 morning (tick 50) by default. TimeDial reads directly from SimulationManager and displays HH:MM format with a 24-hour dial arc. Removed initial_cycle override from JajceLookdev.
2026-07-08 18:04:04 +02:00
admin 4604dc6d5a feat: add NPC schedules with sleep, meal, and work periods
Add ACTION_SLEEP with home-position targeting and energy restoration. Embed schedule periods (SLEEP/MEAL/WORK/DISCRETIONARY) in ActionSelectionSystem with TimeOfDay from SimulationClock. NPCs sleep at night when energy is low, eat during meal periods when hungry, and work/discretionary the rest of the cycle. Home position stored per NPC and serialized through NPCStateRecord with backward-compatible fallback. Cycle duration persisted through save/restore. Includes headless test for sleep period, work period, meal hunger, energy restoration, serialization round-trip, and home targeting without world adapter.
2026-07-08 17:44:39 +02:00
admin 6fdb9ba50f feat: expand resource discovery to 18 finite ResourceNodes
Add 6 new resources: farm_crop_01 (farming), berry_bush_river_02 (river bank), berry_patch_mill_01 (mill foraging), tree_forest_grove_01/02 (deep forest), wood_pile_mill_01 (mill stockpile). Extend tests to 18 resources, >=9 food, >=9 wood, >=6 berry, farming context assertion.
2026-07-08 17:21:05 +02:00
admin 4054e2c0dd feat: strengthen water and foreground silhouettes
- Upgrade river shader with multi-layer UV flow, fresnel edges, foam highlights, and specular sparkle
- Upgrade waterfall shader with dual-band scrolling, distinct foam contrast, and rim transparency
- Widen river (8m) and waterfall (8m) for greater visual presence
- Increase waterfall mist particles to 72 with wider spread and longer lifetime
- Add fortress crenellations (10), three turrets, and prominent ridge banner for readable skyline silhouette
- Remove duplicate banner nodes from JajceWorld instance (now owned by FortressBlockout scene)
- Update BUILD_IN_PUBLIC_PLAN and LEARNING_ROADMAP to reflect completion
2026-07-08 17:17:34 +02:00
admin c6e2d64162 feat: clarify jajce work-site silhouettes 2026-07-08 16:59:20 +02:00
admin 5251644b78 feat: stage simulation garden first read 2026-07-08 16:47:25 +02:00
admin cd0309e224 feat: add cinematic npc task glyphs 2026-07-08 16:31:34 +02:00
admin e813a8b2d5 docs: capture simulation garden baseline 2026-07-08 16:26:06 +02:00
admin 0fa8d80119 docs: capture jajce lookdev baseline 2026-07-08 16:01:08 +02:00
admin 324084f233 feat: bake jajce terrain navigation 2026-07-08 15:53:39 +02:00
admin cc0d6ef548 test: harden jajce terrain navigation 2026-07-08 15:47:01 +02:00
admin d2eca15ecd fix: camera tracks player elevation; snap all world objects to terrain
Camera: desired_focus_position.y now directly tracks the player's global Y. The existing smooth lerp on focus_position handles interpolation, so the camera follows the player up hills and down slopes without losing vertical sight. Previously only horizontally-deadzoned XZ was tracked.

jajce_world.gd: expanded terrain-snapping coverage to include all nodes under WorldObjects (ResourceNodes, StorageSites, ActivitySites) and the FortressBlockout's children. Added a type guard so non-Node3D children are safely skipped. This prevents houses, activity props, storage crates, and resource nodes from floating above or sinking below the sculpted terrain surface.
2026-07-07 17:07:57 +02:00
admin 41b515f474 fix: ground physics on real Terrain3D surface
Enable Terrain3D collision_mode=1 so the sculpted landscape provides real physical ground for player and NPCs. Disable GreyboxGround collision (layer/mask = 0) -- it is no longer the active physical floor.

NpcVisual no longer flattens nav waypoint Y to its current elevation. It now follows 3D path points and uses gravity (30 m/s^2) like the player, settling to terrain surface via is_on_floor() + Terrain3D collision. The flat NavigationMesh_greybox still provides correct lateral guidance; physics handles vertical grounding on slopes.

This fixes the player-walking-through-slopes bug and makes NPC movement follow actual terrain elevation for the village area (gentle slopes, ~2.4m noise amplitude). All 10 scenarios pass.
2026-07-07 16:56:15 +02:00
admin 1c389ec23d feat: enforce activity site capacity 2026-07-07 16:12:13 +02:00
admin 9d87372a15 docs: capture agent workflow 2026-07-07 16:09:37 +02:00
admin b74573fe0e feat: validate expanded resource discovery 2026-07-07 16:08:40 +02:00
admin 4bfc09d5f6 feat: add simulation garden demo controls 2026-07-07 15:56:24 +02:00
admin b0bad6c467 fix: stabilize godot 4.7 headless validation 2026-07-07 15:48:22 +02:00
admin 72d1cfc15d feat: add scattered discoverable resources 2026-07-07 15:38:31 +02:00
admin 036c9d5141 feat: score resource discovery by comfort and safety 2026-07-07 15:10:06 +02:00
admin 4c090f1635 feat: replace activity markers with typed sites 2026-07-07 13:27:02 +02:00
admin e5bd93d705 feat: add typed pantry storage site 2026-07-07 12:57:47 +02:00
admin 33b9c1bdfb feat: add circular time-of-day dial to UI
A 60x60 circular dial drawn at the top-center of the screen shows the current position in the day/night cycle. A filled arc sweeps clockwise from the top; the color transitions from dark blue (night) through orange (dawn), gold (day), and pink (dusk). A small glow dot marks the leading tip; a phase label (Night/Dawn/Day/Dusk) sits below. Redraws only when the cycle fraction changes. References DayNightCycle via node_path so it stays in sync with the environment lighting.
2026-07-06 00:10:07 +02:00
admin 3517762864 feat: add scrollwheel camera zoom
Mouse wheel up/down smoothly zooms the camera in and out. Zoom range: close-in overview (5 high, 4 back) to wide survey (16 high, 12 back). Default sits at midpoint (10 high, 8 back) matching the previous fixed offset. Configurable via min_zoom_offset, max_zoom_offset, and zoom_step exports. Zoom interpolates with the same follow_speed as position tracking for consistent feel.
2026-07-06 00:07:54 +02:00
admin 0aede053cb feat: expand village population from 3 to 6 NPCs
Adds Elma, Mirza, and Lejla to the village alongside Amina, Tarik, and Jasmin. Profession assignment is deterministic per-NPC-id RNG so the original three retain their seeded professions. jajce_runtime_integration_test updated to expect six villagers. All 10 scenarios pass.
2026-07-06 00:01:41 +02:00
admin 097cfaeaca feat: add day/night cycle and visual workplace props
DayNightCycle.gd smoothly rotates DirectionalLight3D and interpolates sky colors, ambient light, fog, and procedural sky material across a configurable 240-second cycle with warm sunset/sunrise peaks and deep night ambiance. No simulation dependency — purely visual.

JajceWorld.tscn now includes visible workplace structures replacing the invisible Marker3Ds: wooden pantry crate, guard tower post, study desk, and rest bench. Each has a distinct material color matching the profession palette.

The DayNightCycle node is added to JajceWorld.tscn with node_paths wired to DirectionalLight3D and WorldEnvironment. Script reference uses path-based ext_resource (uid generation deferred to editor first load).
2026-07-06 00:01:36 +02:00
admin 3379d08b93 chore: remove dead PlayerInteraction.gd stub
The file contained only 'extends Node' with no implementation, was referenced nowhere in any scene or script, and the uid sidecar was orphaned. The next interaction surface should be planned and implemented fresh when needed.
2026-07-05 23:54:59 +02:00
admin 734fe12098 perf: cache SimulationDefinitions lookups in memory
Previously get_action(), get_profession(), get_actions(), get_professions(), get_profession_ids(), and validate() reloaded all .tres files from disk on every call. These are called per-tick (action selection, task completion, NPC inspector refresh) producing unnecessary disk I/O. Now definitions are loaded once and stored in static dicts/lists keyed by ID. Add invalidate_cache() for hot-reload scenarios.
2026-07-05 23:54:55 +02:00
admin 58d3573965 docs: document simulation readability layer 2026-07-05 23:50:29 +02:00
admin d883148132 feat: add npc decision inspector 2026-07-05 23:50:18 +02:00
admin 7edf9acd57 feat: enhance magical world ambience 2026-07-05 23:50:06 +02:00
admin ebbd225628 docs: mark jajce beauty baseline complete 2026-07-05 23:24:26 +02:00
admin ac0282716c fix: run project scenarios in quality gate 2026-07-05 23:24:17 +02:00
admin fde2101690 fix: repair jajce beauty asset pipeline 2026-07-05 23:24:08 +02:00
admin ed0bc3f37f fix: resolve scene dependency errors and missing UID in main.tscn
- WaterBody.tscn, WaterfallBody.tscn: gdshader files were referenced
  with type="Material" instead of type="Shader". Wrapped each
  shader in a ShaderMaterial sub-resource so Godot can load them.
- ChimneySmoke.tscn: reordered sub-resources so StandardMaterial3D
  is defined before QuadMesh that references it.
- WaterfallMist.tscn: fixed node type GpuParticles3D -> GPUParticles3D
  (case-sensitive class name in Godot 4.7).
- jajce_world.gd: added explicit Vector3 type annotation to fix
  'Cannot infer the type of pos variable' parse error.
- main.tscn.uid: created missing UID mapping file. The scene header
  declared uid="uid://rs3hv73svbpa" but no .uid file existed,
  causing Godot to reject the scene as unsupported format.
2026-07-05 23:06:51 +02:00
admin 4463e524aa style: apply gdformat formatting across the entire project
Auto-format all GDScript files using gdformat from gdtoolkit.
This is a baseline formatting pass to ensure consistent style:

- Normalizes indentation and spacing
- Wraps long lines to 100 characters
- Removes trailing whitespace
- Standardizes blank lines between functions

68 files reformatted, 8 files left unchanged (3rd-party addon files
with parse errors excluded).
2026-07-05 23:06:23 +02:00
admin 28a2e42c41 feat: add local quality gate scripts and documentation
Add quality.sh (bash), quality.ps1 (PowerShell), and fix_format.sh
for automated GDScript quality checking. Includes:
- gdformat --check (formatting gate)
- gdlint (lint rules)
- godot --headless project validation (with 30s timeout)
- Optional GUT test support
- --changed mode for checking only modified .gd files
- Compact summary output with NEXT FIX suggestions
- Full logs saved to logs/quality/latest/

Add docs/local_quality_gate.md with setup and usage instructions.
2026-07-05 23:06:18 +02:00
admin 22613a445e WIP: feat(scene) integrate beauty pass — shader water, building blockouts, wind, smoke, 6 textures
- Add 3 missing Terrain3D textures: Dirt Path (id=3), Riverbank Stone (id=4),
  Compacted Ground (id=5) with procedural albedo PNGs + generate tool
- Replace greybox river BoxMesh with animated vertex-displacement water shader
- Replace greybox waterfall BoxMesh with scrolling-UV waterfall shader
- Add WaterfallMist GPUParticles3D with upward drift and alpha fade
- Add wind vertex shader for tree canopies (sin-based, height-weighted sway)
- Replace StylizedTree.tscn with script-driven procedural tree supporting
  3 canopy color variants and position-based randomization
- Replace greybox house boxes with StylizedHouse (walls + pitched roof + door)
- Replace mill box with StylizedMill (body + roof + water wheel cylinder)
- Replace fortress box with FortressBlockout (keep + parapet + corner turret)
- Replace bridge box with BridgeBlockout (deck + pillar supports)
- Add ChimneySmoke GPUParticles3D on each house roof
- Height-snap buildings (fortress + village) to terrain surface in _ready()
- Update JajceWorld.tscn: remove all greybox landmark sub-resources, instance
  new scenes, keep terrain/navigation/sky/fog as-is
- Update scaffold test for new scene structure and beauty elements
2026-07-05 20:10:45 +02:00
admin 6c11dc5dd6 WIP: feat(scene) initial beauty baseline — terrain textures, sky, foam, mist, foliage, shadows
- Add 3 Terrain3D texture assets (limestone, valley grass, warm soil)
- Sculpt initial Terrain3D heightmap with ridge, valley, waterfall drop
- Replace plain water with emissive-tinged material
- Add foam mesh, mist spheres, and procedural sky with valley fog
- Add 10 StylizedTree instances around village perimeter
- Enable directional light shadows with extended cascade range
- Add tree height snapping to terrain surface
- Add refined scaffold tests for material count, terrain height, foliage, sky, fog
- Add terrain texture PNGs and StylizedTree.tscn scene
- Add generate_jajce_terrain.gd tool
2026-07-05 20:05:44 +02:00
admin f12fb6ad78 docs: mark jajce runtime integration complete 2026-07-05 18:18:08 +02:00
admin 5851c0362a feat: integrate jajce world into runtime 2026-07-05 18:17:59 +02:00
admin 40014411fb docs: mark quicksave phase complete 2026-07-05 18:10:44 +02:00
admin 08b7bee0e5 feat: add validated quicksave persistence 2026-07-05 18:10:33 +02:00
admin 2fcf641152 docs: document economic event pipeline 2026-07-05 18:04:20 +02:00
admin f9601a67d1 feat: add structured economic events 2026-07-05 18:04:11 +02:00
admin ebf70a711a docs: document food storage transaction loop 2026-07-05 17:57:33 +02:00
admin 6dbb395bd6 feat: add location-based food storage loop 2026-07-05 17:57:24 +02:00
admin 3ea8f02d55 docs: mark simulation architecture gate complete
- Update ACTION_SYSTEM_ARCHITECTURE.md to describe the active-position
  contract and visual lifecycle proof
- Mark BUILD_IN_PUBLIC_PLAN.md systems-gate section complete
- Mark LEARNING_ROADMAP.md architecture-gate section complete and
  renumber post-gate implementation sequence
- Update PROJECT_CONTEXT.md immediate-milestone section to reflect
  completed gate and next food-loop milestone
- Update SIMULATION_STATE_SCHEMA.md with NPCStateRecord v2 details
- Update ADR 0001 checklist to mark active-position sync done
2026-07-05 17:21:23 +02:00
admin a8ae331f51 feat: synchronize authoritative npc position
- NpcVisual emits position_changed signal during _physics_process
- SimulationManager.synchronize_npc_position() writes active position
  into SimNPC state on movement, arrival, and navigation failure
- WorldViewManager forwards position_changed and syncs before
  despawn/reload
- SimNPC stores travel_target_position and has_travel_target as
  versioned state; NPCStateRecord v2 persists them with v1 migration
- WorldViewManager.spawn_npc_visual() resumes travel for traveling NPCs
  via SimulationManager.request_current_travel()
- WorldViewManager.despawn_npc_visual() syncs position before removal
- WorldViewManager.reload_npc_visual() spawns a visual from persisted
  state without rerolling target selection
- tests/npc_visual_lifecycle_test.gd proves unload/reload preserves
  action, target, reservation, position, RNG state, and checksum
- simulation_state_serialization_test adds legacy v1 NPC migration test
  that does not invent an active travel target
2026-07-05 17:21:17 +02:00
admin 4f9dc842fd docs: mark action separation phase complete 2026-07-05 14:17:57 +02:00
admin 4673f0698d refactor: separate action system responsibilities 2026-07-05 14:17:45 +02:00
admin 81df7b5939 docs: mark simulation definitions phase complete 2026-07-05 13:35:42 +02:00
admin f816f3976a feat: add simulation action definitions 2026-07-05 13:35:33 +02:00
admin 12f9ba616f docs: mark resource authority phase complete 2026-07-05 13:26:26 +02:00
admin 363620b731 refactor: move resource authority into simulation 2026-07-05 13:26:19 +02:00
admin f83d2c7704 docs: mark simulation state phase complete 2026-07-05 12:52:13 +02:00
admin 30cac28fc0 feat: add versioned simulation state records 2026-07-05 12:52:07 +02:00
admin c690937afa docs: update architecture gate progress
BUILD_IN_PUBLIC_PLAN.md:
- Mark systems gate items deterministic clock and headless checksum
  as complete; remaining gate work stays flagged as in-progress
- Rename TaskZone references to ActivityMarkers for consistency

LEARNING_ROADMAP.md:
- Add architecture gate progress note: first deterministic slice
  (SimulationClock, seeded streams, headless checksum) is implemented
- Update recommended implementation order to reflect shifted priorities

PROJECT_CONTEXT.md:
- Note that farm and forest markers have been deleted from main.tscn
- Update activity marker description (4 remaining: guard/study/rest/food)
- Add SimulationClock.gd and deterministic_simulation_test.gd to repo map
- Update technical debt: randomness is now seeded but RNG state is not
  yet serialized; clock is explicit but orchestration still lives on scene tree
- Gate status updated with completed first-slice summary

RESOURCE_NODE_MIGRATION.md:
- Mark Phase 4b (player parity) complete
- Phase 6 renamed from 'remove zone model' to 'replace activity markers'
  with progress note: TaskZone container and FarmZone/ForestZone deleted
- Update divergences to note farm/forest scene nodes have been removed
2026-07-05 11:23:19 +02:00
admin 37610242bf feat: add deterministic simulation foundation
Replace implicit randomness with seeded per-NPC RandomNumberGenerator
streams so the same seed always produces the same scenario outcome.

- Add SimulationClock (RefCounted): explicit fixed-step clock that
  converts frame delta into simulation ticks while preserving sub-tick
  remainder; replaces raw _process tick_timer accumulation
- Add simulation_seed export (int=1337) to SimulationManager; drive
  per-NPC RNG streams via seed + npc_id + stream_id derivation
  (stream 0=init, stream 1=wander)
- Add get_wander_offset(npc_id) to SimulationManager so wander targets
  are deterministic per seed; WorldViewManager calls it instead of
  local randf_range()
- Add get_state_snapshot() and get_state_checksum() to SimulationManager
  for headless verification and future save/load
- Convert SimNPC/SimVillage const DEBUG_LOGS to instance var debug_logs
  so headless tests can silence output without recompilation
- Pass RandomNumberGenerator through SimNPC._init() instead of using
  global randf_range() for hunger/energy/position/scoring noise
- Remove obsolete farm_zone and forest_zone exports from
  WorldViewManager and their marker/tree scene children from main.tscn;
  NPC gather tasks use ResourceNode exclusively
- Rename TaskZone container to ActivityMarkers in main.tscn and update
  all scene paths (player, WVM, tests) to match
- Add headless deterministic_simulation_test.gd: verifies clock
  accuracy, same-seed equality, different-seed divergence, and
  24-tick scenario checksum without loading main.tscn
2026-07-05 11:23:12 +02:00
admin b50fae671a docs: mark Jajce scaffold proof complete 2026-07-04 21:08:16 +02:00
admin 556d0fdc26 feat: add Jajce world scaffold 2026-07-04 21:06:57 +02:00
admin 83fc258afc docs: establish simulation architecture gate 2026-07-04 19:55:30 +02:00
admin 0900022558 docs: mark resource node phase 4 complete 2026-07-04 19:48:15 +02:00
admin 326df51cbe feat: add player resource node harvesting 2026-07-04 19:47:31 +02:00
admin f5e7326d49 docs: update plans with phase progress, bug log, and current architecture state
RESOURCE_NODE_MIGRATION.md:
- Add progress overview table with per-phase completion status
- Document key divergences from original plan (zone fallback removal,
  navigation_failed signal, stale-path prevention, duplicate detection)
- Add bug log table (11 bugs found and fixed during implementation)
- Mark completed phases 1-3 with checkmarks
- Split phase 4 into 4a (wood, done) and 4b (player parity, not started)
- Replace flat implementation order with checkable table
- Update definition-of-done with per-criterion status

PROJECT_CONTEXT.md:
- Advance snapshot commit reference from e30d208 to b8752f8
- Note that gather_food/gather_wood now use ResourceNode instances
- Explain zone marker status (player use only, NPCs use nodes)
- Update runtime flow diagram with navigation_failed pathway
- Add resource_nodes/ to repository map
- Update technical debt entries to reflect current state
2026-07-04 19:31:43 +02:00
admin b8752f8835 feat: add navigation_failed signal with time-based stuck detection and simulation pipeline
NpcVisual:
- Add navigation_failed(sim_id) signal distinct from arrived_at_target
  so the simulation layer can distinguish genuine arrival from failure
- Replace integer stuck_counter with delta-based stuck_time and
  exported stuck_timeout (1.5s) for frame-rate-independent detection
- Add path_request_id / path_pending mechanism to reject stale
  async path results when a new target is set or NPC dies
- On empty path or stuck movement, emit navigation_failed instead
  of arriving; only emit arrived_at_target when truly close to target
- Reduce arrival_distance back to 0.6 now that navigation_failure
  is properly signalled (no longer needed as a workaround)
- clamp arrival_distance reference in apply_dead_visual_state

SimNPC:
- Remove retry_count (replaced by navigation_failed signal pathway)
- Move last_task assignment from set_task() to task completion in
  SimulationManager so it reflects the task that actually finished

SimulationManager:
- Add notify_npc_navigation_failed(): release reservation, record
  last_task, send NPC to wander for 2s, emit task_changed signal
- Add notify_npc_target_unavailable() as public alias of above
- Guard extraction with node.reserved_by == npc.id to prevent
  stale/illegitimate extraction when reservation is lost
- Guard fallback zone task completion (apply_npc_task) so it only
  fires for non-resource tasks (gather_food/wood with no target
  silently skip instead of applying zone values)
- Remove retry_count safety net (fully replaced by signal pipeline)

WorldViewManager:
- Connect navigation_failed signal in spawn_npc_visual()
- Skip target assignment for empty/idle/dead task states
- When no resource node available for gather_food/wood, call
  notify_npc_target_unavailable() so NPC wanders instead of idling
- Add _on_npc_visual_navigation_failed handler delegating to
  SimulationManager.notify_npc_navigation_failed()
- Remove retry_count references
2026-07-04 19:24:30 +02:00
admin 1837bc0d2d fix: detect duplicate ResourceNode node_id and use interaction_point for distance calc
- Push error and disable node when duplicate node_id is registered
  to prevent silent conflict during find_available()
- Use interaction_point.global_position instead of self.global_position
  in find_available() so distance is measured to the marker NPCs
  actually navigate toward
2026-07-04 19:24:19 +02:00
admin 41bfc8d90a fix: empty-path re-entrancy, retry_count reset, := to = for Variant 2026-07-03 18:51:13 +02:00
admin c6033b63fb feat: authoritative extraction from ResourceNode on task completion
Phase 3 of ResourceNode migration:
- Add apply_resource_delta to SimVillage for clean resource changes
- Extract from reserved ResourceNode on task completion instead of
  hard-coded apply_npc_task for gather_food/gather_wood
- Detect depleted/unavailable targets on arrival and replan
2026-07-03 18:32:34 +02:00
admin 0dfaa06f9c fix: remove invalid StringName cast from dictionary get 2026-07-03 18:28:06 +02:00
admin 8049c52e0f fix: use Variant assignment for _try_get_resource_target return type 2026-07-03 18:26:50 +02:00
admin 9cd38a1adb feat: add ResourceNode system with target selection and reservation
Phase 1-2 of the ResourceNode migration plan:

- Create ResourceNode class (world/resource_nodes/) with extraction,
  reservation, nearest-available selection, and debug label
- Place 4 resource instances in main.tscn (2 berry bushes, 2 trees)
- Add target_id to SimNPC for tracking which node an NPC is using
- Add set_npc_target_id and release_npc_reservation to SimulationManager
  with cleanup on task change, completion, and death
- Update WorldViewManager to query available ResourceNodes first,
  falling back to zone markers with a logged warning
2026-07-03 18:18:51 +02:00
admin e30d208c3f chore: standardize cross-platform development setup 2026-07-03 13:10:12 +02:00
Rijad Zuzo 595233e5df feat(plugin): add terrain3d plugin installation 2026-07-03 13:05:56 +02:00
409 changed files with 26821 additions and 610 deletions
+19
View File
@@ -2,3 +2,22 @@ root = true
[*] [*]
charset = utf-8 charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
trim_trailing_whitespace = true
[*.gd]
indent_style = tab
indent_size = 4
tab_width = 4
[*.{json,yaml,yml}]
indent_size = 2
[*.{bat,cmd}]
end_of_line = crlf
[*.md]
trim_trailing_whitespace = false
+68 -1
View File
@@ -1,7 +1,74 @@
# Normalize EOL for all files that Git considers text files. # Use deterministic line endings on every platform.
* text=auto eol=lf * text=auto eol=lf
# Godot source and serialized resources
*.gd text eol=lf *.gd text eol=lf
*.gd.uid text eol=lf
*.gdextension text eol=lf
*.gdnlib text eol=lf
*.tscn text eol=lf *.tscn text eol=lf
*.tres text eol=lf *.tres text eol=lf
*.godot text eol=lf
*.import text eol=lf
*.shader text eol=lf *.shader text eol=lf
*.gdshader text eol=lf *.gdshader text eol=lf
*.gdshaderinc text eol=lf
# Common source, configuration, and documentation formats
*.cs text eol=lf
*.cfg text eol=lf
*.ini text eol=lf
*.json text eol=lf
*.md text eol=lf
*.toml text eol=lf
*.txt text eol=lf
*.xml text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.sh text eol=lf
*.ps1 text eol=lf
# Native Windows command scripts
*.bat text eol=crlf
*.cmd text eol=crlf
# SVG is text; the remaining asset formats must never receive EOL conversion.
*.svg text eol=lf
*.bmp binary
*.dds binary
*.exr binary
*.gif binary
*.hdr binary
*.ico binary
*.jpeg binary
*.jpg binary
*.ktx binary
*.png binary
*.tga binary
*.webp binary
*.blend binary
*.fbx binary
*.glb binary
*.3ds binary
*.dae text eol=lf
*.gltf text eol=lf
*.mtl text eol=lf
*.obj text eol=lf
*.mp3 binary
*.ogg binary
*.wav binary
*.flac binary
*.mp4 binary
*.webm binary
*.avi binary
*.mov binary
*.otf binary
*.ttf binary
*.woff binary
*.woff2 binary
*.7z binary
*.gz binary
*.rar binary
*.tar binary
*.zip binary
*.pck binary
+66 -28
View File
@@ -1,44 +1,82 @@
# Godot import cache # Godot 4 editor data and imported asset cache
.import/
# Godot editor generated files
.godot/ .godot/
# Export templates and builds # Godot 3 imported asset cache
.import/
# Local export output (keep export_presets.cfg tracked)
/export/ /export/
/exports/ /exports/
/build/ /build/
/builds/ /builds/
/dist/
# Mono / C# # Godot C# / Mono generated files
.mono/ .mono/
data_*/ data_*/
mono_crash.* mono_crash.*
# Visual Studio / Rider
.vs/
.idea/
*.csproj *.csproj
*.sln *.sln
*.user
*.DotSettings.user
# macOS # Godot and application crash/log output
.DS_Store crash_handler*
# Linux
.directory
# Windows
Thumbs.db
desktop.ini
# Logs
*.log *.log
# Temporary files # Local environment and secrets
*.tmp .env
*.bak .env.*
!.env.example
# Godot crash reports # Editors and IDEs
crash_handler* .idea/
.fleet/
.vs/
.vscode/
*.code-workspace
*.suo
*.user
*.userosscache
*.sln.docstates
*.DotSettings.user
# Editor swap, backup, and temporary files
*.bak
*.orig
*.rej
*.swp
*.swo
*.temp
*.tmp
*~
~*.dll
.#*
\#*\#
# macOS metadata
.DS_Store
.AppleDouble
.LSOverride
Icon?
._*
.DocumentRevisions-V100/
.Spotlight-V100/
.TemporaryItems/
.Trashes/
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Windows metadata
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
[Dd]esktop.ini
$RECYCLE.BIN/
*.stackdump
# Local quality gate logs
logs/quality/
# Linux desktop and trash metadata
.directory
.Trash-*
+142
View File
@@ -0,0 +1,142 @@
# Agent working guide for The Steward
This repository is a Godot 4.7 simulation prototype. Treat it as a living
systems project, not a content dump. The expected working style is:
## Default development loop
1. Read the relevant roadmap/docs before changing code.
- Start with `docs/README.md`.
- Use `docs/LEARNING_ROADMAP.md` for the current next item.
- Use focused plans such as `docs/RESOURCE_NODE_MIGRATION.md` only within
their stated scope.
2. Inspect the current code and tests before assuming roadmap status is still
accurate. The user often changes things in parallel.
3. Implement the smallest useful vertical slice.
- Prefer functional systems progress over broad polish.
- Avoid deep rabbit holes unless the current slice needs them.
- Keep Jajce visual/design work bounded; prioritize simulation behavior,
validation, WorldEnvironment ambience, and readable presentation.
4. Validate with the local quality gate.
5. Update the smallest relevant docs after completing a phase or decision.
6. Commit with a conventional commit message.
## Validation
Only Godot 4.7 matters for this project.
Use the project quality gate from PowerShell:
```powershell
powershell -ExecutionPolicy Bypass -File .\tools\quality.ps1
```
For changed-file quick checks:
```powershell
powershell -ExecutionPolicy Bypass -File .\tools\quality.ps1 -Changed
```
The quality scripts isolate Godot's user profile under `logs/quality/godot_profile`
so headless Godot 4.7 can run without crashing on blocked Windows app-data paths.
Do not remove that behavior.
Useful extra checks:
```powershell
git diff --check
```
If touching `main.tscn` or runtime wiring, also do a direct Godot 4.7 headless
boot when practical.
## Architecture direction
Preserve the simulation/presentation boundary.
- Simulation state owns authority: resources, storage, NPC inventory, events,
task state, target IDs, positions, RNG streams, and save records.
- World nodes are presentation and interaction geometry. They may register or
bind state, but they should not become the only owner of persistent facts.
- NPCs store stable IDs, not `NodePath`s or node references.
- Target discovery should go through `ActiveWorldAdapter` and
`ActionTargetResolver`.
- Visual movement belongs to `WorldViewManager`/`NpcVisual`; decision and task
execution belong to the simulation systems.
When adding a system, first prove the contract with one real gameplay use case.
Do not extract generic frameworks before multiple real consumers justify them.
## Resource and target rules
Do not reintroduce abstract food/wood task-zone fallbacks.
Current resource gathering should use finite `ResourceNode` instances:
- food: berries, animal camps, village stock, future farms/crops;
- wood: trees, wood piles, future forestry contexts.
Resource additions should preserve:
- stable unique IDs;
- food/wood coverage;
- meaningful placement context;
- reachable interaction points;
- `safety_risk`, `comfort_distance`, and `discovery_priority` metadata;
- clear player interaction ranges so nearby storage/activity/resource targets
do not overlap accidentally.
Storage uses `StorageNode`. Rest, study, and patrol use `ActivitySite`. Do not
turn these back into generic marker zones.
## Testing expectations
Add or update headless scenarios when a change affects:
- resource conservation;
- target selection or reservations;
- save/load or schema behavior;
- navigation/reachability assumptions;
- player/NPC parity;
- deterministic continuation;
- UI/debug state that represents real simulation facts.
Keep tests deterministic. Prefer fixed seeds and stable IDs.
## Documentation expectations
Update docs when the implemented behavior changes the roadmap, architecture, or
current contract. Keep updates local:
- `docs/LEARNING_ROADMAP.md` for next-item sequencing and milestone status;
- `docs/RESOURCE_NODE_MIGRATION.md` for resource-target migration status;
- `docs/SIMULATION_STATE_SCHEMA.md` for serialized state changes;
- `docs/SIMULATION_DEFINITIONS.md` for action/profession/ID contracts;
- `docs/BUILD_IN_PUBLIC_PLAN.md` for Jajce/demo/readability slices;
- `docs/decisions/` only for durable architectural decisions.
Do not duplicate full status tables across many docs. If docs and code differ,
trust code/tests first, then update docs.
## Git expectations
Preserve user changes. Check `git status --short` before editing and before
committing. Do not use destructive cleanup commands unless explicitly asked.
Use conventional commits, for example:
- `feat: validate expanded resource discovery`
- `fix: stabilize godot 4.7 headless validation`
- `docs: capture agent workflow`
Commit only after validation relevant to the change has passed.
## Product taste
The project is aiming for a readable, magical simulation garden that can grow
into a larger systemic game. Prefer honest visible cause-and-effect:
- NPCs should walk to real resources, storage, and activity sites.
- UI/debug overlays should explain actual simulation state.
- Beauty work should represent real state rather than fake activity.
- Each slice should leave the project easier to reason about than before.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+52
View File
@@ -0,0 +1,52 @@
![Terrain3D Logo](/doc/docs/images/terrain3d.jpg)
# Terrain3D
A high performance, editable terrain system for Godot 4.
## Features
* Written in C++ as a GDExtension addon, which works with official builds of Godot Engine
* [Can be accessed](https://terrain3d.readthedocs.io/en/stable/docs/programming_languages.html) by GDScript, C#, and any language Godot supports
* Terrains as small as 64x64m up to 65.5x65.5km (4295km^2) in non-contiguous and variable sized regions
* Up to 32 textures
* Up to 10 levels of detail for the terrain mesh
* Foliage instancing, with up to 10 levels of detail, and a shadow impostor
* Sculpting, holes, texture painting, texture detiling, painting colors and wetness
* Imports heightmaps from [HTerrain](https://github.com/Zylann/godot_heightmap_plugin/), Gaea, World Creator, World Machine, Unity, Unreal and any tool that can export a heightmap. See [heightmaps](https://terrain3d.readthedocs.io/en/stable/docs/heightmaps.html)
## Games Using Terrain3D
Please see the [featured games using Terrain3D](https://terrain3d.readthedocs.io/en/latest/docs/games.html) for examples of what it can do.
## Getting Started
1. Read the [Introduction](https://terrain3d.readthedocs.io/en/stable/docs/introduction.html) to understand how this terrain system works.
2. Read the [Installation & Upgrade](https://terrain3d.readthedocs.io/en/stable/docs/installation.html) instructions.
3. Watch the [tutorial videos](https://terrain3d.readthedocs.io/en/stable/docs/tutorial_videos.html) and read through the documentation.
4. For support, read [Getting Help](https://terrain3d.readthedocs.io/en/stable/docs/getting_help.html) and join our [Discord server](https://tokisan.com/discord).
## Credit
Developed for the Godot community by:
|||
|--|--|
| **Cory Petkovsek, Tokisan Games** | [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/twitter.png?raw=true" width="24"/>](https://twitter.com/TokisanGames) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/github.png?raw=true" width="24"/>](https://github.com/TokisanGames) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/www.png?raw=true" width="24"/>](https://tokisan.com/) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/discord.png?raw=true" width="24"/>](https://tokisan.com/discord) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/youtube.png?raw=true" width="24"/>](https://www.youtube.com/@TokisanGames)|
| **Roope Palmroos, Outobugi Games** | [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/twitter.png?raw=true" width="24"/>](https://twitter.com/outobugi) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/github.png?raw=true" width="24"/>](https://github.com/outobugi) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/www.png?raw=true" width="24"/>](https://outobugi.com/) [<img src="https://github.com/dmhendricks/signature-social-icons/blob/master/icons/round-flat-filled/35px/youtube.png?raw=true" width="24"/>](https://www.youtube.com/@outobugi)|
And the contribution team in [AUTHORS.md](https://terrain3d.readthedocs.io/en/stable/docs/authors.html) and on the right of the github page.
## Contributing
Please see [CONTRIBUTING.md](https://github.com/TokisanGames/Terrain3D/blob/main/CONTRIBUTING.md) if you would like to help make Terrain3D the best terrain system for Godot.
## License
This addon has been released under the [MIT License](https://github.com/TokisanGames/Terrain3D/blob/main/LICENSE.txt).
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+48
View File
@@ -0,0 +1,48 @@
# Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors.
# Import From SimpleGrassTextured
#
# This script demonstrates how to import transforms from SimpleGrassTextured. To use it:
#
# 1. Setup the mesh asset you wish to use in the asset dock.
# 1. Select your Terrain3D node.
# 1. In the inspector, click Script (very bottom) and Quick Load import_sgt.gd.
# 1. At the very top, assign your SimpleGrassTextured node.
# 1. Input the desired mesh asset ID.
# 1. Click import. The output window and console will report when finished.
# 1. Clear the script from your Terrain3D node, and save your scene.
#
# The instance transforms are now stored in your region files.
#
# Use clear_instances to erase all instances that match the assign_mesh_id.
#
# The add_transforms function (called by add_multimesh) applies the height_offset specified in the
# Terrain3DMeshAsset.
# Once the transforms are imported, you can reassign any mesh you like into this mesh slot.
@tool
extends Terrain3D
@export var simple_grass_textured: MultiMeshInstance3D
@export var assign_mesh_id: int
@export var import: bool = false:
set = import_sgt
@export var clear_instances: bool = false:
set = clear_multimeshes
func clear_multimeshes(value: bool) -> void:
get_instancer().clear_by_mesh(assign_mesh_id)
func import_sgt(value: bool) -> void:
var sgt_mm: MultiMesh = simple_grass_textured.multimesh
var global_xform: Transform3D = simple_grass_textured.global_transform
print(
(
"Starting to import %d instances from SimpleGrassTextured using mesh id %d"
% [sgt_mm.instance_count, assign_mesh_id]
)
)
var time: int = Time.get_ticks_msec()
get_instancer().add_multimesh(assign_mesh_id, sgt_mm, simple_grass_textured.global_transform)
print("Import complete in %.2f seconds" % [float(Time.get_ticks_msec() - time) / 1000.])
+1
View File
@@ -0,0 +1 @@
uid://bllcuwetve45k
@@ -0,0 +1,137 @@
# Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors.
# This script is an addon for HungryProton's Scatter https://github.com/HungryProton/scatter
# It provides a `Project on Terrain3D` modifier, which allows Scatter
# to detect the terrain height from Terrain3D without using collision.
#
# Copy this file into /addons/proton_scatter/src/modifiers
# Then uncomment everything below (select, press CTRL+K)
# In the editor, add this modifier to Scatter, then set your Terrain3D node
#@tool
#extends "base_modifier.gd"
#
#
#signal projection_completed
#
#
#@export var terrain_node : NodePath
#@export var align_with_collision_normal : bool = false
#@export_range(0.0, 90.0, 0.1) var max_slope : float = 90.0
#@export var enable_texture_filtering : bool = false
#@export_range(0, 31) var target_texture_id : int = 0
#@export var not_target_texture : bool = false
#@export_range(0.0, 1.0, 0.01) var texture_threshold : float = 0.8
#
#var _terrain: Terrain3D
#
#
#func _init() -> void:
#display_name = "Project On Terrain3D"
#category = "Edit"
#can_restrict_height = false
#global_reference_frame_available = true
#local_reference_frame_available = true
#individual_instances_reference_frame_available = true
#use_global_space_by_default()
#
#documentation.add_paragraph(
#"This is a modified version of `Project on Colliders` that queries Terrain3D
#for heights without using collision. It constrains placement by slope or texture.
#
#This modifier must have terrain_node set to a Terrain3D node.")
#
#var p := documentation.add_parameter("Terrain Node")
#p.set_type("NodePath")
#p.set_description("Set your Terrain3D node.")
#
#p = documentation.add_parameter("Align with collision normal")
#p.set_type("bool")
#p.set_description(
#"Rotate the transform to align it with the collision normal in case
#the ray cast hit a collider.")
#
#p = documentation.add_parameter("Enable Texture Filtering")
#p.set_type("bool")
#p.set_description(
#"If enabled, objects will only be placed based on the ground texture specified.")
#
#p = documentation.add_parameter("Target Texture ID")
#p.set_type("int")
#p.set_description(
#"The ID of the texture to place objects on (0-31). Objects will only be placed on this texture.")
#
#p = documentation.add_parameter("Not Target Texture")
#p.set_type("bool")
#p.set_description(
#"If true, objects will be placed on all textures EXCEPT the target texture.")
#
#p = documentation.add_parameter("Texture Threshold")
#p.set_type("float")
#p.set_description("The blend value required for placement on the texture.")
#
#
#func _process_transforms(transforms, domain, _seed) -> void:
#if transforms.is_empty():
#return
#
#if terrain_node:
#_terrain = domain.get_root().get_node_or_null(terrain_node)
#
#if not _terrain:
#warning += """No Terrain3D node found"""
#return
#
#if not _terrain.data:
#warning += """Terrain3DData is not initialized"""
#return
#
## Review transforms
#var gt: Transform3D = domain.get_global_transform()
#var gt_inverse := gt.affine_inverse()
#var new_transforms_array: Array[Transform3D] = []
#var remapped_max_slope: float = remap(max_slope, 0.0, 90.0, 0.0, 1.0)
#for i in transforms.list.size():
#var t: Transform3D = transforms.list[i]
#
#var location: Vector3 = (gt * t).origin
#var height: float = _terrain.data.get_height(location)
#if is_nan(height):
#continue
#
#var normal: Vector3 = _terrain.data.get_normal(location)
#if not abs(Vector3.UP.dot(normal)) >= (1.0 - remapped_max_slope):
#continue
#
#if enable_texture_filtering:
#var texture_info: Vector3 = _terrain.data.get_texture_id(location)
#var base_id: int = int(texture_info.x)
#var overlay_id: int = int(texture_info.y)
#var blend_value: float = texture_info.z
## Skip if overlay or blend != target texture, unless inverted
#if ((overlay_id != target_texture_id or blend_value < texture_threshold) and \
#(base_id != target_texture_id or blend_value >= texture_threshold)) != not_target_texture:
#continue
#
#if align_with_collision_normal and not is_nan(normal.x):
#t.basis.y = normal
#t.basis.x = -t.basis.z.cross(normal)
#t.basis = t.basis.orthonormalized()
#
#t.origin.y = height - gt.origin.y
#new_transforms_array.push_back(t)
#
#transforms.list.clear()
#transforms.list.append_array(new_transforms_array)
#
#if transforms.is_empty():
#warning += """All transforms have been removed. Possible reasons include: \n"""
#if enable_texture_filtering:
#warning += """+ No matching texture found at any position.
#+ Texture threshold may be too high.
#"""
#warning += """+ No collider is close enough to the shapes.
#+ Ray length is too short.
#+ Ray direction is incorrect.
#+ Collision mask is not set properly.
#+ Max slope is too low.
#"""
@@ -0,0 +1 @@
uid://g3opjh3m3iww
@@ -0,0 +1,25 @@
[gd_scene load_steps=5 format=3 uid="uid://d3sr0a7dxfkr8"]
[ext_resource type="Script" uid="uid://bp7r4ppgq1m0g" path="res://addons/terrain_3d/extras/particle_example/terrain_3D_particles.gd" id="1_gl3qg"]
[ext_resource type="Material" uid="uid://el5y10hnh13g" path="res://addons/terrain_3d/extras/particle_example/process_material.tres" id="2_2gon1"]
[ext_resource type="Material" uid="uid://ljo1wt61kbkq" path="res://addons/terrain_3d/extras/particle_example/grass_material.tres" id="3_qyjnw"]
[sub_resource type="RibbonTrailMesh" id="RibbonTrailMesh_fwrtk"]
shape = 0
section_length = 0.18
section_segments = 1
[node name="Terrain3DParticles" type="Node3D"]
script = ExtResource("1_gl3qg")
instance_spacing = 0.25
cell_width = 24.0
grid_width = 5
rows = 96
amount = 9216
process_material = ExtResource("2_2gon1")
mesh = SubResource("RibbonTrailMesh_fwrtk")
shadow_mode = 0
mesh_material_override = ExtResource("3_qyjnw")
min_draw_distance = 60.0
particle_count = 230400
metadata/_edit_lock_ = true
@@ -0,0 +1,69 @@
// Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors.
shader_type spatial;
render_mode skip_vertex_transform, cull_disabled, blend_mix;
uniform vec2 wind_direction = vec2(1.0, 1.0);
varying vec4 data;
// A lot of hard coded things here atm.
void vertex() {
// Wind effect from model data, in this case no vertex colors,
// so just use vertex Y component, including mesh offset
data[2] = (VERTEX.y + 0.55);
data[2] *= data[2]; // make non-linear
// Ribbon used as a grass mesh.. so pinch the top.
VERTEX.xz *= (1.0 - data[2]);
// Brighten tips
COLOR = mix(COLOR, vec4(1.0), smoothstep(0.9, 1.0, data[2]));
// Darken base, skip is scale is less than threshold, as this means "grow in" is occuring.
COLOR *= INSTANCE_CUSTOM[3] < 0.35 ? 1. : mix(1.0, 0.75, smoothstep(0.35, 0.0, data[2]));
// Save red/green shift for fragment
data.rg = INSTANCE_CUSTOM.rg;
// World space vertex
vec3 w_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
// Get wind force and scale from process()
float scale = pow(INSTANCE_CUSTOM[3] * INSTANCE_CUSTOM[3], 0.707);
float force = INSTANCE_CUSTOM[2] * data[2] * scale;
// Add some cheap jitter at high wind values
force -= fract(force * 256.0) * force * 0.05;
// Curve the result
force = pow(force, 0.707);
// These 2 combined result in a decent bend without resorting to matrices or pivot data.
// Lateral move and wobble
float lateral_wobble = sin(TIME * 2.0 * (1.0 + data.r + data.g)) * 0.25 * (1.0 - INSTANCE_CUSTOM[2]);
vec2 direction = normalize(wind_direction);
w_vertex.xz -= (vec2(-direction.y, direction.x) * lateral_wobble + direction) * force;
// Flatten
w_vertex.y -= INSTANCE_CUSTOM[2] * force * data[2];
// Save final wind force value for fragment.
data[3] = force;
VERTEX = (VIEW_MATRIX * vec4(w_vertex, 1.0)).xyz;
NORMAL = MODELVIEW_NORMAL_MATRIX * NORMAL;
BINORMAL = MODELVIEW_NORMAL_MATRIX * BINORMAL;
TANGENT = MODELVIEW_NORMAL_MATRIX * TANGENT;
}
void fragment() {
// Hard coded color.
ALBEDO = vec3(0.20, 0.22, 0.05) * (data[2] * 0.5 + 0.5);
ALBEDO.rg *= (data.rg * 0.3 + 0.9);
ALBEDO *= pow(COLOR.rgb, vec3(2.2));
// Modify roughness / specular based on wind force for added detail
float spec_rough = clamp(max(data[2], data[3]), 0., 1.);
ROUGHNESS = 1. - spec_rough;
SPECULAR = clamp(spec_rough * 0.25, 0., .15);
BACKLIGHT = vec3(0.33);
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
ALBEDO = pow(ALBEDO, vec3(0.4));
#endif
}
@@ -0,0 +1 @@
uid://dq3lfyp3u5oxt
@@ -0,0 +1,8 @@
[gd_resource type="ShaderMaterial" load_steps=2 format=3 uid="uid://ljo1wt61kbkq"]
[ext_resource type="Shader" uid="uid://dq3lfyp3u5oxt" path="res://addons/terrain_3d/extras/particle_example/grass.gdshader" id="1_nkru0"]
[resource]
render_priority = 0
shader = ExtResource("1_nkru0")
shader_parameter/wind_direction = Vector2(1, 1)
@@ -0,0 +1,275 @@
// Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors.
// This is an example particle shader designed to procedurally place
// particles like grass, small rocks, and other ground effects, on the terrain
// surface by reading the Terrain3D data maps. It works in tandem with the
// provided GDScript.
shader_type particles;
render_mode disable_velocity, disable_force;
group_uniforms options;
uniform sampler2D main_noise;
uniform float main_noise_scale = 0.01;
uniform vec3 position_offset = vec3(0.);
uniform bool align_to_normal = true;
uniform float normal_strength : hint_range(0.01, 1.0, 0.01) = 0.3;
uniform bool random_rotation = true;
uniform float random_spacing : hint_range(0.0, 1.0, 0.01) = 0.5;
uniform vec3 min_scale = vec3(1.0);
uniform vec3 max_scale = vec3(1.0);
group_uniforms wind;
uniform float noise_scale = 0.0041;
uniform float wind_speed = 0.025;
uniform float wind_strength : hint_range(0.0, 1.0, 0.01) = 1.0;
uniform float wind_dithering = 4.0;
uniform vec2 wind_direction = vec2(1.0,1.0);
group_uniforms shapeing;
uniform float clod_scale_boost = 3.0;
uniform float clod_min_threshold : hint_range(0.0, 1.0, 0.001) = 0.2;
uniform float clod_max_threshold : hint_range(0.0, 1.0, 0.001) = 0.5;
uniform float patch_min_threshold : hint_range(0.0, 1.0, 0.001) = 0.025;
uniform float patch_max_threshold : hint_range(0.0, 1.0, 0.001) = 0.2;
group_uniforms filtering;
uniform float condition_dither_range : hint_range(0.0, 1.0, 0.01) = 0.15;
uniform float surface_slope_min : hint_range(0.0, 1.0, 0.01) = 0.87;
uniform float distance_fade_ammount : hint_range(0.0, 1.0, 0.01) = 0.5;
group_uniforms private;
uniform float max_dist = 1.;
uniform vec3 camera_position = vec3(0.);
uniform uint instance_rows = 1;
uniform float instance_spacing = 0.5;
uniform uint _background_mode = 0u;
uniform float _vertex_spacing = 1.0;
uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
uniform float _region_size = 1024.0;
uniform float _region_texel_size = 0.0009765625; // = 1/REGION_SIZE
uniform int _region_map_size = 32;
uniform int _region_map[1024];
uniform vec2 _region_locations[1024];
uniform highp sampler2DArray _height_maps : repeat_disable;
uniform highp sampler2DArray _control_maps : repeat_disable;
uniform highp sampler2DArray _color_maps : repeat_disable;
// Defined Constants
#define SKIP_PASS 0
#define VERTEX_PASS 1
#define FRAGMENT_PASS 2
// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
// Returns ivec3 with:
// XY: (0 to _region_size - 1) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region
ivec3 get_index_coord(const vec2 uv, const int search) {
vec2 r_uv = round(uv);
vec2 o_uv = mod(r_uv,_region_size);
ivec2 pos;
int bounds, layer_index = -1;
for (int i = -1; i < 0; i++) {
if ((layer_index == -1 && _background_mode == 0u) || i < 0) {
r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
}
}
return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
}
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
#define fma(a, b, c) ((a) * (b) + (c))
#endif
float random(vec2 v) {
return fract(1e4 * sin(fma(17.0, v.x, v.y * 0.1)) * (0.1 + abs(sin(fma(v.y, 13.0, v.x)))));
}
mat3 rotation_matrix(vec3 axis, float angle) {
float c = cos(angle);
float s = sin(angle);
float t = 1.0 - c;
vec3 n = normalize(axis);
float x = n.x;
float y = n.y;
float z = n.z;
return mat3(
vec3(t * x * x + c, t * x * y - z * s, t * x * z + y * s),
vec3(t * x * y + z * s, t * y * y + c, t * y * z - x * s),
vec3(t * x * z - y * s, t * y * z + x * s, t * z * z + c));
}
mat3 align_to_vector(vec3 normal) {
vec3 up = vec3(0.0, 1.0, 0.0);
if (abs(dot(normal, up)) > 0.9999) { // Avoid singularity
up = vec3(1.0, 0.0, 0.0);
}
vec3 tangent = normalize(cross(up, normal));
vec3 bitangent = normalize(cross(tangent, normal));
return mat3(tangent, normal, bitangent);
}
void start() {
// Create centered a grid
vec3 pos = vec3(float(INDEX % instance_rows), 0.0, float(INDEX / instance_rows)) - float(instance_rows >> 1u);
// Apply spcaing
pos *= instance_spacing;
// Move the grid to the emitter, snapping is handled CPU side
pos.xz += EMISSION_TRANSFORM[3].xz;
// Create random values per-instance, incorporating the seed, mask bits to avoid NAN/INF
float seed = fract(uintBitsToFloat(RANDOM_SEED & 0x7EFFFFFFu));
vec3 r = fract(vec3(random(pos.xz), random(pos.xz + vec2(0.5)), random(pos.xz - vec2(0.5))) + seed);
// Randomize instance spacing
pos.x += ((r.x * 2.0) - 1.0) * random_spacing * instance_spacing;
pos.z += ((r.z * 2.0) - 1.0) * random_spacing * instance_spacing;
// Lookup offsets, ID and blend weight
const vec3 offsets = vec3(0, 1, 2);
vec2 index_id = floor(pos.xz * _vertex_density);
vec2 weight = fract(pos.xz * _vertex_density);
vec2 invert = 1.0 - weight;
vec4 weights = vec4(
invert.x * weight.y, // 0
weight.x * weight.y, // 1
weight.x * invert.y, // 2
invert.x * invert.y // 3
);
ivec3 index[4];
// Map lookups
index[0] = get_index_coord(index_id + offsets.xy, VERTEX_PASS);
index[1] = get_index_coord(index_id + offsets.yy, VERTEX_PASS);
index[2] = get_index_coord(index_id + offsets.yx, VERTEX_PASS);
index[3] = get_index_coord(index_id + offsets.xx, VERTEX_PASS);
highp float h[8];
h[0] = texelFetch(_height_maps, index[0], 0).r; // 0 (0,1)
h[1] = texelFetch(_height_maps, index[1], 0).r; // 1 (1,1)
h[2] = texelFetch(_height_maps, index[2], 0).r; // 2 (1,0)
h[3] = texelFetch(_height_maps, index[3], 0).r; // 3 (0,0)
h[4] = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, VERTEX_PASS), 0).r; // 4 (1,2)
h[5] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, VERTEX_PASS), 0).r; // 5 (2,1)
h[6] = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, VERTEX_PASS), 0).r; // 6 (2,0)
h[7] = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, VERTEX_PASS), 0).r; // 7 (0,2)
vec3 index_normal[4];
index_normal[0] = vec3(h[0] - h[1], _vertex_spacing, h[0] - h[7]);
index_normal[1] = vec3(h[1] - h[5], _vertex_spacing, h[1] - h[4]);
index_normal[2] = vec3(h[2] - h[6], _vertex_spacing, h[2] - h[1]);
index_normal[3] = vec3(h[3] - h[2], _vertex_spacing, h[3] - h[0]);
vec3 w_normal = normalize(
index_normal[0] * weights[0] +
index_normal[1] * weights[1] +
index_normal[2] * weights[2] +
index_normal[3] * weights[3]);
// Set the height according to the heightmap data
pos.y =
h[0] * weights[0] +
h[1] * weights[1] +
h[2] * weights[2] +
h[3] * weights[3] ;
// Offset, Rotation, Alignment.
TRANSFORM = mat4(1.0);
vec3 orientation = vec3(0., 1., 0.);
vec2 uv = (pos.xz) * main_noise_scale;
float noise = textureLod(main_noise, uv, 0.0).r;
float clods = smoothstep(clod_min_threshold, clod_max_threshold, noise) * clod_scale_boost;
float patch = smoothstep(patch_min_threshold, patch_max_threshold, noise);
float width_modifier = 1.0 + 3.0 * smoothstep(0., max_dist, length(camera_position - pos));
// Calculate scale
vec3 scale = vec3(
mix(min_scale.x, max_scale.x, r.x) * width_modifier,
mix(min_scale.y, max_scale.y, r.y) + clods,
mix(min_scale.z, max_scale.z, r.z) * width_modifier) * patch;
// Apply scale to offset
vec3 offset = position_offset * scale;
// Apply normal orientation
if (align_to_normal) {
orientation = mix(orientation, w_normal, normal_strength);
mat3 alignment = align_to_vector(orientation);
offset = alignment * offset;
TRANSFORM = mat4(alignment);
}
// Apply rotation around orientation
if (random_rotation) {
mat3 rotation = rotation_matrix(orientation, r.x * TAU);
TRANSFORM = mat4(rotation) * TRANSFORM;
}
// Filtering - Causes some particles to be rendered as degenerate triangles
// via 0./0. - Particles filtered this way are still processed by the GPU.
// For compatibility it seems we must shift as well.
// Surface slope filtering
if (surface_slope_min > w_normal.y + (r.y - 0.5) * condition_dither_range) {
pos.y = 0. / 0.;
pos.xz = vec2(100000.0);
}
// Read color map
highp vec4 c[4];
#define COLOR_MAP vec4(1., 1., 1., 0.5)
c[0] = index[0].z >= 0 ? texelFetch(_color_maps, index[0], 0) : COLOR_MAP; // 0 (0,1)
c[1] = index[1].z >= 0 ? texelFetch(_color_maps, index[1], 0) : COLOR_MAP; // 1 (1,1)
c[2] = index[2].z >= 0 ? texelFetch(_color_maps, index[2], 0) : COLOR_MAP; // 2 (1,0)
c[3] = index[3].z >= 0 ? texelFetch(_color_maps, index[3], 0) : COLOR_MAP; // 3 (0,0)
vec4 color_map =
c[0] * weights[0] +
c[1] * weights[1] +
c[2] * weights[2] +
c[3] * weights[3] ;
COLOR = color_map;
// Read control map
uint control = floatBitsToUint(texelFetch(_control_maps, index[3], 0).r);
bool auto = bool(control & 0x1u);
int base = int(control >>27u & 0x1Fu);
int over = int(control >> 22u & 0x1Fu);
float blend = float(control >> 14u & 0xFFu) * 0.003921568627450; // 1. / 255.
// Hardcoded example, hand painted texture id 0 is filtered out.
if (!auto && ((base == 0 && blend < 0.7) || (over == 0 && blend >= 0.3))) {
pos.y = 0. / 0.;
pos.xz = vec2(100000.0);
}
if (length(camera_position - pos) > max_dist) {
pos.y = 0. / 0.;
pos.xz = vec2(100000.0);
} else {
float fade_factor = 1.0 - smoothstep(max_dist * distance_fade_ammount, max_dist + 0.0001, length(camera_position - pos)) + 0.001;
scale.y *= fade_factor;
offset *= fade_factor;
}
// Apply scale
TRANSFORM[0] *= scale.x;
TRANSFORM[1] *= scale.y;
TRANSFORM[2] *= scale.z;
// Apply the position
TRANSFORM[3].xyz = pos.xyz + offset;
// Save Fixed 2 Random values for Reg/Green color randomness
CUSTOM.rg = r.rg;
// Save Y component scale pre-rotation
CUSTOM[3] = scale.y;
}
void process() {
// Extract world space UV from Transform Matrix
vec2 uv = (TRANSFORM[3].xz + CUSTOM.rg * wind_dithering) * noise_scale;
// Scaled wind noise, updated per instance, at process FPS. Passed to Vertex()
CUSTOM[2] = textureLod(main_noise, uv + TIME * wind_speed * normalize(wind_direction), 0.0).r * wind_strength;
}
@@ -0,0 +1 @@
uid://dce675i014xcn
File diff suppressed because one or more lines are too long
@@ -0,0 +1,244 @@
# Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors.
#
# This is an example of using a particle shader with Terrain3D.
# To use it, add `Terrain3DParticles.tscn` to your scene and assign the terrain.
# Then customize the settings, materials and shader to extend it and make it your own.
@tool
extends Node3D
#region settings
## Auto set if attached as a child of a Terrain3D node
@export var terrain: Terrain3D:
set(value):
terrain = value
_create_grid()
## Distance between instances
@export_range(0.125, 2.0, 0.015625) var instance_spacing: float = 0.5:
set(value):
instance_spacing = clamp(round(value * 64.0) * 0.015625, 0.125, 2.0)
rows = maxi(int(cell_width / instance_spacing), 1)
amount = rows * rows
_set_offsets()
## Width of an individual cell of the grid
@export_range(8.0, 256.0, 1.0) var cell_width: float = 32.0:
set(value):
cell_width = clamp(value, 8.0, 256.0)
rows = maxi(int(cell_width / instance_spacing), 1)
amount = rows * rows
min_draw_distance = 1.0
# Have to update aabb
if terrain and terrain.data:
var height_range: Vector2 = terrain.data.get_height_range()
var height: float = height_range[0] - height_range[1]
var aabb: AABB = AABB()
aabb.size = Vector3(cell_width, height, cell_width)
aabb.position = aabb.size * -0.5
aabb.position.y = height_range[1]
for p in particle_nodes:
p.custom_aabb = aabb
_set_offsets()
## Grid width. Must be odd.
## Higher values cull slightly better, draw further out.
@export_range(1, 15, 2) var grid_width: int = 9:
set(value):
grid_width = value
particle_count = 1
min_draw_distance = 1.0
_create_grid()
@export_storage var rows: int = 1
@export_storage var amount: int = 1:
set(value):
amount = value
particle_count = value
last_pos = Vector3.ZERO
for p in particle_nodes:
p.amount = amount
@export_range(1, 256, 1) var process_fixed_fps: int = 30:
set(value):
process_fixed_fps = maxi(value, 1)
for p in particle_nodes:
p.fixed_fps = process_fixed_fps
p.preprocess = 1.0 / float(process_fixed_fps)
## Access to process material parameters
@export var process_material: ShaderMaterial
## The mesh that each particle will render
@export var mesh: Mesh
@export var shadow_mode: GeometryInstance3D.ShadowCastingSetting = (
GeometryInstance3D.ShadowCastingSetting.SHADOW_CASTING_SETTING_ON
):
set(value):
shadow_mode = value
for p in particle_nodes:
p.cast_shadow = value
## Override material for the particle mesh
@export_custom(PROPERTY_HINT_RESOURCE_TYPE, "BaseMaterial3D,ShaderMaterial")
var mesh_material_override: Material:
set(value):
mesh_material_override = value
for p in particle_nodes:
p.material_override = mesh_material_override
@export_group("Info")
## The minimum distance that particles will be drawn upto
## If using fade out effects like pixel alpha this is the limit to use.
@export var min_draw_distance: float = 1.0:
set(value):
min_draw_distance = float(cell_width * grid_width) * 0.5
## Displays current total particle count based on Cell Width and Instance Spacing
@export var particle_count: int = 1:
set(value):
particle_count = amount * grid_width * grid_width
#endregion
var offsets: Array[Vector3]
var last_pos: Vector3 = Vector3.ZERO
var particle_nodes: Array[GPUParticles3D]
func _ready() -> void:
if not terrain:
var parent: Node = get_parent()
if parent is Terrain3D:
terrain = parent
_create_grid()
func _notification(what: int) -> void:
if what == NOTIFICATION_PREDELETE:
_destroy_grid()
func _physics_process(delta: float) -> void:
if terrain:
var camera: Camera3D = terrain.get_camera()
if camera:
if last_pos.distance_squared_to(camera.global_position) > 1.0:
var pos: Vector3 = camera.global_position.snapped(Vector3.ONE)
_position_grid(pos)
RenderingServer.material_set_param(
process_material.get_rid(), "camera_position", pos
)
last_pos = camera.global_position
_update_process_parameters()
else:
set_physics_process(false)
func _create_grid() -> void:
_destroy_grid()
if not terrain:
return
set_physics_process(true)
_set_offsets()
var hr: Vector2 = terrain.data.get_height_range()
var height: float = hr.x - hr.y
var aabb: AABB = AABB()
aabb.size = Vector3(cell_width, height, cell_width)
aabb.position = aabb.size * -0.5
aabb.position.y = hr.y
var half_grid: int = grid_width / 2
# Iterating the array like this allows identifying grid position, in case setting
# different mesh or materials is desired for LODs etc.
for x in range(-half_grid, half_grid + 1):
for z in range(-half_grid, half_grid + 1):
#var ring: int = maxi(maxi(absi(x), absi(z)), 0)
var particle_node = GPUParticles3D.new()
particle_node.lifetime = 600.0
particle_node.amount = amount
particle_node.explosiveness = 1.0
particle_node.amount_ratio = 1.0
particle_node.process_material = process_material
particle_node.draw_pass_1 = mesh
particle_node.speed_scale = 1.0
particle_node.custom_aabb = aabb
particle_node.cast_shadow = shadow_mode
particle_node.fixed_fps = process_fixed_fps
# This prevent minor grid alignment errors when the camera is moving very fast
particle_node.preprocess = 1.0 / float(process_fixed_fps)
if mesh_material_override:
particle_node.material_override = mesh_material_override
particle_node.use_fixed_seed = true
if x > -half_grid and z > -half_grid: # Use the same seed across all nodes
particle_node.seed = particle_nodes[0].seed
self.add_child(particle_node)
particle_node.emitting = true
particle_nodes.push_back(particle_node)
last_pos = Vector3.ZERO
func _set_offsets() -> void:
var half_grid: int = grid_width / 2
offsets.clear()
for x in range(-half_grid, half_grid + 1):
for z in range(-half_grid, half_grid + 1):
var offset := Vector3(
float(x * rows) * instance_spacing, 0.0, float(z * rows) * instance_spacing
)
offsets.append(offset)
func _destroy_grid() -> void:
for node: GPUParticles3D in particle_nodes:
if is_instance_valid(node):
node.queue_free()
particle_nodes.clear()
func _position_grid(pos: Vector3) -> void:
for i in particle_nodes.size():
var node: GPUParticles3D = particle_nodes[i]
var snap = Vector3(pos.x, 0, pos.z).snapped(Vector3.ONE) + offsets[i]
node.global_position = (snap / instance_spacing).round() * instance_spacing
node.reset_physics_interpolation()
node.restart(true) # keep the same seed.
func _update_process_parameters() -> void:
if process_material:
var process_rid: RID = process_material.get_rid()
if terrain and process_rid.is_valid():
RenderingServer.material_set_param(
process_rid, "_background_mode", terrain.material.world_background
)
RenderingServer.material_set_param(
process_rid, "_vertex_spacing", terrain.vertex_spacing
)
RenderingServer.material_set_param(
process_rid, "_vertex_density", 1.0 / terrain.vertex_spacing
)
RenderingServer.material_set_param(process_rid, "_region_size", terrain.region_size)
RenderingServer.material_set_param(
process_rid, "_region_texel_size", 1.0 / terrain.region_size
)
RenderingServer.material_set_param(process_rid, "_region_map_size", 32)
RenderingServer.material_set_param(
process_rid, "_region_map", terrain.data.get_region_map()
)
RenderingServer.material_set_param(
process_rid, "_region_locations", terrain.data.get_region_locations()
)
RenderingServer.material_set_param(
process_rid, "_height_maps", terrain.data.get_height_maps_rid()
)
RenderingServer.material_set_param(
process_rid, "_control_maps", terrain.data.get_control_maps_rid()
)
RenderingServer.material_set_param(
process_rid, "_color_maps", terrain.data.get_color_maps_rid()
)
RenderingServer.material_set_param(process_rid, "instance_spacing", instance_spacing)
RenderingServer.material_set_param(process_rid, "instance_rows", rows)
RenderingServer.material_set_param(process_rid, "max_dist", min_draw_distance)
@@ -0,0 +1 @@
uid://bp7r4ppgq1m0g
@@ -0,0 +1,67 @@
// Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors.
// This shader snippet draws a hex grid
// To use it, add this line to the top of your shader:
// #include "res://addons/terrain_3d/extras/shaders/hex_grid.gdshaderinc"
// And this line at the bottom of your shader:
// draw_hex_grid(uv2, _region_texel_size, w_normal, ALBEDO);
mat2 rotate2d(float _angle) {
return mat2(vec2(cos(_angle),-sin(_angle)), vec2(sin(_angle), cos(_angle)));
}
void draw_hex_grid(vec2 uv, float texel_size, vec3 normal, inout vec3 albedo) {
float hex_size = 0.02;
float line_thickness = 0.04;
vec2 guv = (uv - vec2(0.5 * texel_size)) / hex_size;
// Convert UV to axial hex coordinates
float q = (sqrt(3.0) / 3.0 * guv.x - 1.0 / 3.0 * guv.y);
float r = (2.0 / 3.0 * guv.y);
// Cube coordinates for the hex (q, r, -q-r)
float x = q;
float z = r;
float y = -x - z;
// Round to the nearest hex center
vec3 rounded = round(vec3(x, y, z));
vec3 diff = abs(vec3(x, y, z) - rounded);
// Fix rounding errors
if (diff.x > diff.y && diff.x > diff.z) {
rounded.x = -rounded.y - rounded.z;
} else if (diff.y > diff.z) {
rounded.y = -rounded.x - rounded.z;
} else {
rounded.z = -rounded.x - rounded.y;
}
// Find the hex center in UV space
vec2 hex_center = vec2(
sqrt(3.0) * rounded.x + sqrt(3.0) / 2.0 * rounded.z,
3.0 / 2.0 * rounded.z
);
// Relative position within the hex
vec2 local_pos = guv - hex_center;
vec2 lines_uv = local_pos;
float line = 1.0;
for (int i = 0; i < 6; i++) {
vec2 luv = lines_uv * rotate2d(radians(60.0 * float(i) + 30.0));
float dist = abs(dot(luv + vec2(0.90), vec2(0.0, 1.0)));
line = min(line, dist);
}
// Filter lines by slope
float slope = 4.; // Can also assign to (auto_slope * 4.) to match grass placement
float slope_factor = clamp(dot(vec3(0., 1., 0.), slope * (normal - 1.) + 1.), 0., 1.);
// Draw hex grid
albedo = mix(albedo, vec3(1.0), smoothstep(line_thickness + 0.02, line_thickness, line) * slope_factor);
// Draw Hex center dot
albedo = mix(albedo, vec3(0.0, 0.5, 0.5), smoothstep(0.11, 0.10, length(local_pos)) * slope_factor);
}
@@ -0,0 +1 @@
uid://mri8pfoj2mfk
@@ -0,0 +1,367 @@
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
/* This is an example stripped down shader with maximum performance in mind.
* Only Autoshader/Base/Over/Blend/Holes/Colormap are supported.
* All terrain normal calculations take place in vetex().
*
* Control map indices are processed such that each ID only requires reading ONCE.
* The following features: projection, detiling, and paintable rotation / scale
* cannot work with this method, without the additional samples required for blending
* between same ID textures with different values across indices.
*/
// Defined Constants
#define SKIP_PASS 0
#define VERTEX_PASS 1
#define FRAGMENT_PASS 2
#define COLOR_MAP vec4(1.0, 1.0, 1.0, 0.5)
#define DIV_255 0.003921568627450 // 1. / 255.
// Inline Functions
#define DECODE_BLEND(control) float(control >> 14u & 0xFFu) * DIV_255
#define DECODE_AUTO(control) bool(control & 0x1u)
#define DECODE_BASE(control) int(control >> 27u & 0x1Fu)
#define DECODE_OVER(control) int(control >> 22u & 0x1Fu)
#define DECODE_HOLE(control) bool(control >>2u & 0x1u)
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
#define fma(a, b, c) ((a) * (b) + (c))
#define dFdxCoarse(a) dFdx(a)
#define dFdyCoarse(a) dFdy(a)
#endif
// Private uniforms
uniform vec3 _camera_pos = vec3(0.f);
uniform float _mesh_size = 48.f;
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
uniform uint _mouse_layer = 0x80000000u; // Layer 32
uniform float _vertex_spacing = 1.0;
uniform float _vertex_density = 1.0; // = 1./_vertex_spacing
uniform float _region_size = 1024.0;
uniform float _region_texel_size = 0.0009765625; // = 1./region_size
uniform int _region_map_size = 32;
uniform int _region_map[1024];
uniform vec2 _region_locations[1024];
uniform float _texture_normal_depth_array[32];
uniform float _texture_ao_strength_array[32];
uniform float _texture_roughness_mod_array[32];
uniform float _texture_uv_scale_array[32];
uniform vec4 _texture_color_array[32];
uniform highp sampler2DArray _height_maps : repeat_disable;
uniform highp sampler2DArray _control_maps : repeat_disable;
uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
// Public uniforms
uniform bool enable_texturing = true;
uniform float blend_sharpness : hint_range(0, 1) = 0.5;
uniform bool flat_terrain_normals = false;
// Autoshader
uniform float auto_slope : hint_range(0, 10) = 1.0;
uniform float auto_height_reduction : hint_range(0, 1) = 0.1;
uniform int auto_base_texture : hint_range(0, 31) = 0;
uniform int auto_overlay_texture : hint_range(0, 31) = 1;
// Macro Variation
uniform bool enable_macro_variation = true;
uniform vec3 macro_variation1 : source_color = vec3(1.);
uniform vec3 macro_variation2 : source_color = vec3(1.);
uniform float macro_variation_slope : hint_range(0., 1.) = 0.333;
uniform highp sampler2D noise_texture : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
uniform float noise1_scale : hint_range(0.001, 1.) = 0.04; // Used for macro variation 1. Scaled up 10x
uniform float noise1_angle : hint_range(0, 6.283) = 0.;
uniform vec2 noise1_offset = vec2(0.5);
uniform float noise2_scale : hint_range(0.001, 1.) = 0.076; // Used for macro variation 2. Scaled up 10x
// Varyings & Types
varying vec3 v_normal;
varying vec3 v_vertex;
varying mat3 TBN;
////////////////////////
// Vertex
////////////////////////
// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
// Returns ivec3 with:
// XY: (0 to _region_size - 1) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region
ivec3 get_index_coord(const vec2 uv, const int search) {
vec2 r_uv = round(uv);
vec2 o_uv = mod(r_uv,_region_size);
ivec2 pos;
int bounds, layer_index = -1;
for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
}
}
return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
}
// Takes in descaled (world_space / region_size) world to region space XZ (UV2) coordinates, returns vec3 with:
// XY: (0. to 1.) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region
vec3 get_index_uv(const vec2 uv2) {
ivec2 pos = ivec2(floor(uv2)) + (_region_map_size / 2);
int bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
int layer_index = _region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1;
return vec3(uv2 - _region_locations[layer_index], float(layer_index));
}
void vertex() {
// Get vertex of flat plane in world coordinates and set world UV
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
// Camera distance to vertex on flat plane
float v_vertex_xz_dist = length(v_vertex.xz - _camera_pos.xz);
// Geomorph vertex, set end and start for linear height interpolate
float scale = MODEL_MATRIX[0][0];
float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0));
vec2 v_fract = fract(VERTEX.xz * 0.5) * 2.0;
// For LOD0 morph from a regular grid to an alternating grid to align with LOD1+
vec2 shift = (scale < _vertex_spacing + 1e-6) ? // LOD0 or not
// Shift from regular to symetric
mix(v_fract, vec2(v_fract.x, -v_fract.y),
round(fract(round(mod(v_vertex.z * _vertex_density, 4.0)) *
round(mod(v_vertex.x * _vertex_density, 4.0)) * 0.25))
) :
// Symetric shift
v_fract * round((fract(v_vertex.xz * 0.25 / scale) - 0.5) * 4.0);
vec2 start_pos = v_vertex.xz * _vertex_density;
vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density;
v_vertex.xz -= shift * scale * vertex_lerp;
// UV coordinates in world space. Values are 0 to _region_size within regions
UV = v_vertex.xz * _vertex_density;
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
// Discard vertices for Holes. 1 lookup
ivec3 v_region = get_index_coord(start_pos, VERTEX_PASS);
uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r;
bool hole = DECODE_HOLE(control);
// Show holes to all cameras except mouse camera (on exactly 1 layer)
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
(hole || (_background_mode == 0u && v_region.z == -1))) {
v_vertex.x = 0. / 0.;
} else {
// Set final vertex height & calculate vertex normals. 3 lookups
ivec3 uv_a = get_index_coord(start_pos, VERTEX_PASS);
ivec3 uv_b = get_index_coord(end_pos, VERTEX_PASS);
float h = mix(texelFetch(_height_maps, uv_a, 0).r,texelFetch(_height_maps, uv_b, 0).r,vertex_lerp);
v_vertex.y = h;
// Vertex normals
float u = mix(texelFetch(_height_maps, get_index_coord(start_pos + vec2(1,0), VERTEX_PASS), 0).r,
texelFetch(_height_maps, get_index_coord(end_pos + vec2(1,0), VERTEX_PASS), 0).r, vertex_lerp);
float v = mix(texelFetch(_height_maps, get_index_coord(start_pos + vec2(0,1), VERTEX_PASS), 0).r,
texelFetch(_height_maps, get_index_coord(end_pos + vec2(0,1), VERTEX_PASS), 0).r, vertex_lerp);
v_normal = normalize(vec3(h - u, _vertex_spacing, h - v));
}
// Convert model space to view space w/ skip_vertex_transform render mode
VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz;
// Apply terrain normals
vec3 w_tangent = normalize(cross(v_normal, vec3(0.0, 0.0, 1.0)));
vec3 w_binormal = normalize(cross(v_normal, w_tangent));
TBN = mat3(w_tangent, w_binormal, v_normal);
NORMAL = normalize((VIEW_MATRIX * vec4(v_normal, 0.0)).xyz);
BINORMAL = normalize((VIEW_MATRIX * vec4(w_binormal, 0.0)).xyz);
TANGENT = normalize((VIEW_MATRIX * vec4(w_tangent, 0.0)).xyz);
}
////////////////////////
// Fragment
////////////////////////
mat2 rotate_plane(float angle) {
float c = cos(angle), s = sin(angle);
return mat2(vec2(c, s), vec2(-s, c));
}
void fragment() {
// Recover UVs
vec2 uv = UV;
vec2 uv2 = UV2;
// Lookup offsets, ID and blend weight
vec3 region_uv = get_index_uv(uv2);
const vec3 offsets = vec3(0, 1, 2);
vec2 index_id = floor(uv);
vec2 weight = fract(uv);
vec2 invert = 1.0 - weight;
vec4 weights = vec4(
invert.x * weight.y, // 0
weight.x * weight.y, // 1
weight.x * invert.y, // 2
invert.x * invert.y // 3
);
ivec3 index[4];
// control map lookups, used for some normal lookups as well
index[0] = get_index_coord(index_id + offsets.xy, FRAGMENT_PASS);
index[1] = get_index_coord(index_id + offsets.yy, FRAGMENT_PASS);
index[2] = get_index_coord(index_id + offsets.yx, FRAGMENT_PASS);
index[3] = get_index_coord(index_id + offsets.xx, FRAGMENT_PASS);
vec3 base_ddx = dFdxCoarse(v_vertex);
vec3 base_ddy = dFdyCoarse(v_vertex);
vec4 base_dd = vec4(base_ddx.xz, base_ddy.xz);
// Calculate the effective mipmap for regionspace
float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density);
// Color map
vec4 color_map = region_uv.z > -1.0 ? textureLod(_color_maps, region_uv, region_mip) : COLOR_MAP;
if (flat_terrain_normals) {
NORMAL = normalize(cross(dFdyCoarse(VERTEX),dFdxCoarse(VERTEX)));
TANGENT = normalize(cross(NORMAL, VIEW_MATRIX[2].xyz));
BINORMAL = normalize(cross(NORMAL, TANGENT));
}
// defaults
vec4 normal_rough = vec4(0., 1., 0., 0.7);
vec4 albedo_height = vec4(1.);
float normal_map_depth = 1.;
float ao_strength = 0.;
if (enable_texturing) {
// set to zero before accumulation
albedo_height = vec4(0.);
normal_rough = vec4(0.);
normal_map_depth = 0.;
ao_strength = 0.;
float total_weight = 0.;
float sharpness = fma(56., blend_sharpness, 8.);
// Get index control data
// 1 - 4 lookups
uvec4 control = floatBitsToUint(vec4(
texelFetch(_control_maps, index[0], 0).r,
texelFetch(_control_maps, index[1], 0).r,
texelFetch(_control_maps, index[2], 0).r,
texelFetch(_control_maps, index[3], 0).r));
{
// Auto blend calculation
float auto_blend = clamp(fma(auto_slope * 2.0, (v_normal.y - 1.0), 1.0)
- auto_height_reduction * 0.01 * v_vertex.y, 0.0, 1.0);
// Enable Autoshader if outside regions or painted in regions, otherwise manual painted
uvec4 is_auto = (control & uvec4(0x1u)) | uvec4(uint(region_uv.z < 0.0));
uint u_auto =
((uint(auto_base_texture) & 0x1Fu) << 27u) |
((uint(auto_overlay_texture) & 0x1Fu) << 22u) |
((uint(fma(auto_blend, 255.0 , 0.5)) & 0xFFu) << 14u);
control = control * (1u - is_auto) + u_auto * is_auto;
}
// Texture weights
// Vectorised Deocode of all texture IDs, then swizzle to per index mapping.
ivec4 t_id[2] = {ivec4(control >> uvec4(27u) & uvec4(0x1Fu)),
ivec4(control >> uvec4(22u) & uvec4(0x1Fu))};
ivec2 texture_ids[4] = ivec2[4](
ivec2(t_id[0].x, t_id[1].x),
ivec2(t_id[0].y, t_id[1].y),
ivec2(t_id[0].z, t_id[1].z),
ivec2(t_id[0].w, t_id[1].w));
// interpolated weights.
vec4 weights_id_1 = vec4(control >> uvec4(14u) & uvec4(0xFFu)) * DIV_255 * weights;
vec4 weights_id_0 = weights - weights_id_1;
vec2 t_weights[4] = {vec2(0), vec2(0), vec2(0), vec2(0)};
for (int i = 0; i < 4; i++) {
vec2 w_0 = vec2(weights_id_0[i]);
vec2 w_1 = vec2(weights_id_1[i]);
ivec2 id_0 = texture_ids[i].xx;
ivec2 id_1 = texture_ids[i].yy;
t_weights[0] += fma(w_0, vec2(equal(texture_ids[0], id_0)), w_1 * vec2(equal(texture_ids[0], id_1)));
t_weights[1] += fma(w_0, vec2(equal(texture_ids[1], id_0)), w_1 * vec2(equal(texture_ids[1], id_1)));
t_weights[2] += fma(w_0, vec2(equal(texture_ids[2], id_0)), w_1 * vec2(equal(texture_ids[2], id_1)));
t_weights[3] += fma(w_0, vec2(equal(texture_ids[3], id_0)), w_1 * vec2(equal(texture_ids[3], id_1)));
}
// Process control data to determine each texture ID present, so that only
// a single sample will be needed later, as all id are contiguous when features
// like detiling, scale, rotation, and projection are not present.
// 2 to 16 lookups
uint id_read = 0u; // 1 bit per possible ID
// world normal adjustment requires acess to previous id during next iteration
vec4 nrm = vec4(0.0, 1.0, 0.0, 1.0);
// adjust uv scale to account for vertex spacing
uv *= _vertex_spacing;
for (int i = 0; i < 4; i++) {
for (int t = 0; t < 2; t++) {
int id = texture_ids[i][t];
uint mask = 1u << uint(id);
if ((id_read & mask) == 0u) {
// Set this id bit
id_read |= mask;
float id_w = t_weights[i][t];
float id_scale = _texture_uv_scale_array[id] * 0.5;
vec2 id_uv = fma(uv, vec2(id_scale), vec2(0.5));
vec4 i_dd = base_dd * id_scale;
vec4 alb = textureGrad(_texture_array_albedo, vec3(id_uv, float(id)), i_dd.xy, i_dd.zw);
float world_normal = clamp(fma(TBN[0], vec3(nrm.x), fma(TBN[1], vec3(nrm.z), v_normal * vec3(nrm.y))).y, 0., 1.);
nrm = textureGrad(_texture_array_normal, vec3(id_uv, float(id)), i_dd.xy, i_dd.zw);
alb.rgb *= _texture_color_array[id].rgb;
nrm.a = clamp(nrm.a + _texture_roughness_mod_array[id], 0., 1.);
// Unpack normal map for blending.
nrm.xyz = fma(nrm.xzy, vec3(2.0), vec3(-1.0));
// height weight modifier.
float id_weight = exp2(sharpness * log2(id_w + alb.a * world_normal));
albedo_height += alb * id_weight;
normal_rough += nrm * id_weight;
normal_map_depth += _texture_normal_depth_array[id] * id_weight;
ao_strength += _texture_ao_strength_array[id] * id_weight;
total_weight += id_weight;
}
}
}
// normalize accumulated values back to 0.0 - 1.0 range.
float weight_inv = 1.0 / total_weight;
albedo_height *= weight_inv;
normal_rough *= weight_inv;
normal_map_depth *= weight_inv;
ao_strength *= weight_inv;
}
// Macro variation. 2 lookups
vec3 macrov = vec3(1.);
if (enable_macro_variation) {
float noise1 = texture(noise_texture, (uv * noise1_scale * .1 + noise1_offset) * rotate_plane(noise1_angle)).r;
float noise2 = texture(noise_texture, uv * noise2_scale * .1).r;
macrov = mix(macro_variation1, vec3(1.), noise1);
macrov *= mix(macro_variation2, vec3(1.), noise2);
macrov = mix(vec3(1.0), macrov, clamp(v_normal.y + macro_variation_slope, 0., 1.));
}
// Wetness/roughness modifier, converting 0 - 1 range to -1 to 1 range, clamped to Godot roughness values
float roughness = clamp(fma(color_map.a - 0.5, 2.0, normal_rough.a), 0., 1.);
// Apply PBR
ALBEDO = albedo_height.rgb * color_map.rgb * macrov;
ROUGHNESS = roughness;
SPECULAR = 1. - normal_rough.a;
// Repack final normal map value.
NORMAL_MAP = fma(normalize(normal_rough.xzy), vec3(0.5), vec3(0.5));
NORMAL_MAP_DEPTH = normal_map_depth;
// Higher and/or facing up, less occluded.
float ao = (1. - (albedo_height.a * log(2.1 - ao_strength))) * (1. - normal_rough.y);
AO = clamp(1. - ao * ao_strength, albedo_height.a, 1.0);
AO_LIGHT_AFFECT = (1.0 - albedo_height.a) * clamp(normal_rough.y, 0., 1.);
}
@@ -0,0 +1 @@
uid://bbx2xhanpq5l3
@@ -0,0 +1,217 @@
// Copyright © 2023-2026 Cory Petkovsek, Roope Palmroos, and Contributors.
// This shader is the minimum needed to allow the terrain to function, without any texturing.
shader_type spatial;
render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx,skip_vertex_transform;
// Defined Constants
#define SKIP_PASS 0
#define VERTEX_PASS 1
#define FRAGMENT_PASS 2
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
#define fma(a, b, c) ((a) * (b) + (c))
#define dFdxCoarse(a) dFdx(a)
#define dFdyCoarse(a) dFdy(a)
#endif
// Private uniforms
// Commented uniforms aren't needed for this shader, but are available for your own needs.
uniform vec3 _camera_pos = vec3(0.f);
uniform float _mesh_size = 48.f;
uniform uint _background_mode = 1u; // NONE = 0, FLAT = 1, NOISE = 2
uniform uint _mouse_layer = 0x80000000u; // Layer 32
uniform float _vertex_spacing = 1.0;
uniform float _vertex_density = 1.0; // = 1/_vertex_spacing
uniform float _region_size = 1024.0;
uniform float _region_texel_size = 0.0009765625; // = 1/1024
uniform int _region_map_size = 32;
uniform int _region_map[1024];
//uniform vec2 _region_locations[1024];
//uniform float _texture_uv_scale_array[32];
//uniform float _texture_detile_array[32];
//uniform vec4 _texture_color_array[32];
uniform highp sampler2DArray _height_maps : repeat_disable;
uniform highp sampler2DArray _control_maps : repeat_disable;
//uniform highp sampler2DArray _color_maps : source_color, filter_linear_mipmap_anisotropic, repeat_disable;
//uniform highp sampler2DArray _texture_array_albedo : source_color, filter_linear_mipmap_anisotropic, repeat_enable;
//uniform highp sampler2DArray _texture_array_normal : hint_normal, filter_linear_mipmap_anisotropic, repeat_enable;
// Public Uniforms
uniform bool flat_terrain_normals = false;
// Varyings & Types
// Some are required for editor functions
varying float v_vertex_xz_dist;
varying vec3 v_vertex;
////////////////////////
// Vertex
////////////////////////
// Takes in world space XZ (UV) coordinates & search depth (only applicable for background mode none)
// Returns ivec3 with:
// XY: (0 to _region_size - 1) coordinates within a region
// Z: layer index used for texturearrays, -1 if not in a region
ivec3 get_index_coord(const vec2 uv, const int search) {
vec2 r_uv = round(uv);
vec2 o_uv = mod(r_uv,_region_size);
ivec2 pos;
int bounds, layer_index = -1;
for (int i = -1; i < clamp(search, SKIP_PASS, FRAGMENT_PASS); i++) {
if ((layer_index == -1 && _background_mode == 0u ) || i < 0) {
r_uv -= i == -1 ? vec2(0.0) : vec2(float(o_uv.x <= o_uv.y), float(o_uv.y <= o_uv.x));
pos = ivec2(floor((r_uv) * _region_texel_size)) + (_region_map_size / 2);
bounds = int(uint(pos.x | pos.y) < uint(_region_map_size));
layer_index = (_region_map[ pos.y * _region_map_size + pos.x ] * bounds - 1);
}
}
return ivec3(ivec2(mod(r_uv,_region_size)), layer_index);
}
void vertex() {
// Get vertex of flat plane in world coordinates and set world UV
v_vertex = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz;
// Camera distance to vertex on flat plane
v_vertex_xz_dist = length(v_vertex.xz - _camera_pos.xz);
// Geomorph vertex, set end and start for linear height interpolate
float scale = MODEL_MATRIX[0][0];
float vertex_lerp = smoothstep(0.55, 0.95, (v_vertex_xz_dist / scale - _mesh_size - 4.0) / (_mesh_size - 2.0));
vec2 v_fract = fract(VERTEX.xz * 0.5) * 2.0;
// For LOD0 morph from a regular grid to an alternating grid to align with LOD1+
vec2 shift = (scale < _vertex_spacing + 1e-6) ? // LOD0 or not
// Shift from regular to symetric
mix(v_fract, vec2(v_fract.x, -v_fract.y),
round(fract(round(mod(v_vertex.z * _vertex_density, 4.0)) *
round(mod(v_vertex.x * _vertex_density, 4.0)) * 0.25))
) :
// Symetric shift
v_fract * round((fract(v_vertex.xz * 0.25 / scale) - 0.5) * 4.0);
vec2 start_pos = v_vertex.xz * _vertex_density;
vec2 end_pos = (v_vertex.xz - shift * scale) * _vertex_density;
v_vertex.xz -= shift * scale * vertex_lerp;
// UV coordinates in world space. Values are 0 to _region_size within regions
UV = v_vertex.xz * _vertex_density;
// UV coordinates in region space + texel offset. Values are 0 to 1 within regions
UV2 = fma(UV, vec2(_region_texel_size), vec2(0.5 * _region_texel_size));
// Discard vertices for Holes. 1 lookup
ivec3 v_region = get_index_coord(start_pos, VERTEX_PASS);
uint control = floatBitsToUint(texelFetch(_control_maps, v_region, 0)).r;
bool hole = bool(control >>2u & 0x1u);
// Show holes to all cameras except mouse camera (on exactly 1 layer)
if ( !(CAMERA_VISIBLE_LAYERS == _mouse_layer) &&
(hole || (_background_mode == 0u && v_region.z < 0))) {
v_vertex.x = 0. / 0.;
} else {
// Interpolate Geomorph Start & End, set height. 2 Lookups.
ivec3 uv_a = get_index_coord(start_pos, VERTEX_PASS);
ivec3 uv_b = get_index_coord(end_pos, VERTEX_PASS);
float h = mix(texelFetch(_height_maps, uv_a, 0).r, texelFetch(_height_maps, uv_b, 0).r, vertex_lerp);
v_vertex.y = h;
}
// Convert model space to view space w/ skip_vertex_transform render mode
VERTEX = (VIEW_MATRIX * vec4(v_vertex, 1.0)).xyz;
NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz);
BINORMAL = normalize((MODELVIEW_MATRIX * vec4(BINORMAL, 0.0)).xyz);
TANGENT = normalize((MODELVIEW_MATRIX * vec4(TANGENT, 0.0)).xyz);
}
////////////////////////
// Fragment
////////////////////////
void fragment() {
// Recover UVs
vec2 uv = UV;
//vec2 uv2 = UV2;
// Lookup offsets, ID and blend weight
const vec3 offsets = vec3(0, 1, 2);
vec2 index_id = floor(uv);
vec2 weight = fract(uv);
vec2 invert = 1.0 - weight;
vec4 weights = vec4(
invert.x * weight.y, // 0
weight.x * weight.y, // 1
weight.x * invert.y, // 2
invert.x * invert.y // 3
);
vec3 base_ddx = dFdxCoarse(v_vertex);
vec3 base_ddy = dFdyCoarse(v_vertex);
//vec4 base_derivatives = vec4(base_ddx.xz, base_ddy.xz);
// Calculate the effective mipmap for regionspace, and if less than 0,
// skip all extra lookups required for bilinear blend.
float region_mip = log2(max(length(base_ddx.xz), length(base_ddy.xz)) * _vertex_density);
bool bilerp = region_mip < 0.0;
ivec3 index[4];
// control map lookups, used for some normal lookups as well
index[0] = get_index_coord(index_id + offsets.xy, FRAGMENT_PASS);
index[1] = get_index_coord(index_id + offsets.yy, FRAGMENT_PASS);
index[2] = get_index_coord(index_id + offsets.yx, FRAGMENT_PASS);
index[3] = get_index_coord(index_id + offsets.xx, FRAGMENT_PASS);
// Terrain normals
vec3 index_normal[4];
float h[4];
// allows additional derivatives, eg world noise, brush previews etc
float u = 0.0;
float v = 0.0;
// Re-use index[] for the first lookups, skipping some math. 3 lookups
h[3] = texelFetch(_height_maps, index[3], 0).r; // 0 (0,0)
h[2] = texelFetch(_height_maps, index[2], 0).r; // 1 (1,0)
h[0] = texelFetch(_height_maps, index[0], 0).r; // 2 (0,1)
index_normal[3] = normalize(vec3(h[3] - h[2] + u, _vertex_spacing, h[3] - h[0] + v));
// Set flat world normal - overriden if bilerp is true
vec3 w_normal = index_normal[3];
// Branching smooth normals must be done seperatley for correct normals at all 4 index ids
if (bilerp) {
// 5 lookups
// Fetch the additional required height values for smooth normals
h[1] = texelFetch(_height_maps, index[1], 0).r; // 3 (1,1)
float h_4 = texelFetch(_height_maps, get_index_coord(index_id + offsets.yz, FRAGMENT_PASS), 0).r; // 4 (1,2)
float h_5 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zy, FRAGMENT_PASS), 0).r; // 5 (2,1)
float h_6 = texelFetch(_height_maps, get_index_coord(index_id + offsets.zx, FRAGMENT_PASS), 0).r; // 6 (2,0)
float h_7 = texelFetch(_height_maps, get_index_coord(index_id + offsets.xz, FRAGMENT_PASS), 0).r; // 7 (0,2)
// Calculate the normal for the remaining index ids.
index_normal[0] = normalize(vec3(h[0] - h[1] + u, _vertex_spacing, h[0] - h_7 + v));
index_normal[1] = normalize(vec3(h[1] - h_5 + u, _vertex_spacing, h[1] - h_4 + v));
index_normal[2] = normalize(vec3(h[2] - h_6 + u, _vertex_spacing, h[2] - h[1] + v));
// Set interpolated world normal
w_normal =
index_normal[0] * weights[0] +
index_normal[1] * weights[1] +
index_normal[2] * weights[2] +
index_normal[3] * weights[3] ;
}
// Apply terrain normals
vec3 w_tangent = normalize(cross(w_normal, vec3(0.0, 0.0, 1.0)));
vec3 w_binormal = normalize(cross(w_normal, w_tangent));
NORMAL = mat3(VIEW_MATRIX) * w_normal;
TANGENT = mat3(VIEW_MATRIX) * w_tangent;
BINORMAL = mat3(VIEW_MATRIX) * w_binormal;
if (flat_terrain_normals) {
NORMAL = normalize(cross(dFdyCoarse(VERTEX),dFdxCoarse(VERTEX)));
TANGENT = normalize(cross(NORMAL, VIEW_MATRIX[2].xyz));
BINORMAL = normalize(cross(NORMAL, TANGENT));
}
// Apply PBR
ALBEDO = vec3(.2);
ROUGHNESS = .7;
}
@@ -0,0 +1 @@
uid://01qauauvd8aa
+194
View File
@@ -0,0 +1,194 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg3088"
sodipodi:docname="icon_terrain_material.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs3092">
<linearGradient
id="linearGradient7208"
gradientUnits="userSpaceOnUse"
x1="8.3533525"
x2="8.3533525"
y1="8.3028736"
y2="12.261972">
<stop
offset="0.07497349"
stop-color="#5fb2ff"
id="stop7204"
style="stop-color:#ffd4d4;stop-opacity:1;" />
<stop
offset="0.63987136"
stop-color="#5fb2ff"
id="stop7210"
style="stop-color:#e69971;stop-opacity:1;" />
<stop
offset="1"
stop-color="#a2d2ff"
id="stop7206"
style="stop-color:#88fc7f;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient7200"
gradientUnits="userSpaceOnUse"
x1="8.3533525"
x2="8.3533525"
y1="8.3028736"
y2="12.261972">
<stop
offset="0.0442211"
stop-color="#5fb2ff"
id="stop7186"
style="stop-color:#ff4545;stop-opacity:1;" />
<stop
offset="0.1382958"
stop-color="#5fb2ff"
id="stop7188"
style="stop-color:#ffe345;stop-opacity:1;" />
<stop
offset="0.32638258"
stop-color="#5fb2ff"
id="stop7190"
style="stop-color:#80ff45;stop-opacity:1;" />
<stop
offset="0.5233199"
stop-color="#5fb2ff"
id="stop7192"
style="stop-color:#45ffa2;stop-opacity:1;" />
<stop
offset="0.69092047"
stop-color="#5fb2ff"
id="stop7194"
style="stop-color:#45d7ff;stop-opacity:1;" />
<stop
offset="0.85671425"
stop-color="#5fb2ff"
id="stop7196"
style="stop-color:#8045ff;stop-opacity:1;" />
<stop
offset="1"
stop-color="#a2d2ff"
id="stop7198"
style="stop-color:#ff4596;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="a-78"
gradientUnits="userSpaceOnUse"
x1="8.3533525"
x2="8.3533525"
y1="8.3028736"
y2="12.261972">
<stop
offset="0.04827991"
stop-color="#5fb2ff"
id="stop974"
style="stop-color:#ff4545;stop-opacity:1;" />
<stop
offset="0.1382958"
stop-color="#5fb2ff"
id="stop6790"
style="stop-color:#ffe345;stop-opacity:1;" />
<stop
offset="0.32638258"
stop-color="#5fb2ff"
id="stop6861"
style="stop-color:#80ff45;stop-opacity:1;" />
<stop
offset="0.5233199"
stop-color="#5fb2ff"
id="stop6932"
style="stop-color:#45ffa2;stop-opacity:1;" />
<stop
offset="0.69092047"
stop-color="#5fb2ff"
id="stop6934"
style="stop-color:#45d7ff;stop-opacity:1;" />
<stop
offset="0.85671425"
stop-color="#5fb2ff"
id="stop6936"
style="stop-color:#8045ff;stop-opacity:1;" />
<stop
offset="1"
stop-color="#a2d2ff"
id="stop28-9"
style="stop-color:#ff4596;stop-opacity:1;" />
</linearGradient>
<linearGradient
id="linearGradient986"
gradientUnits="userSpaceOnUse"
x1="8.3533525"
x2="8.3533525"
y1="8.3028736"
y2="12.261972">
<stop
offset="0.33542365"
stop-color="#5fb2ff"
id="stop982"
style="stop-color:#ffa7a7;stop-opacity:1;" />
<stop
offset="1"
stop-color="#a2d2ff"
id="stop984"
style="stop-color:#fc7f7f;stop-opacity:1;" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7208"
id="linearGradient7167"
x1="-11.46914"
y1="2.1907859"
x2="-11.460287"
y2="13.545624"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(19.493797)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient7208"
id="linearGradient7202"
x1="-14.857841"
y1="4.9543786"
x2="-14.958795"
y2="12.934526"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(19.493797)" />
</defs>
<sodipodi:namedview
id="namedview3090"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="12.59375"
inkscape:cx="-7.5037221"
inkscape:cy="12.466501"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg3088" />
<path
id="path31"
style="fill:url(#linearGradient7167);fill-opacity:1;stroke-width:1.11229"
d="m 8.0074686,1.7910156 c -1.368915,0.09588 -1.692429,3.8264137 -2.822266,3.7910156 -1.977176,-0.061946 -2.315719,3.0789623 -3.148437,4.6777348 L 0.20278059,11.044922 7.9898906,14.380859 15.775047,11.044922 13.997703,10.283203 C 13.467188,8.6236354 13.491849,7.2829163 12.616843,6.2304688 11.823643,5.2764167 11.444597,3.7773719 8.6637186,1.9941406 8.4200366,1.8378802 8.2030276,1.7773185 8.0074686,1.7910156 Z" />
<path
d="m 1.6979896,10.77748 c 1.112295,-1.282509 1.320286,-5.8794797 3.544055,-5.8098075 4.6412106,4.5094283 2.498421,3.8179938 4.057225,7.7556465 -3.217392,0.01725 -3.237576,-1.158088 -7.60128,-1.945839 z"
fill="url(#a)"
id="path978"
style="fill:url(#linearGradient7202);fill-opacity:1;stroke-width:1.19437"
sodipodi:nodetypes="cccc" />
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bdwolwswwy8wr"
path="res://.godot/imported/autoshader.svg-9998e61bbc6afd5b134b767acd17a425.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/autoshader.svg"
dest_files=["res://.godot/imported/autoshader.svg-9998e61bbc6afd5b134b767acd17a425.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+175
View File
@@ -0,0 +1,175 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="16"
height="16"
version="1.1"
viewBox="0 0 16 16"
id="svg2"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
sodipodi:docname="icon_color.svg"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
id="metadata12"><rdf:RDF><cc:Work
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
id="defs10"><linearGradient
inkscape:collect="always"
id="linearGradient40333"><stop
style="stop-color:#fc7f7f;stop-opacity:1;"
offset="0"
id="stop40329" /><stop
style="stop-color:#7fb0bd;stop-opacity:1;"
offset="0.50160772"
id="stop40337" /><stop
style="stop-color:#7ffc7f;stop-opacity:1;"
offset="1"
id="stop40331" /></linearGradient><inkscape:path-effect
effect="spiro"
id="path-effect8712"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="powerstroke"
id="path-effect8643"
is_visible="true"
lpeversion="1"
offset_points="0.86174895,0.26516503"
not_jump="true"
sort_points="true"
interpolator_type="CentripetalCatmullRom"
interpolator_beta="0.75"
start_linecap_type="round"
linejoin_type="spiro"
miter_limit="4"
scale_width="1"
end_linecap_type="round" /><inkscape:path-effect
effect="simplify"
id="path-effect8641"
is_visible="true"
lpeversion="1"
steps="1"
threshold="0.0028332064"
smooth_angles="0"
helper_size="0"
simplify_individual_paths="false"
simplify_just_coalesce="false"
step="1" /><inkscape:path-effect
effect="spiro"
id="path-effect5927"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect5921"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect5163"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect4431"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect4425"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect4419"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect4413"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect4407"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect4401"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect4395"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect4389"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect2931"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect2925"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect3156"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect4245"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect4977"
is_visible="true"
lpeversion="1" /><inkscape:path-effect
effect="spiro"
id="path-effect3156-0"
is_visible="true"
lpeversion="1" /><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient40333"
id="linearGradient35201"
x1="5.3624678"
y1="3.5122564"
x2="12.772088"
y2="11.423013"
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1009"
id="namedview8"
showgrid="false"
inkscape:zoom="22.627418"
inkscape:cx="7.5793003"
inkscape:cy="9.4133584"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg2"
inkscape:object-paths="false"
inkscape:snap-smooth-nodes="false"
inkscape:object-nodes="true"
inkscape:snap-grids="false"
inkscape:snap-to-guides="true"
inkscape:pagecheckerboard="0"
inkscape:showpageshadow="0"
inkscape:deskcolor="#d1d1d1"><inkscape:grid
type="xygrid"
id="grid4140" /></sodipodi:namedview><path
fill="none"
d="M -0.76108454,-0.19858458 H 16.761085 V 17.323585 H -0.76108454 Z"
id="path34808"
style="stroke-width:0.73009" /><path
d="M 3.3537048,4.6462952 8,0 12.646295,4.6462952 c 4.13963,4.139681 1.207353,11.2167178 -4.6462951,11.2167178 -5.8536477,0 -8.78592555,-7.0770368 -4.6462951,-11.2167178 z"
id="path34810"
style="fill:url(#linearGradient35201);fill-opacity:1;stroke-width:0.73009"
sodipodi:nodetypes="cccsc" /></svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://krrmpalen8xu"
path="res://.godot/imported/color_paint.svg-2a416ebf35da04135017e5c6ef53ea57.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/color_paint.svg"
dest_files=["res://.godot/imported/color_paint.svg-2a416ebf35da04135017e5c6ef53ea57.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
id="svg2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:docname="icon_height_add.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<defs>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect4977"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect4245"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect3156"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect2324"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="powerstroke" is_visible="true" scale_width="1" miter_limit="4" linejoin_type="spiro" sort_points="true" not_jump="true" offset_points="0.76255776,0.35156248" id="path-effect2285" start_linecap_type="round" end_linecap_type="round" interpolator_type="CentripetalCatmullRom" interpolator_beta="0.75">
</inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="simplify" is_visible="true" step="1" helper_size="0" smooth_angles="0" threshold="0.0028332064" steps="1" id="path-effect2283" simplify_individual_paths="false" simplify_just_coalesce="false">
</inkscape:path-effect>
</defs>
<sodipodi:namedview inkscape:cy="12.81631" inkscape:cx="8.1096304" inkscape:zoom="22.627418" showgrid="false" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" pagecolor="#ffffff" bordercolor="#666666" id="namedview8" inkscape:current-layer="path3154" inkscape:window-maximized="1" inkscape:window-y="-8" inkscape:window-x="-8" inkscape:object-nodes="true" inkscape:snap-grids="false" inkscape:window-height="1009" inkscape:window-width="1920" inkscape:pageshadow="2" inkscape:pageopacity="0" inkscape:object-paths="false" inkscape:snap-smooth-nodes="false" inkscape:snap-to-guides="true" inkscape:pagecheckerboard="0" inkscape:showpageshadow="0" inkscape:deskcolor="#d1d1d1" showguides="false">
<inkscape:grid type="xygrid" id="grid4140"></inkscape:grid>
</sodipodi:namedview>
<path fill="#FC7F7F" d="M15.203,9.736c-0.887-0.184-1.725-0.729-2.248-1.467c-0.525-0.739-0.82-1.83-1.424-2.988
C11.229,4.7,10.833,4.103,10.26,3.613C9.678,3.116,8.924,2.751,8.064,2.732c-0.908-0.02-1.713,0.354-2.332,0.875
C5.124,4.12,4.712,4.752,4.398,5.367c-0.624,1.225-0.924,2.378-1.49,3.139C2.425,9.154,1.684,9.641,0.895,9.82L0,9.979V16h16V9.9
L15.203,9.736z M9,10.688v4H7v-4H4l4-5.25l4,5.25H9z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bcmbqryggekg1"
path="res://.godot/imported/height_add.svg-9e680ce71fa4c541748e081b99167369.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/height_add.svg"
dest_files=["res://.godot/imported/height_add.svg-9e680ce71fa4c541748e081b99167369.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+196
View File
@@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="svg2"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
sodipodi:docname="icon_height_div.svg"
x="0px"
y="0px"
width="16px"
height="16px"
viewBox="0 0 16 16"
enable-background="new 0 0 16 16"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2033">
<inkscape:path-effect
id="path-effect6469"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
id="path-effect6463"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
id="path-effect4977"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
id="path-effect4245"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
id="path-effect3156"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
id="path-effect2324"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
offset_points="0.76255776,0.35156248"
not_jump="true"
sort_points="true"
linejoin_type="spiro"
miter_limit="4"
scale_width="1"
id="path-effect2285"
is_visible="true"
effect="powerstroke"
lpeversion="1"
interpolator_beta="0.75"
interpolator_type="CentripetalCatmullRom"
end_linecap_type="round"
start_linecap_type="round">
</inkscape:path-effect>
<inkscape:path-effect
steps="1"
threshold="0.0028332064"
smooth_angles="0"
helper_size="0"
step="1"
id="path-effect2283"
is_visible="true"
effect="simplify"
lpeversion="1"
simplify_just_coalesce="false"
simplify_individual_paths="false">
</inkscape:path-effect>
<inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect4977-4" /><inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect4245-1" /><inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect3156-8" /><inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect2324-5" /><inkscape:path-effect
lpeversion="1"
effect="powerstroke"
is_visible="true"
scale_width="1"
miter_limit="4"
linejoin_type="spiro"
sort_points="true"
not_jump="true"
offset_points="0.76255776,0.35156248"
id="path-effect2285-9"
start_linecap_type="round"
end_linecap_type="round"
interpolator_type="CentripetalCatmullRom"
interpolator_beta="0.75">
</inkscape:path-effect><inkscape:path-effect
lpeversion="1"
effect="simplify"
is_visible="true"
step="1"
helper_size="0"
smooth_angles="0"
threshold="0.0028332064"
steps="1"
id="path-effect2283-2"
simplify_individual_paths="false"
simplify_just_coalesce="false">
</inkscape:path-effect></defs>
<sodipodi:namedview
showguides="false"
bordercolor="#666666"
pagecolor="#ffffff"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
showgrid="false"
inkscape:zoom="11.313709"
inkscape:cx="3.4913396"
inkscape:cy="13.788582"
id="namedview8"
inkscape:deskcolor="#d1d1d1"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="0"
inkscape:snap-to-guides="true"
inkscape:snap-smooth-nodes="false"
inkscape:object-paths="false"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:snap-grids="false"
inkscape:object-nodes="true"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg2">
<inkscape:grid
type="xygrid"
id="grid4140" />
</sodipodi:namedview>
<path
id="path4012"
style="fill:#fc7f7f;fill-opacity:1;stroke-width:0.764771"
d="M 1.5898438 3.2871094 C 1.0957748 3.280765 0.56716282 3.3249664 0 3.4257812 L 0 15.955078 L 16 15.955078 L 16 12.246094 C 10.09046 15.642843 7.1418918 10.840026 5.1035156 6.2128906 C 6.678498 7.1187996 6.5445981 7.3630122 8.4746094 9.703125 C 11.163354 13.034961 13.74668 11.801049 16 10.460938 L 16 8.2597656 C 8.6991041 14.580269 9.0008764 3.3822748 1.5898438 3.2871094 z " /><path
id="path16308"
sodipodi:nodetypes="sssscccccccccccssss"
display="none"
fill="#FC7F7F"
d="M8.064,1.4 C7.156,1.38,6.351,1.753,5.732,2.275C5.124,2.788,4.711,3.42,4.398,4.035C3.774,5.26,3.475,6.414,2.908,7.173 c-0.483,0.65-1.224,1.135-2.013,1.315L0,8.646V10.8c1.843,0.457,5.033-1.792,5.506-4.325C6.03,5.322,6.454,3.693,7.883,3.408h0.049 l0.1-0.006c1.329,0.167,1.81,1.646,2.282,2.701c0.816,3.09,3.985,4.793,5.687,4.59V8.568l-0.797-0.166 c-0.888-0.183-1.726-0.729-2.248-1.467c-0.524-0.739-0.819-1.83-1.424-2.988c-0.304-0.581-0.698-1.178-1.271-1.668 C9.678,1.784,8.924,1.418,8.064,1.4z" />
<path
id="path16308_2_"
sodipodi:nodetypes="sssscccccccccccssss"
display="none"
fill="#FC7F7F"
d="M8.064,2.835 C7.156,2.815,6.351,3.188,5.732,3.71c-0.608,0.513-1.021,1.145-1.334,1.76C3.774,6.695,3.475,7.85,2.908,8.608 c-0.483,0.65-1.224,1.136-2.013,1.315L0,10.082v2.153c1.843,0.457,5.033-1.792,5.506-4.325C6.03,6.758,6.454,5.128,7.883,4.844 h0.049l0.1-0.006c1.329,0.167,1.81,1.646,2.282,2.701c0.816,3.09,3.985,4.793,5.687,4.59v-2.125l-0.797-0.166 c-0.888-0.183-1.726-0.729-2.248-1.466c-0.524-0.739-0.819-1.83-1.424-2.988c-0.304-0.581-0.698-1.178-1.271-1.668 C9.678,3.22,8.924,2.854,8.064,2.835z" />
<path
id="path16308_1_"
sodipodi:nodetypes="sssscccccccccccssss"
display="none"
fill="#FC7F7F"
d="M0,6.91v2.139 c1.578,0,16.001,0,16.001,0V6.91H0z" />
<path
display="none"
fill="#FC7F7F"
d="M10.064,9.872C9.579,8.813,9.3,6.726,7.882,6.742C6.454,7.026,6.5,8.686,5.976,9.842 C5.39,11.51,1.708,12.982,0,13.574V16c4,0,12,0,16,0v-2.537C14.176,12.84,10.674,11.646,10.064,9.872z"
id="path2040" />
<path
display="none"
fill="#FC7F7F"
d="M0,8c0,0,0.917,2.709,2.104,2.479S3,5.375,4.063,5.167s0.521,6.083,2.188,5.958 S6.104,4.313,8.458,3.438s-1.583,7.979,0.563,8.688s2.208-8.479,4.521-9.729s-1.292,10.271,1.375,10.75"
id="path2042" />
<path
d="M 0,0 14.842,0.004 16,0 Z"
id="rect12437" /></svg>

After

Width:  |  Height:  |  Size: 6.3 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://danh7tb2v6rx7"
path="res://.godot/imported/height_div.svg-449a465f9fdd11ab59f2f1c78815408c.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/height_div.svg"
dest_files=["res://.godot/imported/height_div.svg-449a465f9fdd11ab59f2f1c78815408c.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
id="svg2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:docname="icon_height_flat.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<defs>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect4977"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect4245"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect3156"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect2324"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="powerstroke" is_visible="true" scale_width="1" miter_limit="4" linejoin_type="spiro" sort_points="true" not_jump="true" offset_points="0.76255776,0.35156248" id="path-effect2285" start_linecap_type="round" end_linecap_type="round" interpolator_type="CentripetalCatmullRom" interpolator_beta="0.75">
</inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="simplify" is_visible="true" step="1" helper_size="0" smooth_angles="0" threshold="0.0028332064" steps="1" id="path-effect2283" simplify_individual_paths="false" simplify_just_coalesce="false">
</inkscape:path-effect>
</defs>
<sodipodi:namedview inkscape:cy="6.8059028" inkscape:cx="11.866136" inkscape:zoom="22.627417" showgrid="false" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" pagecolor="#ffffff" bordercolor="#666666" id="namedview8" inkscape:current-layer="svg2" inkscape:window-maximized="1" inkscape:window-y="-8" inkscape:window-x="-8" inkscape:object-nodes="true" inkscape:snap-grids="false" inkscape:window-height="1009" inkscape:window-width="1920" inkscape:pageshadow="2" inkscape:pageopacity="0" inkscape:object-paths="false" inkscape:snap-smooth-nodes="false" inkscape:snap-to-guides="true" inkscape:pagecheckerboard="0" inkscape:showpageshadow="0" inkscape:deskcolor="#d1d1d1" showguides="false">
<inkscape:grid type="xygrid" id="grid4140"></inkscape:grid>
</sodipodi:namedview>
<path id="path3133" sodipodi:nodetypes="cscccccccscc" fill="#FC7F7F" d="M4.398,8.417c-0.624,1.225-0.924,2.378-1.49,3.138
c-0.483,0.649-1.225,1.135-2.014,1.314L0,13.027V16l1.158-0.004L16,16v-3.051l-0.797-0.164c-0.887-0.183-1.725-0.729-2.248-1.467
c-0.525-0.738-0.82-1.83-1.424-2.988L4.398,8.417z"/>
<polygon fill="#FC7F7F" points="3.375,5.331 7.375,5.331 7.375,8.331 12.625,4.331 7.375,0.331 7.375,3.331 3.375,3.331 "/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://crj0xfyiyr45u"
path="res://.godot/imported/height_flat.svg-be726a006bf06e05a7a8867510f3996e.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/height_flat.svg"
dest_files=["res://.godot/imported/height_flat.svg-be726a006bf06e05a7a8867510f3996e.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+201
View File
@@ -0,0 +1,201 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="svg2"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
sodipodi:docname="icon_height_mul.svg"
x="0px"
y="0px"
width="16px"
height="16px"
viewBox="0 0 16 16"
enable-background="new 0 0 16 16"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2033">
<inkscape:path-effect
id="path-effect6469"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
id="path-effect6463"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
id="path-effect4977"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
id="path-effect4245"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
id="path-effect3156"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
id="path-effect2324"
is_visible="true"
effect="spiro"
lpeversion="1" />
<inkscape:path-effect
offset_points="0.76255776,0.35156248"
not_jump="true"
sort_points="true"
linejoin_type="spiro"
miter_limit="4"
scale_width="1"
id="path-effect2285"
is_visible="true"
effect="powerstroke"
lpeversion="1"
interpolator_beta="0.75"
interpolator_type="CentripetalCatmullRom"
end_linecap_type="round"
start_linecap_type="round">
</inkscape:path-effect>
<inkscape:path-effect
steps="1"
threshold="0.0028332064"
smooth_angles="0"
helper_size="0"
step="1"
id="path-effect2283"
is_visible="true"
effect="simplify"
lpeversion="1"
simplify_just_coalesce="false"
simplify_individual_paths="false">
</inkscape:path-effect>
<inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect4977-4" /><inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect4245-1" /><inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect3156-8" /><inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect2324-5" /><inkscape:path-effect
lpeversion="1"
effect="powerstroke"
is_visible="true"
scale_width="1"
miter_limit="4"
linejoin_type="spiro"
sort_points="true"
not_jump="true"
offset_points="0.76255776,0.35156248"
id="path-effect2285-9"
start_linecap_type="round"
end_linecap_type="round"
interpolator_type="CentripetalCatmullRom"
interpolator_beta="0.75">
</inkscape:path-effect><inkscape:path-effect
lpeversion="1"
effect="simplify"
is_visible="true"
step="1"
helper_size="0"
smooth_angles="0"
threshold="0.0028332064"
steps="1"
id="path-effect2283-2"
simplify_individual_paths="false"
simplify_just_coalesce="false">
</inkscape:path-effect></defs>
<sodipodi:namedview
showguides="false"
bordercolor="#666666"
pagecolor="#ffffff"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
showgrid="false"
inkscape:zoom="11.313709"
inkscape:cx="13.832776"
inkscape:cy="18.031222"
id="namedview8"
inkscape:deskcolor="#d1d1d1"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="0"
inkscape:snap-to-guides="true"
inkscape:snap-smooth-nodes="false"
inkscape:object-paths="false"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:snap-grids="false"
inkscape:object-nodes="true"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg2">
<inkscape:grid
type="xygrid"
id="grid4140" />
</sodipodi:namedview>
<path
style="fill:#fc7f7f;fill-opacity:1;stroke-width:0.764771"
d="M 1.5898438,5.5417983 C 1.0957744,5.5354539 0.56716339,5.5780631 0,5.6788781 V 15.955806 H 16 V 10.513229 C 8.6990968,16.833739 9.0008839,5.6369638 1.5898438,5.5417983 Z"
id="path4012"
sodipodi:nodetypes="sccccs" /><path
id="path16308"
sodipodi:nodetypes="sssscccccccccccssss"
display="none"
fill="#FC7F7F"
d="M8.064,1.4 C7.156,1.38,6.351,1.753,5.732,2.275C5.124,2.788,4.711,3.42,4.398,4.035C3.774,5.26,3.475,6.414,2.908,7.173 c-0.483,0.65-1.224,1.135-2.013,1.315L0,8.646V10.8c1.843,0.457,5.033-1.792,5.506-4.325C6.03,5.322,6.454,3.693,7.883,3.408h0.049 l0.1-0.006c1.329,0.167,1.81,1.646,2.282,2.701c0.816,3.09,3.985,4.793,5.687,4.59V8.568l-0.797-0.166 c-0.888-0.183-1.726-0.729-2.248-1.467c-0.524-0.739-0.819-1.83-1.424-2.988c-0.304-0.581-0.698-1.178-1.271-1.668 C9.678,1.784,8.924,1.418,8.064,1.4z" />
<path
id="path16308_2_"
sodipodi:nodetypes="sssscccccccccccssss"
display="none"
fill="#FC7F7F"
d="M8.064,2.835 C7.156,2.815,6.351,3.188,5.732,3.71c-0.608,0.513-1.021,1.145-1.334,1.76C3.774,6.695,3.475,7.85,2.908,8.608 c-0.483,0.65-1.224,1.136-2.013,1.315L0,10.082v2.153c1.843,0.457,5.033-1.792,5.506-4.325C6.03,6.758,6.454,5.128,7.883,4.844 h0.049l0.1-0.006c1.329,0.167,1.81,1.646,2.282,2.701c0.816,3.09,3.985,4.793,5.687,4.59v-2.125l-0.797-0.166 c-0.888-0.183-1.726-0.729-2.248-1.466c-0.524-0.739-0.819-1.83-1.424-2.988c-0.304-0.581-0.698-1.178-1.271-1.668 C9.678,3.22,8.924,2.854,8.064,2.835z" />
<path
id="path16308_1_"
sodipodi:nodetypes="sssscccccccccccssss"
display="none"
fill="#FC7F7F"
d="M0,6.91v2.139 c1.578,0,16.001,0,16.001,0V6.91H0z" />
<path
display="none"
fill="#FC7F7F"
d="M10.064,9.872C9.579,8.813,9.3,6.726,7.882,6.742C6.454,7.026,6.5,8.686,5.976,9.842 C5.39,11.51,1.708,12.982,0,13.574V16c4,0,12,0,16,0v-2.537C14.176,12.84,10.674,11.646,10.064,9.872z"
id="path2040" />
<path
display="none"
fill="#FC7F7F"
d="M0,8c0,0,0.917,2.709,2.104,2.479S3,5.375,4.063,5.167s0.521,6.083,2.188,5.958 S6.104,4.313,8.458,3.438s-1.583,7.979,0.563,8.688s2.208-8.479,4.521-9.729s-1.292,10.271,1.375,10.75"
id="path2042" />
<path
d="M 0,0 14.842,0.004 16,0 Z"
id="rect12437" /><path
id="path7096"
style="fill:#fc7f7f;fill-opacity:1;stroke-width:0.764771"
d="M 0,2.0234375 V 3.8085938 C 2.8512164,3.4688215 4.2747356,4.3685854 5.2041205,5.9137947 6.7118368,6.8603322 6.6718914,7.2163955 8.3808594,8.5644531 6.2801351,4.1747406 5.2031416,1.5917313 0,2.0234375 Z"
sodipodi:nodetypes="ccccc" /></svg>

After

Width:  |  Height:  |  Size: 6.5 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bu3q0645kb3el"
path="res://.godot/imported/height_mul.svg-2dca20fa42a85408713e9bfe411f3c79.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/height_mul.svg"
dest_files=["res://.godot/imported/height_mul.svg-2dca20fa42a85408713e9bfe411f3c79.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+105
View File
@@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg
version="1.1"
id="svg2"
sodipodi:docname="icon_height_slope.svg"
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
x="0px"
y="0px"
width="16px"
height="16px"
viewBox="0 0 16 16"
enable-background="new 0 0 16 16"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">&#10;<defs
id="defs1">&#10; &#10; <inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect4977" />&#10; <inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect4245" />&#10; <inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect3156" />&#10; <inkscape:path-effect
lpeversion="1"
effect="spiro"
is_visible="true"
id="path-effect2324" />&#10; &#10; <inkscape:path-effect
lpeversion="1"
effect="powerstroke"
is_visible="true"
scale_width="1"
miter_limit="4"
linejoin_type="spiro"
sort_points="true"
not_jump="true"
offset_points="0.76255776,0.35156248"
id="path-effect2285"
start_linecap_type="round"
end_linecap_type="round"
interpolator_type="CentripetalCatmullRom"
interpolator_beta="0.75">&#10; </inkscape:path-effect>&#10; &#10; <inkscape:path-effect
lpeversion="1"
effect="simplify"
is_visible="true"
step="1"
helper_size="0"
smooth_angles="0"
threshold="0.0028332064"
steps="1"
id="path-effect2283"
simplify_individual_paths="false"
simplify_just_coalesce="false">&#10; </inkscape:path-effect>&#10;</defs>&#10;<sodipodi:namedview
inkscape:cy="27.665553"
inkscape:cx="-14.760854"
inkscape:zoom="5.6568543"
showgrid="false"
guidetolerance="10"
gridtolerance="10"
objecttolerance="10"
borderopacity="1"
pagecolor="#ffffff"
bordercolor="#666666"
id="namedview8"
inkscape:current-layer="svg2"
inkscape:window-maximized="1"
inkscape:window-y="32"
inkscape:window-x="0"
inkscape:object-nodes="true"
inkscape:snap-grids="false"
inkscape:window-height="1011"
inkscape:window-width="1920"
inkscape:pageshadow="2"
inkscape:pageopacity="0"
inkscape:object-paths="false"
inkscape:snap-smooth-nodes="false"
inkscape:snap-to-guides="true"
inkscape:pagecheckerboard="0"
inkscape:showpageshadow="0"
inkscape:deskcolor="#d1d1d1"
showguides="false">&#10; <inkscape:grid
type="xygrid"
id="grid4140"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="false" />&#10;</sodipodi:namedview>&#10;&#10;<polygon
fill="#fc7f7f"
points="3.375,5.331 7.375,5.331 7.375,8.331 12.625,4.331 7.375,0.331 7.375,3.331 3.375,3.331 "
id="polygon1"
transform="rotate(45,9.0843727,9.1561132)" />&#10;<path
style="fill:#fc7f7f;fill-opacity:1;stroke:none;stroke-width:2"
d="M 0,0 16,16 H 0 Z"
id="path1"
sodipodi:nodetypes="cccc" /></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://0cd7so4kw7da"
path="res://.godot/imported/height_slope.svg-e20540c5538d0c57a9d229a772b3d1b3.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/height_slope.svg"
dest_files=["res://.godot/imported/height_slope.svg-e20540c5538d0c57a9d229a772b3d1b3.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
id="svg2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:docname="icon_heightmap_smooth.svg" inkscape:version="0.91 r13725"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<sodipodi:namedview inkscape:cy="6.6592524" inkscape:cx="-4.9969866" inkscape:zoom="16" showgrid="true" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" pagecolor="#ffffff" bordercolor="#666666" id="namedview8" inkscape:current-layer="svg2" inkscape:window-maximized="0" inkscape:window-y="160" inkscape:window-x="365" inkscape:object-nodes="true" inkscape:snap-page="true" inkscape:snap-bbox="false" inkscape:snap-global="true" inkscape:snap-grids="true" inkscape:window-height="826" inkscape:window-width="1461" inkscape:pageshadow="2" inkscape:pageopacity="0">
<inkscape:grid type="xygrid" id="grid4142"></inkscape:grid>
</sodipodi:namedview>
<path id="path5360" sodipodi:nodetypes="ccccc" inkscape:connector-curvature="0" fill="#F37D7F" fill-opacity="0.9961" d="M0,16
V8.969C6.406,4.281,9.938,4.25,16,9c0,5,0,7,0,7H0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://chrbx4xnxyiel"
path="res://.godot/imported/height_smooth.svg-d8fc43572f5984eef64c886a49988c06.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/height_smooth.svg"
dest_files=["res://.godot/imported/height_smooth.svg-d8fc43572f5984eef64c886a49988c06.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+28
View File
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
id="svg2" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:docname="icon_height_sub.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<defs>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect4977"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect4245"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect3156"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="spiro" is_visible="true" id="path-effect2324"></inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="powerstroke" is_visible="true" scale_width="1" miter_limit="4" linejoin_type="spiro" sort_points="true" not_jump="true" offset_points="0.76255776,0.35156248" id="path-effect2285" start_linecap_type="round" end_linecap_type="round" interpolator_type="CentripetalCatmullRom" interpolator_beta="0.75">
</inkscape:path-effect>
<inkscape:path-effect lpeversion="1" effect="simplify" is_visible="true" step="1" helper_size="0" smooth_angles="0" threshold="0.0028332064" steps="1" id="path-effect2283" simplify_individual_paths="false" simplify_just_coalesce="false">
</inkscape:path-effect>
</defs>
<sodipodi:namedview inkscape:cy="10.871767" inkscape:cx="2.7179417" inkscape:zoom="22.627417" showgrid="false" guidetolerance="10" gridtolerance="10" objecttolerance="10" borderopacity="1" pagecolor="#ffffff" bordercolor="#666666" id="namedview8" inkscape:current-layer="svg2" inkscape:window-maximized="1" inkscape:window-y="-8" inkscape:window-x="-8" inkscape:object-nodes="true" inkscape:snap-grids="false" inkscape:window-height="1009" inkscape:window-width="1920" inkscape:pageshadow="2" inkscape:pageopacity="0" inkscape:object-paths="false" inkscape:snap-smooth-nodes="false" inkscape:snap-to-guides="true" inkscape:pagecheckerboard="0" inkscape:showpageshadow="0" inkscape:deskcolor="#d1d1d1" showguides="false">
<inkscape:grid type="xygrid" id="grid4140"></inkscape:grid>
</sodipodi:namedview>
<path id="rect12437" fill="#FC7F7F" d="M0,0l14.842,0.004L16,0H0z M7,1.25v4H4l4,5.25l4-5.25H9v-4H7z M16,6.021L15.105,6.18
c-0.789,0.18-1.53,0.666-2.014,1.314c-0.566,0.76-0.866,1.914-1.49,3.139c-0.313,0.615-0.726,1.247-1.334,1.76
c-0.619,0.521-1.424,0.895-2.332,0.875c-0.859-0.019-1.613-0.384-2.195-0.881c-0.573-0.489-0.968-1.086-1.271-1.668
C3.865,9.561,3.57,8.469,3.045,7.73C2.521,6.993,1.683,6.447,0.797,6.264L0,6.1V16h16V6.021z"/>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://mo3hnbk3ffjs"
path="res://.godot/imported/height_sub.svg-1a14a9bb856f3db0faa02dba3c807b50.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/height_sub.svg"
dest_files=["res://.godot/imported/height_sub.svg-1a14a9bb856f3db0faa02dba3c807b50.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg version="1.1" baseProfile="tiny"
id="svg2" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" sodipodi:docname="icon_map_add.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px"
viewBox="0 0 16 16" xml:space="preserve">
<path id="rect3461" fill="#FC7F7F" d="M0-0.045V16h16V-0.045H0z M8.542,2.167l0.833,3.125l2.906-1.729l-1.073,3.479l1.886,3.052
L9.875,9.833l0.75,3.979L8,11.188L4.406,13.25l1.635-3.583L2.063,9.469l2.646-1.76l-1.167-4.25l3,1.958L8.542,2.167z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+44
View File
@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bsmaxekrmnuy2"
path="res://.godot/imported/holes.svg-a7cb97bb50d7879cd274646e207b9213.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/holes.svg"
dest_files=["res://.godot/imported/holes.svg-a7cb97bb50d7879cd274646e207b9213.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+102
View File
@@ -0,0 +1,102 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg37"
sodipodi:docname="icon_terrain_layer_material.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xml:space="preserve"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs41"><linearGradient
id="linearGradient5133"
gradientUnits="userSpaceOnUse"
x1="8.3533525"
x2="8.3533525"
y1="8.3028736"
y2="12.261972"><stop
offset="0.22192921"
stop-color="#5fb2ff"
id="stop5129"
style="stop-color:#774343;stop-opacity:1;" /><stop
offset="1"
stop-color="#a2d2ff"
id="stop5131"
style="stop-color:#874949;stop-opacity:0;" /></linearGradient><linearGradient
id="linearGradient5117"
gradientUnits="userSpaceOnUse"
x1="8.3533525"
x2="8.3533525"
y1="8.3028736"
y2="12.261972"><stop
offset="0.33542365"
stop-color="#5fb2ff"
id="stop5113"
style="stop-color:#654444;stop-opacity:1;" /><stop
offset="1"
stop-color="#a2d2ff"
id="stop5115"
style="stop-color:#472323;stop-opacity:0;" /></linearGradient><linearGradient
id="linearGradient986"
gradientUnits="userSpaceOnUse"
x1="8.3533525"
x2="8.3533525"
y1="8.3028736"
y2="12.261972"><stop
offset="0.33542365"
stop-color="#5fb2ff"
id="stop982"
style="stop-color:#ffa7a7;stop-opacity:1;" /><stop
offset="1"
stop-color="#a2d2ff"
id="stop984"
style="stop-color:#fc7f7f;stop-opacity:1;" /></linearGradient></defs><sodipodi:namedview
id="namedview39"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="25.1875"
inkscape:cx="4.4665012"
inkscape:cy="9.9652606"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="g35" /><linearGradient
id="a"
gradientUnits="userSpaceOnUse"
x1="8.3533525"
x2="8.3533525"
y1="8.3028736"
y2="12.261972"><stop
offset="0.22192921"
stop-color="#5fb2ff"
id="stop974"
style="stop-color:#fcaaaa;stop-opacity:1;" /><stop
offset="1"
stop-color="#a2d2ff"
id="stop28"
style="stop-color:#fc7f7f;stop-opacity:1;" /></linearGradient><g
transform="matrix(1.1122949,0,0,1.1122949,-0.89835899,-2.0106539)"
id="g35"><path
id="path31"
style="fill:#80ff45;fill-opacity:1"
d="M 2.99381 10.750674 L 1.0008162 11.605817 L 7.9999998 14.604966 L 14.999183 11.605817 L 13.00619 10.750674 L 7.9999998 12.896434 L 2.99381 10.750674 z " /><path
id="path1728"
style="fill:#ff4545;fill-opacity:1"
d="M 3.3713375 8.1114933 L 1.0008162 9.1264276 L 7.9999998 12.127332 L 14.999183 9.1264276 L 12.628662 8.1114933 L 7.9999998 10.095707 L 3.3713375 8.1114933 z " /><path
d="M 1,6.3889173 8,9.3889174 15,6.3889173 8,3.388917 Z"
fill="#a2d2ff"
fill-rule="evenodd"
id="path1730"
style="fill:#45d7ff;fill-opacity:1" /></g></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

+44
View File
@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cs1la1mashf2e"
path="res://.godot/imported/layers.svg-4a679bb626c5179d3773f33e77e4a5e4.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/layers.svg"
dest_files=["res://.godot/imported/layers.svg-4a679bb626c5179d3773f33e77e4a5e4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+40
View File
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="16"
viewBox="0 0 16 16"
width="16"
version="1.1"
id="svg347"
sodipodi:docname="icon_multimesh.svg"
inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs351" />
<sodipodi:namedview
id="namedview349"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
showgrid="false"
inkscape:zoom="9.1592425"
inkscape:cx="34.282311"
inkscape:cy="22.709301"
inkscape:window-width="1920"
inkscape:window-height="1009"
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
inkscape:current-layer="svg347" />
<path
id="rect407"
style="fill:#fc7f7f;stroke-width:2;stroke-linecap:round;paint-order:stroke fill markers"
d="M 6.0979438,0.77152202 C 6.8581408,4.4407702 5.7042356,7.7264626 5.1336984,11.190982 4.5757087,8.429094 3.2699283,6.7549012 1.5824318,5.6359318 c 1.3386435,2.9076959 1.5327268,5.8153912 1.6595367,8.7230872 0.077524,1.556539 3.6893493,0.794317 8.7241545,-0.0772 -0.03898,-3.016292 1.350549,-4.3130746 2.624849,-7.6815876 C 13.134598,7.4583791 11.199788,9.072073 10.151276,11.663638 10.854206,8.7574539 11.968962,6.3154278 11.232097,2.8950308 10.675451,6.4379169 8.5380144,8.4925307 7.6805734,11.33356 7.3816525,7.5053532 8.4544829,3.7178929 6.0979438,0.77152202 Z"
sodipodi:nodetypes="cccccccccc" />
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,44 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cjlcl5lf20ve0"
path="res://.godot/imported/multimesh.svg-5487b93b04ddbaae37b5d3e91f10750b.ctex"
metadata={
"has_editor_variant": true,
"vram_texture": false
}
[deps]
source_file="res://addons/terrain_3d/icons/multimesh.svg"
dest_files=["res://.godot/imported/multimesh.svg-5487b93b04ddbaae37b5d3e91f10750b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=true
editor/convert_colors_with_editor_theme=false
+42
View File
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1"
id="svg2" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" sodipodi:docname="icon_height_add.svg" inkscape:version="1.2.2 (732a01da63, 2022-12-09)"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="16px" height="16px"
viewBox="0 0 16 16" enable-background="new 0 0 16 16" xml:space="preserve">
<g id="Compass_Rose">
<g>
<g>
<path fill="none" stroke="#FC7F7F" stroke-width="0.2239" d="M6.345,5.199c0.211-0.125,0.434-0.226,0.671-0.301
C7.326,4.8,7.658,4.747,8,4.747c0.604,0,1.171,0.164,1.655,0.452L7.966,0.34L6.345,5.199z"/>
<path fill="#FC7F7F" stroke="#FC7F7F" stroke-width="0.2239" d="M8,4.747c0.604,0,1.171,0.164,1.655,0.452L7.966,0.34v4.408
L8,4.747z"/>
</g>
<g>
<path fill="none" stroke="#FC7F7F" stroke-width="0.2239" d="M5.199,9.655C5.075,9.447,4.975,9.221,4.898,8.984
C4.801,8.674,4.748,8.342,4.748,8c0-0.605,0.164-1.171,0.452-1.656L0.34,8.034L5.199,9.655z"/>
<path fill="#FC7F7F" stroke="#FC7F7F" stroke-width="0.2239" d="M4.748,8c0-0.605,0.164-1.171,0.452-1.656L0.34,8.034h4.408
L4.748,8z"/>
</g>
<g>
<path fill="none" stroke="#FC7F7F" stroke-width="0.2239" d="M9.655,10.801c-0.208,0.125-0.435,0.226-0.671,0.3
c-0.311,0.099-0.642,0.15-0.984,0.15c-0.605,0-1.17-0.163-1.655-0.45l1.689,4.859L9.655,10.801z"/>
<path fill="#FC7F7F" stroke="#FC7F7F" stroke-width="0.2239" d="M8,11.251c-0.605,0-1.17-0.163-1.655-0.45l1.689,4.859v-4.409H8z
"/>
</g>
<g>
<path fill="none" stroke="#FC7F7F" stroke-width="0.2239" d="M10.801,6.345c0.125,0.21,0.226,0.435,0.3,0.67
C11.2,7.326,11.255,7.657,11.255,8c0,0.603-0.167,1.17-0.454,1.655l4.859-1.689L10.801,6.345z"/>
<path fill="#FC7F7F" stroke="#FC7F7F" stroke-width="0.2239" d="M11.255,8c0,0.603-0.167,1.17-0.454,1.655l4.859-1.689h-4.409
L11.255,8z"/>
</g>
</g>
<path fill="none" stroke="#FC7F7F" stroke-width="0.2239" d="M9.655,5.199c0.957,0.566,1.598,1.61,1.598,2.801
c0,1.795-1.457,3.25-3.253,3.25c-1.796,0-3.252-1.455-3.252-3.25c0-1.193,0.641-2.236,1.597-2.801
c0.211-0.125,0.434-0.226,0.671-0.301C7.326,4.8,7.658,4.747,8,4.747C8.604,4.747,9.171,4.911,9.655,5.199z"/>
<path display="none" fill="#FC7F7F" stroke="#FC7F7F" stroke-width="0.2239" d="M8.373,7.369C8.588,7.497,8.732,7.731,8.732,8
c0,0.405-0.328,0.732-0.732,0.732C7.596,8.731,7.268,8.404,7.268,8c0-0.268,0.144-0.503,0.36-0.63
c0.047-0.028,0.098-0.051,0.15-0.067C7.849,7.279,7.923,7.268,8,7.268C8.136,7.268,8.264,7.304,8.373,7.369z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

Some files were not shown because too many files have changed in this diff Show More