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

202 lines
8.2 KiB
GDScript

extends RefCounted
class_name VizBend
var _error : String
var _bend_normal : Vector3
var _bend_plane : Plane
var _begin_on_bend_plane : Vector3
var _end_on_bend_plane : Vector3
var _intersect_data : OptVector3
var _plane_join_point : Vector3
var _bend_side : float
var _curve_side : float
var _bend_begin_triangle : VizTriangle
var _bend_end_triangle : VizTriangle
var _arc_begin_mid : Vector3
var _arc_end_mid : Vector3
var _begin : Vector3
var _begin_dir : Vector3
var _end : Vector3
var _end_dir : Vector3
class OptVector3:
var vector : Vector3
func _init(p_data : Vector3):
vector = p_data
func _init(p_begin : VisualizationSpot, p_end : VisualizationSpot, p_width : float, p_bend_lip : float):
if not _is_bendable(p_begin, p_end):
return
_calc_bend_normal(p_begin, p_end)
if not _calc_intersection_point(p_begin, p_end):
return
_calc_join_point(p_begin, p_end)
_calc_bend_side(p_begin, p_end)
_calc_triangles(p_begin, p_end, p_width, p_bend_lip)
_calc_arc_plane()
_begin = _bend_begin_triangle.get_leg_mid()
if not _is_in_order(p_begin.point, _begin, _plane_join_point):
_error = "bend point is too close to beginning for %s to %s with lip %s" % [p_begin.point, p_end.point, p_bend_lip]
return
_end = _bend_end_triangle.get_leg_mid()
if not _is_in_order(_plane_join_point, _end, p_end.point):
_error = "bend point is too close to end for %s to %s with lip %s" % [p_begin.point, p_end.point, p_bend_lip]
return
func _to_string():
return "%s->%s->%s" % [_begin, _plane_join_point, _end]
# ---------------------------------------------
# Public Methoeds
func get_error() -> String:
return _error
func is_invalid() -> bool:
if _error != "":
return true
return false
func get_begin() -> Vector3:
return _begin
func get_end() -> Vector3:
return _end
func is_angled() -> bool:
return not is_zero_approx(_bend_side)
func is_angled_against_curve() -> bool:
if _bend_side * _curve_side < 0:
return true
return false
func get_half_leg_width() -> Vector3:
return (_bend_begin_triangle.leg_left - _bend_begin_triangle.leg_right) / 2.0
func get_half_bend_width() -> Vector3:
return (_bend_begin_triangle.bend_left - _bend_begin_triangle.bend_right) / 2.0
func get_begin_triangle() -> VizTriangle:
return _bend_begin_triangle
func get_end_triangle() -> VizTriangle:
return _bend_end_triangle
func get_shift_width() -> Vector3:
return _arc_begin_mid - _bend_begin_triangle.get_bend_mid() + _bend_end_triangle.get_bend_mid() - _arc_end_mid
func get_shift_offset() -> Vector3:
return _arc_begin_mid - _bend_begin_triangle.get_bend_mid()
func get_arc_begin_point() -> Vector3:
return _arc_begin_mid
func get_arc_end_point() -> Vector3:
return _arc_end_mid
func get_arc_point(weight : float, sharpness : float) -> Vector3:
return _curve_point(_arc_begin_mid, _plane_join_point, _arc_end_mid, weight, sharpness)
# ---------------------------------------------
# Private Methods
func _curve_point(p0 : Vector3, p1 : Vector3, p2 : Vector3, t : float, s : float) -> Vector3:
var p := p1 + pow(1-t, 2.0) * s * (p0-p1) + pow(t, 2.0) * s * (p2-p1)
return p
func _is_in_order(b : Vector3, m : Vector3, e : Vector3) -> bool:
return (e-m).normalized().dot((m-b).normalized()) > 0
func _is_bendable(p_begin : VisualizationSpot, p_end : VisualizationSpot) -> bool:
var dir := (p_end.point - p_begin.point).normalized()
var begin_norm_sign := signf(dir.dot(p_begin.normal))
var end_norm_sign := signf(dir.dot(p_end.normal))
if begin_norm_sign == end_norm_sign:
_error = "%s cannot be twisted into %s" % [ p_begin.normal, p_end.normal ]
return false
_curve_side = end_norm_sign
return true
func _calc_bend_normal(p_begin : VisualizationSpot, p_end : VisualizationSpot):
var middle = p_begin.point + (p_end.point - p_begin.point) / 2.0
_bend_normal = p_begin.normal.cross(p_end.normal).normalized()
_bend_plane = Plane(_bend_normal, middle)
func _calc_intersection_point(p_begin : VisualizationSpot, p_end : VisualizationSpot) -> bool:
_begin_on_bend_plane = _bend_plane.project(p_begin.point)
_end_on_bend_plane = _bend_plane.project(p_end.point)
var dir := _end_on_bend_plane - _begin_on_bend_plane
var begin_normal_on_bend_plane := _bend_plane.project(p_begin.point + p_begin.normal) - _begin_on_bend_plane
var end_normal_on_bend_plane := _bend_plane.project(p_end.point + p_end.normal) - _end_on_bend_plane
var begin_bend_binormal := begin_normal_on_bend_plane.cross(dir)
var begin_bend_tangent := begin_bend_binormal.cross(begin_normal_on_bend_plane).normalized()
var end_bend_binormal := end_normal_on_bend_plane.cross(dir)
var end_bend_tangent := end_bend_binormal.cross(end_normal_on_bend_plane).normalized()
_intersect_data = _intersect(_begin_on_bend_plane, begin_bend_tangent, _end_on_bend_plane, end_bend_tangent, _bend_normal)
if _intersect_data == null:
_error = "intersection failed unexpectedly: normal %s at %s is parallel with normal %s at %s" % [ begin_normal_on_bend_plane, _begin_on_bend_plane, end_normal_on_bend_plane, _end_on_bend_plane ]
return false
return true
func _calc_join_point(p_begin : VisualizationSpot, p_end : VisualizationSpot):
var begin_bend_from_intersect := p_begin.point + _intersect_data.vector - _begin_on_bend_plane
var end_bend_from_intersect := p_end.point + _intersect_data.vector - _end_on_bend_plane
var begin_intersect_distance := begin_bend_from_intersect.distance_to(p_begin.point)
var end_intersect_distance := end_bend_from_intersect.distance_to(p_end.point)
var begin_to_end_on_bend_distance := end_bend_from_intersect.distance_to(begin_bend_from_intersect)
var begin_to_end_intersect_join_numerator := begin_to_end_on_bend_distance * begin_intersect_distance / end_intersect_distance
var begin_to_end_intersect_join_denominator := 1.0 + begin_intersect_distance / end_intersect_distance
var begin_on_end_intersect_join_distance := begin_to_end_intersect_join_numerator / begin_to_end_intersect_join_denominator
var begin_bend_dir := begin_bend_from_intersect - p_begin.point
var end_bend_dir := end_bend_from_intersect - p_begin.point
var begin_to_end_normal := begin_bend_dir.cross(end_bend_dir)
var begin_to_end_binormal := begin_to_end_normal.cross(_bend_normal)
var begin_on_end_intersect_side = signf(end_bend_dir.dot(begin_to_end_binormal))
_plane_join_point = begin_bend_from_intersect - begin_on_end_intersect_side * begin_on_end_intersect_join_distance * _bend_normal
func _calc_bend_side(p_begin : VisualizationSpot, p_end : VisualizationSpot):
_begin_dir = Vector3(_plane_join_point - p_begin.point).normalized()
_end_dir = Vector3(p_end.point - _plane_join_point).normalized()
_bend_side = signf(_bend_normal.dot(_end_dir))
func _calc_triangles(p_begin : VisualizationSpot, p_end : VisualizationSpot, p_width : float, p_bend_lip : float):
var begin_binormal := _begin_dir.cross(p_begin.normal).normalized()
var end_binormal := _end_dir.cross(p_end.normal).normalized()
_bend_begin_triangle = VizTriangle.new()
_bend_begin_triangle.update(_begin_dir, _plane_join_point, begin_binormal, p_width, p_bend_lip, _bend_normal, _bend_side, _curve_side)
_bend_end_triangle = VizTriangle.new()
_bend_end_triangle.update(-_end_dir, _plane_join_point, end_binormal, p_width, p_bend_lip, _bend_normal, -_bend_side, _curve_side)
func _calc_arc_plane():
var begin_mid := _bend_begin_triangle.get_leg_mid()
var end_mid := _bend_end_triangle.get_leg_mid()
var arc_center := _plane_join_point - (_plane_join_point - begin_mid) - (_plane_join_point - end_mid)
var arc_plane := Plane(_bend_normal, arc_center)
_arc_begin_mid = arc_plane.project(_bend_begin_triangle.get_bend_mid())
_arc_end_mid = arc_plane.project(_bend_end_triangle.get_bend_mid())
func _intersect(a : Vector3, a_norm : Vector3, b : Vector3, b_norm : Vector3, plane_norm : Vector3) -> OptVector3:
var seg := b - a
var seg_len := seg.length()
var seg_norm := seg.normalized()
var alpha_sign := signf(b_norm.cross(a_norm).dot(plane_norm))
var cos_alpha := b_norm.dot(a_norm)
var beta_sign := signf(seg_norm.cross(b_norm).dot(plane_norm))
var cos_beta := seg_norm.dot(b_norm)
var intersect_side := alpha_sign * beta_sign
var sin_alpha := sqrt(1 - cos_alpha*cos_alpha)
var sin_beta := sqrt(1 - cos_beta*cos_beta)
if is_equal_approx(sin_alpha, 0.0):
return null
var q := seg_len * sin_beta / sin_alpha
var p := a - intersect_side * q * a_norm
return OptVector3.new(p)