Created
February 17, 2018 16:51
-
-
Save pwab/fd8bb562604f1d2b967efb6d5e80487d to your computer and use it in GitHub Desktop.
[Godot 3] Line2D intersection points implemented with CollisionShapes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This implementation is based on CollisionShapes and RayCasts. | |
# It has one major flaw: It detects intersections of different lines and not only of the line itself. | |
# I'm pretty sure this can be circumvented by changing the collision_layers of each line | |
# or by keeping track of each line and then excluding their collision_bodies when ray casting | |
# or by removing all the collision bodies when intersection_scanning is done | |
extends Line2D | |
var intersections = [] # Keep track of every intersection | |
var segments = [] # Keep track of every segment | |
var collision_shapes = [] # Just a hack to keep the included information of the segment-shapes | |
func _ready(): | |
# If the script is attached on a line created in the editor | |
add_collision_areas() | |
func add_collision_areas(): | |
# The line is devided into segments | |
for i in range(points.size()-1): | |
var segment = create_segment(points[i], points[i+1], i) | |
segments.append(segment) | |
add_child(segment) | |
# The areas shape_entered signal is used to get the information that it collides with something | |
segment.connect("area_shape_entered", self, "_on_segment_area_shape_entered") | |
func create_segment(from, to, name=""): | |
var segment = Area2D.new() | |
# The naming is optional but looks cleaner in the remote inspector | |
segment.name = "segment" + str(name) | |
segment.position = from | |
var collision = CollisionShape2D.new() | |
collision.shape = SegmentShape2D.new() | |
# Local positions (relative to shapes origin) | |
collision.shape.a = Vector2(0,0) | |
collision.shape.b = to - from | |
segment.add_child(collision) | |
# We want to keep track of the collision_shapes for later use (just a hack) | |
collision_shapes.append(collision) | |
return segment | |
func _draw(): | |
# Draw the starting points of each segment | |
for segment in get_children(): | |
draw_circle(segment.position, 3, ColorN("Black", 0.5)) | |
# Draw the intersection points if there are any | |
for intersection in intersections: | |
draw_circle(intersection[2], 7, ColorN("Red", 1)) | |
func _on_segment_area_shape_entered(segment_id, segment, segment_shape, self_shape): | |
# This is the heart of the script | |
var overlapping_segments = segment.get_overlapping_areas() | |
# Define the segments neighbours (previous and next segment) | |
# This could be refactored with a linked-list... | |
var segment_number = segments.find(segment) | |
var neighbour_segments = [] | |
if segment_number == 0: | |
# The first segment only has one neigbour: the next one | |
neighbour_segments = [segments[segment_number + 1]] | |
elif segment_number == segments.size()-1: | |
# The last segment only has one neigbour: the previous one | |
neighbour_segments = [segments[segment_number - 1]] | |
else: | |
neighbour_segments = [segments[segment_number - 1], segments[segment_number + 1]] | |
for overlapping_segment in overlapping_segments: | |
# Neighbour segments have nothing to do with the intersection | |
if not overlapping_segment in neighbour_segments: | |
# We have a real overlapping here! | |
# Now shoot a ray to get the intersection point | |
var ray = RayCast2D.new() | |
# Exclude the segment itself (the one which is overlapped) | |
ray.exclude_parent = true | |
# Exclude the neighbours | |
for neighbour_segment in neighbour_segments: | |
ray.add_exception(neighbour_segment) | |
# Send the raycast along the segments length | |
# Here we need our little shapes hack... | |
ray.cast_to = collision_shapes[segment_number].shape.b | |
# And here we go: pew pew | |
segment.add_child(ray) | |
ray.enabled = true | |
var intersection_point | |
var intersection_point_found = false | |
# This sound funny at first but we have to do this if there are multiple | |
# intersections on one line. We have to be sure to get the right one. | |
while not intersection_point_found: | |
# The raycast should be updated immediately | |
ray.force_raycast_update() | |
var collider = ray.get_collider() | |
# A straight line has no collisions | |
if ray.is_colliding(): | |
if collider != overlapping_segment: | |
# Here we remove the segments which overlap the line but | |
# aren't interesting for us right now. | |
ray.add_exception(collider) | |
else: | |
# RayCast point positions are global | |
intersection_point = to_local(ray.get_collision_point()) | |
intersection_point_found = true | |
# Organise the intersections | |
var intersection = [segment, overlapping_segment, intersection_point] | |
if not intersection in intersections: | |
intersections.append(intersection) | |
update() | |
else: | |
# Prevent endless loop | |
break | |
# We don't need the raycast anymore | |
ray.queue_free() | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# For this implementation you will need to activate emulate_touchscreen | |
# See Project Settings -> Display -> Window -> Handheld -> emulate_touchscreen | |
# | |
# (I wanted to do it with: | |
# ProjectSettings.set_setting("display/window/handheld/emulate_touchscreen", true) | |
# but it did not work...) | |
extends Node2D | |
var active_line | |
var line_script = preload("res://line.gd") | |
func _input(event): | |
# Touch/Mouse Button | |
if event is InputEventMouseButton: | |
# Button down | |
if event.is_pressed(): | |
# Create new line | |
active_line = Line2D.new() | |
active_line.position = event.position | |
active_line.points = [Vector2(0,0)] | |
active_line.default_color = Color(randf(), randf(), randf(), 1) | |
add_child(active_line) | |
# Button up | |
else: | |
# Let's do the magic | |
active_line.set_script(line_script) | |
active_line.add_collision_areas() | |
if event is InputEventScreenDrag: | |
# Add some more points to the line when dragging | |
var points = active_line.points | |
points.append(event.position - active_line.position) | |
active_line.points = points |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment