Implement Phase 10a - Prop Scaling
- Added specifications for Phase 10a, introducing permanent size changes to `.prop` documents and reversible uniform instance scaling in Sandbox EDIT mode. - Updated `PropCanvas`, `PropAnchorData`, `PropEditor`, `StageGizmos`, `SandboxStage`, and `StageSelection` classes to support new scaling features. - Implemented document transactions for scaling, including normalization to a 96 px maximum dimension. - Introduced a new **Transform Whole Prop** tool in the Prop Editor for moving, rotating, and resizing props. - Created unit tests for the new scaling functionality, validating shape scaling, anchor transformations, and whole-prop interactions. - Ensured compatibility with existing prop formats and maintained the integrity of the document structure during scaling operations.
This commit is contained in:
+66
-1
@@ -95,6 +95,9 @@ var _collision_shape: CollisionShape2D
|
||||
var _circle_shape: CircleShape2D
|
||||
var _custom_shapes: bool = false
|
||||
var _visual_local_bounds := Rect2()
|
||||
var _custom_collision_hull: PackedVector2Array = PackedVector2Array()
|
||||
var _play_instance_scale: Vector2 = Vector2.ONE
|
||||
var _play_scale_active: bool = false
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Signals
|
||||
@@ -257,7 +260,8 @@ func set_custom_shapes(shapes: Array[Dictionary], collision_hull: PackedVector2A
|
||||
_outline.visible = false
|
||||
_set_collision_shape_enabled(false)
|
||||
_set_collision_polygon_enabled(true)
|
||||
_collision_polygon.polygon = collision_hull
|
||||
_custom_collision_hull = collision_hull.duplicate()
|
||||
_collision_polygon.polygon = _custom_collision_hull
|
||||
var visuals := get_node_or_null(NodePath("Visuals")) as Node2D
|
||||
if visuals == null:
|
||||
visuals = Node2D.new(); visuals.name = "Visuals"; add_child(visuals)
|
||||
@@ -273,6 +277,67 @@ func has_custom_shapes() -> bool:
|
||||
func get_visual_local_bounds() -> Rect2:
|
||||
return _visual_local_bounds
|
||||
|
||||
## RigidBody2D transforms do not reliably retain scale during simulation. Move
|
||||
## the authored uniform scale into child visuals and collision geometry for
|
||||
## PLAY, then restore the root scale on return to EDIT for scene persistence.
|
||||
func begin_play_instance_scale() -> void:
|
||||
if _play_scale_active:
|
||||
return
|
||||
_play_instance_scale = scale
|
||||
if _play_instance_scale.is_equal_approx(Vector2.ONE):
|
||||
return
|
||||
_play_scale_active = true
|
||||
scale = Vector2.ONE
|
||||
_apply_play_scale()
|
||||
|
||||
func end_play_instance_scale() -> void:
|
||||
if not _play_scale_active:
|
||||
return
|
||||
_play_scale_active = false
|
||||
_set_visual_scale(Vector2.ONE)
|
||||
_apply_shape()
|
||||
if _custom_shapes and _collision_polygon != null:
|
||||
_collision_polygon.polygon = _custom_collision_hull
|
||||
scale = _play_instance_scale
|
||||
|
||||
func _apply_play_scale() -> void:
|
||||
_set_visual_scale(_play_instance_scale)
|
||||
if _custom_shapes:
|
||||
if _collision_polygon != null:
|
||||
_collision_polygon.polygon = _scaled_points(_custom_collision_hull, _play_instance_scale)
|
||||
return
|
||||
if shape_type == ShapeType.CIRCLE:
|
||||
# A non-uniform legacy instance scale makes an ellipse. Use the generated
|
||||
# polygon hull while simulating so collision follows both axes exactly.
|
||||
if _collision_polygon != null:
|
||||
_collision_polygon.polygon = _scaled_points(_circle_loop(), _play_instance_scale)
|
||||
_set_collision_shape_enabled(false)
|
||||
_set_collision_polygon_enabled(true)
|
||||
elif _collision_polygon != null:
|
||||
_collision_polygon.polygon = _scaled_points(polygon_points, _play_instance_scale)
|
||||
|
||||
func _set_visual_scale(value: Vector2) -> void:
|
||||
if _polygon != null:
|
||||
_polygon.scale = value
|
||||
if _outline != null:
|
||||
_outline.scale = value
|
||||
var visuals: Node2D = get_node_or_null(NodePath("Visuals")) as Node2D
|
||||
if visuals != null:
|
||||
visuals.scale = value
|
||||
|
||||
func _scaled_points(points: PackedVector2Array, value: Vector2) -> PackedVector2Array:
|
||||
var result: PackedVector2Array = PackedVector2Array()
|
||||
for point: Vector2 in points:
|
||||
result.append(point * value)
|
||||
return result
|
||||
|
||||
func _circle_loop() -> PackedVector2Array:
|
||||
var result: PackedVector2Array = PackedVector2Array()
|
||||
for index: int in CIRCLE_SEGMENTS:
|
||||
var angle: float = TAU * float(index) / float(CIRCLE_SEGMENTS)
|
||||
result.append(Vector2(cos(angle), sin(angle)) * radius)
|
||||
return result
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Static preset factories
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user