Bezier Map curves
This commit is contained in:
parent
e1178d5f5c
commit
b4db4afd5f
2 changed files with 33 additions and 568 deletions
|
|
@ -169,63 +169,7 @@ func generate_map():
|
||||||
|
|
||||||
if from_valid and to_valid:
|
if from_valid and to_valid:
|
||||||
valid_connections.append(conn)
|
valid_connections.append(conn)
|
||||||
valid_connections = ensure_path_exists(valid_nodes, valid_connections)
|
|
||||||
|
|
||||||
# Then filter out overlapping connections while maintaining connectivity
|
|
||||||
valid_connections = filter_overlapping_connections(valid_nodes, valid_connections)
|
|
||||||
|
|
||||||
# Perform a final check to make sure all nodes are connected
|
|
||||||
var reachable_nodes = {}
|
|
||||||
reachable_nodes[0] = true # Start node id is always 0
|
|
||||||
|
|
||||||
# Build the graph again
|
|
||||||
var graph = {}
|
|
||||||
for node in valid_nodes:
|
|
||||||
graph[node.id] = []
|
|
||||||
|
|
||||||
for conn in valid_connections:
|
|
||||||
if not graph.has(conn.from):
|
|
||||||
graph[conn.from] = []
|
|
||||||
graph[conn.from].append(conn.to)
|
|
||||||
|
|
||||||
# Do a BFS from the start node
|
|
||||||
var queue = [0] # Starting node ID
|
|
||||||
while queue.size() > 0:
|
|
||||||
var current = queue.pop_front()
|
|
||||||
|
|
||||||
if graph.has(current):
|
|
||||||
for neighbor in graph[current]:
|
|
||||||
if not reachable_nodes.has(neighbor):
|
|
||||||
reachable_nodes[neighbor] = true
|
|
||||||
queue.append(neighbor)
|
|
||||||
|
|
||||||
# Check if all nodes are reachable
|
|
||||||
var all_reachable = true
|
|
||||||
for node in valid_nodes:
|
|
||||||
if not reachable_nodes.has(node.id):
|
|
||||||
all_reachable = false
|
|
||||||
print("Node not reachable: ", node.id, " - Type: ", node.type)
|
|
||||||
break
|
|
||||||
|
|
||||||
# If not all nodes are reachable, only keep the reachable ones
|
|
||||||
if not all_reachable:
|
|
||||||
var truly_valid_nodes = []
|
|
||||||
for node in valid_nodes:
|
|
||||||
if reachable_nodes.has(node.id):
|
|
||||||
truly_valid_nodes.append(node)
|
|
||||||
|
|
||||||
# Update valid_nodes to only include reachable nodes
|
|
||||||
valid_nodes = truly_valid_nodes
|
|
||||||
|
|
||||||
# Also update connections to only include those between reachable nodes
|
|
||||||
var truly_valid_connections = []
|
|
||||||
for conn in valid_connections:
|
|
||||||
if reachable_nodes.has(conn.from) and reachable_nodes.has(conn.to):
|
|
||||||
truly_valid_connections.append(conn)
|
|
||||||
|
|
||||||
valid_connections = truly_valid_connections
|
|
||||||
|
|
||||||
space_nodes_horizontally(valid_nodes, valid_connections)
|
|
||||||
# Return the generated map
|
# Return the generated map
|
||||||
return {
|
return {
|
||||||
"nodes": valid_nodes,
|
"nodes": valid_nodes,
|
||||||
|
|
@ -268,363 +212,3 @@ func _get_next_id():
|
||||||
var id = _next_id
|
var id = _next_id
|
||||||
_next_id += 1
|
_next_id += 1
|
||||||
return id
|
return id
|
||||||
|
|
||||||
|
|
||||||
func filter_overlapping_connections(nodes, connections):
|
|
||||||
var valid_connections = []
|
|
||||||
var node_positions = {}
|
|
||||||
|
|
||||||
# Create a lookup for node positions
|
|
||||||
for node in nodes:
|
|
||||||
node_positions[node.id] = Vector2(node.position.x, node.position.y)
|
|
||||||
|
|
||||||
# Create a graph representation to check connectivity
|
|
||||||
var graph = {}
|
|
||||||
for node in nodes:
|
|
||||||
graph[node.id] = []
|
|
||||||
|
|
||||||
for connection in connections:
|
|
||||||
if not graph.has(connection.from):
|
|
||||||
graph[connection.from] = []
|
|
||||||
if not graph.has(connection.to):
|
|
||||||
graph[connection.to] = []
|
|
||||||
graph[connection.from].append(connection.to)
|
|
||||||
|
|
||||||
# Find start and end nodes
|
|
||||||
var start_node_id = -1
|
|
||||||
var end_node_id = -1
|
|
||||||
for node in nodes:
|
|
||||||
if node.type == RoomType.STARTING:
|
|
||||||
start_node_id = node.id
|
|
||||||
elif node.type == RoomType.FINAL:
|
|
||||||
end_node_id = node.id
|
|
||||||
|
|
||||||
# Sort connections by priority, giving preference to:
|
|
||||||
# 1. Critical path connections
|
|
||||||
# 2. More vertical connections (to reduce crossing)
|
|
||||||
# 3. Shorter connections
|
|
||||||
var sorted_connections = connections.duplicate()
|
|
||||||
sorted_connections.sort_custom(func(a, b):
|
|
||||||
var a_from_pos = node_positions[a.from]
|
|
||||||
var a_to_pos = node_positions[a.to]
|
|
||||||
var b_from_pos = node_positions[b.from]
|
|
||||||
var b_to_pos = node_positions[b.to]
|
|
||||||
|
|
||||||
# Critical path connections (ones needed to reach the final boss) get highest priority
|
|
||||||
var a_critical = is_on_critical_path(a.from, a.to, start_node_id, end_node_id, graph)
|
|
||||||
var b_critical = is_on_critical_path(b.from, b.to, start_node_id, end_node_id, graph)
|
|
||||||
|
|
||||||
if a_critical and not b_critical:
|
|
||||||
return true
|
|
||||||
elif not a_critical and b_critical:
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Connections from the same node should be spread out by x-position
|
|
||||||
if a.from == b.from:
|
|
||||||
var a_x_diff = abs(a_from_pos.x - a_to_pos.x)
|
|
||||||
var b_x_diff = abs(b_from_pos.x - b_to_pos.x)
|
|
||||||
# Prefer more aligned connections (less horizontal distance)
|
|
||||||
return a_x_diff < b_x_diff
|
|
||||||
|
|
||||||
# Calculate how vertical each connection is
|
|
||||||
var a_dx = abs(a_to_pos.x - a_from_pos.x)
|
|
||||||
var a_dy = abs(a_to_pos.y - a_from_pos.y)
|
|
||||||
var b_dx = abs(b_to_pos.x - b_from_pos.x)
|
|
||||||
var b_dy = abs(b_to_pos.y - b_from_pos.y)
|
|
||||||
|
|
||||||
var a_vertical_ratio = a_dy / max(a_dx, 0.1) # Avoid division by zero
|
|
||||||
var b_vertical_ratio = b_dy / max(b_dx, 0.1)
|
|
||||||
|
|
||||||
# Prefer more vertical connections
|
|
||||||
if a_vertical_ratio > b_vertical_ratio * 1.5: # Threshold to make the difference significant
|
|
||||||
return true
|
|
||||||
elif b_vertical_ratio > a_vertical_ratio * 1.5:
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Then prioritize by level progression (forward progress)
|
|
||||||
var a_forward = a_from_pos.y < a_to_pos.y
|
|
||||||
var b_forward = b_from_pos.y < b_to_pos.y
|
|
||||||
|
|
||||||
if a_forward and not b_forward:
|
|
||||||
return true
|
|
||||||
elif not a_forward and b_forward:
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Finally by length
|
|
||||||
var a_dist = (a_to_pos - a_from_pos).length_squared()
|
|
||||||
var b_dist = (b_to_pos - b_from_pos).length_squared()
|
|
||||||
return a_dist < b_dist
|
|
||||||
)
|
|
||||||
|
|
||||||
# First pass: Add all critical path connections
|
|
||||||
for connection in sorted_connections:
|
|
||||||
if is_on_critical_path(connection.from, connection.to, start_node_id, end_node_id, graph):
|
|
||||||
valid_connections.append(connection)
|
|
||||||
|
|
||||||
# Create a set of connected nodes from the critical path
|
|
||||||
var connected_nodes = {}
|
|
||||||
connected_nodes[start_node_id] = true
|
|
||||||
|
|
||||||
for connection in valid_connections:
|
|
||||||
connected_nodes[connection.from] = true
|
|
||||||
connected_nodes[connection.to] = true
|
|
||||||
|
|
||||||
# Second pass: Add remaining connections, avoiding overlaps
|
|
||||||
for connection in sorted_connections:
|
|
||||||
# Skip if already added
|
|
||||||
if connection in valid_connections:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Only consider connections that link to already connected nodes
|
|
||||||
if not connected_nodes.has(connection.from) and not connected_nodes.has(connection.to):
|
|
||||||
continue
|
|
||||||
|
|
||||||
var from_pos = node_positions[connection.from]
|
|
||||||
var to_pos = node_positions[connection.to]
|
|
||||||
|
|
||||||
# Check if this connection would intersect with any valid connection
|
|
||||||
var has_intersection = false
|
|
||||||
for valid_conn in valid_connections:
|
|
||||||
var valid_from_pos = node_positions[valid_conn.from]
|
|
||||||
var valid_to_pos = node_positions[valid_conn.to]
|
|
||||||
|
|
||||||
# Skip if they share a node
|
|
||||||
if connection.from == valid_conn.from or connection.from == valid_conn.to or \
|
|
||||||
connection.to == valid_conn.from or connection.to == valid_conn.to:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check intersection
|
|
||||||
if do_lines_intersect(from_pos, to_pos, valid_from_pos, valid_to_pos):
|
|
||||||
has_intersection = true
|
|
||||||
break
|
|
||||||
|
|
||||||
# If no intersections, add to valid connections
|
|
||||||
if not has_intersection:
|
|
||||||
valid_connections.append(connection)
|
|
||||||
connected_nodes[connection.from] = true
|
|
||||||
connected_nodes[connection.to] = true
|
|
||||||
|
|
||||||
# Final pass: Ensure all nodes are reachable
|
|
||||||
# This part can remain the same as your current implementation
|
|
||||||
|
|
||||||
return valid_connections
|
|
||||||
# Helper function to check if a connection is on a critical path
|
|
||||||
func is_on_critical_path(from_id, to_id, start_id, end_id, graph):
|
|
||||||
# Skip the check if we're directly connecting start to end
|
|
||||||
if (from_id == start_id and to_id == end_id):
|
|
||||||
return true
|
|
||||||
|
|
||||||
# Use breadth-first search to find paths
|
|
||||||
var visited = {from_id: true}
|
|
||||||
var queue = [from_id]
|
|
||||||
|
|
||||||
# First check if we can reach end_id from to_id
|
|
||||||
while queue.size() > 0:
|
|
||||||
var current = queue.pop_front()
|
|
||||||
|
|
||||||
if current == end_id:
|
|
||||||
return true
|
|
||||||
|
|
||||||
for neighbor in graph[current]:
|
|
||||||
if not visited.has(neighbor):
|
|
||||||
visited[neighbor] = true
|
|
||||||
queue.append(neighbor)
|
|
||||||
|
|
||||||
# If we're not on a path to the end, this isn't critical
|
|
||||||
return false
|
|
||||||
|
|
||||||
# Helper function to ensure at least one valid path exists from start to end
|
|
||||||
func ensure_path_exists(nodes, connections):
|
|
||||||
# Find start and end node IDs
|
|
||||||
var start_id = -1
|
|
||||||
var end_id = -1
|
|
||||||
for node in nodes:
|
|
||||||
if node.type == RoomType.STARTING:
|
|
||||||
start_id = node.id
|
|
||||||
elif node.type == RoomType.FINAL:
|
|
||||||
end_id = node.id
|
|
||||||
|
|
||||||
# Build a graph representation
|
|
||||||
var graph = {}
|
|
||||||
for node in nodes:
|
|
||||||
graph[node.id] = []
|
|
||||||
|
|
||||||
for conn in connections:
|
|
||||||
if not graph.has(conn.from):
|
|
||||||
graph[conn.from] = []
|
|
||||||
graph[conn.from].append(conn.to)
|
|
||||||
|
|
||||||
# Use BFS to check if end is reachable from start
|
|
||||||
var visited = {start_id: true}
|
|
||||||
var queue = [start_id]
|
|
||||||
var path_exists = false
|
|
||||||
|
|
||||||
while queue.size() > 0:
|
|
||||||
var current = queue.pop_front()
|
|
||||||
|
|
||||||
if current == end_id:
|
|
||||||
path_exists = true # Path exists
|
|
||||||
break
|
|
||||||
|
|
||||||
for neighbor in graph[current]:
|
|
||||||
if not visited.has(neighbor):
|
|
||||||
visited[neighbor] = true
|
|
||||||
queue.append(neighbor)
|
|
||||||
|
|
||||||
# If path exists, return the existing connections
|
|
||||||
if path_exists:
|
|
||||||
return connections # Return the unmodified connections array
|
|
||||||
|
|
||||||
# No path exists, need to add one
|
|
||||||
# Find nodes by level
|
|
||||||
var nodes_by_level = {}
|
|
||||||
for node in nodes:
|
|
||||||
if not nodes_by_level.has(node.level):
|
|
||||||
nodes_by_level[node.level] = []
|
|
||||||
nodes_by_level[node.level].append(node)
|
|
||||||
|
|
||||||
# Create a direct path from start to end through each level
|
|
||||||
var current_id = start_id
|
|
||||||
var levels = nodes_by_level.keys()
|
|
||||||
levels.sort()
|
|
||||||
|
|
||||||
# Copy connections array to avoid modifying the original
|
|
||||||
var new_connections = connections.duplicate()
|
|
||||||
|
|
||||||
for i in range(1, levels.size()):
|
|
||||||
var level = levels[i]
|
|
||||||
if nodes_by_level.has(level) and nodes_by_level[level].size() > 0:
|
|
||||||
# Pick the middle node in this level
|
|
||||||
var target_node = nodes_by_level[level][nodes_by_level[level].size() / 2]
|
|
||||||
|
|
||||||
# Add a connection from current to this node
|
|
||||||
new_connections.append({
|
|
||||||
"from": current_id,
|
|
||||||
"to": target_node.id
|
|
||||||
})
|
|
||||||
|
|
||||||
current_id = target_node.id
|
|
||||||
|
|
||||||
# If we've reached the final level, connect to end
|
|
||||||
if level == levels[-1]:
|
|
||||||
new_connections.append({
|
|
||||||
"from": current_id,
|
|
||||||
"to": end_id
|
|
||||||
})
|
|
||||||
|
|
||||||
return new_connections
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func do_lines_intersect(a_start: Vector2, a_end: Vector2, b_start: Vector2, b_end: Vector2) -> bool:
|
|
||||||
# Line segment intersection using the parametric equation approach
|
|
||||||
var s1_x = a_end.x - a_start.x
|
|
||||||
var s1_y = a_end.y - a_start.y
|
|
||||||
var s2_x = b_end.x - b_start.x
|
|
||||||
var s2_y = b_end.y - b_start.y
|
|
||||||
|
|
||||||
var denominator = (-s2_x * s1_y + s1_x * s2_y)
|
|
||||||
if denominator == 0:
|
|
||||||
return false # Collinear or parallel lines
|
|
||||||
|
|
||||||
var s = (-s1_y * (a_start.x - b_start.x) + s1_x * (a_start.y - b_start.y)) / denominator
|
|
||||||
var t = (s2_x * (a_start.y - b_start.y) - s2_y * (a_start.x - b_start.x)) / denominator
|
|
||||||
|
|
||||||
# If s and t are between 0-1, the lines intersect
|
|
||||||
return (s >= 0 && s <= 1 && t >= 0 && t <= 1)
|
|
||||||
|
|
||||||
|
|
||||||
# Fixed version of space_nodes_horizontally with proper connection tracking
|
|
||||||
func space_nodes_horizontally(nodes, connections):
|
|
||||||
# Group nodes by level
|
|
||||||
var nodes_by_level = {}
|
|
||||||
for node in nodes:
|
|
||||||
if not nodes_by_level.has(node.level):
|
|
||||||
nodes_by_level[node.level] = []
|
|
||||||
nodes_by_level[node.level].append(node)
|
|
||||||
|
|
||||||
# Process each level to spread out nodes
|
|
||||||
for level in nodes_by_level.keys():
|
|
||||||
var level_nodes = nodes_by_level[level]
|
|
||||||
|
|
||||||
# Skip levels with just one node
|
|
||||||
if level_nodes.size() <= 1:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Sort nodes by their current horizontal position
|
|
||||||
level_nodes.sort_custom(func(a, b): return a.position.x < b.position.x)
|
|
||||||
|
|
||||||
# First pass: calculate connections for each node
|
|
||||||
var node_connections = {}
|
|
||||||
for node in level_nodes:
|
|
||||||
node_connections[node.id] = {
|
|
||||||
"above": [],
|
|
||||||
"below": []
|
|
||||||
}
|
|
||||||
|
|
||||||
# Find connections to this node
|
|
||||||
for conn in connections:
|
|
||||||
# Connection from above
|
|
||||||
if conn.to == node.id:
|
|
||||||
var from_node = null
|
|
||||||
for n in nodes:
|
|
||||||
if n.id == conn.from and n.level < node.level:
|
|
||||||
from_node = n
|
|
||||||
break
|
|
||||||
if from_node:
|
|
||||||
node_connections[node.id].above.append(from_node)
|
|
||||||
|
|
||||||
# Connection to below
|
|
||||||
elif conn.from == node.id:
|
|
||||||
var to_node = null
|
|
||||||
for n in nodes:
|
|
||||||
if n.id == conn.to and n.level > node.level:
|
|
||||||
to_node = n
|
|
||||||
break
|
|
||||||
if to_node:
|
|
||||||
node_connections[node.id].below.append(to_node)
|
|
||||||
|
|
||||||
# Second pass: adjust node positions based on connections
|
|
||||||
for node in level_nodes:
|
|
||||||
# Get this node's connections
|
|
||||||
var connections_above = node_connections[node.id].above
|
|
||||||
var connections_below = node_connections[node.id].below
|
|
||||||
|
|
||||||
# Calculate "pull" forces from connected nodes
|
|
||||||
var pull_x = 0.0
|
|
||||||
var total_pull = 0.0
|
|
||||||
|
|
||||||
# Look at connections to nodes above
|
|
||||||
for conn_node in connections_above:
|
|
||||||
pull_x += conn_node.position.x
|
|
||||||
total_pull += 1.0
|
|
||||||
|
|
||||||
# Look at connections to nodes below
|
|
||||||
for conn_node in connections_below:
|
|
||||||
pull_x += conn_node.position.x
|
|
||||||
total_pull += 1.0
|
|
||||||
|
|
||||||
# Adjust position based on pull if there are connections
|
|
||||||
if total_pull > 0:
|
|
||||||
# Calculate ideal x position (average of connected nodes)
|
|
||||||
var ideal_x = pull_x / total_pull
|
|
||||||
|
|
||||||
# Move slightly toward ideal position (don't move fully to maintain ordering)
|
|
||||||
node.position.x = node.position.x * 0.7 + ideal_x * 0.3
|
|
||||||
|
|
||||||
# Ensure minimum spacing between nodes
|
|
||||||
var min_spacing = 1.0 # Minimum horizontal distance between nodes
|
|
||||||
|
|
||||||
for i in range(1, level_nodes.size()):
|
|
||||||
var prev_node = level_nodes[i-1]
|
|
||||||
var curr_node = level_nodes[i]
|
|
||||||
|
|
||||||
# Check if nodes are too close
|
|
||||||
if curr_node.position.x - prev_node.position.x < min_spacing:
|
|
||||||
# Shift this node and all subsequent nodes to the right
|
|
||||||
var shift = min_spacing - (curr_node.position.x - prev_node.position.x)
|
|
||||||
|
|
||||||
for j in range(i, level_nodes.size()):
|
|
||||||
level_nodes[j].position.x += shift
|
|
||||||
|
|
||||||
return nodes
|
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ const NODE_SPACING_X = 150
|
||||||
const NODE_SPACING_Y = 120
|
const NODE_SPACING_Y = 120
|
||||||
const LINE_WIDTH = 3
|
const LINE_WIDTH = 3
|
||||||
const LINE_COLOR = Color(0.2, 0.2, 0.2)
|
const LINE_COLOR = Color(0.2, 0.2, 0.2)
|
||||||
const LINE_COLOR_CRIT = Color(0.9, 0.8, 0.2, 0.8)
|
|
||||||
const LINE_COLOR_SELECTED = Color(0.2, 0.6, 0.2)
|
const LINE_COLOR_SELECTED = Color(0.2, 0.6, 0.2)
|
||||||
const LINE_COLOR_ACCESSIBLE = Color(0.6, 0.6, 0.2)
|
const LINE_COLOR_ACCESSIBLE = Color(0.6, 0.6, 0.2)
|
||||||
|
|
||||||
|
|
@ -189,7 +188,6 @@ func display_map():
|
||||||
var map_height = max_y - min_y + NODE_SIZE.y * 2 + SCROLL_PADDING_TOP + SCROLL_PADDING_BOTTOM
|
var map_height = max_y - min_y + NODE_SIZE.y * 2 + SCROLL_PADDING_TOP + SCROLL_PADDING_BOTTOM
|
||||||
map_container.custom_minimum_size = Vector2(map_width, map_height)
|
map_container.custom_minimum_size = Vector2(map_width, map_height)
|
||||||
|
|
||||||
# Create padding nodes
|
|
||||||
# Create a padding node at the top
|
# Create a padding node at the top
|
||||||
var top_padding = Control.new()
|
var top_padding = Control.new()
|
||||||
top_padding.custom_minimum_size = Vector2(map_width, SCROLL_PADDING_TOP)
|
top_padding.custom_minimum_size = Vector2(map_width, SCROLL_PADDING_TOP)
|
||||||
|
|
@ -213,46 +211,24 @@ func display_map():
|
||||||
right_padding.position = Vector2(map_width - SCROLL_PADDING_RIGHT, 0)
|
right_padding.position = Vector2(map_width - SCROLL_PADDING_RIGHT, 0)
|
||||||
map_container.add_child(right_padding)
|
map_container.add_child(right_padding)
|
||||||
|
|
||||||
# Find the optimal path from start to end for highlighting
|
# Draw connections first (so they're behind nodes)
|
||||||
var critical_path = find_unique_path_to_end()
|
|
||||||
var critical_connections = []
|
|
||||||
|
|
||||||
# Convert path nodes to connections
|
# Modify your display_map function to use the curved connections
|
||||||
if critical_path.size() > 1:
|
# Replace your loop that draws connections with this:
|
||||||
for i in range(critical_path.size() - 1):
|
|
||||||
critical_connections.append({
|
|
||||||
"from": critical_path[i],
|
|
||||||
"to": critical_path[i + 1]
|
|
||||||
})
|
|
||||||
|
|
||||||
# First draw non-critical connections (so critical ones are on top)
|
# Draw connections first (so they're behind nodes)
|
||||||
for connection in map_connections:
|
for connection in map_connections:
|
||||||
var is_critical = false
|
var from_node = get_node_by_id(connection.from)
|
||||||
for crit_conn in critical_connections:
|
var to_node = get_node_by_id(connection.to)
|
||||||
if connection.from == crit_conn.from and connection.to == crit_conn.to:
|
|
||||||
is_critical = true
|
|
||||||
break
|
|
||||||
|
|
||||||
if not is_critical:
|
|
||||||
var from_node = get_node_by_id(connection.from)
|
|
||||||
var to_node = get_node_by_id(connection.to)
|
|
||||||
|
|
||||||
if from_node and to_node:
|
|
||||||
draw_curved_connection(from_node, to_node, false)
|
|
||||||
|
|
||||||
# Then draw critical connections on top
|
|
||||||
for crit_conn in critical_connections:
|
|
||||||
var from_node = get_node_by_id(crit_conn.from)
|
|
||||||
var to_node = get_node_by_id(crit_conn.to)
|
|
||||||
|
|
||||||
if from_node and to_node:
|
if from_node and to_node:
|
||||||
draw_curved_connection(from_node, to_node, true)
|
draw_curved_connection(from_node, to_node)
|
||||||
|
|
||||||
|
|
||||||
# Draw nodes
|
# Draw nodes
|
||||||
for node_data in map_nodes:
|
for node_data in map_nodes:
|
||||||
draw_node(node_data)
|
draw_node(node_data)
|
||||||
|
|
||||||
|
|
||||||
func get_node_by_id(id):
|
func get_node_by_id(id):
|
||||||
for node in map_nodes:
|
for node in map_nodes:
|
||||||
if node.id == id:
|
if node.id == id:
|
||||||
|
|
@ -365,12 +341,11 @@ func _on_back_button_pressed():
|
||||||
emit_signal("back_pressed")
|
emit_signal("back_pressed")
|
||||||
visible = false
|
visible = false
|
||||||
|
|
||||||
func draw_curved_connection(from_node, to_node, isCritPath):
|
|
||||||
|
func draw_curved_connection(from_node, to_node):
|
||||||
var line = Line2D.new()
|
var line = Line2D.new()
|
||||||
line.width = LINE_WIDTH
|
line.width = LINE_WIDTH
|
||||||
line.default_color = LINE_COLOR
|
line.default_color = LINE_COLOR
|
||||||
# if isCritPath:
|
|
||||||
# line.default_color = LINE_COLOR_CRIT
|
|
||||||
if from_node.id == current_node.id:
|
if from_node.id == current_node.id:
|
||||||
line.default_color = LINE_COLOR_ACCESSIBLE
|
line.default_color = LINE_COLOR_ACCESSIBLE
|
||||||
elif traversed_map.has(to_node.id) and (traversed_map.has(from_node.id) || from_node.id == 0):
|
elif traversed_map.has(to_node.id) and (traversed_map.has(from_node.id) || from_node.id == 0):
|
||||||
|
|
@ -380,92 +355,42 @@ func draw_curved_connection(from_node, to_node, isCritPath):
|
||||||
var start_pos = from_node.position * Vector2(NODE_SPACING_X, NODE_SPACING_Y) + Vector2(SCROLL_PADDING_LEFT, SCROLL_PADDING_TOP)
|
var start_pos = from_node.position * Vector2(NODE_SPACING_X, NODE_SPACING_Y) + Vector2(SCROLL_PADDING_LEFT, SCROLL_PADDING_TOP)
|
||||||
var end_pos = to_node.position * Vector2(NODE_SPACING_X, NODE_SPACING_Y) + Vector2(SCROLL_PADDING_LEFT, SCROLL_PADDING_TOP)
|
var end_pos = to_node.position * Vector2(NODE_SPACING_X, NODE_SPACING_Y) + Vector2(SCROLL_PADDING_LEFT, SCROLL_PADDING_TOP)
|
||||||
|
|
||||||
# Find all connections from the same starting node to help with path separation
|
|
||||||
var sibling_connections = []
|
|
||||||
for conn in map_connections:
|
|
||||||
if conn.from == from_node.id and conn.to != to_node.id:
|
|
||||||
var other_node = get_node_by_id(conn.to)
|
|
||||||
if other_node:
|
|
||||||
sibling_connections.append(other_node)
|
|
||||||
|
|
||||||
# Count how many connections this source node has
|
|
||||||
var connection_count = sibling_connections.size() + 1 # +1 for this connection
|
|
||||||
|
|
||||||
# Determine position of this connection among siblings (for consistent curve offsets)
|
|
||||||
var connection_index = 0
|
|
||||||
for i in range(sibling_connections.size()):
|
|
||||||
if sibling_connections[i].id < to_node.id:
|
|
||||||
connection_index += 1
|
|
||||||
|
|
||||||
# Create a bezier curve path
|
# Create a bezier curve path
|
||||||
var points = []
|
var points = []
|
||||||
|
|
||||||
# Calculate direction vector between nodes
|
# If nodes are on different levels (y positions)
|
||||||
var direction = (end_pos - start_pos).normalized()
|
|
||||||
var perp_direction = Vector2(-direction.y, direction.x) # Perpendicular to direction
|
|
||||||
|
|
||||||
# Different curve handling based on node positions
|
|
||||||
if from_node.position.y != to_node.position.y:
|
if from_node.position.y != to_node.position.y:
|
||||||
# Vertical/diagonal connection between different levels
|
# Calculate control points for a curve
|
||||||
|
var mid_y = (start_pos.y + end_pos.y) / 2
|
||||||
|
var control_offset = NODE_SPACING_Y * 0.5 * (1 + abs(from_node.position.x - to_node.position.x) * 0.2)
|
||||||
|
|
||||||
# Calculate curve offset based on connection count and index
|
var control1 = Vector2(start_pos.x, start_pos.y + control_offset)
|
||||||
var curve_offset = 0.0
|
var control2 = Vector2(end_pos.x, end_pos.y - control_offset)
|
||||||
if connection_count > 1:
|
|
||||||
# Calculate spacing between -0.5 and 0.5
|
|
||||||
var spacing = 1.0 / (connection_count + 1)
|
|
||||||
curve_offset = (connection_index + 1) * spacing - 0.5
|
|
||||||
curve_offset *= NODE_SPACING_X * 0.8 # Scale by node spacing
|
|
||||||
|
|
||||||
# Generate control points with horizontal offset
|
# Add more points for a smoother curve
|
||||||
var dist = (end_pos - start_pos).length()
|
for i in range(11): # 0.0, 0.1, 0.2, ..., 1.0
|
||||||
var mid_point = start_pos + (end_pos - start_pos) * 0.5
|
var t = i / 10.0
|
||||||
|
|
||||||
# Apply the offset perpendicular to the path direction
|
|
||||||
mid_point += perp_direction * curve_offset
|
|
||||||
|
|
||||||
# Calculate control points with vertical offsets
|
|
||||||
var control1 = start_pos + direction * (dist * 0.25) + perp_direction * curve_offset * 1.5
|
|
||||||
var control2 = end_pos - direction * (dist * 0.25) + perp_direction * curve_offset * 1.5
|
|
||||||
|
|
||||||
# Generate points for a cubic bezier curve
|
|
||||||
for i in range(21): # More points for a smoother curve
|
|
||||||
var t = i / 20.0
|
|
||||||
var point = cubic_bezier(start_pos, control1, control2, end_pos, t)
|
var point = cubic_bezier(start_pos, control1, control2, end_pos, t)
|
||||||
points.append(point)
|
points.append(point)
|
||||||
else:
|
else:
|
||||||
# Horizontal connection (nodes at same level)
|
# For nodes on the same level, use a simple curved path
|
||||||
var curve_height = NODE_SPACING_Y * 0.4 # Base curve height
|
|
||||||
|
|
||||||
# Adjust curve height based on connection position
|
|
||||||
if connection_count > 1:
|
|
||||||
curve_height *= (1.0 + 0.3 * connection_index)
|
|
||||||
|
|
||||||
# Determine curve direction (up or down)
|
|
||||||
var curve_up = true
|
|
||||||
|
|
||||||
# If nodes are further apart horizontally, make a higher curve
|
|
||||||
var x_distance = abs(from_node.position.x - to_node.position.x)
|
|
||||||
if x_distance > 1:
|
|
||||||
curve_height *= (1.0 + 0.2 * x_distance)
|
|
||||||
|
|
||||||
# Calculate mid point with vertical offset
|
|
||||||
var mid_x = (start_pos.x + end_pos.x) / 2
|
var mid_x = (start_pos.x + end_pos.x) / 2
|
||||||
var mid_y = null
|
var mid_y = start_pos.y
|
||||||
if curve_up:
|
var curve_height = NODE_SPACING_Y * 0.3 * (1 + abs(from_node.position.x - to_node.position.x) * 0.1)
|
||||||
mid_y = start_pos - curve_height
|
|
||||||
else :
|
if to_node.position.x > from_node.position.x:
|
||||||
mid_y = start_pos - (-curve_height)
|
mid_y -= curve_height
|
||||||
|
else:
|
||||||
|
mid_y += curve_height
|
||||||
|
|
||||||
var mid_point = Vector2(mid_x, mid_y)
|
var mid_point = Vector2(mid_x, mid_y)
|
||||||
|
|
||||||
# Generate points for a quadratic bezier curve
|
# Create a quadratic bezier curve with just 3 points
|
||||||
for i in range(21):
|
for i in range(11):
|
||||||
var t = i / 20.0
|
var t = i / 10.0
|
||||||
var point = quadratic_bezier(start_pos, mid_point, end_pos, t)
|
var point = quadratic_bezier(start_pos, mid_point, end_pos, t)
|
||||||
points.append(point)
|
points.append(point)
|
||||||
|
|
||||||
# Apply anti-aliasing
|
|
||||||
line.antialiased = true
|
|
||||||
|
|
||||||
# Add the points to the line
|
# Add the points to the line
|
||||||
for point in points:
|
for point in points:
|
||||||
line.add_point(point)
|
line.add_point(point)
|
||||||
|
|
@ -474,50 +399,6 @@ func draw_curved_connection(from_node, to_node, isCritPath):
|
||||||
map_container.add_child(line)
|
map_container.add_child(line)
|
||||||
connection_lines.append(line)
|
connection_lines.append(line)
|
||||||
|
|
||||||
func find_unique_path_to_end():
|
|
||||||
# Find start and end nodes
|
|
||||||
var start_node = null
|
|
||||||
var end_node = null
|
|
||||||
|
|
||||||
for node in map_nodes:
|
|
||||||
if node.type == RoomType.STARTING:
|
|
||||||
start_node = node
|
|
||||||
elif node.type == RoomType.FINAL:
|
|
||||||
end_node = node
|
|
||||||
|
|
||||||
if not start_node or not end_node:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Build a graph representation
|
|
||||||
var graph = {}
|
|
||||||
for node in map_nodes:
|
|
||||||
graph[node.id] = []
|
|
||||||
|
|
||||||
for conn in map_connections:
|
|
||||||
if graph.has(conn.from):
|
|
||||||
graph[conn.from].append(conn.to)
|
|
||||||
|
|
||||||
# Use BFS to find the shortest path
|
|
||||||
var queue = [[start_node.id]] # Queue of paths
|
|
||||||
var visited = {start_node.id: true}
|
|
||||||
|
|
||||||
while queue.size() > 0:
|
|
||||||
var path = queue.pop_front()
|
|
||||||
var node_id = path[path.size() - 1]
|
|
||||||
|
|
||||||
if node_id == end_node.id:
|
|
||||||
return path # Return the full path
|
|
||||||
|
|
||||||
if graph.has(node_id):
|
|
||||||
for neighbor in graph[node_id]:
|
|
||||||
if not visited.has(neighbor):
|
|
||||||
visited[neighbor] = true
|
|
||||||
var new_path = path.duplicate()
|
|
||||||
new_path.append(neighbor)
|
|
||||||
queue.append(new_path)
|
|
||||||
|
|
||||||
return [] # No path found
|
|
||||||
|
|
||||||
# Helper function for cubic bezier curve calculation
|
# Helper function for cubic bezier curve calculation
|
||||||
func cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float) -> Vector2:
|
func cubic_bezier(p0: Vector2, p1: Vector2, p2: Vector2, p3: Vector2, t: float) -> Vector2:
|
||||||
var q0 = p0.lerp(p1, t)
|
var q0 = p0.lerp(p1, t)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue