Skip to content

Commit 70f5b3b

Browse files
authored
Add the ToHeight2D/3D node, used to create side-view or 3D terrain. (#403)
* Add the ToHeight node * Add description and 2D/3D variants * Add documentation. * Fix `_get_data` type * Add tests * Fix unused argument warning * Add gradient * Update to_height_2d_and_3d.gd * Change gradient value * Move `z_range` check outside loop * Update to_height_2d_and_3d.gd * Remove print
1 parent 8b268af commit 70f5b3b

File tree

5 files changed

+164
-1
lines changed

5 files changed

+164
-1
lines changed

addons/gaea/graph/graph_nodes/root/data/generation/snake_path_2d.gd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ func _get_output_port_type(_output_name: StringName) -> GaeaValue.Type:
6363
return GaeaValue.Type.DATA
6464

6565

66-
func _get_data(output_port: StringName, area: AABB, graph: GaeaGraph) -> Dictionary:
66+
func _get_data(_output_port: StringName, area: AABB, graph: GaeaGraph) -> Dictionary:
6767
var direction_weights: Dictionary[Vector2i, float] = {
6868
Vector2i.LEFT: _get_arg(&"move_left_weight", area, graph),
6969
Vector2i.RIGHT: _get_arg(&"move_right_weight", area, graph),
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
@tool
2+
class_name GaeaNodeToHeight
3+
extends GaeaNodeResource
4+
## Transforms [param reference_data] into a new data grid where the height of each column is determined by [param height_offset] + ([param reference_data] * [param displacement_intensity])
5+
##
6+
## For each cell in [param reference_data]'s [param reference_y] row, it'll get the [code]float[/code] value,
7+
## multiply it by [param displacement_intensity] and add [param height_offset] to it. This will be
8+
## the column's height, and every cell below that height (inclusive) will be full while every cell above
9+
## will be empty.[br][br]
10+
## This functions to create a heightmap, which can be used to create 2D side-view or
11+
## 3D terrain.[br][br]
12+
## [b]Note: Keep in mind the y axis in Godot is negative for up in 2D and down in 3D.[/b]
13+
14+
enum Type {
15+
TYPE_2D, ## Referenced data will only take into account the x coordinate of the cell.
16+
TYPE_3D ## Referenced data will take into account both the x and the z coordinates of the cell.
17+
}
18+
19+
20+
func _get_title() -> String:
21+
return "ToHeight"
22+
23+
24+
func _get_description() -> String:
25+
var desc: String = "Transforms [param reference_data] into a new data grid where the height of each column is determined by\
26+
[param height_offset] + ([param reference_data] * [param displacement_intensity]).\n"
27+
match get_enum_selection(0):
28+
Type.TYPE_2D:
29+
desc += "\nReferences all the x values of the [param reference_y] row."
30+
Type.TYPE_3D:
31+
desc += "\nReferences all the x,z values of the [param reference_y] row."
32+
return desc
33+
34+
35+
func _get_tree_items() -> Array[GaeaNodeResource]:
36+
var items: Array[GaeaNodeResource]
37+
for type in Type.values():
38+
var item: GaeaNodeToHeight = get_script().new()
39+
item.set_tree_name_override(_get_title() + _get_enum_option_display_name(0, type))
40+
item.set_default_enum_value_override(0, type)
41+
items.append(item)
42+
43+
return items
44+
45+
46+
func _get_enums_count() -> int:
47+
return 1
48+
49+
50+
func _get_enum_options(_enum_idx: int) -> Dictionary:
51+
return Type
52+
53+
54+
func _get_enum_option_display_name(_enum_idx: int, option_value: int) -> String:
55+
return Type.find_key(option_value).trim_prefix("TYPE_")
56+
57+
58+
59+
func _get_arguments_list() -> Array[StringName]:
60+
return [&"reference_data", &"reference_y",
61+
&"height_offset", &"displacement_intensity",
62+
&"gradient_intensity"]
63+
64+
65+
func _get_argument_type(arg_name: StringName) -> GaeaValue.Type:
66+
match arg_name:
67+
&"reference_data": return GaeaValue.Type.DATA
68+
&"gradient_intensity": return GaeaValue.Type.FLOAT
69+
_: return GaeaValue.Type.INT
70+
71+
72+
func _get_argument_default_value(arg_name: StringName) -> Variant:
73+
match arg_name:
74+
&"displacement_intensity": return 16
75+
&"gradient_intensity": return 1.0
76+
return super(arg_name)
77+
78+
79+
80+
func _get_output_ports_list() -> Array[StringName]:
81+
return [&"data"]
82+
83+
84+
func _get_output_port_type(_output_name: StringName) -> GaeaValue.Type:
85+
return GaeaValue.Type.DATA
86+
87+
88+
func _get_data(_output_port: StringName, area: AABB, graph: GaeaGraph) -> Dictionary:
89+
var reference_data: Dictionary = _get_arg(&"reference_data", area, graph)
90+
var row: int = _get_arg(&"reference_y", area, graph)
91+
var height_offset: int = _get_arg(&"height_offset", area, graph)
92+
var displacement: int = _get_arg(&"displacement_intensity", area, graph)
93+
var gradient_intensity: float = _get_arg(&"gradient_intensity", area, graph)
94+
var data: Dictionary[Vector3i, float] = {}
95+
var type: Type = get_enum_selection(0) as Type
96+
97+
var remap_offset: float = 0.0
98+
if not is_zero_approx(gradient_intensity):
99+
remap_offset = 100.0 / gradient_intensity
100+
101+
var z_range: Array = [0] if (type == Type.TYPE_2D) else (_get_axis_range(Vector3i.AXIS_Z, area))
102+
for x in _get_axis_range(Vector3i.AXIS_X, area):
103+
if not reference_data.has(Vector3i(x, row, 0)):
104+
continue
105+
for z in z_range:
106+
var height: int = floor(reference_data[Vector3i(x, row, z)] * displacement + height_offset)
107+
for y in _get_axis_range(Vector3i.AXIS_Y, area):
108+
if y >= -height and type == Type.TYPE_2D:
109+
data[Vector3i(x, y, z)] = 1.0 if is_zero_approx(remap_offset) else remap(
110+
y, -height + remap_offset, -height, 0, 1.0
111+
)
112+
elif y <= height and type == Type.TYPE_3D:
113+
data[Vector3i(x, y, z)] = 1.0 if is_zero_approx(remap_offset) else remap(
114+
y, height, height - remap_offset, 1.0, 0.0
115+
)
116+
return data
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://b0xqvegteqx7c
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
extends GdUnitTestSuite
2+
3+
4+
const AREA: AABB = AABB(Vector3.ZERO, Vector3(1, 4, 1) * 16)
5+
const EXPECTED_HASH_2D: int = 3725516071
6+
const EXPECTED_HASH_3D: int = 2178371583
7+
8+
var reference_data: Dictionary = {}
9+
var node: GaeaNodeToHeight
10+
11+
12+
func before() -> void:
13+
var noise: FastNoiseLite = FastNoiseLite.new()
14+
for x in range(AREA.position.x, AREA.end.x):
15+
for z in range(AREA.position.z, AREA.end.z):
16+
reference_data[Vector3i(x, 1, z)] = noise.get_noise_3d(x, 0, z)
17+
node = GaeaNodeToHeight.new()
18+
node.set_argument_value(&"reference_data", reference_data)
19+
node.set_argument_value(&"reference_y", 1)
20+
21+
22+
func test_2d() -> void:
23+
node.set_enum_value(0, GaeaNodeToHeight.Type.TYPE_2D)
24+
node.set_argument_value(&"height_offset", -4)
25+
var generated_data: Dictionary = node._get_data(&"data", AREA, null)
26+
assert_dict(generated_data)\
27+
.override_failure_message("Empty result from [b]GaeaNodeToHeight2D[/b].")\
28+
.is_not_empty()
29+
assert_int(generated_data.hash())\
30+
.override_failure_message("Unexpected result from [b]GaeaNodeToHeight2D[/b].")\
31+
.append_failure_message("Generated: %s\nExpected: %s" % [generated_data.hash(), EXPECTED_HASH_2D])\
32+
.is_equal(EXPECTED_HASH_2D)
33+
34+
35+
func test_3d() -> void:
36+
node.set_enum_value(0, GaeaNodeToHeight.Type.TYPE_3D)
37+
node.set_argument_value(&"height_offset", 4)
38+
var generated_data: Dictionary = node._get_data(&"data", AREA, null)
39+
assert_dict(generated_data)\
40+
.override_failure_message("Empty result from [b]GaeaNodeToHeight3D[/b].")\
41+
.is_not_empty()
42+
assert_int(generated_data.hash())\
43+
.override_failure_message("Unexpected result from [b]GaeaNodeToHeight3D[/b].")\
44+
.append_failure_message("Generated: %s\nExpected: %s" % [generated_data.hash(), EXPECTED_HASH_3D])\
45+
.is_equal(EXPECTED_HASH_3D)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://ds4py82j4mpkj

0 commit comments

Comments
 (0)