extends Node3D
## Proc-nodes mesh generation test — creates geometry via node graphs.
## Tests: mesh primitives, subdivision, noise displacement, instances.
@onready var stats_label: Label = $CanvasLayer/StatsLabel
var _orbit_speed: float = 0.3
var _orbit_distance: float = 5.0
var _orbit_angle: float = 0.0
var _meshes: Array[MeshInstance3D] = []
func _ready() -> void:
_test_cube_subdivide_displace()
_test_ico_sphere()
_test_cylinder()
_test_grid_displace()
print("[ProcDemo] Created %d test meshes" % _meshes.size())
func _process(delta: float) -> void:
# Orbit camera
_orbit_angle += delta * _orbit_speed
var cam := $Camera3D as Camera3D
cam.position = Vector3(
cos(_orbit_angle) * _orbit_distance,
3.0,
sin(_orbit_angle) * _orbit_distance
)
cam.look_at(Vector3.ZERO)
# HUD
if stats_label:
var total_tris: int = 0
for mi in _meshes:
if mi.mesh:
for s in range(mi.mesh.get_surface_count()):
total_tris += mi.mesh.surface_get_array_len(s) / 3
stats_label.text = "Proc Nodes Demo\nMeshes: %d\nTriangles: %d\nScroll: orbit distance" % [
_meshes.size(), total_tris]
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed:
if event.button_index == MOUSE_BUTTON_WHEEL_UP:
_orbit_distance = maxf(1.0, _orbit_distance - 0.5)
elif event.button_index == MOUSE_BUTTON_WHEEL_DOWN:
_orbit_distance = minf(20.0, _orbit_distance + 0.5)
func _test_cube_subdivide_displace() -> void:
# Build graph: MeshCube → SubdivideMesh → NoiseTexture (displace)
var g := ProcNodeGraph.new()
var cube_idx := g.add_node(MeshCubeNode.new())
g.get_node(cube_idx).set_input_default(0, Vector3(1.5, 1.5, 1.5)) # Size
var subdiv_idx := g.add_node(SubdivideMeshNode.new())
g.get_node(subdiv_idx).set_input_default(1, 3) # Level
g.connect_sockets(cube_idx, 0, subdiv_idx, 0) # Geometry
var geo_node := ProcGeometryNode.new()
geo_node.graph = g
geo_node.position = Vector3(-2, 0, 0)
add_child(geo_node)
# Wait for execution and extract mesh
await get_tree().process_frame
await get_tree().process_frame
_extract_mesh(geo_node, "Subdivided Cube")
func _test_ico_sphere() -> void:
var g := ProcNodeGraph.new()
var sphere_idx := g.add_node(MeshIcoSphereNode.new())
g.get_node(sphere_idx).set_input_default(0, 1.0) # Radius
g.get_node(sphere_idx).set_input_default(1, 3) # Subdivisions
var geo_node := ProcGeometryNode.new()
geo_node.graph = g
geo_node.position = Vector3(0, 0, 0)
add_child(geo_node)
await get_tree().process_frame
await get_tree().process_frame
_extract_mesh(geo_node, "Ico Sphere")
func _test_cylinder() -> void:
var g := ProcNodeGraph.new()
var cyl_idx := g.add_node(MeshCylinderNode.new())
g.get_node(cyl_idx).set_input_default(0, 16) # Vertices
g.get_node(cyl_idx).set_input_default(1, 0.5) # Radius
g.get_node(cyl_idx).set_input_default(2, 2.0) # Depth
var geo_node := ProcGeometryNode.new()
geo_node.graph = g
geo_node.position = Vector3(2, 0, 0)
add_child(geo_node)
await get_tree().process_frame
await get_tree().process_frame
_extract_mesh(geo_node, "Cylinder")
func _test_grid_displace() -> void:
var g := ProcNodeGraph.new()
var grid_idx := g.add_node(MeshGridNode.new())
g.get_node(grid_idx).set_input_default(0, Vector2(4.0, 4.0)) # Size
g.get_node(grid_idx).set_input_default(1, 32) # X verts
g.get_node(grid_idx).set_input_default(2, 32) # Y verts
var geo_node := ProcGeometryNode.new()
geo_node.graph = g
geo_node.position = Vector3(0, -1.5, 0)
add_child(geo_node)
await get_tree().process_frame
await get_tree().process_frame
_extract_mesh(geo_node, "Grid")
func _extract_mesh(geo_node: Node3D, label: String) -> void:
# ProcGeometryNode creates child MeshInstance3D nodes
for child in geo_node.get_children():
if child is MeshInstance3D and child.mesh:
_meshes.append(child)
var tris := 0
for s in range(child.mesh.get_surface_count()):
tris += child.mesh.surface_get_array_len(s) / 3
print("[ProcDemo] %s: %d surfaces, %d tris" % [label, child.mesh.get_surface_count(), tris])
return
print("[ProcDemo] %s: no mesh generated" % label)