OverviewFilesCommitsIssuesDocsReleasesPeers

Attila772-hypergodot / HyperEdge.gd

147 lines · gd ref:
extends Node2D

var nodes: Dictionary
var color: Color
var width = 5
var dragging = false
var group = ""
var point_count = 50
var is_dirty = true
var cached_hull = []
var cached_node_positions = {}

func _ready():
	pass

func _process(delta):
	if dragging:
		queue_redraw()

func _input(event):
	if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
		if event.pressed:
			dragging = true  # Start dragging
		else:
			dragging = false  # Stop dragging
			queue_redraw()  # Optionally, queue a redraw when you stop dragging
			
func _draw():
	# Only recalculate if dirty or nodes have moved
	if is_dirty or has_nodes_moved():
		recalculate_edge()
		is_dirty = false
	
	# Draw the cached hull
	draw_cached_hull()


func update_position():
	queue_redraw()  # This will trigger a redraw with updated node positions and sizes

func mark_dirty():
	is_dirty = true
	queue_redraw()

func has_nodes_moved() -> bool:
	for node_id in nodes:
		var node = get_node_from_group(node_id)
		if node and cached_node_positions.has(node_id):
			if node.position != cached_node_positions[node_id]:
				return true
	return false

func recalculate_edge():
	var points = []
	cached_node_positions.clear()
	
	for node_id in nodes:
		var node = get_node_from_group(node_id)
		if node:
			cached_node_positions[node_id] = node.position
			var center = node.position
			var radius = nodes[node_id]
			var actual_node_radius = node.radius_global
			radius += actual_node_radius
			radius -= 30
			
			var config = ConfigFile.new()
			var err = config.load("res://conf.cfg")
			if err == OK:
				point_count = config.get_value("graph_settings", "point_count", 2.0)
			if Global.high_quality:
				point_count = 300
			points += generate_circle_points(center, radius, point_count)
	
	cached_hull = andrew_monotone_chain(points)
	if cached_hull.size() > 0 and cached_hull[0] != cached_hull[cached_hull.size() - 1]:
		cached_hull.append(cached_hull[0])

func draw_cached_hull():
	var group_index = Global.get_group_index(group)
	for i in range(cached_hull.size() - 1):
		match group_index:
			1:
				draw_line(cached_hull[i], cached_hull[i + 1], color, width)
			2:
				draw_dashed_line(cached_hull[i], cached_hull[i + 1], color, width, 10.0)
			_:
				draw_line(cached_hull[i], cached_hull[i + 1], color, width)

func generate_circle_points(center, radius, point_count):
	var points = []
	for i in range(point_count):
		var angle = 2 * PI * i / point_count
		points.append(center + Vector2(cos(angle), sin(angle)) * radius)
	return points

func orientation(p, q, r):
	var val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y)
	return 0 if val == 0 else (1 if val > 0 else 2)

func get_node_from_group(node_id) -> Node2D:
	var nodes_group = get_tree().get_nodes_in_group("nodes")
	for node in nodes_group:
		if str(node.name) == str(node_id):
			return node
	return null

func find_lowest_point(points):
	var lowest = points[0]
	for point in points:
		if point.y < lowest.y or (point.y == lowest.y and point.x < lowest.x):
			lowest = point
	return lowest


func andrew_monotone_chain(points):
	if points.size() < 3:
		return points
	# Sort points 
	points.sort_custom(func(a, b):
		return a.x < b.x if a.x != b.x else a.y < b.y
	)
	# Lower hull
	var lower = []
	for p in points:
		while lower.size() >= 2 and cross(lower[lower.size() - 2], lower[lower.size() - 1], p) <= 0:
			lower.pop_back()
		lower.append(p)
	# Upper hull
	var upper = []
	for i in range(points.size() - 1, -1, -1):
		var p = points[i]
		while upper.size() >= 2 and cross(upper[upper.size() - 2], upper[upper.size() - 1], p) <= 0:
			upper.pop_back()
		upper.append(p)
	# Concatenate the hulls
	# The first and last points in lower will be the same as the last two points in upper,
	# so we drop them from the result
	lower.pop_back()
	upper.pop_back()
	return lower + upper

func cross(o, a, b):
	return (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x)