Files
przygody-pana-cegly/addons/rmsmartshape/shapes/edge.gd

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)