feat: carve Jajce river into terrain
This commit is contained in:
@@ -774,17 +774,23 @@ Completed:
|
|||||||
stable-ID resource anchors in a resource-specific horizontal grid. Exact
|
stable-ID resource anchors in a resource-specific horizontal grid. Exact
|
||||||
expanding queries preserve current scoring and selected targets while the
|
expanding queries preserve current scoring and selected targets while the
|
||||||
reviewed 1,800-source case inspects about 4.4 candidates instead of 1,800.
|
reviewed 1,800-source case inspects about 4.4 candidates instead of 1,800.
|
||||||
|
36. `Jajce River 03`: the deterministic Terrain3D source now carves a raised
|
||||||
|
waterfall shelf, upper stream, plunge basin, and curved downstream channel.
|
||||||
|
Shared watercourse profiles keep generated terrain and both water ribbons
|
||||||
|
aligned; softened foam/mist and a smaller rock frame make the falls part of
|
||||||
|
the landform. A matched Metal capture records the change, and the rebaked
|
||||||
|
navigation mesh passes terrain-height and runtime route checks.
|
||||||
|
|
||||||
Next:
|
Next:
|
||||||
|
|
||||||
1. Sculpt a real Terrain3D river channel and waterfall shelf before adding a
|
1. Prove one bounded foliage/resource authoring workflow at a riverbank or
|
||||||
broad foliage asset pass. Use Terrain3D particles for nearby grass and
|
forest edge. Use decorative Terrain3D/intentional instances for tree, fern,
|
||||||
intentional instancing for tree/fern/rock masses, then rebake and validate
|
flower, and rock masses, but create simulation-owned finite resources only
|
||||||
the required village/resource routes.
|
through selected stable-ID `ResourceNode` anchors.
|
||||||
2. During that first larger foliage pass, prove a bounded authoring workflow
|
2. Validate that villagers discover both nearby and farther authored anchors,
|
||||||
that places intentional stable-ID `ResourceNode` anchors beside decorative
|
preserve reachable interaction points, and keep the visual batch inside a
|
||||||
Terrain3D instances. Add livestock only with a real identity and interaction
|
measured presentation budget. Add livestock only with a real identity and
|
||||||
contract.
|
interaction contract.
|
||||||
|
|
||||||
Do not start with GIS data, a full city, a large asset pack, or more NPC
|
Do not start with GIS data, a full city, a large asset pack, or more NPC
|
||||||
mechanics. The next proof is a beautiful stage for the systems that already
|
mechanics. The next proof is a beautiful stage for the systems that already
|
||||||
|
|||||||
@@ -889,15 +889,29 @@ Unload/rebind state authority, stable tie order, moved anchors, reservations,
|
|||||||
player parity, and a deliberately far high-priority winner remain covered. See
|
player parity, and a deliberately far high-priority winner remain covered. See
|
||||||
[Loaded Resource Discovery 01](benchmarks/LOADED_RESOURCE_DISCOVERY_01.md).
|
[Loaded Resource Discovery 01](benchmarks/LOADED_RESOURCE_DISCOVERY_01.md).
|
||||||
|
|
||||||
The immediate next slice should pair this systems foundation with the authored
|
The authored-watercourse slice is complete. `JajceWatercourse` now gives the
|
||||||
world: sculpt the real Terrain3D river channel and waterfall shelf, rebake and
|
deterministic terrain generator and both water ribbons one shared profile; the
|
||||||
validate navigation, then use the first larger foliage/resource pass to prove a
|
generated Terrain3D data contains an upper stream, raised waterfall shelf,
|
||||||
bounded stable-ID anchor placement workflow alongside decorative Terrain3D
|
plunge basin, and curved downstream channel. A matched Metal capture records
|
||||||
instancing. Do not generalize the resource grid to people/buildings/events or
|
the result, and the Terrain3D-derived navigation mesh was rebaked and validated
|
||||||
|
against required village/resource routes.
|
||||||
|
|
||||||
|
The immediate next slice should prove one bounded foliage/resource authoring
|
||||||
|
workflow in the real world: build a restrained riverbank or forest-edge cluster
|
||||||
|
with decorative Terrain3D/intentional instances, pair only selected harvestable
|
||||||
|
trees or berry patches with stable-ID `ResourceNode` anchors, and verify nearby
|
||||||
|
and farther loaded anchors remain discoverable through `ActiveWorldAdapter`.
|
||||||
|
Keep decorative instances out of simulation state, measure the bounded visual
|
||||||
|
cost, and do not generalize the resource grid to people/buildings/events or
|
||||||
introduce active/abstract LOD before those consumers need it.
|
introduce active/abstract LOD before those consumers need it.
|
||||||
|
|
||||||
Recently completed:
|
Recently completed:
|
||||||
|
|
||||||
|
- `Jajce River 03`: deterministic Terrain3D generation now carves the upper
|
||||||
|
stream, raised waterfall shelf, plunge basin, and curved downstream channel;
|
||||||
|
shared watercourse geometry keeps both water ribbons aligned, soft masked
|
||||||
|
mist replaces hard billboards, and the rebaked navigation mesh passes
|
||||||
|
terrain-height and runtime resource-route checks.
|
||||||
- Loaded-resource spatial discovery: a resource-specific active-world grid
|
- Loaded-resource spatial discovery: a resource-specific active-world grid
|
||||||
preserves exact score winners and lifecycle authority while reducing the
|
preserves exact score winners and lifecycle authority while reducing the
|
||||||
reviewed 1,800-anchor query from 6.45 ms to 46.94 µs.
|
reviewed 1,800-anchor query from 6.45 ms to 46.94 µs.
|
||||||
|
|||||||
@@ -341,8 +341,9 @@ but retain far sources whenever their authoritative metadata can still win.
|
|||||||
|
|
||||||
- a centered 512 m Terrain3D landscape split across four regions;
|
- a centered 512 m Terrain3D landscape split across four regions;
|
||||||
- deterministic shaped terrain data and six game-owned surface layers;
|
- deterministic shaped terrain data and six game-owned surface layers;
|
||||||
- a larger summit citadel, eight cascading houses, mill, full-span bridge,
|
- a larger summit citadel, eight cascading houses, mill, full-span bridge, and
|
||||||
rock-framed waterfall, mist, smoke, and a Terrain3D-sampled river ribbon;
|
a deterministic Terrain3D watercourse with an upper stream, raised waterfall
|
||||||
|
shelf, rock-framed drop, plunge basin, mist, and curved downstream ribbon;
|
||||||
- authored dirt path strips that make the current village task loop readable;
|
- authored dirt path strips that make the current village task loop readable;
|
||||||
- compact silhouette props for the ridge landmark, guard, study, and rest
|
- compact silhouette props for the ridge landmark, guard, study, and rest
|
||||||
sites, plus a physical pantry whose empty/low/stocked arrangement follows
|
sites, plus a physical pantry whose empty/low/stocked arrangement follows
|
||||||
@@ -979,10 +980,12 @@ Raw samples and workload exclusions live in
|
|||||||
[Simulation scaling baseline 02](benchmarks/SIMULATION_SCALING_BASELINE_02.md)
|
[Simulation scaling baseline 02](benchmarks/SIMULATION_SCALING_BASELINE_02.md)
|
||||||
and [Loaded Resource Discovery 01](benchmarks/LOADED_RESOURCE_DISCOVERY_01.md).
|
and [Loaded Resource Discovery 01](benchmarks/LOADED_RESOURCE_DISCOVERY_01.md).
|
||||||
|
|
||||||
The next slice should return to the authored Jajce stage: sculpt the real
|
The authored Jajce watercourse and its Terrain3D-derived navigation rebake are
|
||||||
Terrain3D river channel and waterfall shelf, rebake/validate navigation, and
|
complete. The next slice should prove a bounded stable-ID resource-anchor
|
||||||
use the following foliage expansion to prove a bounded stable-ID resource
|
placement workflow beside decorative Terrain3D/intentional foliage instances
|
||||||
anchor placement workflow beside decorative Terrain3D instances. Do not yet
|
at one riverbank or forest edge. It should validate nearby and farther loaded
|
||||||
|
resource discovery, reachable interaction points, and a measured presentation
|
||||||
|
budget without making decorative instances authoritative. Do not yet
|
||||||
generalize the resource grid or introduce active/abstract simulation LOD.
|
generalize the resource grid or introduce active/abstract simulation LOD.
|
||||||
|
|
||||||
The remaining simulation-garden target still aims for:
|
The remaining simulation-garden target still aims for:
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
# Jajce River 03
|
||||||
|
|
||||||
|
Captured: 2026-07-17
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- [before overview](jajce_river_03_before.png)
|
||||||
|
- [after overview](jajce_river_03_after.png)
|
||||||
|
- `tools/capture_jajce_lookdev.gd`
|
||||||
|
- `tools/generate_jajce_terrain.gd`
|
||||||
|
- `tools/bake_jajce_navigation.gd`
|
||||||
|
|
||||||
|
The images use the same 1280×720 camera, fixed daylight value, warmup, and
|
||||||
|
Metal renderer. They are a matched checkpoint rather than two independently
|
||||||
|
composed beauty shots.
|
||||||
|
|
||||||
|
## What changed
|
||||||
|
|
||||||
|
The previous river was a turquoise presentation ribbon laid across a mostly
|
||||||
|
flat valley. Its waterfall was held by a large freestanding rock frame, so the
|
||||||
|
water feature read more like a prop than part of the landscape.
|
||||||
|
|
||||||
|
The deterministic Terrain3D source now creates:
|
||||||
|
|
||||||
|
- a raised shelf upstream of the falls;
|
||||||
|
- a shallow upper stream widening toward the lip;
|
||||||
|
- an approximately 12 m drop into a shaped plunge basin;
|
||||||
|
- a curved downstream channel with visible banks; and
|
||||||
|
- slope-driven rock texture around the drop and steeper channel edges.
|
||||||
|
|
||||||
|
The waterfall frame is smaller and nested into that landform. Its foam is more
|
||||||
|
restrained, and the mist uses a soft radial mask so individual particle quads
|
||||||
|
do not appear against the dark cliff. The result remains deliberately reduced:
|
||||||
|
citadel, roofs, shelf, falls, bridge, and river form one readable village-center
|
||||||
|
composition without attempting a literal reconstruction of Jajce.
|
||||||
|
|
||||||
|
## Deterministic authoring contract
|
||||||
|
|
||||||
|
`JajceWatercourse` owns the shared center lines, widths, bed heights, shelf
|
||||||
|
height, and water offset. `generate_jajce_terrain.gd` uses that profile to
|
||||||
|
produce the checked-in Terrain3D regions; `TerrainRiverRibbon.gd` uses the same
|
||||||
|
profile and samples the generated terrain for both water meshes. Changing the
|
||||||
|
watercourse therefore has one explicit regeneration and navigation-rebake
|
||||||
|
path instead of unrelated hand-tuned terrain and presentation coordinates.
|
||||||
|
|
||||||
|
This is presentation and interaction geometry only. No resource IDs,
|
||||||
|
simulation records, RNG streams, save data, or NPC decision rules changed.
|
||||||
|
|
||||||
|
## Validation
|
||||||
|
|
||||||
|
The focused watercourse scenario records an upper-stream height of 11.28 m, a
|
||||||
|
12.43 m shelf bank, and a -0.65 m plunge basin. It also checks multiple points
|
||||||
|
along the downstream bed and verifies both generated water ribbons remain
|
||||||
|
within 3 cm of the Terrain3D surface plus the shared water offset.
|
||||||
|
|
||||||
|
The navigation bake now contains 403 vertices and 620 polygons. The Jajce
|
||||||
|
scaffold verifies required paths reach their destinations and track the new
|
||||||
|
terrain heights, while the runtime integration scenario still completes real
|
||||||
|
player resource gathering with all 18 finite resource anchors loaded.
|
||||||
|
|
||||||
|
## Next visual/system proof
|
||||||
|
|
||||||
|
The next pass should author one bounded riverbank or forest-edge foliage mass,
|
||||||
|
using decorative Terrain3D/intentional instances for visual density and only a
|
||||||
|
small selected set of stable-ID `ResourceNode` anchors for finite harvestable
|
||||||
|
resources. Bank stones, ferns, flowers, and tree masses can improve the channel
|
||||||
|
edge during that proof. Bridge, buildings, and fortress geometry remain
|
||||||
|
prototype blockouts and should be replaced gradually rather than through a
|
||||||
|
broad asset dump.
|
||||||
|
|
||||||
|
## Capture command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HOME="$PWD/logs/quality/godot_profile" \
|
||||||
|
APPDATA="$PWD/logs/quality/godot_profile" \
|
||||||
|
LOCALAPPDATA="$PWD/logs/quality/godot_profile" \
|
||||||
|
/Applications/Godot.app/Contents/MacOS/Godot \
|
||||||
|
--path "$PWD" --script res://tools/capture_jajce_lookdev.gd -- \
|
||||||
|
--output=res://docs/baselines/jajce_river_03_after.png
|
||||||
|
```
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 742 KiB |
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://r87qvthnulve"
|
||||||
|
path="res://.godot/imported/jajce_river_03_after.png-43f5610f2e4f42d23b2a77674f846c7e.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://docs/baselines/jajce_river_03_after.png"
|
||||||
|
dest_files=["res://.godot/imported/jajce_river_03_after.png-43f5610f2e4f42d23b2a77674f846c7e.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
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 730 KiB |
@@ -0,0 +1,40 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://2whbruuqbywc"
|
||||||
|
path="res://.godot/imported/jajce_river_03_before.png-1cca7b1c7659c390906ee3738cc8b95f.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://docs/baselines/jajce_river_03_before.png"
|
||||||
|
dest_files=["res://.godot/imported/jajce_river_03_before.png-1cca7b1c7659c390906ee3738cc8b95f.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
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,117 @@
|
|||||||
|
extends SceneTree
|
||||||
|
|
||||||
|
const JajceWatercourse := preload("res://world/jajce/JajceWatercourse.gd")
|
||||||
|
|
||||||
|
var failures: Array[String] = []
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
call_deferred("_run")
|
||||||
|
|
||||||
|
|
||||||
|
func _run() -> void:
|
||||||
|
var camera := Camera3D.new()
|
||||||
|
camera.current = true
|
||||||
|
root.add_child(camera)
|
||||||
|
var world: Node3D = load("res://world/jajce/JajceWorld.tscn").instantiate()
|
||||||
|
root.add_child(world)
|
||||||
|
await process_frame
|
||||||
|
for _frame in 4:
|
||||||
|
await physics_frame
|
||||||
|
|
||||||
|
var terrain := world.get_node("TerrainRoot/Terrain3D") as Terrain3D
|
||||||
|
_check(terrain != null, "Jajce watercourse validation requires Terrain3D")
|
||||||
|
if terrain == null:
|
||||||
|
_finish()
|
||||||
|
return
|
||||||
|
|
||||||
|
var shelf_z := -36.0
|
||||||
|
var shelf_center_x := JajceWatercourse.upper_stream_center_x(shelf_z)
|
||||||
|
var shelf_half_width := JajceWatercourse.upper_stream_half_width(shelf_z)
|
||||||
|
var shelf_stream_height := _terrain_height(terrain, shelf_center_x, shelf_z)
|
||||||
|
var shelf_bank_height := _terrain_height(
|
||||||
|
terrain, shelf_center_x + shelf_half_width + 4.0, shelf_z
|
||||||
|
)
|
||||||
|
var plunge_height := _terrain_height(
|
||||||
|
terrain, JajceWatercourse.PLUNGE_POOL_CENTER.x, JajceWatercourse.PLUNGE_POOL_CENTER.y
|
||||||
|
)
|
||||||
|
_check(shelf_stream_height >= 10.8, "Upper stream should remain on the raised waterfall shelf")
|
||||||
|
_check(
|
||||||
|
shelf_bank_height - shelf_stream_height >= 0.45,
|
||||||
|
"Upper stream should be visibly carved below its shelf bank"
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
shelf_stream_height - plunge_height >= 10.0,
|
||||||
|
"Waterfall shelf should create a readable drop into the plunge basin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var downstream_samples := PackedFloat32Array([-10.0, 12.0, 34.0])
|
||||||
|
for world_z in downstream_samples:
|
||||||
|
var center_x := JajceWatercourse.downstream_center_x(world_z)
|
||||||
|
var half_width := JajceWatercourse.downstream_half_width(world_z)
|
||||||
|
var channel_height := _terrain_height(terrain, center_x, world_z)
|
||||||
|
var left_bank_height := _terrain_height(terrain, center_x - half_width - 5.0, world_z)
|
||||||
|
var right_bank_height := _terrain_height(terrain, center_x + half_width + 5.0, world_z)
|
||||||
|
_check(
|
||||||
|
channel_height <= JajceWatercourse.downstream_bed_height(world_z) + 0.08,
|
||||||
|
"Downstream bed should follow its authored profile at z=%.1f" % world_z
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
maxf(left_bank_height, right_bank_height) - channel_height >= 0.45,
|
||||||
|
"Downstream channel should read below a natural bank at z=%.1f" % world_z
|
||||||
|
)
|
||||||
|
|
||||||
|
var river_surface := world.get_node("WaterRoot/RiverSurface") as Node3D
|
||||||
|
_check_ribbon_tracks_terrain(
|
||||||
|
terrain, river_surface.get_node("RiverRibbon") as MeshInstance3D, "Downstream water"
|
||||||
|
)
|
||||||
|
_check_ribbon_tracks_terrain(
|
||||||
|
terrain, river_surface.get_node("UpperRiverRibbon") as MeshInstance3D, "Upper-stream water"
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
(
|
||||||
|
"[TEST] Jajce watercourse samples | shelf=%.2f bank=%.2f pool=%.2f"
|
||||||
|
% [shelf_stream_height, shelf_bank_height, plunge_height]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
_finish()
|
||||||
|
|
||||||
|
|
||||||
|
func _terrain_height(terrain: Terrain3D, world_x: float, world_z: float) -> float:
|
||||||
|
return terrain.data.get_height(Vector3(world_x, 0.0, world_z))
|
||||||
|
|
||||||
|
|
||||||
|
func _check_ribbon_tracks_terrain(
|
||||||
|
terrain: Terrain3D, ribbon: MeshInstance3D, label: String
|
||||||
|
) -> void:
|
||||||
|
_check(ribbon != null and ribbon.mesh is ArrayMesh, "%s ribbon should be generated" % label)
|
||||||
|
if ribbon == null or not ribbon.mesh is ArrayMesh:
|
||||||
|
return
|
||||||
|
var arrays := ribbon.mesh.surface_get_arrays(0)
|
||||||
|
var vertices: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
|
||||||
|
_check(not vertices.is_empty(), "%s ribbon should contain vertices" % label)
|
||||||
|
for vertex_index in range(0, vertices.size(), maxi(1, vertices.size() / 12)):
|
||||||
|
var world_vertex := ribbon.to_global(vertices[vertex_index])
|
||||||
|
var terrain_height := terrain.data.get_height(world_vertex)
|
||||||
|
if is_nan(terrain_height):
|
||||||
|
failures.append("%s ribbon sampled outside Terrain3D" % label)
|
||||||
|
return
|
||||||
|
if absf(world_vertex.y - terrain_height - JajceWatercourse.WATER_SURFACE_OFFSET) > 0.03:
|
||||||
|
failures.append("%s ribbon should hug the carved terrain" % label)
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
func _check(condition: bool, message: String) -> void:
|
||||||
|
if not condition:
|
||||||
|
failures.append(message)
|
||||||
|
|
||||||
|
|
||||||
|
func _finish() -> void:
|
||||||
|
if failures.is_empty():
|
||||||
|
print("[TEST] Jajce watercourse terrain passed")
|
||||||
|
quit(0)
|
||||||
|
return
|
||||||
|
for failure in failures:
|
||||||
|
push_error("[TEST] " + failure)
|
||||||
|
quit(1)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://bs5xfsxii5j8w
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
extends SceneTree
|
extends SceneTree
|
||||||
|
|
||||||
|
const JajceWatercourse := preload("res://world/jajce/JajceWatercourse.gd")
|
||||||
|
|
||||||
var failures: Array[String] = []
|
var failures: Array[String] = []
|
||||||
|
|
||||||
|
|
||||||
@@ -213,7 +215,7 @@ func _run() -> void:
|
|||||||
"Waterfall mist particles should have visible billboard geometry"
|
"Waterfall mist particles should have visible billboard geometry"
|
||||||
)
|
)
|
||||||
_check(
|
_check(
|
||||||
waterfall_mist.position.distance_to(Vector3(25.0, 2.05, -24.0)) < 0.01,
|
waterfall_mist.position.distance_to(Vector3(25.0, 0.65, -21.5)) < 0.01,
|
||||||
"Waterfall mist should stay at the waterfall base instead of obscuring the village"
|
"Waterfall mist should stay at the waterfall base instead of obscuring the village"
|
||||||
)
|
)
|
||||||
_check(world.has_node("WaterRoot/RiverSurface"), "WaterRoot should include a river surface")
|
_check(world.has_node("WaterRoot/RiverSurface"), "WaterRoot should include a river surface")
|
||||||
@@ -222,10 +224,34 @@ func _run() -> void:
|
|||||||
river_ribbon.mesh is ArrayMesh,
|
river_ribbon.mesh is ArrayMesh,
|
||||||
"River should build a lightweight ribbon from the current Terrain3D surface"
|
"River should build a lightweight ribbon from the current Terrain3D surface"
|
||||||
)
|
)
|
||||||
|
var upper_river_ribbon := (
|
||||||
|
world.get_node("WaterRoot/RiverSurface/UpperRiverRibbon") as MeshInstance3D
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
upper_river_ribbon.mesh is ArrayMesh,
|
||||||
|
"Waterfall shelf should expose a terrain-following upper stream"
|
||||||
|
)
|
||||||
_check(
|
_check(
|
||||||
world.has_node("WaterRoot/RiverSurface/PlungePool"),
|
world.has_node("WaterRoot/RiverSurface/PlungePool"),
|
||||||
"River should widen into a readable plunge pool beneath the waterfall"
|
"River should widen into a readable plunge pool beneath the waterfall"
|
||||||
)
|
)
|
||||||
|
var plunge_pool := world.get_node("WaterRoot/RiverSurface/PlungePool") as MeshInstance3D
|
||||||
|
var expected_pool_height: float = terrain.data.get_height(
|
||||||
|
Vector3(JajceWatercourse.PLUNGE_POOL_CENTER.x, 0.0, JajceWatercourse.PLUNGE_POOL_CENTER.y)
|
||||||
|
)
|
||||||
|
_check(
|
||||||
|
(
|
||||||
|
absf(
|
||||||
|
(
|
||||||
|
plunge_pool.global_position.y
|
||||||
|
- expected_pool_height
|
||||||
|
- JajceWatercourse.WATER_SURFACE_OFFSET
|
||||||
|
)
|
||||||
|
)
|
||||||
|
< 0.03
|
||||||
|
),
|
||||||
|
"Plunge-pool water should follow the carved basin height"
|
||||||
|
)
|
||||||
var environment: Environment = (
|
var environment: Environment = (
|
||||||
(world.get_node("WorldEnvironment") as WorldEnvironment).environment
|
(world.get_node("WorldEnvironment") as WorldEnvironment).environment
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ const HALF_SIZE := MAP_SIZE / 2
|
|||||||
const HEIGHT_SCALE := 40.0
|
const HEIGHT_SCALE := 40.0
|
||||||
const DATA_DIRECTORY := "res://terrain/jajce/data"
|
const DATA_DIRECTORY := "res://terrain/jajce/data"
|
||||||
const TEXTURE_DIRECTORY := "res://terrain/jajce/textures"
|
const TEXTURE_DIRECTORY := "res://terrain/jajce/textures"
|
||||||
|
const Watercourse := preload("res://world/jajce/JajceWatercourse.gd")
|
||||||
|
|
||||||
|
|
||||||
func _initialize() -> void:
|
func _initialize() -> void:
|
||||||
@@ -41,13 +42,15 @@ func _generate() -> void:
|
|||||||
var southern_hill := (
|
var southern_hill := (
|
||||||
11.0 * _elliptical_peak(world_x, world_z, -35.0, -125.0, 130.0, 85.0)
|
11.0 * _elliptical_peak(world_x, world_z, -35.0, -125.0, 130.0, 85.0)
|
||||||
)
|
)
|
||||||
var river_trench := -5.0 * exp(-pow((world_x - 25.0) / 18.0, 2.0))
|
|
||||||
var broad_slope := maxf(0.0, (-world_x - 30.0) * 0.035)
|
var broad_slope := maxf(0.0, (-world_x - 30.0) * 0.035)
|
||||||
var detail := noise.get_noise_2d(world_x, world_z) * 2.4
|
var detail := noise.get_noise_2d(world_x, world_z) * 2.4
|
||||||
var height := (
|
var height := (
|
||||||
(ridge + eastern_hill + southern_hill + river_trench + broad_slope + detail)
|
(ridge + eastern_hill + southern_hill + broad_slope + detail) * outside_play_area
|
||||||
* outside_play_area
|
|
||||||
)
|
)
|
||||||
|
height = _shape_waterfall_shelf(height, world_x, world_z, detail)
|
||||||
|
height = _shape_upper_stream(height, world_x, world_z)
|
||||||
|
height = _shape_plunge_pool(height, world_x, world_z)
|
||||||
|
height = _shape_downstream_channel(height, world_x, world_z)
|
||||||
height_map.set_pixel(image_x, image_z, Color(height / HEIGHT_SCALE, 0.0, 0.0, 1.0))
|
height_map.set_pixel(image_x, image_z, Color(height / HEIGHT_SCALE, 0.0, 0.0, 1.0))
|
||||||
|
|
||||||
terrain.data.import_images(
|
terrain.data.import_images(
|
||||||
@@ -64,6 +67,44 @@ func _elliptical_peak(
|
|||||||
return exp(-(pow((x - center_x) / width, 2.0) + pow((z - center_z) / depth, 2.0)))
|
return exp(-(pow((x - center_x) / width, 2.0) + pow((z - center_z) / depth, 2.0)))
|
||||||
|
|
||||||
|
|
||||||
|
func _shape_waterfall_shelf(height: float, x: float, z: float, detail: float) -> float:
|
||||||
|
var horizontal_mask := 1.0 - smoothstep(10.0, 28.0, absf(x - Watercourse.WATERFALL_CENTER.x))
|
||||||
|
var upstream_mask := smoothstep(-76.0, -54.0, z)
|
||||||
|
var drop_mask := 1.0 - smoothstep(-25.0, -21.0, z)
|
||||||
|
var shelf_mask := horizontal_mask * upstream_mask * drop_mask
|
||||||
|
var shelf_height := Watercourse.SHELF_TOP_HEIGHT + detail * 0.08
|
||||||
|
return lerpf(height, maxf(height, shelf_height), shelf_mask)
|
||||||
|
|
||||||
|
|
||||||
|
func _shape_upper_stream(height: float, x: float, z: float) -> float:
|
||||||
|
if z < Watercourse.UPPER_STREAM_START_Z or z > Watercourse.UPPER_STREAM_END_Z:
|
||||||
|
return height
|
||||||
|
var center_x := Watercourse.upper_stream_center_x(z)
|
||||||
|
var half_width := Watercourse.upper_stream_half_width(z)
|
||||||
|
var channel_mask := 1.0 - smoothstep(half_width, half_width + 3.2, absf(x - center_x))
|
||||||
|
var target_height := minf(height, Watercourse.upper_stream_bed_height(z))
|
||||||
|
return lerpf(height, target_height, channel_mask)
|
||||||
|
|
||||||
|
|
||||||
|
func _shape_plunge_pool(height: float, x: float, z: float) -> float:
|
||||||
|
var offset := Vector2(x, z) - Watercourse.PLUNGE_POOL_CENTER
|
||||||
|
var elliptical_distance := Vector2(offset.x / 10.5, offset.y / 7.8).length()
|
||||||
|
var basin_mask := 1.0 - smoothstep(0.68, 1.15, elliptical_distance)
|
||||||
|
var cliff_front_mask := smoothstep(-25.0, -20.5, z)
|
||||||
|
var target_height := minf(height, Watercourse.PLUNGE_POOL_BED_HEIGHT)
|
||||||
|
return lerpf(height, target_height, basin_mask * cliff_front_mask)
|
||||||
|
|
||||||
|
|
||||||
|
func _shape_downstream_channel(height: float, x: float, z: float) -> float:
|
||||||
|
if z < Watercourse.DOWNSTREAM_START_Z or z > Watercourse.DOWNSTREAM_END_Z:
|
||||||
|
return height
|
||||||
|
var center_x := Watercourse.downstream_center_x(z)
|
||||||
|
var half_width := Watercourse.downstream_half_width(z)
|
||||||
|
var bank_mask := 1.0 - smoothstep(half_width, half_width + 4.5, absf(x - center_x))
|
||||||
|
var target_height := minf(height, Watercourse.downstream_bed_height(z))
|
||||||
|
return lerpf(height, target_height, bank_mask)
|
||||||
|
|
||||||
|
|
||||||
func _generate_texture(
|
func _generate_texture(
|
||||||
file_name: String, dark_color: Color, light_color: Color, seed_value: int
|
file_name: String, dark_color: Color, light_color: Color, seed_value: int
|
||||||
) -> void:
|
) -> void:
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,49 @@
|
|||||||
|
class_name JajceWatercourse
|
||||||
|
extends RefCounted
|
||||||
|
|
||||||
|
const WATERFALL_CENTER := Vector2(25.0, -24.0)
|
||||||
|
const SHELF_TOP_HEIGHT := 12.4
|
||||||
|
const UPPER_STREAM_START_Z := -58.0
|
||||||
|
const UPPER_STREAM_END_Z := -25.0
|
||||||
|
const PLUNGE_POOL_CENTER := Vector2(25.0, -19.5)
|
||||||
|
const PLUNGE_POOL_BED_HEIGHT := -0.65
|
||||||
|
const DOWNSTREAM_START_Z := -20.0
|
||||||
|
const DOWNSTREAM_END_Z := 42.0
|
||||||
|
const WATER_SURFACE_OFFSET := 0.32
|
||||||
|
|
||||||
|
|
||||||
|
static func downstream_progress(world_z: float) -> float:
|
||||||
|
return clampf(inverse_lerp(DOWNSTREAM_START_Z, DOWNSTREAM_END_Z, world_z), 0.0, 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
static func downstream_center_x(world_z: float) -> float:
|
||||||
|
var progress := downstream_progress(world_z)
|
||||||
|
var downstream_curve := smoothstep(0.58, 1.0, progress) * 12.0
|
||||||
|
return WATERFALL_CENTER.x + sin(progress * TAU) * 1.4 + downstream_curve
|
||||||
|
|
||||||
|
|
||||||
|
static func downstream_half_width(world_z: float) -> float:
|
||||||
|
var progress := downstream_progress(world_z)
|
||||||
|
var base_width := lerpf(4.8, 3.8, smoothstep(0.0, 0.62, progress))
|
||||||
|
return base_width + smoothstep(0.78, 1.0, progress) * 0.8
|
||||||
|
|
||||||
|
|
||||||
|
static func downstream_bed_height(world_z: float) -> float:
|
||||||
|
return lerpf(-0.55, -1.3, downstream_progress(world_z))
|
||||||
|
|
||||||
|
|
||||||
|
static func upper_stream_progress(world_z: float) -> float:
|
||||||
|
return clampf(inverse_lerp(UPPER_STREAM_START_Z, UPPER_STREAM_END_Z, world_z), 0.0, 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
static func upper_stream_center_x(world_z: float) -> float:
|
||||||
|
var progress := upper_stream_progress(world_z)
|
||||||
|
return WATERFALL_CENTER.x + sin(progress * PI * 1.6) * 0.9
|
||||||
|
|
||||||
|
|
||||||
|
static func upper_stream_half_width(world_z: float) -> float:
|
||||||
|
return lerpf(2.8, 4.4, smoothstep(0.55, 1.0, upper_stream_progress(world_z)))
|
||||||
|
|
||||||
|
|
||||||
|
static func upper_stream_bed_height(world_z: float) -> float:
|
||||||
|
return lerpf(11.55, 11.15, upper_stream_progress(world_z))
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
uid://dl3523bi00bto
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
extends Node3D
|
extends Node3D
|
||||||
|
|
||||||
|
const Watercourse := preload("res://world/jajce/JajceWatercourse.gd")
|
||||||
|
|
||||||
@export var terrain_path := NodePath("../../TerrainRoot/Terrain3D")
|
@export var terrain_path := NodePath("../../TerrainRoot/Terrain3D")
|
||||||
@export var river_material: Material
|
@export var river_material: Material
|
||||||
@export_range(6.0, 20.0, 0.5) var river_width := 8.0
|
|
||||||
@export_range(2, 12, 1) var width_segments := 6
|
@export_range(2, 12, 1) var width_segments := 6
|
||||||
@export_range(8, 96, 1) var length_segments := 36
|
@export_range(8, 96, 1) var length_segments := 36
|
||||||
@export var local_start_z := -25.0
|
@export_range(8, 64, 1) var upper_length_segments := 18
|
||||||
@export var local_end_z := 40.0
|
@export_range(0.05, 0.5, 0.01) var water_offset := Watercourse.WATER_SURFACE_OFFSET
|
||||||
@export_range(0.05, 0.5, 0.01) var water_offset := 0.18
|
|
||||||
|
|
||||||
@onready var river_ribbon: MeshInstance3D = $RiverRibbon
|
@onready var river_ribbon: MeshInstance3D = $RiverRibbon
|
||||||
|
@onready var upper_river_ribbon: MeshInstance3D = $UpperRiverRibbon
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
@@ -22,22 +23,48 @@ func _build_terrain_ribbon() -> void:
|
|||||||
push_warning("TerrainRiverRibbon: terrain or material is unavailable")
|
push_warning("TerrainRiverRibbon: terrain or material is unavailable")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
river_ribbon.mesh = _build_ribbon(
|
||||||
|
terrain,
|
||||||
|
Watercourse.DOWNSTREAM_START_Z,
|
||||||
|
Watercourse.DOWNSTREAM_END_Z,
|
||||||
|
length_segments,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
upper_river_ribbon.mesh = _build_ribbon(
|
||||||
|
terrain,
|
||||||
|
Watercourse.UPPER_STREAM_START_Z,
|
||||||
|
Watercourse.UPPER_STREAM_END_Z,
|
||||||
|
upper_length_segments,
|
||||||
|
true
|
||||||
|
)
|
||||||
|
_align_pool_to_terrain(terrain)
|
||||||
|
|
||||||
|
|
||||||
|
func _build_ribbon(
|
||||||
|
terrain: Terrain3D, start_z: float, end_z: float, segment_count: int, is_upper: bool
|
||||||
|
) -> ArrayMesh:
|
||||||
var vertices := PackedVector3Array()
|
var vertices := PackedVector3Array()
|
||||||
var normals := PackedVector3Array()
|
var normals := PackedVector3Array()
|
||||||
var uvs := PackedVector2Array()
|
var uvs := PackedVector2Array()
|
||||||
var indices := PackedInt32Array()
|
var indices := PackedInt32Array()
|
||||||
var row_size := width_segments + 1
|
var row_size := width_segments + 1
|
||||||
for z_index in length_segments + 1:
|
for z_index in segment_count + 1:
|
||||||
var progress := float(z_index) / float(length_segments)
|
var progress := float(z_index) / float(segment_count)
|
||||||
var local_z := lerpf(local_start_z, local_end_z, progress)
|
var world_z := lerpf(start_z, end_z, progress)
|
||||||
var downstream_curve := smoothstep(0.58, 1.0, progress) * 12.0
|
var center_x := (
|
||||||
var center_offset := sin(progress * TAU) * 1.4 + downstream_curve
|
Watercourse.upper_stream_center_x(world_z)
|
||||||
var downstream_taper := lerpf(1.0, 0.45, smoothstep(0.65, 1.0, progress))
|
if is_upper
|
||||||
var half_width := river_width * lerpf(0.52, 0.45, progress) * downstream_taper
|
else Watercourse.downstream_center_x(world_z)
|
||||||
|
)
|
||||||
|
var half_width := (
|
||||||
|
Watercourse.upper_stream_half_width(world_z)
|
||||||
|
if is_upper
|
||||||
|
else Watercourse.downstream_half_width(world_z)
|
||||||
|
)
|
||||||
for x_index in width_segments + 1:
|
for x_index in width_segments + 1:
|
||||||
var across := float(x_index) / float(width_segments)
|
var across := float(x_index) / float(width_segments)
|
||||||
var local_x := center_offset + lerpf(-half_width, half_width, across)
|
var world_x := center_x + lerpf(-half_width, half_width, across)
|
||||||
var world_sample := to_global(Vector3(local_x, 0.0, local_z))
|
var world_sample := Vector3(world_x, 0.0, world_z)
|
||||||
var terrain_height := terrain.data.get_height(world_sample)
|
var terrain_height := terrain.data.get_height(world_sample)
|
||||||
if is_nan(terrain_height):
|
if is_nan(terrain_height):
|
||||||
terrain_height = global_position.y
|
terrain_height = global_position.y
|
||||||
@@ -47,7 +74,7 @@ func _build_terrain_ribbon() -> void:
|
|||||||
normals.append(Vector3.UP)
|
normals.append(Vector3.UP)
|
||||||
uvs.append(Vector2(across, progress))
|
uvs.append(Vector2(across, progress))
|
||||||
|
|
||||||
for z_index in length_segments:
|
for z_index in segment_count:
|
||||||
for x_index in width_segments:
|
for x_index in width_segments:
|
||||||
var top_left := z_index * row_size + x_index
|
var top_left := z_index * row_size + x_index
|
||||||
var top_right := top_left + 1
|
var top_right := top_left + 1
|
||||||
@@ -66,17 +93,20 @@ func _build_terrain_ribbon() -> void:
|
|||||||
var mesh := ArrayMesh.new()
|
var mesh := ArrayMesh.new()
|
||||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
||||||
mesh.surface_set_material(0, river_material)
|
mesh.surface_set_material(0, river_material)
|
||||||
river_ribbon.mesh = mesh
|
return mesh
|
||||||
_align_pool_to_terrain(terrain)
|
|
||||||
|
|
||||||
|
|
||||||
func _align_pool_to_terrain(terrain: Terrain3D) -> void:
|
func _align_pool_to_terrain(terrain: Terrain3D) -> void:
|
||||||
var pool := $PlungePool as MeshInstance3D
|
var pool := $PlungePool as MeshInstance3D
|
||||||
var pool_sample := to_global(Vector3(pool.position.x, 0.0, pool.position.z))
|
var pool_sample := Vector3(
|
||||||
|
Watercourse.PLUNGE_POOL_CENTER.x, 0.0, Watercourse.PLUNGE_POOL_CENTER.y
|
||||||
|
)
|
||||||
var pool_height := terrain.data.get_height(pool_sample)
|
var pool_height := terrain.data.get_height(pool_sample)
|
||||||
if is_nan(pool_height):
|
if is_nan(pool_height):
|
||||||
return
|
return
|
||||||
var local_surface := to_local(Vector3(pool_sample.x, pool_height + water_offset, pool_sample.z))
|
var local_surface := to_local(Vector3(pool_sample.x, pool_height + water_offset, pool_sample.z))
|
||||||
for node_name in [&"PlungePool", &"FoamRing", &"FoamAtFall"]:
|
pool.position = local_surface
|
||||||
var surface := get_node(NodePath(node_name)) as MeshInstance3D
|
var foam_ring := $FoamRing as MeshInstance3D
|
||||||
surface.position.y = local_surface.y
|
foam_ring.position = local_surface + Vector3(0.0, 0.1, 0.0)
|
||||||
|
var foam_at_fall := $FoamAtFall as MeshInstance3D
|
||||||
|
foam_at_fall.position = local_surface + Vector3(0.0, 0.13, -2.7)
|
||||||
|
|||||||
@@ -17,22 +17,22 @@ rings = 2
|
|||||||
[sub_resource type="StandardMaterial3D" id="Material_foam"]
|
[sub_resource type="StandardMaterial3D" id="Material_foam"]
|
||||||
transparency = 1
|
transparency = 1
|
||||||
shading_mode = 0
|
shading_mode = 0
|
||||||
albedo_color = Color(0.8, 0.93, 0.86, 0.46)
|
albedo_color = Color(0.8, 0.93, 0.86, 0.22)
|
||||||
emission_enabled = true
|
emission_enabled = true
|
||||||
emission = Color(0.34, 0.56, 0.48, 1)
|
emission = Color(0.34, 0.56, 0.48, 1)
|
||||||
emission_energy_multiplier = 0.18
|
emission_energy_multiplier = 0.06
|
||||||
roughness = 0.74
|
roughness = 0.74
|
||||||
|
|
||||||
[sub_resource type="TorusMesh" id="Mesh_foam_ring"]
|
[sub_resource type="TorusMesh" id="Mesh_foam_ring"]
|
||||||
material = SubResource("Material_foam")
|
material = SubResource("Material_foam")
|
||||||
inner_radius = 4.8
|
inner_radius = 5.15
|
||||||
outer_radius = 5.65
|
outer_radius = 5.5
|
||||||
rings = 28
|
rings = 28
|
||||||
ring_segments = 10
|
ring_segments = 10
|
||||||
|
|
||||||
[sub_resource type="PlaneMesh" id="Mesh_foam_patch"]
|
[sub_resource type="PlaneMesh" id="Mesh_foam_patch"]
|
||||||
material = SubResource("Material_foam")
|
material = SubResource("Material_foam")
|
||||||
size = Vector2(10.5, 2.2)
|
size = Vector2(6, 1.5)
|
||||||
subdivide_width = 8
|
subdivide_width = 8
|
||||||
subdivide_depth = 2
|
subdivide_depth = 2
|
||||||
|
|
||||||
@@ -44,18 +44,18 @@ river_material = SubResource("ShaderMaterial_water")
|
|||||||
[node name="RiverRibbon" type="MeshInstance3D" parent="."]
|
[node name="RiverRibbon" type="MeshInstance3D" parent="."]
|
||||||
cast_shadow = 0
|
cast_shadow = 0
|
||||||
|
|
||||||
|
[node name="UpperRiverRibbon" type="MeshInstance3D" parent="."]
|
||||||
|
cast_shadow = 0
|
||||||
|
|
||||||
[node name="PlungePool" type="MeshInstance3D" parent="."]
|
[node name="PlungePool" type="MeshInstance3D" parent="."]
|
||||||
position = Vector3(0, -0.02, -25)
|
|
||||||
scale = Vector3(1.25, 1, 0.82)
|
scale = Vector3(1.25, 1, 0.82)
|
||||||
mesh = SubResource("Mesh_plunge_pool")
|
mesh = SubResource("Mesh_plunge_pool")
|
||||||
|
|
||||||
[node name="FoamRing" type="MeshInstance3D" parent="."]
|
[node name="FoamRing" type="MeshInstance3D" parent="."]
|
||||||
position = Vector3(0, 0.09, -25)
|
|
||||||
scale = Vector3(1.25, 0.12, 0.82)
|
scale = Vector3(1.25, 0.12, 0.82)
|
||||||
cast_shadow = 0
|
cast_shadow = 0
|
||||||
mesh = SubResource("Mesh_foam_ring")
|
mesh = SubResource("Mesh_foam_ring")
|
||||||
|
|
||||||
[node name="FoamAtFall" type="MeshInstance3D" parent="."]
|
[node name="FoamAtFall" type="MeshInstance3D" parent="."]
|
||||||
position = Vector3(0, 0.12, -25.8)
|
|
||||||
cast_shadow = 0
|
cast_shadow = 0
|
||||||
mesh = SubResource("Mesh_foam_patch")
|
mesh = SubResource("Mesh_foam_patch")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
[gd_scene load_steps=10 format=3]
|
[gd_scene load_steps=12 format=3]
|
||||||
|
|
||||||
[ext_resource type="Shader" path="res://world/jajce/materials/waterfall.gdshader" id="1_waterfall_shader"]
|
[ext_resource type="Shader" path="res://world/jajce/materials/waterfall.gdshader" id="1_waterfall_shader"]
|
||||||
|
|
||||||
@@ -7,7 +7,7 @@ shader = ExtResource("1_waterfall_shader")
|
|||||||
|
|
||||||
[sub_resource type="QuadMesh" id="Mesh_waterfall_body"]
|
[sub_resource type="QuadMesh" id="Mesh_waterfall_body"]
|
||||||
material = SubResource("ShaderMaterial_waterfall")
|
material = SubResource("ShaderMaterial_waterfall")
|
||||||
size = Vector2(10.8, 14)
|
size = Vector2(9.6, 11.4)
|
||||||
subdivide_width = 18
|
subdivide_width = 18
|
||||||
subdivide_depth = 28
|
subdivide_depth = 28
|
||||||
|
|
||||||
@@ -32,50 +32,74 @@ size = Vector3(4.8, 5.5, 3.4)
|
|||||||
|
|
||||||
[sub_resource type="BoxMesh" id="Mesh_lip"]
|
[sub_resource type="BoxMesh" id="Mesh_lip"]
|
||||||
material = SubResource("Material_rock_moss")
|
material = SubResource("Material_rock_moss")
|
||||||
size = Vector3(11.6, 0.7, 3.2)
|
size = Vector3(10.4, 0.65, 3.0)
|
||||||
|
|
||||||
|
[sub_resource type="StandardMaterial3D" id="Material_lip_foam"]
|
||||||
|
transparency = 1
|
||||||
|
shading_mode = 0
|
||||||
|
albedo_color = Color(0.82, 0.95, 0.87, 0.72)
|
||||||
|
emission_enabled = true
|
||||||
|
emission = Color(0.46, 0.72, 0.62, 1)
|
||||||
|
emission_energy_multiplier = 0.22
|
||||||
|
roughness = 0.62
|
||||||
|
|
||||||
|
[sub_resource type="PlaneMesh" id="Mesh_lip_foam"]
|
||||||
|
material = SubResource("Material_lip_foam")
|
||||||
|
size = Vector2(9.2, 1.45)
|
||||||
|
subdivide_width = 8
|
||||||
|
subdivide_depth = 2
|
||||||
|
|
||||||
[node name="WaterfallBody" type="Node3D"]
|
[node name="WaterfallBody" type="Node3D"]
|
||||||
position = Vector3(25, 8.9, -24)
|
position = Vector3(25, 5.8, -24)
|
||||||
|
|
||||||
[node name="RockFrame" type="Node3D" parent="."]
|
[node name="RockFrame" type="Node3D" parent="."]
|
||||||
|
|
||||||
[node name="WestCliff" type="MeshInstance3D" parent="RockFrame"]
|
[node name="WestCliff" type="MeshInstance3D" parent="RockFrame"]
|
||||||
position = Vector3(-7.2, -0.45, -1)
|
position = Vector3(-6.1, -0.15, -0.9)
|
||||||
rotation_degrees = Vector3(0, 8, -3)
|
rotation_degrees = Vector3(0, 8, -3)
|
||||||
|
scale = Vector3(0.72, 0.86, 0.8)
|
||||||
mesh = SubResource("Mesh_cliff_side")
|
mesh = SubResource("Mesh_cliff_side")
|
||||||
|
|
||||||
[node name="EastCliff" type="MeshInstance3D" parent="RockFrame"]
|
[node name="EastCliff" type="MeshInstance3D" parent="RockFrame"]
|
||||||
position = Vector3(7.2, -0.25, -1.2)
|
position = Vector3(6.2, 0.05, -1.1)
|
||||||
rotation_degrees = Vector3(0, -7, 4)
|
rotation_degrees = Vector3(0, -7, 4)
|
||||||
|
scale = Vector3(0.68, 0.88, 0.76)
|
||||||
mesh = SubResource("Mesh_cliff_side")
|
mesh = SubResource("Mesh_cliff_side")
|
||||||
|
|
||||||
[node name="WestCrownRock" type="MeshInstance3D" parent="RockFrame"]
|
[node name="WestCrownRock" type="MeshInstance3D" parent="RockFrame"]
|
||||||
position = Vector3(-5.0, 7.45, -1.25)
|
position = Vector3(-4.9, 5.8, -1.2)
|
||||||
rotation_degrees = Vector3(8, -14, -11)
|
rotation_degrees = Vector3(8, -14, -11)
|
||||||
scale = Vector3(0.85, 0.62, 0.9)
|
scale = Vector3(0.82, 0.58, 0.88)
|
||||||
mesh = SubResource("Mesh_rock_lobe")
|
mesh = SubResource("Mesh_rock_lobe")
|
||||||
|
|
||||||
[node name="EastCrownRock" type="MeshInstance3D" parent="RockFrame"]
|
[node name="EastCrownRock" type="MeshInstance3D" parent="RockFrame"]
|
||||||
position = Vector3(5.1, 7.55, -1.4)
|
position = Vector3(4.8, 5.9, -1.35)
|
||||||
rotation_degrees = Vector3(-6, 12, 9)
|
rotation_degrees = Vector3(-6, 12, 9)
|
||||||
scale = Vector3(0.82, 0.6, 0.86)
|
scale = Vector3(0.78, 0.58, 0.84)
|
||||||
mesh = SubResource("Mesh_rock_lobe")
|
mesh = SubResource("Mesh_rock_lobe")
|
||||||
|
|
||||||
[node name="WestLowerRock" type="MeshInstance3D" parent="RockFrame"]
|
[node name="WestLowerRock" type="MeshInstance3D" parent="RockFrame"]
|
||||||
position = Vector3(-5.9, -4.8, 0.1)
|
position = Vector3(-5.35, -5.0, 0.15)
|
||||||
rotation_degrees = Vector3(7, 14, 12)
|
rotation_degrees = Vector3(7, 14, 12)
|
||||||
|
scale = Vector3(0.88, 0.72, 0.9)
|
||||||
mesh = SubResource("Mesh_rock_lobe")
|
mesh = SubResource("Mesh_rock_lobe")
|
||||||
|
|
||||||
[node name="EastLowerRock" type="MeshInstance3D" parent="RockFrame"]
|
[node name="EastLowerRock" type="MeshInstance3D" parent="RockFrame"]
|
||||||
position = Vector3(6.0, -4.4, 0)
|
position = Vector3(5.45, -4.75, 0)
|
||||||
rotation_degrees = Vector3(-4, -12, -9)
|
rotation_degrees = Vector3(-4, -12, -9)
|
||||||
|
scale = Vector3(0.9, 0.74, 0.88)
|
||||||
mesh = SubResource("Mesh_rock_lobe")
|
mesh = SubResource("Mesh_rock_lobe")
|
||||||
|
|
||||||
[node name="RiverLip" type="MeshInstance3D" parent="RockFrame"]
|
[node name="RiverLip" type="MeshInstance3D" parent="RockFrame"]
|
||||||
position = Vector3(0, 7.05, 0.2)
|
position = Vector3(0, 5.42, -0.2)
|
||||||
mesh = SubResource("Mesh_lip")
|
mesh = SubResource("Mesh_lip")
|
||||||
|
|
||||||
|
[node name="LipFoam" type="MeshInstance3D" parent="RockFrame"]
|
||||||
|
position = Vector3(0, 5.74, -0.85)
|
||||||
|
cast_shadow = 0
|
||||||
|
mesh = SubResource("Mesh_lip_foam")
|
||||||
|
|
||||||
[node name="FallingWater" type="MeshInstance3D" parent="."]
|
[node name="FallingWater" type="MeshInstance3D" parent="."]
|
||||||
position = Vector3(0, 0, 1.05)
|
position = Vector3(0, 0, 1.1)
|
||||||
cast_shadow = 0
|
cast_shadow = 0
|
||||||
mesh = SubResource("Mesh_waterfall_body")
|
mesh = SubResource("Mesh_waterfall_body")
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
[gd_scene load_steps=4 format=3]
|
[gd_scene load_steps=5 format=3]
|
||||||
|
|
||||||
|
[ext_resource type="Texture2D" path="res://world/jajce/materials/mist_soft.svg" id="1_mist_texture"]
|
||||||
|
|
||||||
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_mist"]
|
[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_mist"]
|
||||||
lifetime_randomness = 0.35
|
lifetime_randomness = 0.35
|
||||||
@@ -7,26 +9,27 @@ gravity = Vector3(0, -0.35, 0)
|
|||||||
initial_velocity_min = 0.25
|
initial_velocity_min = 0.25
|
||||||
initial_velocity_max = 1.2
|
initial_velocity_max = 1.2
|
||||||
scale_min = 0.08
|
scale_min = 0.08
|
||||||
scale_max = 2.0
|
scale_max = 1.35
|
||||||
color = Color(0.82, 0.94, 0.96, 0.35)
|
color = Color(0.82, 0.94, 0.96, 0.22)
|
||||||
emission_shape = 1
|
emission_shape = 1
|
||||||
emission_box_extents = Vector3(5.8, 1.5, 1.35)
|
emission_box_extents = Vector3(5.8, 1.5, 1.35)
|
||||||
|
|
||||||
[sub_resource type="StandardMaterial3D" id="Material_mist"]
|
[sub_resource type="StandardMaterial3D" id="Material_mist"]
|
||||||
albedo_color = Color(0.82, 0.94, 0.96, 0.28)
|
albedo_color = Color(0.82, 0.94, 0.96, 0.3)
|
||||||
transparency = 1
|
transparency = 1
|
||||||
shading_mode = 0
|
shading_mode = 0
|
||||||
|
albedo_texture = ExtResource("1_mist_texture")
|
||||||
billboard_mode = 1
|
billboard_mode = 1
|
||||||
cull_mode = 0
|
cull_mode = 0
|
||||||
|
|
||||||
[sub_resource type="QuadMesh" id="Mesh_mist"]
|
[sub_resource type="QuadMesh" id="Mesh_mist"]
|
||||||
material = SubResource("Material_mist")
|
material = SubResource("Material_mist")
|
||||||
size = Vector2(1.8, 1.8)
|
size = Vector2(1.45, 1.45)
|
||||||
|
|
||||||
[node name="WaterfallMist" type="GPUParticles3D"]
|
[node name="WaterfallMist" type="GPUParticles3D"]
|
||||||
position = Vector3(25, 2.05, -24)
|
position = Vector3(25, 0.65, -21.5)
|
||||||
emitting = true
|
emitting = true
|
||||||
amount = 96
|
amount = 56
|
||||||
lifetime = 3.0
|
lifetime = 3.0
|
||||||
one_shot = false
|
one_shot = false
|
||||||
preprocess = 1.5
|
preprocess = 1.5
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64">
|
||||||
|
<defs>
|
||||||
|
<radialGradient id="mist" cx="50%" cy="48%" r="50%">
|
||||||
|
<stop offset="0" stop-color="#dff4ef" stop-opacity="0.82"/>
|
||||||
|
<stop offset="0.42" stop-color="#c8e7e3" stop-opacity="0.48"/>
|
||||||
|
<stop offset="0.76" stop-color="#b8d9d7" stop-opacity="0.14"/>
|
||||||
|
<stop offset="1" stop-color="#b8d9d7" stop-opacity="0"/>
|
||||||
|
</radialGradient>
|
||||||
|
</defs>
|
||||||
|
<circle cx="32" cy="32" r="31" fill="url(#mist)"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 509 B |
@@ -0,0 +1,43 @@
|
|||||||
|
[remap]
|
||||||
|
|
||||||
|
importer="texture"
|
||||||
|
type="CompressedTexture2D"
|
||||||
|
uid="uid://c0ie27xnkmfd8"
|
||||||
|
path="res://.godot/imported/mist_soft.svg-8d0454048e96ea4cac3f1157714c34e4.ctex"
|
||||||
|
metadata={
|
||||||
|
"vram_texture": false
|
||||||
|
}
|
||||||
|
|
||||||
|
[deps]
|
||||||
|
|
||||||
|
source_file="res://world/jajce/materials/mist_soft.svg"
|
||||||
|
dest_files=["res://.godot/imported/mist_soft.svg-8d0454048e96ea4cac3f1157714c34e4.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=false
|
||||||
|
editor/convert_colors_with_editor_theme=false
|
||||||
Reference in New Issue
Block a user