108 lines
4.2 KiB
GDScript
108 lines
4.2 KiB
GDScript
extends SceneTree
|
|
|
|
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
|
|
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
|
|
|
|
var _checks: int = 0
|
|
var _failures: int = 0
|
|
|
|
|
|
func _initialize() -> void:
|
|
call_deferred("_run")
|
|
|
|
|
|
func _run() -> void:
|
|
var world := Node2D.new()
|
|
root.add_child(world)
|
|
var camera := Camera2D.new()
|
|
root.add_child(camera)
|
|
var selection: StageSelection = STAGE_SELECTION.new(world, camera) as StageSelection
|
|
var triangle: PropBlock = _make_prop("Triangle")
|
|
world.add_child(triangle)
|
|
var triangle_points := PackedVector2Array([Vector2(-50.0, -50.0), Vector2(50.0, -50.0), Vector2(0.0, 50.0)])
|
|
triangle.set_custom_shapes([{
|
|
"shape_type": "polygon",
|
|
"points": triangle_points,
|
|
"closed": true,
|
|
"fill_color": "#55AAFFFF",
|
|
"outline_color": "#111111FF",
|
|
"outline_width": 2.0,
|
|
}], triangle_points)
|
|
await process_frame
|
|
_check(selection.hit_test_all(Vector2.ZERO).has(triangle), "visible polygon interior is selectable")
|
|
_check(not selection.hit_test_all(Vector2(40.0, 40.0)).has(triangle), "empty point inside polygon AABB is rejected")
|
|
|
|
var front: PropBlock = _make_prop("Front")
|
|
front.position = Vector2.ZERO
|
|
front.z_index = 2
|
|
world.add_child(front)
|
|
front.set_custom_shapes([{
|
|
"shape_type": "polygon",
|
|
"points": triangle_points,
|
|
"closed": true,
|
|
"fill_color": "#FFAA55FF",
|
|
"outline_color": "#111111FF",
|
|
"outline_width": 2.0,
|
|
}], triangle_points)
|
|
await process_frame
|
|
var overlap_hits: Array[Node2D] = selection.hit_test_all(Vector2.ZERO)
|
|
_check(overlap_hits.size() == 2 and overlap_hits[0] == front and overlap_hits[1] == triangle, "overlap candidates preserve front-to-back draw order")
|
|
_check(not selection.hit_test_all(Vector2(40.0, 40.0)).has(front), "nearby overlapping prop is excluded outside its visible shape")
|
|
|
|
triangle.position = Vector2(200.0, 0.0)
|
|
triangle.rotation = PI * 0.5
|
|
triangle.scale = Vector2(2.0, 1.0)
|
|
await process_frame
|
|
var transformed_center: Vector2 = triangle.to_global(Vector2.ZERO)
|
|
var transformed_empty: Vector2 = triangle.to_global(Vector2(40.0, 40.0))
|
|
_check(selection.hit_test_all(transformed_center).has(triangle), "polygon hit testing follows object translation, rotation, and scale")
|
|
_check(not selection.hit_test_all(transformed_empty).has(triangle), "transformed empty polygon area remains unselectable")
|
|
|
|
var line_prop: PropBlock = PROP_BLOCK.new() as PropBlock
|
|
line_prop.name = "Thin Line"
|
|
line_prop.position = Vector2(400.0, 100.0)
|
|
world.add_child(line_prop)
|
|
var line_points := PackedVector2Array([Vector2(-20.0, 0.0), Vector2(20.0, 0.0)])
|
|
var line_shape: Array[Dictionary] = [{
|
|
"shape_type": "line",
|
|
"points": line_points,
|
|
"closed": false,
|
|
"fill_color": "#00000000",
|
|
"outline_color": "#FFFFFFFF",
|
|
"outline_width": 2.0,
|
|
}]
|
|
line_prop.set_custom_shapes(line_shape, PackedVector2Array())
|
|
await process_frame
|
|
_check(selection.hit_test_all(Vector2(400.0, 105.0)).has(line_prop), "thin visible line remains selectable within the screen-space touch tolerance")
|
|
_check(not selection.hit_test_all(Vector2(400.0, 109.0)).has(line_prop), "thin line rejects points beyond the touch tolerance")
|
|
camera.zoom = Vector2(2.0, 2.0)
|
|
_check(selection.hit_test_all(Vector2(400.0, 104.0)).has(line_prop), "line touch tolerance scales with camera zoom")
|
|
_check(not selection.hit_test_all(Vector2(400.0, 105.0)).has(line_prop), "zoomed line does not retain an oversized world-space tolerance")
|
|
camera.zoom = Vector2.ONE
|
|
line_prop.modulate.a = 0.0
|
|
_check(not selection.hit_test_all(Vector2(400.0, 100.0)).has(line_prop), "fully transparent line is not selectable")
|
|
line_prop.modulate.a = 1.0
|
|
line_prop.visible = false
|
|
_check(not selection.hit_test_all(Vector2(400.0, 100.0)).has(line_prop), "hidden line is not selectable")
|
|
|
|
print("PHASE 20A HIT TESTING: %d checks, %d failures" % [_checks, _failures])
|
|
world.queue_free()
|
|
camera.queue_free()
|
|
await process_frame
|
|
quit(1 if _failures > 0 else 0)
|
|
|
|
|
|
func _make_prop(prop_name: String) -> PropBlock:
|
|
var prop := PROP_BLOCK.new() as PropBlock
|
|
prop.name = prop_name
|
|
return prop
|
|
|
|
|
|
func _check(condition: bool, message: String) -> void:
|
|
_checks += 1
|
|
if condition:
|
|
print("PASS: " + message)
|
|
else:
|
|
_failures += 1
|
|
push_error("FAIL: " + message)
|