push .static contents
This commit is contained in:
178
articles/2d-visibility/.static/Visibility2D.gd.txt
Normal file
178
articles/2d-visibility/.static/Visibility2D.gd.txt
Normal file
@ -0,0 +1,178 @@
|
||||
extends Node
|
||||
class_name Visibility2D
|
||||
|
||||
# Based on: https://www.redblobgames.com/articles/visibility/Visibility.hx
|
||||
|
||||
# Limitations:
|
||||
# - Segments cant intersect each other, splitting is required for such cases.
|
||||
|
||||
# todo: Make it extend plain object, handle lifetime manually.
|
||||
class EndPoint:
|
||||
var point: Vector2
|
||||
var begin: bool
|
||||
var segment: int
|
||||
var angle: float
|
||||
|
||||
static func sort(p_a: EndPoint, p_b: EndPoint) -> bool:
|
||||
if p_a.angle > p_b.angle: return true
|
||||
elif p_a.angle < p_b.angle: return false
|
||||
elif not p_a.begin and p_b.begin: return true
|
||||
else: return false
|
||||
|
||||
var _endpoints: Array # of EndPoint
|
||||
var _sorted_endpoints: Array # of EndPoint
|
||||
var _open: PoolIntArray # of Segment indices
|
||||
|
||||
var center: Vector2
|
||||
var output: PoolVector2Array
|
||||
|
||||
# todo: Ability to cache builder state for static geometry.
|
||||
class Builder:
|
||||
var target
|
||||
|
||||
func view_point(p_point: Vector2) -> Builder:
|
||||
target.center = p_point
|
||||
return self
|
||||
|
||||
# todo: Use it to cull out endpoints out of working region.
|
||||
func bounds(p_area: Rect2) -> Builder:
|
||||
target._add_segment(p_area.position, Vector2(p_area.end.x, p_area.position.y))
|
||||
target._add_segment(Vector2(p_area.end.x, p_area.position.y), p_area.end)
|
||||
target._add_segment(p_area.end, Vector2(p_area.position.x, p_area.end.y))
|
||||
target._add_segment(Vector2(p_area.position.x, p_area.end.y), p_area.position)
|
||||
return self
|
||||
|
||||
func line(p_line: Line2D) -> Builder:
|
||||
for i in range(0, p_line.points.size() - 1):
|
||||
target._add_segment(p_line.position + p_line.points[i],
|
||||
p_line.position + p_line.points[i + 1])
|
||||
return self
|
||||
|
||||
func polygon(p_polygon: Polygon2D) -> Builder:
|
||||
var points := p_polygon.polygon
|
||||
for i in range(0, points.size() - 1):
|
||||
target._add_segment(p_polygon.position + points[i],
|
||||
p_polygon.position + points[i + 1])
|
||||
target._add_segment(p_polygon.position + points[points.size() - 1],
|
||||
p_polygon.position + points[0])
|
||||
return self
|
||||
|
||||
func occluder(p_object: Object) -> Builder:
|
||||
if p_object is Line2D:
|
||||
return line(p_object)
|
||||
elif p_object is Polygon2D:
|
||||
return polygon(p_object)
|
||||
else:
|
||||
push_error("Unknown occluder type")
|
||||
return self
|
||||
|
||||
func finalize():
|
||||
target._finalize()
|
||||
|
||||
func _add_segment(p_point0: Vector2, p_point1: Vector2):
|
||||
var point0 := EndPoint.new()
|
||||
var point1 := EndPoint.new()
|
||||
point0.segment = _endpoints.size()
|
||||
point1.segment = _endpoints.size()
|
||||
point0.point = p_point0
|
||||
point1.point = p_point1
|
||||
_endpoints.append(point0)
|
||||
_endpoints.append(point1)
|
||||
|
||||
func init_builder() -> Builder:
|
||||
# todo: Reuse
|
||||
_endpoints.resize(0)
|
||||
var result := Builder.new()
|
||||
result.target = self
|
||||
return result
|
||||
|
||||
func _finalize():
|
||||
# todo: Only needs to be done when endpoints or center is changed.
|
||||
for segment in range(0, _endpoints.size(), 2):
|
||||
var p1 := _endpoints[segment] as EndPoint
|
||||
var p2 := _endpoints[segment + 1] as EndPoint
|
||||
p1.angle = (p1.point - center).angle()
|
||||
p2.angle = (p2.point - center).angle()
|
||||
# todo: Simplify to one expression.
|
||||
var da := p2.angle - p1.angle
|
||||
if da <= PI: da += TAU
|
||||
if da > PI: da -= TAU
|
||||
p1.begin = da > 0.0
|
||||
p2.begin = not p1.begin
|
||||
|
||||
func _is_segment_in_front(p_segment1: int, p_segment2: int) -> bool:
|
||||
var s1p1 := _endpoints[p_segment1].point as Vector2
|
||||
var s1p2 := _endpoints[p_segment1 + 1].point as Vector2
|
||||
var s2p1 := _endpoints[p_segment2].point as Vector2
|
||||
var s2p2 := _endpoints[p_segment2 + 1].point as Vector2
|
||||
|
||||
# todo: Can we use something simpler than interpolation?
|
||||
var d := s1p2 - s1p1
|
||||
var p := s2p1.linear_interpolate(s2p2, 0.01)
|
||||
var a1 := (d.x * (p.y - s1p1.y) \
|
||||
- d.y * (p.x - s1p1.x)) < 0.0
|
||||
p = s2p2.linear_interpolate(s2p1, 0.01)
|
||||
var a2 := (d.x * (p.y - s1p1.y) \
|
||||
- d.y * (p.x - s1p1.x)) < 0.0
|
||||
var a3 := (d.x * (center.y - s1p1.y) \
|
||||
- d.y * (center.x - s1p1.x)) < 0.0
|
||||
|
||||
if a1 == a2 and a2 == a3: return true
|
||||
|
||||
d = s2p2 - s2p1
|
||||
p = s1p1.linear_interpolate(s1p2, 0.01)
|
||||
var b1 := (d.x * (p.y - s2p1.y) \
|
||||
- d.y * (p.x - s2p1.x)) < 0.0
|
||||
p = s1p2.linear_interpolate(s1p1, 0.01)
|
||||
var b2 := (d.x * (p.y - s2p1.y) \
|
||||
- d.y * (p.x - s2p1.x)) < 0.0
|
||||
var b3 := (d.x * (center.y - s2p1.y) \
|
||||
- d.y * (center.x - s2p1.x)) < 0.0
|
||||
|
||||
return b1 == b2 and b2 != b3
|
||||
|
||||
func sweep() -> PoolVector2Array:
|
||||
output.resize(0)
|
||||
# todo: Only duplicate and sort on change.
|
||||
_sorted_endpoints = _endpoints.duplicate()
|
||||
_sorted_endpoints.sort_custom(EndPoint, "sort")
|
||||
|
||||
var start_angle := 0.0
|
||||
|
||||
# todo: Inline passes.
|
||||
for n_pass in range(2):
|
||||
for p_idx in range(_sorted_endpoints.size() - 1, -1, -1):
|
||||
var p := _sorted_endpoints[p_idx] as EndPoint
|
||||
var old := -1 if _open.empty() else _open[0]
|
||||
|
||||
if p.begin:
|
||||
var idx := 0
|
||||
while idx < _open.size() and _is_segment_in_front(p.segment, _open[idx]):
|
||||
idx += 1
|
||||
# warning-ignore:return_value_discarded
|
||||
_open.insert(idx, p.segment)
|
||||
else:
|
||||
var idx := _open.rfind(p.segment)
|
||||
if idx != -1: _open.remove(idx)
|
||||
# todo: Second pass can assume that it will be found.
|
||||
# _open.remove(_open.rfind(p.segment))
|
||||
|
||||
if old != (-1 if _open.empty() else _open[0]):
|
||||
if n_pass == 1:
|
||||
# todo: Distance should be configurable.
|
||||
var p3 := _endpoints[old].point as Vector2 if old != -1 else \
|
||||
center + Vector2(cos(start_angle), sin(start_angle)) * 500.0
|
||||
var t2 := Vector2(cos(p.angle), sin(p.angle))
|
||||
var p4 := p3.direction_to(_endpoints[old + 1].point) if old != -1 else t2
|
||||
|
||||
var l = Geometry.line_intersects_line_2d(p3, p4, center,
|
||||
Vector2(cos(start_angle), sin(start_angle)))
|
||||
if l != null: output.append(l)
|
||||
l = Geometry.line_intersects_line_2d(p3, p4, center, t2)
|
||||
if l != null: output.append(l)
|
||||
|
||||
start_angle = p.angle
|
||||
|
||||
_open.resize(0)
|
||||
|
||||
return output
|
BIN
articles/2d-visibility/.static/example.gif
(Stored with Git LFS)
Normal file
BIN
articles/2d-visibility/.static/example.gif
(Stored with Git LFS)
Normal file
Binary file not shown.
Reference in New Issue
Block a user