File size: 5,509 Bytes
84e8204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
extends Node
##
## SelfTest - headless verification of every core subsystem.
## Run:  godot --headless res://tests/self_test.tscn
##
## Exits with code 0 if all checks pass, 1 otherwise. This is the regression
## gate for the modules; it does not need a display or GPU.
##

var _failures := 0
var _host: Node3D


func _ready() -> void:
	_host = Node3D.new()
	_host.name = "TestHost"
	var geom := Node3D.new()
	geom.name = "Geometry"
	_host.add_child(geom)
	var rig := Node3D.new()
	rig.name = "Rig"
	_host.add_child(rig)
	add_child(_host)

	_test_memory_manager()
	_test_mesh_generators()
	_test_sculpt()
	_test_rigging()
	_test_simulation()
	_test_timeline()

	print("")
	if _failures == 0:
		print("SELFTEST: ALL PASS")
		get_tree().quit(0)
	else:
		print("SELFTEST: %d FAILURE(S)" % _failures)
		get_tree().quit(1)


func _check(name: String, cond: bool) -> void:
	if cond:
		print("  [PASS] ", name)
	else:
		_failures += 1
		print("  [FAIL] ", name)


func _test_memory_manager() -> void:
	print("[Test] MemoryManager")
	_check("budget set", MemoryManager.asset_budget_mb > 0.0)
	_check("feature count >= 20", FeatureRegistry.feature_count() >= 20)
	var snap := MemoryManager.debug_snapshot()
	_check("snapshot has budget", snap.has("budget_mb"))


func _load_module(path: String) -> Node:
	var script: GDScript = load(path)
	var n: Node = script.new()
	_host.add_child(n)
	if n.has_method("_activate"):
		n.call("_activate", _host)
	return n


func _test_mesh_generators() -> void:
	print("[Test] Mesh generators")
	var mg := _load_module("res://scripts/modules/mesh_gen.gd")
	for kind in ["cube", "sphere", "cylinder", "plane", "torus", "ico", "suzanne"]:
		var data: Dictionary = mg.call("generate", kind, {})
		var vcount: int = (data["verts"] as PackedVector3Array).size()
		var icount: int = (data["indices"] as PackedInt32Array).size()
		_check("%s has verts & tris (v=%d,i=%d)" % [kind, vcount, icount], vcount > 0 and icount > 0)
	var mi: MeshInstance3D = mg.call("spawn", "sphere", {"radius": 1.0})
	_check("spawn returns MeshInstance3D", mi != null and mi.mesh != null and mi.mesh.get_surface_count() == 1)
	var cost := VertexBufferManager.estimate_cost_mb(1000)
	_check("cost estimate > 0", cost > 0.0)
	mg.queue_free()


func _test_sculpt() -> void:
	print("[Test] Sculpting")
	var mg := _load_module("res://scripts/modules/mesh_gen.gd")
	var sc := _load_module("res://scripts/modules/sculpt.gd")
	var mi: MeshInstance3D = mg.call("spawn", "sphere", {"radius": 1.0})
	sc.call("set_target", mi)
	var before: PackedVector3Array = (mi.mesh.surface_get_arrays(0)[Mesh.ARRAY_VERTEX] as PackedVector3Array).duplicate()
	for brush in ["draw", "grab", "pinch", "smooth", "flatten"]:
		sc.set("_brush_id", brush)
		sc.call("begin", Vector3(0, 1, 0))
		sc.call("apply", Vector3(0, 1, 0), Vector3(0, 0.1, 0))
		sc.call("apply", Vector3(0, 1, 0), Vector3(0.05, 0.1, 0))
		sc.call("end")
	var after: PackedVector3Array = mi.mesh.surface_get_arrays(0)[Mesh.ARRAY_VERTEX]
	var changed := false
	for i in range(mini(before.size(), after.size())):
		if before[i].distance_to(after[i]) > 0.0001:
			changed = true
			break
	_check("sculpt modified vertices", changed)
	_check("mesh still valid after sculpt", mi.mesh.get_surface_count() == 1)
	mg.queue_free()
	sc.queue_free()


func _test_rigging() -> void:
	print("[Test] Rigging")
	var mg := _load_module("res://scripts/modules/mesh_gen.gd")
	var rg := _load_module("res://scripts/modules/rigging.gd")
	var mi: MeshInstance3D = mg.call("spawn", "cube", {"size": 1.0})
	rg.call("generate_default_armature", mi)
	_check("bones created", int(rg.call("bone_count")) >= 9)
	_check("mesh has skin", mi.skin != null)
	_check("mesh has skeleton path", mi.skeleton != NodePath())
	if mi.mesh.get_surface_count() > 0:
		var arr: Array = mi.mesh.surface_get_arrays(0)
		_check("bones channel present", arr[Mesh.ARRAY_BONES] != null)
		_check("weights channel present", arr[Mesh.ARRAY_WEIGHTS] != null)
	mg.queue_free()
	rg.queue_free()


func _test_simulation() -> void:
	print("[Test] Simulation")
	var sim := _load_module("res://scripts/modules/simulation.gd")
	sim.call("create_cloth", "sim.cloth", 6, 6, 1.0)
	var before: PackedVector3Array = (sim.get("_sims")["sim.cloth"]["particles"] as PackedVector3Array).duplicate()
	for i in range(30):
		sim.call("_step", sim.get("_sims")["sim.cloth"])
	sim.call("_upload", sim.get("_sims")["sim.cloth"])
	var after: PackedVector3Array = sim.get("_sims")["sim.cloth"]["particles"]
	var moved := false
	for i in range(after.size()):
		if before[i].distance_to(after[i]) > 0.0001:
			moved = true
			break
	_check("cloth fell under gravity", moved)
	var mesh: ArrayMesh = sim.get("_sims")["sim.cloth"]["mesh"]
	_check("cloth mesh uploaded", mesh.get_surface_count() == 1)
	sim.call("create_hair", "sim.hair", 4, 5)
	_check("hair sim created", int(sim.call("active_sim_count")) >= 2)
	sim.queue_free()


func _test_timeline() -> void:
	print("[Test] Timeline")
	var mg := _load_module("res://scripts/modules/mesh_gen.gd")
	var tl := _load_module("res://scripts/modules/timeline.gd")
	var mi: MeshInstance3D = mg.call("spawn", "cube", {"size": 1.0})
	tl.call("add_keyframe", mi, "position", 0.0, Vector3.ZERO)
	tl.call("add_keyframe", mi, "position", 1.0, Vector3(0, 2, 0))
	_check("track created", int(tl.call("track_count")) >= 1)
	_check("key count == 2", int(tl.call("key_count")) == 2)
	tl.call("scrub", 0.5)
	_check("scrub clamps/works", absf(float(tl.call("playhead")) - 0.5) < 0.001)
	mg.queue_free()
	tl.queue_free()