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
|
||||
expanding queries preserve current scoring and selected targets while the
|
||||
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:
|
||||
|
||||
1. Sculpt a real Terrain3D river channel and waterfall shelf before adding a
|
||||
broad foliage asset pass. Use Terrain3D particles for nearby grass and
|
||||
intentional instancing for tree/fern/rock masses, then rebake and validate
|
||||
the required village/resource routes.
|
||||
2. During that first larger foliage pass, prove a bounded authoring workflow
|
||||
that places intentional stable-ID `ResourceNode` anchors beside decorative
|
||||
Terrain3D instances. Add livestock only with a real identity and interaction
|
||||
contract.
|
||||
1. Prove one bounded foliage/resource authoring workflow at a riverbank or
|
||||
forest edge. Use decorative Terrain3D/intentional instances for tree, fern,
|
||||
flower, and rock masses, but create simulation-owned finite resources only
|
||||
through selected stable-ID `ResourceNode` anchors.
|
||||
2. Validate that villagers discover both nearby and farther authored anchors,
|
||||
preserve reachable interaction points, and keep the visual batch inside a
|
||||
measured presentation budget. Add livestock only with a real identity and
|
||||
interaction contract.
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
[Loaded Resource Discovery 01](benchmarks/LOADED_RESOURCE_DISCOVERY_01.md).
|
||||
|
||||
The immediate next slice should pair this systems foundation with the authored
|
||||
world: sculpt the real Terrain3D river channel and waterfall shelf, rebake and
|
||||
validate navigation, then use the first larger foliage/resource pass to prove a
|
||||
bounded stable-ID anchor placement workflow alongside decorative Terrain3D
|
||||
instancing. Do not generalize the resource grid to people/buildings/events or
|
||||
The authored-watercourse slice is complete. `JajceWatercourse` now gives the
|
||||
deterministic terrain generator and both water ribbons one shared profile; the
|
||||
generated Terrain3D data contains an upper stream, raised waterfall shelf,
|
||||
plunge basin, and curved downstream channel. A matched Metal capture records
|
||||
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.
|
||||
|
||||
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
|
||||
preserves exact score winners and lifecycle authority while reducing the
|
||||
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;
|
||||
- deterministic shaped terrain data and six game-owned surface layers;
|
||||
- a larger summit citadel, eight cascading houses, mill, full-span bridge,
|
||||
rock-framed waterfall, mist, smoke, and a Terrain3D-sampled river ribbon;
|
||||
- a larger summit citadel, eight cascading houses, mill, full-span bridge, and
|
||||
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;
|
||||
- compact silhouette props for the ridge landmark, guard, study, and rest
|
||||
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)
|
||||
and [Loaded Resource Discovery 01](benchmarks/LOADED_RESOURCE_DISCOVERY_01.md).
|
||||
|
||||
The next slice should return to the authored Jajce stage: sculpt the real
|
||||
Terrain3D river channel and waterfall shelf, rebake/validate navigation, and
|
||||
use the following foliage expansion to prove a bounded stable-ID resource
|
||||
anchor placement workflow beside decorative Terrain3D instances. Do not yet
|
||||
The authored Jajce watercourse and its Terrain3D-derived navigation rebake are
|
||||
complete. The next slice should prove a bounded stable-ID resource-anchor
|
||||
placement workflow beside decorative Terrain3D/intentional foliage instances
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
const JajceWatercourse := preload("res://world/jajce/JajceWatercourse.gd")
|
||||
|
||||
var failures: Array[String] = []
|
||||
|
||||
|
||||
@@ -213,7 +215,7 @@ func _run() -> void:
|
||||
"Waterfall mist particles should have visible billboard geometry"
|
||||
)
|
||||
_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"
|
||||
)
|
||||
_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 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(
|
||||
world.has_node("WaterRoot/RiverSurface/PlungePool"),
|
||||
"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 = (
|
||||
(world.get_node("WorldEnvironment") as WorldEnvironment).environment
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@ const HALF_SIZE := MAP_SIZE / 2
|
||||
const HEIGHT_SCALE := 40.0
|
||||
const DATA_DIRECTORY := "res://terrain/jajce/data"
|
||||
const TEXTURE_DIRECTORY := "res://terrain/jajce/textures"
|
||||
const Watercourse := preload("res://world/jajce/JajceWatercourse.gd")
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
@@ -41,13 +42,15 @@ func _generate() -> void:
|
||||
var southern_hill := (
|
||||
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 detail := noise.get_noise_2d(world_x, world_z) * 2.4
|
||||
var height := (
|
||||
(ridge + eastern_hill + southern_hill + river_trench + broad_slope + detail)
|
||||
* outside_play_area
|
||||
(ridge + eastern_hill + southern_hill + broad_slope + detail) * 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))
|
||||
|
||||
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)))
|
||||
|
||||
|
||||
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(
|
||||
file_name: String, dark_color: Color, light_color: Color, seed_value: int
|
||||
) -> 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
|
||||
|
||||
const Watercourse := preload("res://world/jajce/JajceWatercourse.gd")
|
||||
|
||||
@export var terrain_path := NodePath("../../TerrainRoot/Terrain3D")
|
||||
@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(8, 96, 1) var length_segments := 36
|
||||
@export var local_start_z := -25.0
|
||||
@export var local_end_z := 40.0
|
||||
@export_range(0.05, 0.5, 0.01) var water_offset := 0.18
|
||||
@export_range(8, 64, 1) var upper_length_segments := 18
|
||||
@export_range(0.05, 0.5, 0.01) var water_offset := Watercourse.WATER_SURFACE_OFFSET
|
||||
|
||||
@onready var river_ribbon: MeshInstance3D = $RiverRibbon
|
||||
@onready var upper_river_ribbon: MeshInstance3D = $UpperRiverRibbon
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -22,22 +23,48 @@ func _build_terrain_ribbon() -> void:
|
||||
push_warning("TerrainRiverRibbon: terrain or material is unavailable")
|
||||
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 normals := PackedVector3Array()
|
||||
var uvs := PackedVector2Array()
|
||||
var indices := PackedInt32Array()
|
||||
var row_size := width_segments + 1
|
||||
for z_index in length_segments + 1:
|
||||
var progress := float(z_index) / float(length_segments)
|
||||
var local_z := lerpf(local_start_z, local_end_z, progress)
|
||||
var downstream_curve := smoothstep(0.58, 1.0, progress) * 12.0
|
||||
var center_offset := sin(progress * TAU) * 1.4 + downstream_curve
|
||||
var downstream_taper := lerpf(1.0, 0.45, smoothstep(0.65, 1.0, progress))
|
||||
var half_width := river_width * lerpf(0.52, 0.45, progress) * downstream_taper
|
||||
for z_index in segment_count + 1:
|
||||
var progress := float(z_index) / float(segment_count)
|
||||
var world_z := lerpf(start_z, end_z, progress)
|
||||
var center_x := (
|
||||
Watercourse.upper_stream_center_x(world_z)
|
||||
if is_upper
|
||||
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:
|
||||
var across := float(x_index) / float(width_segments)
|
||||
var local_x := center_offset + lerpf(-half_width, half_width, across)
|
||||
var world_sample := to_global(Vector3(local_x, 0.0, local_z))
|
||||
var world_x := center_x + lerpf(-half_width, half_width, across)
|
||||
var world_sample := Vector3(world_x, 0.0, world_z)
|
||||
var terrain_height := terrain.data.get_height(world_sample)
|
||||
if is_nan(terrain_height):
|
||||
terrain_height = global_position.y
|
||||
@@ -47,7 +74,7 @@ func _build_terrain_ribbon() -> void:
|
||||
normals.append(Vector3.UP)
|
||||
uvs.append(Vector2(across, progress))
|
||||
|
||||
for z_index in length_segments:
|
||||
for z_index in segment_count:
|
||||
for x_index in width_segments:
|
||||
var top_left := z_index * row_size + x_index
|
||||
var top_right := top_left + 1
|
||||
@@ -66,17 +93,20 @@ func _build_terrain_ribbon() -> void:
|
||||
var mesh := ArrayMesh.new()
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
||||
mesh.surface_set_material(0, river_material)
|
||||
river_ribbon.mesh = mesh
|
||||
_align_pool_to_terrain(terrain)
|
||||
return mesh
|
||||
|
||||
|
||||
func _align_pool_to_terrain(terrain: Terrain3D) -> void:
|
||||
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)
|
||||
if is_nan(pool_height):
|
||||
return
|
||||
var local_surface := to_local(Vector3(pool_sample.x, pool_height + water_offset, pool_sample.z))
|
||||
for node_name in [&"PlungePool", &"FoamRing", &"FoamAtFall"]:
|
||||
var surface := get_node(NodePath(node_name)) as MeshInstance3D
|
||||
surface.position.y = local_surface.y
|
||||
pool.position = local_surface
|
||||
var foam_ring := $FoamRing as MeshInstance3D
|
||||
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"]
|
||||
transparency = 1
|
||||
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 = Color(0.34, 0.56, 0.48, 1)
|
||||
emission_energy_multiplier = 0.18
|
||||
emission_energy_multiplier = 0.06
|
||||
roughness = 0.74
|
||||
|
||||
[sub_resource type="TorusMesh" id="Mesh_foam_ring"]
|
||||
material = SubResource("Material_foam")
|
||||
inner_radius = 4.8
|
||||
outer_radius = 5.65
|
||||
inner_radius = 5.15
|
||||
outer_radius = 5.5
|
||||
rings = 28
|
||||
ring_segments = 10
|
||||
|
||||
[sub_resource type="PlaneMesh" id="Mesh_foam_patch"]
|
||||
material = SubResource("Material_foam")
|
||||
size = Vector2(10.5, 2.2)
|
||||
size = Vector2(6, 1.5)
|
||||
subdivide_width = 8
|
||||
subdivide_depth = 2
|
||||
|
||||
@@ -44,18 +44,18 @@ river_material = SubResource("ShaderMaterial_water")
|
||||
[node name="RiverRibbon" type="MeshInstance3D" parent="."]
|
||||
cast_shadow = 0
|
||||
|
||||
[node name="UpperRiverRibbon" type="MeshInstance3D" parent="."]
|
||||
cast_shadow = 0
|
||||
|
||||
[node name="PlungePool" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0, -0.02, -25)
|
||||
scale = Vector3(1.25, 1, 0.82)
|
||||
mesh = SubResource("Mesh_plunge_pool")
|
||||
|
||||
[node name="FoamRing" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0, 0.09, -25)
|
||||
scale = Vector3(1.25, 0.12, 0.82)
|
||||
cast_shadow = 0
|
||||
mesh = SubResource("Mesh_foam_ring")
|
||||
|
||||
[node name="FoamAtFall" type="MeshInstance3D" parent="."]
|
||||
position = Vector3(0, 0.12, -25.8)
|
||||
cast_shadow = 0
|
||||
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"]
|
||||
|
||||
@@ -7,7 +7,7 @@ shader = ExtResource("1_waterfall_shader")
|
||||
|
||||
[sub_resource type="QuadMesh" id="Mesh_waterfall_body"]
|
||||
material = SubResource("ShaderMaterial_waterfall")
|
||||
size = Vector2(10.8, 14)
|
||||
size = Vector2(9.6, 11.4)
|
||||
subdivide_width = 18
|
||||
subdivide_depth = 28
|
||||
|
||||
@@ -32,50 +32,74 @@ size = Vector3(4.8, 5.5, 3.4)
|
||||
|
||||
[sub_resource type="BoxMesh" id="Mesh_lip"]
|
||||
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"]
|
||||
position = Vector3(25, 8.9, -24)
|
||||
position = Vector3(25, 5.8, -24)
|
||||
|
||||
[node name="RockFrame" type="Node3D" parent="."]
|
||||
|
||||
[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)
|
||||
scale = Vector3(0.72, 0.86, 0.8)
|
||||
mesh = SubResource("Mesh_cliff_side")
|
||||
|
||||
[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)
|
||||
scale = Vector3(0.68, 0.88, 0.76)
|
||||
mesh = SubResource("Mesh_cliff_side")
|
||||
|
||||
[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)
|
||||
scale = Vector3(0.85, 0.62, 0.9)
|
||||
scale = Vector3(0.82, 0.58, 0.88)
|
||||
mesh = SubResource("Mesh_rock_lobe")
|
||||
|
||||
[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)
|
||||
scale = Vector3(0.82, 0.6, 0.86)
|
||||
scale = Vector3(0.78, 0.58, 0.84)
|
||||
mesh = SubResource("Mesh_rock_lobe")
|
||||
|
||||
[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)
|
||||
scale = Vector3(0.88, 0.72, 0.9)
|
||||
mesh = SubResource("Mesh_rock_lobe")
|
||||
|
||||
[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)
|
||||
scale = Vector3(0.9, 0.74, 0.88)
|
||||
mesh = SubResource("Mesh_rock_lobe")
|
||||
|
||||
[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")
|
||||
|
||||
[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="."]
|
||||
position = Vector3(0, 0, 1.05)
|
||||
position = Vector3(0, 0, 1.1)
|
||||
cast_shadow = 0
|
||||
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"]
|
||||
lifetime_randomness = 0.35
|
||||
@@ -7,26 +9,27 @@ gravity = Vector3(0, -0.35, 0)
|
||||
initial_velocity_min = 0.25
|
||||
initial_velocity_max = 1.2
|
||||
scale_min = 0.08
|
||||
scale_max = 2.0
|
||||
color = Color(0.82, 0.94, 0.96, 0.35)
|
||||
scale_max = 1.35
|
||||
color = Color(0.82, 0.94, 0.96, 0.22)
|
||||
emission_shape = 1
|
||||
emission_box_extents = Vector3(5.8, 1.5, 1.35)
|
||||
|
||||
[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
|
||||
shading_mode = 0
|
||||
albedo_texture = ExtResource("1_mist_texture")
|
||||
billboard_mode = 1
|
||||
cull_mode = 0
|
||||
|
||||
[sub_resource type="QuadMesh" id="Mesh_mist"]
|
||||
material = SubResource("Material_mist")
|
||||
size = Vector2(1.8, 1.8)
|
||||
size = Vector2(1.45, 1.45)
|
||||
|
||||
[node name="WaterfallMist" type="GPUParticles3D"]
|
||||
position = Vector3(25, 2.05, -24)
|
||||
position = Vector3(25, 0.65, -21.5)
|
||||
emitting = true
|
||||
amount = 96
|
||||
amount = 56
|
||||
lifetime = 3.0
|
||||
one_shot = false
|
||||
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