projektluzid/addons/vizpath/utilities/viz_simple_leg.gd
2025-06-07 19:10:56 +02:00

307 lines
10 KiB
GDScript

extends RefCounted
class_name VizSimpleLeg
var _error : String = ""
var _begin : VisualizationSpot
var _end : VisualizationSpot
var _width : float
var _bend_lip : float
var _bend : VizBend
const MIN_SEGMENT_LENGTH=0.01
const EPSILON=0.00001
func _init(p_begin : VisualizationSpot, p_end : VisualizationSpot, p_width : float, p_bend_lip : float):
_begin = p_begin
_end = p_end
_width = p_width
_bend_lip = p_bend_lip
if not _has_valid_normal():
return
if _is_straight():
return
_bend = VizBend.new(_begin, _end, p_width, p_bend_lip)
if not _bend.is_invalid():
_check_offsets(_begin, _end)
# ---------------------------------------------
# Public Methods
func get_error() -> String:
if _error != "":
return _error
if _bend != null:
return _bend.get_error()
return ""
func is_invalid() -> bool:
if _error != "":
return true
if _bend != null:
return _bend.is_invalid()
return false
func extend_mesh(mesh_node : Node3D, u : float, left : Vector3, right : Vector3, segs : int, sharpness : float, mat : Material) -> float:
if _bend == null:
u = _extend_straight_leg(mesh_node, u, left, right, _begin.point, _end.point, _begin.normal, mat)
else:
u = _extend_straight_leg(mesh_node, u, left, right, _begin.point, _bend.get_begin(), _begin.normal, mat)
if _bend.is_angled():
u = _create_begin_triangle(mesh_node, u, _begin.normal, _bend.get_begin_triangle(), mat)
u = _create_bend(mesh_node, u, segs, sharpness, mat)
if _bend.is_angled():
u = _create_end_triangle(mesh_node, u, _end.normal, _bend.get_end_triangle(), mat)
u = _create_straight_leg(mesh_node, u, _bend.get_end(), _end.point, _end.normal, mat)
return u
func update_mesh(mesh_node : Node3D, u : float, segs : int, sharpness : float, mat : Material) -> float:
if _bend == null:
u = _create_straight_leg(mesh_node, u, _begin.point, _end.point, _begin.normal, mat)
else:
u = _create_straight_leg(mesh_node, u, _begin.point, _bend.get_begin(), _begin.normal, mat)
if _bend.is_angled():
u = _create_begin_triangle(mesh_node, u, _begin.normal, _bend.get_begin_triangle(), mat)
u = _create_bend(mesh_node, u, segs, sharpness, mat)
if _bend.is_angled():
u = _create_end_triangle(mesh_node, u, _end.normal, _bend.get_end_triangle(), mat)
u = _create_straight_leg(mesh_node, u, _bend.get_end(), _end.point, _end.normal, mat)
return u
func get_begin() -> VisualizationSpot:
return _begin
func get_leg_bend_begin() -> Vector3:
if _bend == null:
return (_end.point - _begin.point)/2.0
return _bend.get_begin_triangle().get_leg_mid()
func get_begin_ray() -> Vector3:
if _bend == null:
return (_end.point - _begin.point).normalized()
return (_bend.get_begin() - _begin.point).normalized()
func adjust_begin(distance : float):
var new_begin := VisualizationSpot.new()
new_begin.point = _begin.point + distance * get_begin_ray()
new_begin.normal = _begin.normal
if _check_offsets(new_begin, _end):
_begin = new_begin
func get_end() -> VisualizationSpot:
return _end
func get_leg_bend_end() -> Vector3:
if _bend == null:
return (_end.point - _begin.point)/2.0
return _bend.get_end_triangle().get_leg_mid()
func get_end_left() -> Vector3:
var segment := _end.point - get_leg_bend_end()
var binormal := segment.cross(_end.normal).normalized()
var half_width = binormal*_width/2.0
return _end.point - half_width
func get_end_right() -> Vector3:
var segment := _end.point - get_leg_bend_end()
var binormal := segment.cross(_end.normal).normalized()
var half_width = binormal*_width/2.0
return _end.point + half_width
func get_end_ray() -> Vector3:
if _bend == null:
return (_end.point - _begin.point).normalized()
return (_end.point - _bend.get_end()).normalized()
func adjust_end(distance : float):
var new_end := VisualizationSpot.new()
new_end.point = _end.point - distance * get_end_ray()
new_end.normal = _end.normal
if _check_offsets(_begin, new_end):
_end = new_end
# ---------------------------------------------
# Local Methods
func _check_offsets(p_begin : VisualizationSpot, p_end : VisualizationSpot) -> bool:
var dir := (_end.point - _begin.point).normalized()
var min_len := MIN_SEGMENT_LENGTH + EPSILON
var max_begin := _end.point - dir * min_len
var max_end := _begin.point + dir * min_len
if _bend != null:
max_begin = _bend.get_begin() - dir * min_len
max_end = _bend.get_end() + dir * min_len
var new_begin_dir := (max_begin - p_begin.point).normalized()
var new_end_dir := (p_end.point - max_end).normalized()
if new_begin_dir.dot(dir) <= 0.0:
_error = "bend start %s is too near %s to be bent with this width and lip" % [ _begin.point, _end.point ]
return false
if new_end_dir.dot(dir) <= 0.0:
_error = "bend end %s is too near %s to be bent with this width and lip" % [ _end.point, _begin.point ]
return false
return true
func _has_valid_normal() -> bool:
var segment = _end.point - _begin.point
var binormal = segment.cross(_begin.normal).normalized()
if not binormal.is_normalized():
_error = "%s to %s is parallel to normal %s" % [ _begin.point, _end.point, _begin.normal ]
return false
return true
func _is_straight() -> bool:
if _begin.normal.is_equal_approx(_end.normal):
var segment = _end.point - _begin.point
if is_equal_approx(segment.dot(_begin.normal), 0.0):
return true
_error = "%s to %s have same normal but are not in same plane" % [ _begin.point, _end.point ]
return false
func _extend_straight_leg(mesh_node : Node3D, u : float, left : Vector3, right : Vector3, begin : Vector3, end : Vector3, normal : Vector3, mat : Material):
var segment := end - begin
var binormal := segment.cross(normal).normalized()
var half_width = binormal*_width/2.0
var seg := begin - end
var seg_len := seg.length()
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
st.set_color(Color(1, 0, 0))
st.set_normal(normal)
st.set_uv(Vector2(u, 1))
st.add_vertex(right)
st.set_uv(Vector2(u, 0))
st.add_vertex(left)
st.set_uv(Vector2(u+seg_len, 1))
st.add_vertex(end+half_width)
st.set_uv(Vector2(u+seg_len, 0))
st.add_vertex(end-half_width)
st.set_material(mat)
mesh_node.mesh = st.commit(mesh_node.mesh)
return u + seg_len
func _create_straight_leg(mesh_node : Node3D, u : float, begin : Vector3, end : Vector3, normal : Vector3, mat : Material):
var segment := end - begin
var binormal := segment.cross(normal).normalized()
var half_width = binormal*_width/2.0
var seg := begin - end
var seg_len := seg.length()
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
st.set_color(Color(1, 0, 0))
st.set_normal(normal)
st.set_uv(Vector2(u, 1))
st.add_vertex(begin+half_width)
st.set_uv(Vector2(u, 0))
st.add_vertex(begin-half_width)
st.set_uv(Vector2(u+seg_len, 1))
st.add_vertex(end+half_width)
st.set_uv(Vector2(u+seg_len, 0))
st.add_vertex(end-half_width)
st.set_material(mat)
mesh_node.mesh = st.commit(mesh_node.mesh)
return u + seg_len
func _create_begin_triangle(mesh_node : Node3D, u : float, normal : Vector3, triangle : VizTriangle, mat : Material):
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
st.set_color(Color(1, 0, 0))
st.set_normal(normal)
st.set_uv(Vector2(u, 0))
st.add_vertex(triangle.leg_left)
st.set_uv(Vector2(u + triangle.distance, triangle.get_fwd_side()))
st.add_vertex(triangle.leg_fwd)
st.set_uv(Vector2(u, 1))
st.add_vertex(triangle.leg_right)
st.set_material(mat)
mesh_node.mesh = st.commit(mesh_node.mesh)
return u + triangle.distance
func _create_end_triangle(mesh_node : Node3D, u : float, normal : Vector3, triangle : VizTriangle, mat : Material):
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLES)
st.set_color(Color(1, 0, 0))
st.set_normal(normal)
st.set_uv(Vector2(u + triangle.distance, 0))
st.add_vertex(triangle.leg_left)
st.set_uv(Vector2(u + triangle.distance, 1))
st.add_vertex(triangle.leg_right)
st.set_uv(Vector2(u, triangle.get_fwd_side()))
st.add_vertex(triangle.leg_fwd)
st.set_material(mat)
mesh_node.mesh = st.commit(mesh_node.mesh)
return u + triangle.distance
func _create_bend(mesh_node : Node3D, u : float, segs : int, sharpness : float, mat : Material):
var right_u := u - _bend.get_begin_triangle().distance
var left_u := u
if _bend.is_angled_against_curve():
right_u = u
left_u = u - _bend.get_begin_triangle().distance
var arc_len := 0.0
var segment := _bend.get_begin() - _begin.point
var binormal := segment.cross(_begin.normal).normalized()
var half_width = _bend.get_half_bend_width()
var half_width_normal := half_width.normalized()
var st := SurfaceTool.new()
st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)
st.set_normal(_begin.normal)
st.set_uv(Vector2(right_u, 1))
st.add_vertex(_bend.get_begin_triangle().bend_right)
st.set_uv(Vector2(left_u, 0))
st.add_vertex(_bend.get_begin_triangle().bend_left)
var shift_width := _bend.get_shift_width()
var last_point := _bend.get_arc_begin_point()
var total_len := 0.0
for i in range(0, segs):
var weight := float(i) / float(segs-1)
var cur_point := _bend.get_arc_point(weight, sharpness)
total_len += cur_point.distance_to(last_point)
last_point = cur_point
total_len += _bend.get_arc_end_point().distance_to(last_point)
last_point = _bend.get_arc_begin_point()
for i in range(1, segs-1):
var weight := float(i) / float(segs-1)
var cur_point := _bend.get_arc_point(weight, sharpness)
arc_len += cur_point.distance_to(last_point)
var shift := shift_width * (arc_len / total_len)
var pos := cur_point + shift - _bend.get_shift_offset()
var cur_normal := Vector3(cur_point - last_point).normalized().cross(half_width_normal)
last_point = cur_point
st.set_normal(cur_normal)
st.set_uv(Vector2(right_u + arc_len, 1))
st.add_vertex(pos-half_width)
st.set_uv(Vector2(left_u + arc_len, 0))
st.add_vertex(pos+half_width)
st.set_normal(_end.normal)
st.set_uv(Vector2(right_u + total_len, 1))
st.add_vertex(_bend.get_end_triangle().bend_right)
st.set_uv(Vector2(left_u + total_len, 0))
st.add_vertex(_bend.get_end_triangle().bend_left)
st.set_material(mat)
mesh_node.mesh = st.commit(mesh_node.mesh)
u = right_u + total_len
if _bend.is_angled_against_curve():
u = left_u + total_len
return u
func _to_string():
if _bend != null:
return "SLEG(%s->%s through %s)" % [ _begin, _end, _bend ]
return "SLEG(%s->%s)" % [ _begin, _end ]