Add Phase 19 Starter Scene Composition UI/UX Specification and Tests
- Introduced a new UI/UX specification document for the Phase 19 Starter Scene Composition, detailing user paths, composition rules for Town Street and Living Room, camera framing, and observable acceptance outcomes. - Implemented a visual capture test for different sky states in the sandbox stage, ensuring proper rendering and visual consistency. - Created a comprehensive test suite for the Phase 19 starter scene composition, validating scene loading, geometry, navigation, and interaction behaviors for both Town Street and Living Room. - Added checks for visual alignment, object placements, and scene transitions to ensure adherence to the new UI/UX specifications.
This commit is contained in:
@@ -5,27 +5,54 @@ extends Control
|
||||
const SKY_IDS: Array[StringName] = [&"sky_clear_day", &"sky_cloudy_day", &"sky_sunset", &"sky_starry_night", &"sky_neutral"]
|
||||
const GRADIENT_TEXTURE_HEIGHT: int = 2048
|
||||
const CLOUD_VIEWBOX: Vector2 = Vector2(210.0, 110.0)
|
||||
const BASE_VIEWPORT_SIZE: Vector2 = Vector2(800.0, 480.0)
|
||||
const REF_POSITION: Vector2 = Vector2(0.0, -400.0)
|
||||
const REF_ZOOM: float = 0.5
|
||||
const NIGHT_STAR_COLOR: Color = Color("#FFF4CE")
|
||||
const CAMERA_RESPONSE_PROFILES: Dictionary = {
|
||||
&"sky_clear_day": {"pan_limits": Vector2(0.025, 0.015), "pan_spans": Vector2(0.8, 0.8), "zoom_gain": 0.10, "zoom_span": 1.2},
|
||||
&"sky_cloudy_day": {"pan_limits": Vector2(0.020, 0.012), "pan_spans": Vector2(0.85, 0.85), "zoom_gain": 0.075, "zoom_span": 1.2},
|
||||
&"sky_sunset": {"pan_limits": Vector2(0.030, 0.018), "pan_spans": Vector2(0.9, 0.9), "zoom_gain": 0.11, "zoom_span": 1.2},
|
||||
&"sky_starry_night": {"pan_limits": Vector2(0.035, 0.025), "pan_spans": Vector2(1.0, 1.0), "zoom_gain": 0.12, "zoom_span": 1.2},
|
||||
&"sky_neutral": {"pan_limits": Vector2.ZERO, "pan_spans": Vector2.ONE, "zoom_gain": 0.0, "zoom_span": 1.0},
|
||||
}
|
||||
|
||||
var _sky_id: StringName = &"sky_neutral"
|
||||
var _stage_theme: SandboxTheme
|
||||
var _gradient_texture: GradientTexture2D
|
||||
var _bound_camera: Camera2D
|
||||
var _motif_shift: Vector2 = Vector2.ZERO
|
||||
var _motif_scale: float = 1.0
|
||||
var _cached_cloud_outline: PackedVector2Array = PackedVector2Array()
|
||||
var _cached_cloud_underside: PackedVector2Array = PackedVector2Array()
|
||||
var _cached_cloud_accents: Array[PackedVector2Array] = []
|
||||
var _cached_moon_outline: PackedVector2Array = PackedVector2Array()
|
||||
var _cached_moon_outer_arc: PackedVector2Array = PackedVector2Array()
|
||||
var _cached_moon_warm_rim: PackedVector2Array = PackedVector2Array()
|
||||
var _cached_moon_highlight: PackedVector2Array = PackedVector2Array()
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE
|
||||
texture_filter = CanvasItem.TEXTURE_FILTER_LINEAR
|
||||
_cache_motif_geometry()
|
||||
_update_gradient_texture()
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func bind_camera(camera: Camera2D) -> void:
|
||||
_bound_camera = camera
|
||||
set_process(camera != null)
|
||||
_refresh_camera_response(true)
|
||||
|
||||
|
||||
func set_sky(id: StringName) -> void:
|
||||
var next_sky_id: StringName = id if SKY_IDS.has(id) else &"sky_neutral"
|
||||
if _sky_id == next_sky_id:
|
||||
return
|
||||
var changed: bool = _sky_id != next_sky_id
|
||||
_sky_id = next_sky_id
|
||||
_update_gradient_texture()
|
||||
queue_redraw()
|
||||
if changed:
|
||||
_update_gradient_texture()
|
||||
_refresh_camera_response(true)
|
||||
|
||||
|
||||
func get_sky() -> StringName:
|
||||
@@ -39,9 +66,73 @@ func apply_theme(theme: SandboxTheme) -> void:
|
||||
|
||||
func _notification(what: int) -> void:
|
||||
if what == NOTIFICATION_RESIZED:
|
||||
_refresh_camera_response(true)
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
_refresh_camera_response(false)
|
||||
|
||||
|
||||
static func compute_camera_response(sky_id: StringName, camera_position: Vector2, camera_zoom: float, viewport_size: Vector2) -> Dictionary:
|
||||
var fallback: Dictionary = {"shift": Vector2.ZERO, "scale": 1.0}
|
||||
if not camera_position.is_finite() or not viewport_size.is_finite() or viewport_size.x <= 0.0 or viewport_size.y <= 0.0:
|
||||
return fallback
|
||||
if not is_finite(camera_zoom) or camera_zoom <= 0.0:
|
||||
return fallback
|
||||
var profile_value: Variant = CAMERA_RESPONSE_PROFILES.get(sky_id, CAMERA_RESPONSE_PROFILES[&"sky_neutral"])
|
||||
var profile: Dictionary = profile_value as Dictionary
|
||||
var pan_limits: Vector2 = profile.get("pan_limits", Vector2.ZERO)
|
||||
var pan_spans: Vector2 = profile.get("pan_spans", Vector2.ONE)
|
||||
var zoom_gain: float = float(profile.get("zoom_gain", 0.0))
|
||||
var zoom_span: float = maxf(float(profile.get("zoom_span", 1.0)), 0.001)
|
||||
var reference_frame_delta: Vector2 = (camera_position - REF_POSITION) * REF_ZOOM
|
||||
var normalized_pan: Vector2 = reference_frame_delta / viewport_size
|
||||
var shift: Vector2 = Vector2(
|
||||
-viewport_size.x * pan_limits.x * tanh(normalized_pan.x / maxf(pan_spans.x, 0.001)),
|
||||
-viewport_size.y * pan_limits.y * tanh(normalized_pan.y / maxf(pan_spans.y, 0.001))
|
||||
)
|
||||
var log_zoom: float = log(camera_zoom / REF_ZOOM)
|
||||
var motif_scale: float = exp(zoom_gain * tanh(log_zoom / zoom_span))
|
||||
return {"shift": shift, "scale": motif_scale}
|
||||
|
||||
|
||||
static func star_anchor_for_index(index: int) -> Vector2:
|
||||
return Vector2(
|
||||
fposmod(float(index) * 0.61803398875 + 0.13, 1.0),
|
||||
fposmod(float(index) * 0.41421356237 + 0.08, 0.62)
|
||||
)
|
||||
|
||||
|
||||
func _refresh_camera_response(force_redraw: bool) -> void:
|
||||
var next_shift: Vector2 = Vector2.ZERO
|
||||
var next_scale: float = 1.0
|
||||
if _bound_camera != null and is_instance_valid(_bound_camera):
|
||||
var camera_position: Vector2 = _bound_camera.global_position
|
||||
var camera_zoom: Vector2 = _bound_camera.zoom
|
||||
if camera_position.is_finite() and camera_zoom.is_finite() and camera_zoom.x > 0.0 and camera_zoom.y > 0.0 and is_equal_approx(camera_zoom.x, camera_zoom.y):
|
||||
var response: Dictionary = compute_camera_response(_sky_id, camera_position, camera_zoom.x, size)
|
||||
next_shift = response.get("shift", Vector2.ZERO)
|
||||
next_scale = float(response.get("scale", 1.0))
|
||||
var changed: bool = not _motif_shift.is_equal_approx(next_shift) or not is_equal_approx(_motif_scale, next_scale)
|
||||
_motif_shift = next_shift
|
||||
_motif_scale = next_scale
|
||||
if force_redraw or changed:
|
||||
queue_redraw()
|
||||
|
||||
|
||||
func _cache_motif_geometry() -> void:
|
||||
_cached_cloud_outline = _build_cloud_outline()
|
||||
_cached_cloud_underside = _build_cloud_underside()
|
||||
_cached_cloud_accents.clear()
|
||||
for accent_curve: PackedVector2Array in _build_cloud_accents():
|
||||
_cached_cloud_accents.append(_sample_curve_chain([accent_curve], 12))
|
||||
_cached_moon_outline = _sample_curve_chain(_build_moon_curves(), 16)
|
||||
_cached_moon_outer_arc = _sample_curve_chain(_build_moon_outer_arc_curves(), 16)
|
||||
_cached_moon_warm_rim = _build_moon_warm_rim(_cached_moon_outer_arc, 7.5)
|
||||
_cached_moon_highlight = _sample_curve_chain(_build_moon_highlight(), 18)
|
||||
|
||||
|
||||
func _draw() -> void:
|
||||
if size.x <= 0.0 or size.y <= 0.0:
|
||||
return
|
||||
@@ -90,8 +181,9 @@ func _update_gradient_texture() -> void:
|
||||
|
||||
|
||||
func _draw_sun() -> void:
|
||||
var center: Vector2 = Vector2(size.x * 0.78125, size.y * (0.241 if _sky_id == &"sky_sunset" else 0.242))
|
||||
var radius: float = size.y * (0.0979 if _sky_id == &"sky_sunset" else 0.0792)
|
||||
var center_anchor: Vector2 = Vector2(0.72, 0.241 if _sky_id == &"sky_sunset" else 0.242)
|
||||
var center: Vector2 = _motif_anchor(center_anchor)
|
||||
var radius: float = _base_motif_scale() * _motif_scale * (47.0 if _sky_id == &"sky_sunset" else 38.0)
|
||||
var halo_color: Color = Color("#FFE1A2") if _sky_id == &"sky_sunset" else Color("#FFF1B3")
|
||||
var sun_color: Color = Color("#FFE296") if _sky_id == &"sky_sunset" else Color("#FFE990")
|
||||
draw_circle(center, radius * 1.5, Color(halo_color.r, halo_color.g, halo_color.b, 0.14))
|
||||
@@ -101,87 +193,87 @@ func _draw_sun() -> void:
|
||||
|
||||
func _draw_stars() -> void:
|
||||
for index: int in 48:
|
||||
var x: float = fposmod(float(index * 137 + 39), size.x)
|
||||
var y: float = fposmod(float(index * 83 + 21), size.y * 0.62)
|
||||
draw_circle(Vector2(x, y), 1.3 if index % 4 != 0 else 2.0, NIGHT_STAR_COLOR)
|
||||
var anchor: Vector2 = star_anchor_for_index(index)
|
||||
var center: Vector2 = _motif_anchor(anchor)
|
||||
var radius: float = _base_motif_scale() * _motif_scale * (1.3 if index % 4 != 0 else 2.0)
|
||||
draw_circle(center, radius, NIGHT_STAR_COLOR)
|
||||
for star_index: int in 3:
|
||||
var center: Vector2 = Vector2(size.x * [0.3675, 0.9425, 0.22125][star_index], size.y * [0.085, 0.229, 0.348][star_index])
|
||||
var radius: float = maxf(1.3, size.y / 480.0 * 7.0)
|
||||
var anchors: Array[Vector2] = [Vector2(0.3675, 0.085), Vector2(0.9425, 0.229), Vector2(0.22125, 0.348)]
|
||||
var center: Vector2 = _motif_anchor(anchors[star_index])
|
||||
var radius: float = _base_motif_scale() * _motif_scale * 7.0
|
||||
draw_line(center - Vector2(0.0, radius), center + Vector2(0.0, radius), Color("#FFEABB"), maxf(1.0, radius * 0.22), true)
|
||||
draw_line(center - Vector2(radius, 0.0), center + Vector2(radius, 0.0), Color("#FFEABB"), maxf(1.0, radius * 0.22), true)
|
||||
|
||||
|
||||
func _draw_clouds() -> void:
|
||||
var origins: Array[Vector4] = []
|
||||
var origins: Array[Vector3] = []
|
||||
var opacities: Array[float] = []
|
||||
var outer_color: Color
|
||||
var underside_color: Color
|
||||
var accent_color: Color
|
||||
match _sky_id:
|
||||
&"sky_clear_day":
|
||||
origins = [Vector4(42.0, 66.0, 205.0, 108.0), Vector4(282.0, 167.0, 244.0, 127.0), Vector4(612.0, 288.0, 187.0, 98.0)]
|
||||
origins = [Vector3(0.18, 0.17, 200.0), Vector3(0.51, 0.33, 220.0), Vector3(0.88, 0.48, 174.0)]
|
||||
opacities = [0.93, 0.88, 0.8]
|
||||
outer_color = Color("#FFFFFF")
|
||||
underside_color = Color("#CBE6FF", 0.72)
|
||||
accent_color = Color("#D9EEFF", 0.86)
|
||||
&"sky_cloudy_day":
|
||||
origins = [Vector4(-16.0, 47.0, 246.0, 129.0), Vector4(188.0, 145.0, 242.0, 127.0), Vector4(508.0, 64.0, 281.0, 147.0), Vector4(74.0, 302.0, 190.0, 100.0), Vector4(535.0, 324.0, 231.0, 121.0)]
|
||||
origins = [Vector3(0.14, 0.17, 205.0), Vector3(0.48, 0.28, 208.0), Vector3(0.83, 0.17, 226.0), Vector3(0.28, 0.45, 170.0), Vector3(0.78, 0.47, 194.0)]
|
||||
opacities = [1.0, 0.93, 0.95, 0.7, 0.69]
|
||||
outer_color = Color("#F8FCFF")
|
||||
underside_color = Color("#BED7EF", 0.9)
|
||||
accent_color = Color("#D0E5F7", 1.0)
|
||||
&"sky_sunset":
|
||||
origins = [Vector4(24.0, 82.0, 235.0, 123.0), Vector4(294.0, 244.0, 258.0, 135.0), Vector4(585.0, 310.0, 218.0, 114.0)]
|
||||
origins = [Vector3(0.18, 0.20, 210.0), Vector3(0.55, 0.37, 226.0), Vector3(0.88, 0.50, 192.0)]
|
||||
opacities = [0.85, 0.9, 0.81]
|
||||
outer_color = Color("#FFE3D1")
|
||||
underside_color = Color("#E9A8BA", 0.8)
|
||||
accent_color = Color("#FFF2D7", 0.8)
|
||||
_:
|
||||
origins = [Vector4(-4.0, 331.0, 231.0, 121.0), Vector4(314.0, 380.0, 188.0, 98.0)]
|
||||
origins = [Vector3(0.16, 0.55, 220.0), Vector3(0.68, 0.62, 178.0)]
|
||||
opacities = [0.28, 0.23]
|
||||
outer_color = Color("#A9BCE1")
|
||||
underside_color = Color("#7898C5", 0.8)
|
||||
accent_color = Color("#D7E3F7", 0.45)
|
||||
var viewport_scale: Vector2 = Vector2(size.x / 800.0, size.y / 480.0)
|
||||
for cloud_index: int in origins.size():
|
||||
var placement: Vector4 = origins[cloud_index]
|
||||
var cloud_rect: Rect2 = Rect2(
|
||||
Vector2(placement.x, placement.y) * viewport_scale,
|
||||
Vector2(placement.z, placement.w) * viewport_scale
|
||||
)
|
||||
_draw_cloud(cloud_rect, outer_color, underside_color, accent_color, opacities[cloud_index])
|
||||
var placement: Vector3 = origins[cloud_index]
|
||||
_draw_cloud(Vector2(placement.x, placement.y), placement.z, outer_color, underside_color, accent_color, opacities[cloud_index])
|
||||
|
||||
|
||||
func _draw_cloud(rect: Rect2, outer_color: Color, underside_color: Color, accent_color: Color, opacity: float) -> void:
|
||||
var outline: PackedVector2Array = _map_cloud_points(_build_cloud_outline(), rect)
|
||||
func _draw_cloud(anchor: Vector2, authored_width: float, outer_color: Color, underside_color: Color, accent_color: Color, opacity: float) -> void:
|
||||
var cloud_scale: float = _base_motif_scale() * _motif_scale * authored_width / CLOUD_VIEWBOX.x
|
||||
var center: Vector2 = _motif_anchor(anchor)
|
||||
var outline: PackedVector2Array = _map_cloud_points(_cached_cloud_outline, center, cloud_scale)
|
||||
var cloud_color: Color = _with_opacity(outer_color, opacity)
|
||||
draw_colored_polygon(outline, cloud_color)
|
||||
draw_polyline(outline, cloud_color, maxf(1.0, rect.size.y / CLOUD_VIEWBOX.y * 1.5), true)
|
||||
var underside: PackedVector2Array = _map_cloud_points(_build_cloud_underside(), rect)
|
||||
draw_polyline(outline, cloud_color, maxf(1.0, cloud_scale * 1.5), true)
|
||||
var underside: PackedVector2Array = _map_cloud_points(_cached_cloud_underside, center, cloud_scale)
|
||||
draw_colored_polygon(underside, _with_opacity(underside_color, opacity))
|
||||
for accent_curve: PackedVector2Array in _build_cloud_accents():
|
||||
var accent_points: PackedVector2Array = _map_cloud_points(_sample_curve_chain([accent_curve], 12), rect)
|
||||
draw_polyline(accent_points, _with_opacity(accent_color, opacity), maxf(1.0, rect.size.y / CLOUD_VIEWBOX.y * 3.0), true)
|
||||
for accent_points_local: PackedVector2Array in _cached_cloud_accents:
|
||||
var accent_points: PackedVector2Array = _map_cloud_points(accent_points_local, center, cloud_scale)
|
||||
draw_polyline(accent_points, _with_opacity(accent_color, opacity), maxf(1.0, cloud_scale * 3.0), true)
|
||||
|
||||
|
||||
func _draw_moon() -> void:
|
||||
var moon_curves: Array[PackedVector2Array] = _build_moon_curves()
|
||||
var moon_local: PackedVector2Array = _sample_curve_chain(moon_curves, 16)
|
||||
var outer_arc: PackedVector2Array = _sample_curve_chain(_build_moon_outer_arc_curves(), 16)
|
||||
var moon_scale: float = size.y / 480.0 * 0.63
|
||||
var moon_origin: Vector2 = Vector2(size.x * 0.7625, size.y * 0.09583)
|
||||
var moon_size: Vector2 = Vector2(150.0, 240.0) * moon_scale
|
||||
var moon_rect: Rect2 = Rect2(moon_origin, moon_size)
|
||||
var moon_outline: PackedVector2Array = _map_moon_points(moon_local, moon_rect)
|
||||
var moon_scale: float = _base_motif_scale() * _motif_scale * 0.63
|
||||
var moon_center: Vector2 = _motif_anchor(Vector2(0.75, 0.25))
|
||||
var moon_outline: PackedVector2Array = _map_moon_points(_cached_moon_outline, moon_center, moon_scale)
|
||||
draw_colored_polygon(moon_outline, Color("#17213A"))
|
||||
draw_colored_polygon(moon_outline, Color("#FFEA31"))
|
||||
var warm_rim: PackedVector2Array = _build_moon_warm_rim(outer_arc, 7.5)
|
||||
draw_colored_polygon(_map_moon_points(warm_rim, moon_rect), Color("#F4A629"))
|
||||
var highlight: PackedVector2Array = _sample_curve_chain(_build_moon_highlight(), 18)
|
||||
draw_polyline(_map_moon_points(highlight, moon_rect), Color(1.0, 0.98, 0.63, 0.62), maxf(1.0, moon_scale * 8.0), true)
|
||||
draw_colored_polygon(_map_moon_points(_cached_moon_warm_rim, moon_center, moon_scale), Color("#F4A629"))
|
||||
draw_polyline(_map_moon_points(_cached_moon_highlight, moon_center, moon_scale), Color(1.0, 0.98, 0.63, 0.62), maxf(1.0, moon_scale * 8.0), true)
|
||||
draw_polyline(moon_outline, Color("#17213A"), maxf(1.0, moon_scale * 5.5), true)
|
||||
|
||||
|
||||
func _base_motif_scale() -> float:
|
||||
return maxf(0.001, minf(size.x / BASE_VIEWPORT_SIZE.x, size.y / BASE_VIEWPORT_SIZE.y))
|
||||
|
||||
|
||||
func _motif_anchor(normalized_anchor: Vector2) -> Vector2:
|
||||
return normalized_anchor * size + _motif_shift
|
||||
|
||||
|
||||
func _build_cloud_outline() -> PackedVector2Array:
|
||||
return _sample_curve_chain(_build_cloud_outer_curves(), 10)
|
||||
|
||||
@@ -284,17 +376,17 @@ func _cubic_point(curve: PackedVector2Array, t: float) -> Vector2:
|
||||
)
|
||||
|
||||
|
||||
func _map_cloud_points(points: PackedVector2Array, rect: Rect2) -> PackedVector2Array:
|
||||
func _map_cloud_points(points: PackedVector2Array, center: Vector2, uniform_scale: float) -> PackedVector2Array:
|
||||
var mapped: PackedVector2Array = PackedVector2Array()
|
||||
for point: Vector2 in points:
|
||||
mapped.append(rect.position + Vector2(point.x / CLOUD_VIEWBOX.x * rect.size.x, point.y / CLOUD_VIEWBOX.y * rect.size.y))
|
||||
mapped.append(center + (point - CLOUD_VIEWBOX * 0.5) * uniform_scale)
|
||||
return mapped
|
||||
|
||||
|
||||
func _map_moon_points(points: PackedVector2Array, rect: Rect2) -> PackedVector2Array:
|
||||
func _map_moon_points(points: PackedVector2Array, center: Vector2, uniform_scale: float) -> PackedVector2Array:
|
||||
var mapped: PackedVector2Array = PackedVector2Array()
|
||||
for point: Vector2 in points:
|
||||
mapped.append(rect.position + point * (rect.size.y / 240.0))
|
||||
mapped.append(center + (point - Vector2(75.0, 120.0)) * uniform_scale)
|
||||
return mapped
|
||||
|
||||
|
||||
|
||||
@@ -5,15 +5,12 @@ extends SubViewportContainer
|
||||
const STAGE_SCENE_IO := preload("res://scripts/stage/stage_scene_io.gd")
|
||||
const STAGE_SCENE_ADAPTER := preload("res://scripts/editors/stage/stage_scene_adapter.gd")
|
||||
const STAGE_SPAWNER := preload("res://scripts/stage/stage_spawner.gd")
|
||||
const STAGE_SELECTION := preload("res://scripts/stage/stage_selection.gd")
|
||||
const STAGE_SKY_VIEW := preload("res://scripts/stage/scenery/stage_sky_view.gd")
|
||||
const STAGE_WALL_PANEL := preload("res://scripts/stage/scenery/stage_wall_panel.gd")
|
||||
const PROP_BLOCK := preload("res://scripts/props/prop_block.gd")
|
||||
|
||||
const PREVIEW_SIZE := Vector2i(256, 256)
|
||||
const PREVIEW_MARGIN := 18.0
|
||||
const MIN_PREVIEW_ZOOM := 0.08
|
||||
const MAX_PREVIEW_ZOOM := 0.42
|
||||
const REFERENCE_VIEWPORT_SIZE := Vector2(1280.0, 720.0)
|
||||
|
||||
var _viewport: SubViewport
|
||||
var _sky_view: StageSkyView
|
||||
@@ -82,35 +79,33 @@ func _build_preview_viewport(data: Dictionary) -> void:
|
||||
prop.gravity_scale = 0.0
|
||||
prop.collision_layer = 0
|
||||
prop.collision_mask = 0
|
||||
call_deferred("_fit_scene_to_preview")
|
||||
call_deferred("_fit_scene_to_preview", data.get("camera", {}) as Dictionary)
|
||||
|
||||
|
||||
func _fit_scene_to_preview() -> void:
|
||||
func _fit_scene_to_preview(camera_state: Dictionary) -> void:
|
||||
if _world == null or not is_instance_valid(_world):
|
||||
return
|
||||
var content_bounds := Rect2()
|
||||
var has_bounds: bool = false
|
||||
for node: Node2D in _loaded_nodes:
|
||||
if node == null or not is_instance_valid(node):
|
||||
continue
|
||||
var bounds: Rect2
|
||||
if node.get_script() == STAGE_WALL_PANEL:
|
||||
bounds = node.global_transform * (node as StageWallPanel).get_local_bounds()
|
||||
else:
|
||||
bounds = STAGE_SELECTION.get_world_aabb(node)
|
||||
bounds = bounds.abs()
|
||||
if bounds.size.x <= 0.0 or bounds.size.y <= 0.0:
|
||||
continue
|
||||
content_bounds = bounds if not has_bounds else content_bounds.merge(bounds)
|
||||
has_bounds = true
|
||||
if not has_bounds:
|
||||
_world.scale = Vector2.ONE * 0.2
|
||||
_world.position = Vector2(PREVIEW_SIZE) * 0.5
|
||||
_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
|
||||
return
|
||||
var usable_size: Vector2 = Vector2(PREVIEW_SIZE) - Vector2.ONE * PREVIEW_MARGIN * 2.0
|
||||
var fit_zoom: float = minf(usable_size.x / content_bounds.size.x, usable_size.y / content_bounds.size.y)
|
||||
fit_zoom = clampf(fit_zoom, MIN_PREVIEW_ZOOM, MAX_PREVIEW_ZOOM)
|
||||
_world.scale = Vector2.ONE * fit_zoom
|
||||
_world.position = Vector2(PREVIEW_SIZE) * 0.5 - content_bounds.get_center() * fit_zoom
|
||||
var preview_frame: Dictionary = _preview_transform_for_camera(camera_state)
|
||||
var preview_scale: float = float(preview_frame["scale"])
|
||||
_world.scale = Vector2.ONE * preview_scale
|
||||
_world.position = preview_frame["position"] as Vector2
|
||||
_viewport.render_target_update_mode = SubViewport.UPDATE_ONCE
|
||||
|
||||
|
||||
static func _preview_transform_for_camera(camera_state: Dictionary) -> Dictionary:
|
||||
var camera_position: Vector2 = STAGE_SCENE_IO.data_to_vector(camera_state.get("position"), Vector2.ZERO)
|
||||
var camera_zoom: float = float(camera_state.get("zoom", 0.5))
|
||||
if not is_finite(camera_zoom) or camera_zoom <= 0.0:
|
||||
camera_zoom = 0.5
|
||||
var square_world_extent: float = REFERENCE_VIEWPORT_SIZE.y / camera_zoom
|
||||
var focus_rect := Rect2(
|
||||
camera_position - Vector2.ONE * square_world_extent * 0.5,
|
||||
Vector2.ONE * square_world_extent
|
||||
)
|
||||
var usable_size: Vector2 = Vector2(PREVIEW_SIZE) - Vector2.ONE * PREVIEW_MARGIN * 2.0
|
||||
var preview_scale: float = minf(usable_size.x / focus_rect.size.x, usable_size.y / focus_rect.size.y)
|
||||
return {
|
||||
"focus_rect": focus_rect,
|
||||
"scale": preview_scale,
|
||||
"position": Vector2(PREVIEW_SIZE) * 0.5 - camera_position * preview_scale,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user