|
@@ -2,7 +2,7 @@ class_name Block
|
|
|
extends AnimatableBody2D
|
|
extends AnimatableBody2D
|
|
|
## One arc-shaped segment of a ring around the core.
|
|
## One arc-shaped segment of a ring around the core.
|
|
|
|
|
|
|
|
-const GAP := 4.0 # px between neighbouring tiles, drawn only (collision has no gap)
|
|
|
|
|
|
|
+const GAP := 4.0 # px between neighbouring tiles
|
|
|
const CORNER := 4.0 # corner rounding radius in px
|
|
const CORNER := 4.0 # corner rounding radius in px
|
|
|
const BREAK_SECONDS := 0.15 # fade-out time, after a short flash
|
|
const BREAK_SECONDS := 0.15 # fade-out time, after a short flash
|
|
|
|
|
|
|
@@ -22,11 +22,14 @@ func _init(inner: float, outer: float, begin: float, end: float, is_indestructib
|
|
|
collision_mask = 0
|
|
collision_mask = 0
|
|
|
indestructible = is_indestructible
|
|
indestructible = is_indestructible
|
|
|
|
|
|
|
|
- var collision := CollisionPolygon2D.new()
|
|
|
|
|
- collision.polygon = _sector(inner, outer, begin + 1.0 / outer, end - 1.0 / outer, 0.0)
|
|
|
|
|
- add_child(collision)
|
|
|
|
|
-
|
|
|
|
|
|
|
+ # Collision uses the drawn outline, so the ball bounces exactly where it touches.
|
|
|
_polygon = _round_corners(_sector(inner + GAP / 2.0, outer - GAP / 2.0, begin, end, GAP / 2.0))
|
|
_polygon = _round_corners(_sector(inner + GAP / 2.0, outer - GAP / 2.0, begin, end, GAP / 2.0))
|
|
|
|
|
+ # Very short segments can leave a sliver too thin to split into convex pieces; skip its
|
|
|
|
|
+ # collision rather than let Godot fail on it.
|
|
|
|
|
+ if not Geometry2D.decompose_polygon_in_convex(_polygon).is_empty():
|
|
|
|
|
+ var collision := CollisionPolygon2D.new()
|
|
|
|
|
+ collision.polygon = _polygon
|
|
|
|
|
+ add_child(collision)
|
|
|
|
|
|
|
|
if indestructible:
|
|
if indestructible:
|
|
|
color = Params.COLOR_ORANGE
|
|
color = Params.COLOR_ORANGE
|
|
@@ -48,6 +51,8 @@ func destroy() -> void:
|
|
|
|
|
|
|
|
|
|
|
|
|
func _draw() -> void:
|
|
func _draw() -> void:
|
|
|
|
|
+ if _polygon.size() < 3:
|
|
|
|
|
+ return # segment too short to show at all
|
|
|
var fill := color.lerp(Color.WHITE, _flash)
|
|
var fill := color.lerp(Color.WHITE, _flash)
|
|
|
draw_colored_polygon(_polygon, fill)
|
|
draw_colored_polygon(_polygon, fill)
|
|
|
# Polygons have no antialiasing of their own; a smooth outline softens the edge.
|
|
# Polygons have no antialiasing of their own; a smooth outline softens the edge.
|
|
@@ -57,11 +62,19 @@ func _draw() -> void:
|
|
|
|
|
|
|
|
|
|
|
|
|
## Ring segment between two radii. [param gap] trims each side by that many pixels,
|
|
## Ring segment between two radii. [param gap] trims each side by that many pixels,
|
|
|
-## so the space between neighbours has the same width at every radius.
|
|
|
|
|
|
|
+## so the space between neighbours has the same width at every radius. An edge too short
|
|
|
|
|
+## to trim collapses to a point, turning the segment into a wedge.
|
|
|
static func _sector(inner: float, outer: float, begin: float, end: float, gap: float) -> PackedVector2Array:
|
|
static func _sector(inner: float, outer: float, begin: float, end: float, gap: float) -> PackedVector2Array:
|
|
|
var steps := maxi(2, int((end - begin) * sqrt(outer) / 1.5))
|
|
var steps := maxi(2, int((end - begin) * sqrt(outer) / 1.5))
|
|
|
- var outer_arc := _arc(outer, begin + gap / outer, end - gap / outer, steps)
|
|
|
|
|
- return outer_arc + _arc(inner, end - gap / inner, begin + gap / inner, steps)
|
|
|
|
|
|
|
+ return _trimmed_arc(outer, begin, end, gap, steps) + _trimmed_arc(inner, end, begin, -gap, steps)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+static func _trimmed_arc(radius: float, from: float, to: float, gap: float, steps: int) -> PackedVector2Array:
|
|
|
|
|
+ var trim := gap / radius
|
|
|
|
|
+ if (to - from - 2.0 * trim) * signf(to - from) <= 0.0:
|
|
|
|
|
+ var middle := (from + to) / 2.0
|
|
|
|
|
+ return PackedVector2Array([Vector2(sin(middle), cos(middle)) * radius])
|
|
|
|
|
+ return _arc(radius, from + trim, to - trim, steps)
|
|
|
|
|
|
|
|
|
|
|
|
|
## Shrinks then regrows the polygon with round joins, which rounds its corners.
|
|
## Shrinks then regrows the polygon with round joins, which rounds its corners.
|
|
@@ -70,7 +83,18 @@ static func _round_corners(polygon: PackedVector2Array) -> PackedVector2Array:
|
|
|
if shrunk.is_empty():
|
|
if shrunk.is_empty():
|
|
|
return polygon
|
|
return polygon
|
|
|
var rounded := Geometry2D.offset_polygon(shrunk[0], CORNER, Geometry2D.JOIN_ROUND)
|
|
var rounded := Geometry2D.offset_polygon(shrunk[0], CORNER, Geometry2D.JOIN_ROUND)
|
|
|
- return polygon if rounded.is_empty() else rounded[0]
|
|
|
|
|
|
|
+ return polygon if rounded.is_empty() else _without_duplicates(rounded[0])
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+## offset_polygon can emit repeated points; zero-length edges break convex decomposition.
|
|
|
|
|
+static func _without_duplicates(polygon: PackedVector2Array) -> PackedVector2Array:
|
|
|
|
|
+ var result := PackedVector2Array()
|
|
|
|
|
+ for point in polygon:
|
|
|
|
|
+ if result.is_empty() or not point.is_equal_approx(result[-1]):
|
|
|
|
|
+ result.append(point)
|
|
|
|
|
+ if result.size() > 1 and result[0].is_equal_approx(result[-1]):
|
|
|
|
|
+ result.remove_at(result.size() - 1)
|
|
|
|
|
+ return result
|
|
|
|
|
|
|
|
|
|
|
|
|
static func _arc(radius: float, from: float, to: float, steps: int) -> PackedVector2Array:
|
|
static func _arc(radius: float, from: float, to: float, steps: int) -> PackedVector2Array:
|