Skip to content

Instantly share code, notes, and snippets.

@pwab
Created February 17, 2018 16:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save pwab/fd8bb562604f1d2b967efb6d5e80487d to your computer and use it in GitHub Desktop.
Save pwab/fd8bb562604f1d2b967efb6d5e80487d to your computer and use it in GitHub Desktop.
[Godot 3] Line2D intersection points implemented with CollisionShapes
# 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()
# 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