- Implement test for popup anchor behavior in rule-builder menus to ensure consistent anchor positioning during menu transitions. - Create tests for stage logic, including mode transitions, toolbar visibility, and status bar updates. - Add terrain drag-painting tests to verify correct block placement behavior and conflict handling. - Introduce walk waypoint tests to check for arrival conditions and position stability after navigation.
113 lines
3.3 KiB
GDScript
113 lines
3.3 KiB
GDScript
class_name PropThumbnail
|
|
extends Node
|
|
|
|
const PROP_BLOCK := preload("res://scripts/prop_block.gd")
|
|
|
|
const SIZE := Vector2i(200, 200)
|
|
|
|
var _viewport: SubViewport
|
|
var _world: Node2D
|
|
var _camera: Camera2D
|
|
|
|
|
|
func _ready() -> void:
|
|
_viewport = SubViewport.new()
|
|
_viewport.size = SIZE
|
|
_viewport.transparent_bg = true
|
|
_viewport.render_target_update_mode = SubViewport.UPDATE_ALWAYS
|
|
add_child(_viewport)
|
|
_world = Node2D.new()
|
|
_viewport.add_child(_world)
|
|
_camera = Camera2D.new()
|
|
_camera.enabled = true
|
|
_viewport.add_child(_camera)
|
|
_camera.make_current()
|
|
|
|
|
|
func render(payload: Dictionary, material_preset: int) -> Texture2D:
|
|
_clear_world()
|
|
var points := _compute_points(payload)
|
|
var fill := Color(payload.get("fill_color", Color.WHITE))
|
|
var outline := Color(payload.get("outline_color", Color.BLACK))
|
|
if material_preset != PROP_BLOCK.MaterialPreset.NONE:
|
|
var tint := PROP_BLOCK.tint_for(material_preset)
|
|
fill = tint
|
|
outline = tint.darkened(0.55)
|
|
var outline_width := float(payload.get("outline_width", 2.0))
|
|
var visual := Node2D.new()
|
|
var poly := Polygon2D.new()
|
|
poly.polygon = points
|
|
poly.color = fill
|
|
var line := Line2D.new()
|
|
var loop := points.duplicate()
|
|
if not loop.is_empty():
|
|
loop.append(points[0])
|
|
line.points = loop
|
|
line.default_color = outline
|
|
line.width = outline_width
|
|
line.joint_mode = Line2D.LINE_JOINT_ROUND
|
|
line.begin_cap_mode = Line2D.LINE_CAP_ROUND
|
|
line.end_cap_mode = Line2D.LINE_CAP_ROUND
|
|
visual.add_child(poly)
|
|
visual.add_child(line)
|
|
_world.add_child(visual)
|
|
_frame_camera(_points_bbox(points))
|
|
await RenderingServer.frame_post_draw
|
|
await RenderingServer.frame_post_draw
|
|
var tex := _viewport.get_texture()
|
|
visual.queue_free()
|
|
if tex == null:
|
|
return null
|
|
var img := tex.get_image()
|
|
if _is_blank(img):
|
|
return null
|
|
return ImageTexture.create_from_image(img)
|
|
|
|
|
|
func _compute_points(payload: Dictionary) -> PackedVector2Array:
|
|
var shape_type: int = int(payload.get("type", PROP_BLOCK.ShapeType.POLYGON))
|
|
if shape_type == PROP_BLOCK.ShapeType.CIRCLE:
|
|
var radius := float(payload.get("radius", 24.0))
|
|
var loop := PackedVector2Array()
|
|
for i: int in PROP_BLOCK.CIRCLE_SEGMENTS:
|
|
var angle: float = TAU * float(i) / float(PROP_BLOCK.CIRCLE_SEGMENTS)
|
|
loop.append(Vector2(cos(angle), sin(angle)) * radius)
|
|
return loop
|
|
return payload.get("points", PackedVector2Array())
|
|
|
|
|
|
func _points_bbox(points: PackedVector2Array) -> Rect2:
|
|
if points.is_empty():
|
|
return Rect2(Vector2(-24, -24), Vector2(48, 48))
|
|
var rect := Rect2(points[0], Vector2.ZERO)
|
|
for p: Vector2 in points:
|
|
rect = rect.expand(p)
|
|
if rect.size.x < 1.0 or rect.size.y < 1.0:
|
|
return Rect2(Vector2(-24, -24), Vector2(48, 48))
|
|
return rect
|
|
|
|
|
|
func _clear_world() -> void:
|
|
for child: Node in _world.get_children():
|
|
child.queue_free()
|
|
|
|
|
|
func _frame_camera(bbox: Rect2) -> void:
|
|
var margin := 12.0
|
|
var fit := minf((SIZE.x - margin * 2.0) / bbox.size.x, (SIZE.y - margin * 2.0) / bbox.size.y)
|
|
_camera.position = bbox.get_center()
|
|
_camera.zoom = Vector2(maxf(fit, 0.05), maxf(fit, 0.05))
|
|
|
|
|
|
func _is_blank(img: Image) -> bool:
|
|
if img == null or img.is_empty():
|
|
return true
|
|
var used := img.get_used_rect()
|
|
if used.size.x <= 0 or used.size.y <= 0:
|
|
return true
|
|
for y: int in range(used.position.y, used.end.y):
|
|
for x: int in range(used.position.x, used.end.x):
|
|
if img.get_pixel(x, y).a > 0.0:
|
|
return false
|
|
return true
|