diff --git a/Game/addons/Trail/LICENSE b/Game/addons/Trail/LICENSE
new file mode 100644
index 0000000..bfe8154
--- /dev/null
+++ b/Game/addons/Trail/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 Oussama
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/Game/addons/Trail/plugin.cfg b/Game/addons/Trail/plugin.cfg
new file mode 100644
index 0000000..7302aa3
--- /dev/null
+++ b/Game/addons/Trail/plugin.cfg
@@ -0,0 +1,7 @@
+[plugin]
+
+name="Trail System"
+description="Advanced 2D/3D Trail system."
+author="Oussama BOUKHELF"
+version="0.1"
+script="plugin.gd"
diff --git a/Game/addons/Trail/plugin.gd b/Game/addons/Trail/plugin.gd
new file mode 100644
index 0000000..4c96d31
--- /dev/null
+++ b/Game/addons/Trail/plugin.gd
@@ -0,0 +1,12 @@
+tool
+extends EditorPlugin
+
+func _enter_tree():
+ add_custom_type("Trail3D","ImmediateGeometry",preload("res://addons/Trail/trail_3d.gd"),preload("res://addons/Trail/trail3d_icon.svg"))
+ add_custom_type("Trail2D","Line2D",preload("res://addons/Trail/trail_2d.gd"),preload("res://addons/Trail/trail2d_icon.svg"))
+ pass
+
+func _exit_tree():
+ remove_custom_type("Trail3D")
+ remove_custom_type("Trail2D")
+ pass
diff --git a/Game/addons/Trail/trail2d_icon.svg b/Game/addons/Trail/trail2d_icon.svg
new file mode 100644
index 0000000..69ba4a2
--- /dev/null
+++ b/Game/addons/Trail/trail2d_icon.svg
@@ -0,0 +1,35 @@
+
+
diff --git a/Game/addons/Trail/trail2d_icon.svg.import b/Game/addons/Trail/trail2d_icon.svg.import
new file mode 100644
index 0000000..e2737cb
--- /dev/null
+++ b/Game/addons/Trail/trail2d_icon.svg.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/trail2d_icon.svg-607ea772beb499297607579128e70a1c.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Addons/addons/Trail/trail2d_icon.svg"
+dest_files=[ "res://.import/trail2d_icon.svg-607ea772beb499297607579128e70a1c.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/Game/addons/Trail/trail3d_icon.svg b/Game/addons/Trail/trail3d_icon.svg
new file mode 100644
index 0000000..c6ff9ef
--- /dev/null
+++ b/Game/addons/Trail/trail3d_icon.svg
@@ -0,0 +1,83 @@
+
+
+
+
diff --git a/Game/addons/Trail/trail3d_icon.svg.import b/Game/addons/Trail/trail3d_icon.svg.import
new file mode 100644
index 0000000..05cf7ad
--- /dev/null
+++ b/Game/addons/Trail/trail3d_icon.svg.import
@@ -0,0 +1,34 @@
+[remap]
+
+importer="texture"
+type="StreamTexture"
+path="res://.import/trail3d_icon.svg-8b210038e0dc69dfd0c78cba261254df.stex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://Addons/addons/Trail/trail3d_icon.svg"
+dest_files=[ "res://.import/trail3d_icon.svg-8b210038e0dc69dfd0c78cba261254df.stex" ]
+
+[params]
+
+compress/mode=0
+compress/lossy_quality=0.7
+compress/hdr_mode=0
+compress/bptc_ldr=0
+compress/normal_map=0
+flags/repeat=0
+flags/filter=true
+flags/mipmaps=false
+flags/anisotropic=false
+flags/srgb=2
+process/fix_alpha_border=true
+process/premult_alpha=false
+process/HDR_as_SRGB=false
+process/invert_color=false
+stream=false
+size_limit=0
+detect_3d=true
+svg/scale=1.0
diff --git a/Game/addons/Trail/trail_2d.gd b/Game/addons/Trail/trail_2d.gd
new file mode 100644
index 0000000..71f931e
--- /dev/null
+++ b/Game/addons/Trail/trail_2d.gd
@@ -0,0 +1,77 @@
+"""
+Author: Oussama BOUKHELF
+License: MIT
+Version: 0.1
+Email: o.boukhelf@gmail.com
+Description: Advanced 2D/3D Trail system.
+Note: This is a simple implementation, I will update it later on.
+"""
+
+extends Line2D
+
+
+export(bool) var emit := true
+export(float) var lifetime := 0.5
+export(float) var distance := 20.0
+export(int) var segments := 20
+var target
+
+var trail_points := []
+var offset := Vector2()
+
+
+class Point:
+ var position := Vector2()
+ var age := 0.0
+
+ func _init(position :Vector2, age :float) -> void:
+ self.position = position
+ self.age = age
+
+ func update(delta :float, points :Array) -> void:
+ self.age -= delta
+ if self.age <= 0:
+ points.erase(self)
+
+
+func _ready():
+ offset = position
+ show_behind_parent = true
+ target = get_parent()
+ clear_points()
+ set_as_toplevel(true)
+ position = Vector2()
+
+func _emit():
+ var _position :Vector2 = target.global_transform.origin + offset
+ var point = Point.new(_position, lifetime)
+
+ if trail_points.size() < 1:
+ trail_points.push_back(point)
+ return
+
+ if trail_points[-1].position.distance_squared_to(_position) > distance*distance:
+ trail_points.push_back(point)
+
+ update_points()
+
+func update_points() -> void:
+ var delta = get_process_delta_time()
+
+ if trail_points.size() > segments:
+ trail_points.invert()
+ trail_points.resize(segments)
+ trail_points.invert()
+
+ clear_points()
+ for point in trail_points:
+ point.update(delta, trail_points)
+
+# if point:
+ add_point(point.position)
+
+
+func _process(delta):
+ if emit:
+ _emit()
+
diff --git a/Game/addons/Trail/trail_3d.gd b/Game/addons/Trail/trail_3d.gd
new file mode 100644
index 0000000..09e72d0
--- /dev/null
+++ b/Game/addons/Trail/trail_3d.gd
@@ -0,0 +1,349 @@
+"""
+Author: Oussama BOUKHELF
+License: MIT
+Version: 0.1
+Email: o.boukhelf@gmail.com
+Description: Advanced 2D/3D Trail system.
+"""
+
+extends ImmediateGeometry
+
+
+export(bool) var emit := true
+export(float) var distance := 0.1
+export(int, 0, 99999) var segments := 20
+export(float) var lifetime := 0.5
+export(float, 0, 99999) var base_width := 0.5
+export(bool) var tiled_texture := false
+export(int) var tiling := 0
+export(Curve) var width_profile
+export(Gradient) var color_gradient
+export(int, 0, 3) var smoothing_iterations := 0
+export(float, 0, 0.5) var smoothing_ratio := 0.25
+export(String, "View", "Normal", "Object") var alignment := "View"
+export(String, "X", "Y", "Z") var axe := "Y"
+export(bool) var show_wireframe := false
+export(Color) var wireframe_color := Color(1, 1, 1, 1)
+export(float, 0, 100, 0.1) var wire_line_width := 1.0
+
+var points := []
+var color := Color(1, 1, 1, 1)
+var always_update = false
+
+var _target :Spatial
+var _wire_obj :ImmediateGeometry = ImmediateGeometry.new()
+var _wire_mat :SpatialMaterial = SpatialMaterial.new()
+var _A: Point
+var _B: Point
+var _C: Point
+var _temp_segment := []
+var _points := []
+
+
+class Point:
+ """
+ Class for the 3D point that will be emmited when the object move.
+ """
+ var transform := Transform()
+ var age := 0.0
+
+ func _init(transform :Transform, age :float) -> void:
+ self.transform = transform
+ self.age = age
+
+ func update(delta :float, points :Array) -> void:
+ self.age -= delta
+ if self.age <= 0:
+ points.erase(self)
+
+
+func add_point(transform :Transform) -> void:
+ """
+ Add a point to the list of points.
+ This function is called programmatically.
+ """
+ var point = Point.new(transform, lifetime)
+ points.push_back(point)
+
+
+func clear_points() -> void:
+ """
+ Cleat points list.
+ This function is called programmatically.
+ """
+ points.clear()
+
+
+func _prepare_geometry(point_prev :Point, point :Point, half_width :float, factor :float) -> Array:
+ """
+ Generate and transform the trail geometry based on the path points that
+ the target object generated.
+ """
+ var normal := Vector3()
+
+ if alignment == "View":
+ if get_viewport().get_camera():
+ var cam_pos = get_viewport().get_camera().get_global_transform().origin
+ var path_direction :Vector3 = (point.transform.origin - point_prev.transform.origin).normalized()
+ normal = (cam_pos - (point.transform.origin + point_prev.transform.origin)/2).cross(path_direction).normalized()
+ else:
+ print("There is no camera in the scene")
+
+ elif alignment == "Normal":
+ if axe == "X":
+ normal = point.transform.basis.x.normalized()
+ elif axe == "Y":
+ normal = point.transform.basis.y.normalized()
+ else:
+ normal = point.transform.basis.z.normalized()
+
+ else:
+ if axe == "X":
+ normal = _target.global_transform.basis.x.normalized()
+ elif axe == "Y":
+ normal = _target.global_transform.basis.y.normalized()
+ else:
+ normal = _target.global_transform.basis.z.normalized()
+
+ var width = half_width
+ if width_profile:
+ width = half_width * width_profile.interpolate(factor)
+
+ var p1 = point.transform.origin-normal*width
+ var p2 = point.transform.origin+normal*width
+ return [p1, p2]
+
+
+func render(update := false) -> void:
+ """
+ Render the points.
+ This function is called programmatically.
+ """
+ if update:
+ always_update = update
+ else:
+ _render_geometry(points)
+
+
+func _render_realtime() -> void:
+ """
+ Render the points every frame when "emit" is set to True.
+ """
+ var render_points = _points+_temp_segment+[_C]
+ _render_geometry(render_points)
+
+
+func _render_geometry(source: Array) -> void:
+ """
+ Base function for rendering the generated geometry to the screen.
+ Renders the trail, and the wireframe if set in parameters.
+ """
+ var points_count = source.size()
+ if points_count < 2:
+ return
+
+ # The following section is a hack to make orientation "view" work.
+ # but it may cause an artifact at the end of the trail.
+ # You can use transparency in the gradient to hide it for now.
+ var _d :Vector3 = source[0].transform.origin - source[1].transform.origin
+ var _t :Transform = source[0].transform
+ _t.origin = _t.origin + _d
+ var point = Point.new(_t, source[0].age)
+ var to_be_rendered = [point]+source
+ points_count += 1
+
+ var half_width :float = base_width/2.0
+ var wire_points = []
+ var u := 0.0
+
+ clear()
+ begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, null)
+ for i in range(1, points_count):
+ var factor :float = float(i)/(points_count-1)
+
+ var _color = color
+ if color_gradient:
+ _color = color * color_gradient.interpolate(1.0-factor)
+
+ var vertices = _prepare_geometry(to_be_rendered[i-1], to_be_rendered[i], half_width, 1.0-factor)
+ if tiled_texture:
+ if tiling > 0:
+ factor *= tiling
+ else:
+ var travel = (to_be_rendered[i-1].transform.origin - to_be_rendered[i].transform.origin).length()
+ u += travel/base_width
+ factor = u
+
+ set_color(_color)
+ set_uv(Vector2(factor, 0))
+ add_vertex(vertices[0])
+ set_uv(Vector2(factor, 1))
+ add_vertex(vertices[1])
+
+ if show_wireframe:
+ wire_points += vertices
+ end()
+
+ # For some reason I had to add a second Meshinstance as a child to make the
+ # wireframe to render, normally you can just draw on top.
+ if show_wireframe:
+ _wire_mat.params_line_width = wire_line_width
+ _wire_obj.clear()
+ _wire_obj.begin(Mesh.PRIMITIVE_LINE_STRIP, null)
+ _wire_obj.set_color(wireframe_color)
+ _wire_obj.set_uv(Vector2(0.5, 0.5))
+ for i in range(1, wire_points.size()-2, 2):
+ ## order: i-1, i+1, i, i+2
+ _wire_obj.add_vertex(wire_points[i-1])
+ _wire_obj.add_vertex(wire_points[i+1])
+ _wire_obj.add_vertex(wire_points[i])
+ _wire_obj.add_vertex(wire_points[i+2])
+ _wire_obj.end()
+
+
+func _update_points() -> void:
+ """
+ Update ages of the points and remove extra ones.
+ """
+ var delta = get_process_delta_time()
+
+ _A.update(delta, _points)
+ _B.update(delta, _points)
+ _C.update(delta, _points)
+ for point in _points:
+ point.update(delta, _points)
+
+ var size_multiplier = [1, 2, 4, 6][smoothing_iterations]
+ var max_points_count :int = segments * size_multiplier
+ if _points.size() > max_points_count:
+ _points.invert()
+ _points.resize(max_points_count)
+ _points.invert()
+
+
+func smooth() -> void:
+ """
+ Smooth the given path.
+ This function is called programmatically.
+ """
+ if points.size() < 3:
+ return
+
+ var output := [points[0]]
+ for i in range(1, points.size()-1):
+ output += _chaikin(points[i-1], points[i], points[i+1])
+
+ output.push_back(points[-1])
+ points = output
+
+
+func _chaikin(A, B, C) -> Array:
+ """
+ Chaikin’s smoothing Algorithm
+ https://www.cs.unc.edu/~dm/UNC/COMP258/LECTURES/Chaikins-Algorithm.pdf
+
+ Ps: I could have avoided a lot of trouble automating this function using FOR loop,
+ but I opted for a more optimized approach which maybe helpful when dealing with a
+ large amount of objects.
+ """
+ if smoothing_iterations == 0:
+ return [B]
+
+ var out := []
+ var x :float = smoothing_ratio
+
+ # Pre-calculate some parameters to improve performance
+ var xi :float = (1-x)
+ var xpa :float = (x*x-2*x+1)
+ var xpb :float = (-x*x+2*x)
+ # transforms
+ var A1_t :Transform = A.transform.interpolate_with(B.transform, xi)
+ var B1_t :Transform = B.transform.interpolate_with(C.transform, x)
+ # ages
+ var A1_a :float = lerp(A.age, B.age, xi)
+ var B1_a :float = lerp(B.age, C.age, x)
+
+ if smoothing_iterations == 1:
+ out = [Point.new(A1_t, A1_a), Point.new(B1_t, B1_a)]
+
+ else:
+ # transforms
+ var A2_t :Transform = A.transform.interpolate_with(B.transform, xpa)
+ var B2_t :Transform = B.transform.interpolate_with(C.transform, xpb)
+ var A11_t :Transform = A1_t.interpolate_with(B1_t, x)
+ var B11_t :Transform = A1_t.interpolate_with(B1_t, xi)
+ # ages
+ var A2_a :float = lerp(A.age, B.age, xpa)
+ var B2_a :float = lerp(B.age, C.age, xpb)
+ var A11_a :float = lerp(A1_a, B1_a, x)
+ var B11_a :float = lerp(A1_a, B1_a, xi)
+
+ if smoothing_iterations == 2:
+ out += [Point.new(A2_t, A2_a), Point.new(A11_t, A11_a),
+ Point.new(B11_t, B11_a), Point.new(B2_t, B2_a)]
+ elif smoothing_iterations == 3:
+ # transforms
+ var A12_t :Transform = A1_t.interpolate_with(B1_t, xpb)
+ var B12_t :Transform = A1_t.interpolate_with(B1_t, xpa)
+ var A121_t :Transform = A11_t.interpolate_with(A2_t, x)
+ var B121_t :Transform = B11_t.interpolate_with(B2_t, x)
+ # ages
+ var A12_a :float = lerp(A1_a, B1_a, xpb)
+ var B12_a :float = lerp(A1_a, B1_a, xpa)
+ var A121_a :float = lerp(A11_a, A2_a, x)
+ var B121_a :float = lerp(B11_a, B2_a, x)
+ out += [Point.new(A2_t, A2_a), Point.new(A121_t, A121_a), Point.new(A12_t, A12_a),
+ Point.new(B12_t, B12_a), Point.new(B121_t, B121_a), Point.new(B2_t, B2_a)]
+
+ return out
+
+
+func _emit(delta) -> void:
+ """
+ Adding points to be rendered, called every frame when "emit" is set to True.
+ """
+ var _transform :Transform = _target.global_transform
+
+ var point = Point.new(_transform, lifetime)
+ if not _A:
+ _A = point
+ return
+ elif not _B:
+ _A.update(delta, _points)
+ _B = point
+ return
+
+ if _B.transform.origin.distance_squared_to(_transform.origin) >= distance*distance:
+ _A = _B
+ _B = point
+ _points += _temp_segment
+
+ _C = point
+
+ _update_points()
+ _temp_segment = _chaikin(_A, _B, _C)
+ _render_realtime()
+
+
+func _ready() -> void:
+ _target = get_parent()
+
+ _wire_mat.flags_unshaded = true
+ _wire_mat.flags_use_point_size = true
+ _wire_mat.vertex_color_use_as_albedo = true
+ _wire_mat.params_line_width = 10.0
+ _wire_obj.material_override = _wire_mat
+ add_child(_wire_obj)
+
+ set_as_toplevel(true)
+ global_transform = Transform()
+
+
+func _process(delta) -> void:
+ if emit:
+ _emit(delta)
+
+ elif always_update:
+ # This is needed for alignment == view, so it can be updated every frame.
+ _render_geometry(points)
+
diff --git a/Game/addons/Trail/trail_3d_v1.gd b/Game/addons/Trail/trail_3d_v1.gd
new file mode 100644
index 0000000..312e920
--- /dev/null
+++ b/Game/addons/Trail/trail_3d_v1.gd
@@ -0,0 +1,194 @@
+#tool
+extends ImmediateGeometry
+
+export(bool) var emit = true
+export(float) var max_distance = 0.5
+export(int, 0, 99999) var segments = 20
+export(float) var life_time = 5.0
+export(float, 0, 99999) var base_width = 1.0
+export(bool) var tiled_texture = false
+export(int) var tiling = 0
+export(Curve) var width_profile
+export(Curve) var width_over_time
+export(Gradient) var color_gradient
+export(float, 0, 0.5) var smoothing_ratio = 0.2
+export(int, 4) var smoothing_iterations = 1
+export(String, "View", "Motion", "Object") var alignment = "View"
+export(String, "Idle", "Fixed") var prcess_mode = "Idle"
+export(bool) var show_wireframe = false
+export(Color) var wireframe_color = Color(1, 1, 1)
+
+var target
+var path_points = []
+
+
+class Point:
+ var position = Vector3()
+ var normal = Vector3()
+ var age = 0
+
+ func _init(position, normal, age):
+ self.position = position
+ self.normal = normal
+ self.age = age
+
+ func update(delta):
+ age -= delta
+
+func _ready():
+ set_as_toplevel(true)
+ target = get_parent()
+ global_transform = Transform()
+
+func _process(delta):
+ if emit:
+ add_point()
+ update_points()
+ render()
+
+
+func add_point():
+ if target:
+ var pos = target.global_transform.origin
+ var normal = target.global_transform.basis.y.normalized()
+
+ if emit:
+ var points_count = path_points.size()
+
+ if points_count < 1:
+ var point = Point.new(pos, normal, life_time)
+ path_points.append(point)
+ else:
+ var distance = path_points[points_count-2].position.distance_squared_to(pos)
+ if distance > (max_distance * max_distance):
+ var point = Point.new(pos, normal, life_time)
+ path_points.append(point)
+
+ if points_count > 1:
+ path_points[points_count-1].position = pos
+
+
+func update_points():
+ var delta = 0
+ if prcess_mode == "Fixed":
+ delta = get_physics_process_delta_time()
+ else:
+ delta = get_process_delta_time()
+
+ var points_count = path_points.size()
+ if points_count > segments:
+ path_points.pop_front()
+
+ for i in range(path_points.size()-1):
+ path_points[i].update(delta)
+ if path_points[i].age <= 0:
+ path_points.remove(i)
+
+
+func render():
+ if path_points.size() < 2:
+ clear()
+ return
+
+# path_points = [Vector3(-5, 2, 0),Vector3(-5, 2, 0),Vector3(5, 2, 0)]
+ var to_be_rendered: Array = []
+ for point in path_points:
+ to_be_rendered.append(point.position)
+ to_be_rendered = chaikin(to_be_rendered, smoothing_iterations)
+
+ var points_to_render: int = to_be_rendered.size()
+# var tiling_factor: float = segments*max_distance/base_width if tiled_texture else 0
+ var step: float = 1.0/(points_to_render-1)
+ var factor: float = 0
+ var wire_points: Array = []
+ var _u = 0
+
+ clear()
+ begin(Mesh.PRIMITIVE_TRIANGLE_STRIP, null)
+
+ for i in range(1, to_be_rendered.size()):
+ var mapped_index = floor(float(i) / points_to_render * path_points.size())
+ var normal = Vector3()
+ if alignment == "Motion":
+ normal = path_points[mapped_index].normal
+
+ elif alignment == "View":
+ var path_direction = (to_be_rendered[i] - to_be_rendered[i-1]).normalized()
+ var cam_pos = get_viewport().get_camera().get_global_transform().origin
+ normal = (cam_pos - (to_be_rendered[i] + to_be_rendered[i-1])/2).cross(path_direction).normalized()
+
+ else:
+ normal = target.get_global_transform().basis.y.normalized()
+
+ var rr = 1-factor
+ var width = base_width
+ if width_profile:
+ width = base_width * width_profile.interpolate(rr)
+ if width_over_time:
+ var fact = 1 - path_points[mapped_index].age/life_time
+ width = width * width_over_time.interpolate(fact)
+
+ var color = Color(1, 1, 1)
+ if color_gradient:
+ color = color_gradient.interpolate(rr)
+
+ # --------------------------RENDERING----------------------------
+ var p1 = to_be_rendered[i] - normal*width/2
+ var p2 = to_be_rendered[i] + normal*width/2
+ var u: float = factor
+
+ if tiled_texture:
+ if tiling:
+ u *= tiling
+ else:
+ _u += (to_be_rendered[i] - to_be_rendered[i-1]).length()/base_width
+ u = _u
+
+ set_color(color)
+ set_uv(Vector2(u, 0))
+ add_vertex(p1)
+ set_uv(Vector2(u, 1))
+ add_vertex(p2)
+ factor += step
+
+ wire_points += [p1, p2]
+ end()
+
+ if show_wireframe:
+ begin(Mesh.PRIMITIVE_LINE_STRIP, null)
+ set_color(wireframe_color)
+ for i in range(1, wire_points.size()-2, 2):
+ ## i-1, i+1, i, i+2
+ add_vertex(wire_points[i-1])
+ add_vertex(wire_points[i+1])
+ add_vertex(wire_points[i])
+ add_vertex(wire_points[i+2])
+ end()
+
+
+func chaikin(points, iterations):
+ """ Chaikin’s Algorithms for curves """
+ if points.size() > 1:
+ if (iterations == 0):
+ return points
+
+ var result = [points[0]]
+ for i in range(0, points.size()-1):
+ result += chaikin_cut(points[i], points[i+1])
+ result += [points[points.size()-1]]
+
+ return chaikin(result, iterations-1)
+ return points
+
+func chaikin_cut(a, b):
+ """ Cutting one segment """
+ var ratio = clamp(smoothing_ratio, 0, 1)
+ if (ratio > 0.5): ratio = 1 - ratio;
+
+ # Find point at a given ratio going from A to B
+ var p1 = a.linear_interpolate(b, ratio)
+ # Find point at a given ratio going from B to A
+ var p2 = b.linear_interpolate(a, ratio)
+
+ return [p1, p2]
+