Addd the Godot Trail System Add-on.
parent
e6366f8fea
commit
0427e9dd2f
|
@ -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.
|
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="Trail System"
|
||||
description="Advanced 2D/3D Trail system."
|
||||
author="Oussama BOUKHELF"
|
||||
version="0.1"
|
||||
script="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
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8"
|
||||
version="1.1"
|
||||
viewBox="0 0 4.2333332 4.2333334"
|
||||
height="16"
|
||||
width="16">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
transform="translate(0,-292.76665)"
|
||||
id="layer1">
|
||||
<path
|
||||
d="m 2.3792718,293.03275 c -0.066383,-0.003 -0.1328569,-0.002 -0.1990224,0.002 -0.3186668,0.0183 -0.6312182,0.1085 -0.9125781,0.26693 -0.73836421,0.4467 -1.07809046,1.28277 -0.92563728,2.0456 0.17028391,0.76048 0.80425238,1.29996 1.49449678,1.38009 0.692005,0.0713 1.3427391,-0.32717 1.5947914,-0.88489 -0.3569657,0.49715 -1.0210943,0.69443 -1.5237493,0.52812 -0.5044012,-0.15751 -0.8564865,-0.67125 -0.8451925,-1.1184 -0.00653,-0.4448 0.3338894,-0.88113 0.6879595,-0.97317 0.1010793,-0.0343 0.2112059,-0.041 0.320212,-0.0303 a 1.1446979,1.1446979 0 0 1 -0.015149,-0.1823 1.1446979,1.1446979 0 0 1 0.5944555,-1.00191 c -0.089937,-0.0156 -0.1801976,-0.0276 -0.2705869,-0.0313 z m 1.5477998,1.03344 a 0.7268029,0.7268029 0 0 1 -0.7268029,0.7268 0.7268029,0.7268029 0 0 1 -0.7268029,-0.7268 0.7268029,0.7268029 0 0 1 0.7268029,-0.72681 0.7268029,0.7268029 0 0 1 0.7268029,0.72681 z"
|
||||
style="vector-effect:none;fill:#a5b7f3;fill-opacity:1;stroke:none;stroke-width:0.26499999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
id="icon" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -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
|
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 4.2333332 4.2333334"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="trail3d_icon.svg">
|
||||
<defs
|
||||
id="defs2">
|
||||
<inkscape:path-effect
|
||||
effect="skeletal"
|
||||
id="path-effect847"
|
||||
is_visible="true"
|
||||
pattern="m 0.45166302,293.46861 1.24543248,0.71906 v -1.43811 z"
|
||||
copytype="single_stretched"
|
||||
prop_scale="1"
|
||||
scale_y_rel="false"
|
||||
spacing="0"
|
||||
normal_offset="0"
|
||||
tang_offset="0"
|
||||
prop_units="false"
|
||||
vertical_pattern="false"
|
||||
fuse_tolerance="0" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="32"
|
||||
inkscape:cx="7.1053991"
|
||||
inkscape:cy="10.736157"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="712"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="30"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:snap-global="true"
|
||||
inkscape:object-paths="true"
|
||||
inkscape:snap-intersection-paths="true"
|
||||
inkscape:snap-center="true"
|
||||
inkscape:snap-object-midpoints="true" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-292.76665)">
|
||||
<path
|
||||
id="icon"
|
||||
style="vector-effect:none;fill:#fc9c9c;fill-opacity:1;stroke:none;stroke-width:0.26499999;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 2.3792718,293.03275 c -0.066383,-0.003 -0.1328569,-0.002 -0.1990224,0.002 -0.3186668,0.0183 -0.6312182,0.1085 -0.9125781,0.26693 -0.73836421,0.4467 -1.07809046,1.28277 -0.92563728,2.0456 0.17028391,0.76048 0.80425238,1.29996 1.49449678,1.38009 0.692005,0.0713 1.3427391,-0.32717 1.5947914,-0.88489 -0.3569657,0.49715 -1.0210943,0.69443 -1.5237493,0.52812 -0.5044012,-0.15751 -0.8564865,-0.67125 -0.8451925,-1.1184 -0.00653,-0.4448 0.3338894,-0.88113 0.6879595,-0.97317 0.1010793,-0.0343 0.2112059,-0.041 0.320212,-0.0303 a 1.1446979,1.1446979 0 0 1 -0.015149,-0.1823 1.1446979,1.1446979 0 0 1 0.5944555,-1.00191 c -0.089937,-0.0156 -0.1801976,-0.0276 -0.2705869,-0.0313 z m 1.5477998,1.03344 a 0.7268029,0.7268029 0 0 1 -0.7268029,0.7268 0.7268029,0.7268029 0 0 1 -0.7268029,-0.7268 0.7268029,0.7268029 0 0 1 0.7268029,-0.72681 0.7268029,0.7268029 0 0 1 0.7268029,0.72681 z"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
|
@ -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
|
|
@ -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()
|
||||
|
|
@ -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)
|
||||
|
|
@ -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]
|
||||
|
Reference in New Issue