338 lines
10 KiB
GDScript
338 lines
10 KiB
GDScript
@tool
|
|
extends RefCounted
|
|
class_name SS2D_Edge
|
|
|
|
## An SS2D_Edge represents an edge that will be rendered.
|
|
##
|
|
## It contains: [br]
|
|
## - A list of quads that should be rendered [br]
|
|
## - A [Material] that dictates how the edge should be rendered [br]
|
|
|
|
## What to encode in the color data (for use by shaders).
|
|
enum COLOR_ENCODING {
|
|
COLOR, ## Encode a diffuse value to offset the quads color by (currently only ever white).
|
|
NORMALS, ## Encode normal data in the colors to be unpacked by a shader later.
|
|
}
|
|
|
|
var quads: Array[SS2D_Quad] = []
|
|
var first_point_key: int = -1
|
|
var last_point_key: int = -1
|
|
var z_index: int = 0
|
|
var z_as_relative: bool = false
|
|
## If final point is connected to first point.
|
|
var wrap_around: bool = false
|
|
var material: Material = null
|
|
|
|
|
|
## Will return true if the 2 quads must be drawn in two calls.
|
|
static func different_render(q1: SS2D_Quad, q2: SS2D_Quad) -> bool:
|
|
if q1.matches_quad(q2):
|
|
return false
|
|
return true
|
|
|
|
|
|
static func get_consecutive_quads_for_mesh(_quads: Array[SS2D_Quad]) -> Array[Array]:
|
|
var quad_ranges: Array[Array] = []
|
|
|
|
if _quads.is_empty():
|
|
return quad_ranges
|
|
|
|
var quad_range: Array[SS2D_Quad] = []
|
|
quad_range.push_back(_quads[0])
|
|
for i in range(1, _quads.size(), 1):
|
|
var quad_prev: SS2D_Quad = _quads[i - 1]
|
|
var quad: SS2D_Quad = _quads[i]
|
|
if different_render(quad, quad_prev):
|
|
quad_ranges.push_back(quad_range)
|
|
quad_range = [quad]
|
|
else:
|
|
quad_range.push_back(quad)
|
|
|
|
quad_ranges.push_back(quad_range)
|
|
return quad_ranges
|
|
|
|
|
|
## Will generate normals for a given quad
|
|
## and interpolate with previous and next quads.
|
|
static func generate_normals_for_quad_interpolated(qp: SS2D_Quad, q: SS2D_Quad, qn: SS2D_Quad) -> Array[Color]:
|
|
# Interpolation and normalization
|
|
#First, consider everything to be a non corner
|
|
var tg_a: Vector2 = (q.tg_a + qp.tg_d)
|
|
var bn_a: Vector2 = (q.bn_a + qp.bn_d)
|
|
|
|
var tg_b: Vector2 = (q.tg_b + qp.tg_c)
|
|
var bn_b: Vector2 = (q.bn_b + qp.bn_c)
|
|
|
|
var tg_c: Vector2 = (q.tg_c + qn.tg_b)
|
|
var bn_c: Vector2 = (q.bn_c + qn.bn_b)
|
|
|
|
var tg_d: Vector2 = (q.tg_d + qn.tg_a)
|
|
var bn_d: Vector2 = (q.bn_d + qn.bn_a)
|
|
|
|
#then, fix values for corner cases (and edge ends)
|
|
if q.corner == q.CORNER.NONE:
|
|
if qp.corner == q.CORNER.NONE:
|
|
#check validity
|
|
if (not q.pt_a.is_equal_approx(qp.pt_d)) or (not q.pt_b.is_equal_approx(qp.pt_c)):
|
|
tg_a = q.tg_a
|
|
tg_b = q.tg_b
|
|
bn_a = q.bn_a
|
|
bn_b = q.bn_b
|
|
elif qp.corner == q.CORNER.INNER:
|
|
tg_a = (-qp.bn_d)
|
|
bn_a = (-qp.tg_d)
|
|
tg_b = (q.tg_b - qp.bn_a)
|
|
bn_b = (q.bn_b - qp.tg_a)
|
|
#check validity
|
|
if (not q.pt_a.is_equal_approx(qp.pt_d)) or (not q.pt_b.is_equal_approx(qp.pt_a)):
|
|
tg_a = q.tg_a
|
|
tg_b = q.tg_b
|
|
bn_a = q.bn_a
|
|
bn_b = q.bn_b
|
|
elif qp.corner == q.CORNER.OUTER:
|
|
tg_a = (q.tg_a + qp.bn_c)
|
|
bn_a = (q.bn_a - qp.tg_c)
|
|
tg_b = (qp.bn_b)
|
|
bn_b = (-qp.tg_b)
|
|
#check validity
|
|
if (not q.pt_a.is_equal_approx(qp.pt_c)) or (not q.pt_b.is_equal_approx(qp.pt_b)):
|
|
tg_a = q.tg_a
|
|
tg_b = q.tg_b
|
|
bn_a = q.bn_a
|
|
bn_b = q.bn_b
|
|
if qn.corner == q.CORNER.NONE:
|
|
#check validity
|
|
if (not q.pt_c.is_equal_approx(qn.pt_b)) or (not q.pt_d.is_equal_approx(qn.pt_a)):
|
|
tg_c = q.tg_c
|
|
tg_d = q.tg_d
|
|
bn_c = q.bn_c
|
|
bn_d = q.bn_d
|
|
elif qn.corner == q.CORNER.INNER:
|
|
tg_d = (-qn.tg_d)
|
|
bn_d = (qn.bn_d)
|
|
tg_c = (q.tg_c - qn.tg_c)
|
|
bn_c = (q.bn_c + qn.bn_c)
|
|
#check validity
|
|
if (not q.pt_c.is_equal_approx(qn.pt_c)) or (not q.pt_d.is_equal_approx(qn.pt_d)):
|
|
tg_c = q.tg_c
|
|
tg_d = q.tg_d
|
|
bn_c = q.bn_c
|
|
bn_d = q.bn_d
|
|
elif qn.corner == q.CORNER.OUTER:
|
|
tg_c = (qn.tg_b)
|
|
bn_c = (qn.bn_b)
|
|
#check validity
|
|
if (not q.pt_c.is_equal_approx(qn.pt_b)) or (not q.pt_d.is_equal_approx(qn.pt_a)):
|
|
tg_c = q.tg_c
|
|
tg_d = q.tg_d
|
|
bn_c = q.bn_c
|
|
bn_d = q.bn_d
|
|
|
|
elif q.corner == q.CORNER.INNER:
|
|
#common
|
|
tg_d = q.tg_d
|
|
bn_d = q.bn_d
|
|
tg_b = (q.tg_b)
|
|
bn_b = (q.bn_b)
|
|
#previous
|
|
tg_c = (q.tg_c - qp.tg_c)
|
|
bn_c = (q.bn_c + qp.bn_c)
|
|
#next
|
|
tg_a = (q.tg_a - qn.bn_b)
|
|
bn_a = (q.bn_a - qn.tg_b)
|
|
#check validity
|
|
if qp.corner != qp.CORNER.NONE or (not q.pt_c.is_equal_approx(qp.pt_c)) or (not q.pt_d.is_equal_approx(qp.pt_d)):
|
|
tg_c = q.tg_c
|
|
bn_c = q.bn_c
|
|
if qn.corner != qp.CORNER.NONE or (not q.pt_a.is_equal_approx(qn.pt_b)) or (not q.pt_d.is_equal_approx(qn.pt_a)):
|
|
tg_a = q.tg_a
|
|
bn_a = q.bn_a
|
|
|
|
elif q.corner == q.CORNER.OUTER:
|
|
tg_d = q.tg_d
|
|
bn_d = q.bn_d
|
|
tg_b = (q.tg_b)
|
|
bn_b = (q.bn_b)
|
|
#previous
|
|
tg_a = (q.tg_a + qp.tg_d)
|
|
bn_a = (q.bn_a + qp.bn_d)
|
|
#qn
|
|
tg_c = (q.tg_c - qn.bn_a)
|
|
bn_c = (q.bn_c + qn.tg_a)
|
|
#check validity
|
|
if qp.corner != qp.CORNER.NONE or (not q.pt_a.is_equal_approx(qp.pt_d)) or (not q.pt_b.is_equal_approx(qp.pt_c)):
|
|
tg_a = q.tg_a
|
|
bn_a = q.bn_a
|
|
if qn.corner != qp.CORNER.NONE or (not q.pt_b.is_equal_approx(qn.pt_b)) or (not q.pt_c.is_equal_approx(qn.pt_a)):
|
|
tg_c = q.tg_c
|
|
bn_c = q.bn_c
|
|
|
|
if q.flip_texture:
|
|
bn_a = -bn_a;
|
|
bn_b = -bn_b;
|
|
bn_c = -bn_c;
|
|
bn_d = -bn_d;
|
|
|
|
#Normalize the values
|
|
var half_vector: Vector2 = Vector2.ONE * 0.5
|
|
tg_a = tg_a.normalized()*0.5 + half_vector
|
|
tg_b = tg_b.normalized()*0.5 + half_vector
|
|
tg_c = tg_c.normalized()*0.5 + half_vector
|
|
tg_d = tg_d.normalized()*0.5 + half_vector
|
|
|
|
bn_a = bn_a.normalized()*0.5 + half_vector
|
|
bn_b = bn_b.normalized()*0.5 + half_vector
|
|
bn_c = bn_c.normalized()*0.5 + half_vector
|
|
bn_d = bn_d.normalized()*0.5 + half_vector
|
|
|
|
var normal_pt_a := Color(tg_a.x, tg_a.y, bn_a.x, bn_a.y)
|
|
var normal_pt_b := Color(tg_b.x, tg_b.y, bn_b.x, bn_b.y)
|
|
var normal_pt_c := Color(tg_c.x, tg_c.y, bn_c.x, bn_c.y)
|
|
var normal_pt_d := Color(tg_d.x, tg_d.y, bn_d.x, bn_d.y)
|
|
|
|
return [normal_pt_a, normal_pt_b, normal_pt_c, normal_pt_d]
|
|
|
|
|
|
## Assumes each quad in the sequence is of the same render type (same textures, values, etc...).[br]
|
|
## [param _quads] should have been generated by [method get_consecutive_quads_for_mesh].
|
|
static func generate_array_mesh_from_quad_sequence(_quads: Array[SS2D_Quad], _wrap_around: bool, color_encoding: int) -> ArrayMesh:
|
|
# FIXME: _wrap_around is unused.
|
|
if _quads.is_empty():
|
|
return ArrayMesh.new()
|
|
|
|
var total_length: float = 0.0
|
|
for q in _quads:
|
|
total_length += q.get_length_average()
|
|
if total_length == 0.0:
|
|
return ArrayMesh.new()
|
|
|
|
var first_quad: SS2D_Quad = _quads[0]
|
|
var tex: Texture2D = first_quad.texture
|
|
# The change in length required to apply to each quad
|
|
# to make the textures begin and end at the start and end of each texture
|
|
var change_in_length: float = -1.0
|
|
if tex != null:
|
|
# How many times the texture is repeated
|
|
var texture_reps: float = roundf(total_length / tex.get_size().x)
|
|
# Length required to display all the reps with the texture's full width
|
|
var texture_full_length: float = texture_reps * tex.get_size().x
|
|
# How much each quad's texture must be offset to make up the difference in full length vs total length
|
|
change_in_length = (texture_full_length / total_length)
|
|
|
|
if first_quad.fit_texture == SS2D_Material_Edge.FITMODE.CROP:
|
|
change_in_length = 1.0
|
|
|
|
var length_elapsed: float = 0.0
|
|
var st := SurfaceTool.new()
|
|
st.begin(Mesh.PRIMITIVE_TRIANGLES)
|
|
for q in _quads:
|
|
q.update_tangents()
|
|
for i in _quads.size():
|
|
var q: SS2D_Quad = _quads[i]
|
|
var section_length: float = q.get_length_average() * change_in_length
|
|
# var highest_value: float = max(q.get_height_left(), q.get_height_right())
|
|
# When welding and using different widths, quads can look a little weird
|
|
# This is because they are no longer parallelograms
|
|
# This is a tough problem to solve
|
|
# See http://reedbeta.com/blog/quadrilateral-interpolation-part-1/
|
|
var uv_a := Vector2(0, 0)
|
|
var uv_b := Vector2(0, 1)
|
|
var uv_c := Vector2(1, 1)
|
|
var uv_d := Vector2(1, 0)
|
|
# If we have a valid texture and this quad isn't a corner
|
|
if tex != null and q.corner == q.CORNER.NONE:
|
|
var x_left: float = (length_elapsed) / tex.get_size().x
|
|
var x_right: float = (length_elapsed + section_length) / tex.get_size().x
|
|
uv_a.x = x_left
|
|
uv_b.x = x_left
|
|
uv_c.x = x_right
|
|
uv_d.x = x_right
|
|
if q.flip_texture:
|
|
var t: Vector2 = uv_a
|
|
uv_a = uv_b
|
|
uv_b = t
|
|
t = uv_c
|
|
uv_c = uv_d
|
|
uv_d = t
|
|
|
|
var color_a := q.color
|
|
var color_b := q.color
|
|
var color_c := q.color
|
|
var color_d := q.color
|
|
|
|
if color_encoding == COLOR_ENCODING.NORMALS:
|
|
var next := _quads[wrapi(i + 1, 0, _quads.size())]
|
|
var prev := _quads[wrapi(i - 1, 0, _quads.size())]
|
|
|
|
var normals: Array[Color] = generate_normals_for_quad_interpolated(next, q, prev)
|
|
color_a = normals[0]
|
|
color_b = normals[1]
|
|
color_c = normals[2]
|
|
color_d = normals[3]
|
|
|
|
# A
|
|
_add_uv_to_surface_tool(st, uv_a)
|
|
st.set_color(color_a)
|
|
st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_a))
|
|
|
|
# B
|
|
_add_uv_to_surface_tool(st, uv_b)
|
|
st.set_color(color_b)
|
|
st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_b))
|
|
|
|
# C
|
|
_add_uv_to_surface_tool(st, uv_c)
|
|
st.set_color(color_c)
|
|
st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_c))
|
|
|
|
# A
|
|
_add_uv_to_surface_tool(st, uv_a)
|
|
st.set_color(color_a)
|
|
st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_a))
|
|
|
|
# C
|
|
_add_uv_to_surface_tool(st, uv_c)
|
|
st.set_color(color_c)
|
|
st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_c))
|
|
|
|
# D
|
|
_add_uv_to_surface_tool(st, uv_d)
|
|
st.set_color(color_d)
|
|
st.add_vertex(SS2D_Common_Functions.to_vector3(q.pt_d))
|
|
|
|
length_elapsed += section_length
|
|
|
|
st.index()
|
|
st.generate_normals()
|
|
return st.commit()
|
|
|
|
|
|
func get_meshes(color_encoding: SS2D_Edge.COLOR_ENCODING) -> Array[SS2D_Mesh]:
|
|
# Get Arrays of consecutive quads with the same mesh data.
|
|
# For each array, generate Mesh Data from the quad.
|
|
|
|
var consecutive_quad_arrays := SS2D_Edge.get_consecutive_quads_for_mesh(quads)
|
|
#print("Arrays: %s" % consecutive_quad_arrays.size())
|
|
var meshes: Array[SS2D_Mesh] = []
|
|
for consecutive_quads in consecutive_quad_arrays:
|
|
if consecutive_quads.is_empty():
|
|
continue
|
|
var array_mesh: ArrayMesh = SS2D_Edge.generate_array_mesh_from_quad_sequence(
|
|
consecutive_quads, wrap_around, color_encoding
|
|
)
|
|
var quad: SS2D_Quad = consecutive_quads[0]
|
|
var tex: Texture2D = quad.texture
|
|
var flip: bool = quad.flip_texture
|
|
var mesh_data := SS2D_Mesh.new(tex, flip, Transform2D(), [array_mesh], material)
|
|
mesh_data.force_no_tiling = quad.is_tapered or quad.corner != SS2D_Quad.CORNER.NONE
|
|
mesh_data.z_index = z_index
|
|
mesh_data.z_as_relative = z_as_relative
|
|
meshes.push_back(mesh_data)
|
|
|
|
return meshes
|
|
|
|
|
|
static func _add_uv_to_surface_tool(surface_tool: SurfaceTool, uv: Vector2) -> void:
|
|
surface_tool.set_uv(uv)
|
|
surface_tool.set_uv2(uv)
|