It took time to get the code working correctly. This is rope mesh generator and spring mechanic.
create MeshIstance3D
- mesh > new ImmediateMesh
#links:
It took time to get the code working correctly. This is rope mesh generator and spring mechanic.
create MeshIstance3D
- mesh > new ImmediateMesh
#links:
| @tool #real time update for editor | |
| extends MeshInstance3D | |
| # https://www.youtube.com/watch?v=yuU6DO9-enM 2.46 | |
| """ | |
| #... | |
| if Input.is_action_just_pressed("shoot"): | |
| lanuch() | |
| rope_generator.visible = true | |
| rope_generator.StartDrawing() | |
| if Input.is_action_just_released("shoot"): | |
| retract() | |
| rope_generator.visible = false | |
| rope_generator.StopDrawing() | |
| #... | |
| func lanuch(): | |
| if ray.is_colliding(): | |
| target = ray.get_collision_point() | |
| launched = true | |
| rope_generator.grapple_hook_position = target | |
| #... | |
| if !launched: | |
| rope_generator.visible = false | |
| return | |
| rope_generator.SetPlayerPosition(player.global_position) | |
| """ | |
| @onready var tmp_player: Node3D = $tmpPlayer | |
| @onready var tmp_hook: Node3D = $tmpHook | |
| ## first time to create points rope | |
| @export var firstTime:bool = true | |
| ## create and build mesh real time rope | |
| @export var isDrawing:bool = false: | |
| set(value): | |
| isDrawing = value | |
| firstTime = value | |
| @export var dirty:bool = false | |
| ## springs for rope if disable it will fall stretch. | |
| @export var iterations: int = 10 | |
| ## many points for rope to draw line mesh | |
| @export var point_count: int = 20 | |
| ## this handle rope drop which required iterations to stop infi fall. 0 to stop drop. | |
| @export var gravity_default: float = 9.8 | |
| @export var is_editor:bool = false | |
| var point_spacing: float = 0.1 | |
| @export var resoulution:int = 4 | |
| var rope_length: float | |
| @export var rope_width: float = 0.1 | |
| # mesh generate | |
| var points: Array[Vector3] = [] | |
| var points_old: Array[Vector3] = [] | |
| var tangent_array: Array[Vector3] = [] | |
| var normal_array: Array[Vector3] = [] | |
| var vertex_array: Array[Vector3] = [] | |
| var index_array: Array[int] = [] | |
| var uv_array: Array[Vector2] = [] # For UV mapping | |
| # placeholder node3d position | |
| @export var player_position:Vector3 = Vector3.ZERO | |
| @export var grapple_hook_position:Vector3 = Vector3.ZERO | |
| #func _ready() -> void: | |
| #pass | |
| # called every frame 'delta' is the elapsed time since the previus frame | |
| func _process(delta: float) -> void: | |
| #if not tmp_player or not tmp_hook: | |
| #printerr("Runtime: tmp_player or tmp_hook is not assigned! tmp_player: ", tmp_player, ", tmp_hook: ", tmp_hook) | |
| #return | |
| if is_editor: | |
| ##print("update?") | |
| SetPlayerPosition(tmp_player.position) | |
| SetGrappleHookPosition(tmp_hook.position) | |
| if grapple_hook_position.length() == 0: | |
| return | |
| if isDrawing || dirty: | |
| if firstTime: | |
| #print("first time") | |
| PreparePoints() | |
| #print("PreparePoints") | |
| firstTime = false | |
| UpdatePoints(delta) | |
| #print("UpdatePoints") | |
| GenerateMesh() | |
| #print("GenerateMesh") | |
| dirty = false | |
| #pass | |
| func SetPlayerPosition(pos): | |
| player_position = pos | |
| func SetGrappleHookPosition(pos): | |
| grapple_hook_position = pos | |
| func StartDrawing(): | |
| isDrawing = true | |
| #visible = true | |
| func StopDrawing(): | |
| isDrawing = false | |
| #visible = false | |
| func PreparePoints(): | |
| points.clear() | |
| points_old.clear() | |
| for i in range(point_count): | |
| var t = i / (point_count - 1.0) | |
| points.append(lerp(player_position, grapple_hook_position, t)) | |
| points_old.append(points[i]) | |
| _UpdatePointSpacing() | |
| print("points:", points.size()) | |
| #pass | |
| func _UpdatePointSpacing(): | |
| rope_length = (grapple_hook_position - player_position).length() | |
| point_spacing = rope_length / (point_count - 1.0) | |
| #pass | |
| func UpdatePoints(delta): | |
| #print("grapple_hook_position?", grapple_hook_position) | |
| points[0] = player_position | |
| points[point_count-1] = grapple_hook_position | |
| _UpdatePointSpacing() | |
| for i in range(1, point_count - 1): | |
| var curr:Vector3 = points[i] | |
| points[i] = points[i] + (points[i] - points_old[i]) + ( | |
| Vector3.DOWN * gravity_default * delta * delta) | |
| points_old[i] = curr | |
| # animation spring | |
| for i in range(iterations): | |
| ConstraintConnections() | |
| pass | |
| func ConstraintConnections(): | |
| #print("ConstraintConnections") | |
| for i in range(point_count - 1): | |
| var center:Vector3 = (points[i+1] + points[i]) / 2.0 | |
| var offset:Vector3 = (points[i+1] - points[i]) | |
| var length:float = offset.length() | |
| var dir :Vector3 = offset.normalized() | |
| var d = length - point_spacing | |
| if i != 0: | |
| points[i] += dir * d * 0.5 | |
| if i + 1 != point_count - 1: | |
| points[i+1] -= dir * d * 0.5 | |
| #pass | |
| func GenerateMesh(): | |
| vertex_array.clear() | |
| CalcuateNormals() | |
| index_array.clear() | |
| #segment / point | |
| for p in range(point_count): | |
| var center:Vector3 = points[p] | |
| var forward = tangent_array[p] | |
| var norm = normal_array[p] | |
| var bitangent = norm.cross(forward).normalized() | |
| #current resolution | |
| for c in range(resoulution): | |
| var angle = (float(c) / resoulution ) * 2.0 * PI | |
| var xVal = sin(angle) * rope_width | |
| var yVal = cos(angle) * rope_width | |
| var point = (norm * xVal) + (bitangent * yVal) + center | |
| vertex_array.append(point) | |
| if p < point_count - 1: | |
| var start_index = resoulution * p | |
| index_array.append(start_index + c) | |
| index_array.append(start_index + c + resoulution) | |
| index_array.append(start_index + (c + 1 ) % resoulution) | |
| index_array.append(start_index + (c + 1) % resoulution) | |
| index_array.append(start_index + c + resoulution) | |
| index_array.append(start_index + (c + 1 ) % resoulution + resoulution) | |
| mesh.clear_surfaces() | |
| mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES) | |
| for i in range(index_array.size() / 3): | |
| var p1 = vertex_array[index_array[3*i]] | |
| var p2 = vertex_array[index_array[3*i+1]] | |
| var p3 = vertex_array[index_array[3*i+2]] | |
| var tangent = Plane(p1, p2, p3) | |
| var normal = tangent.normal | |
| mesh.surface_set_tangent(tangent) | |
| mesh.surface_set_normal(normal) | |
| mesh.surface_add_vertex(p1) | |
| mesh.surface_set_tangent(tangent) | |
| mesh.surface_set_normal(normal) | |
| mesh.surface_add_vertex(p2) | |
| mesh.surface_set_tangent(tangent) | |
| mesh.surface_set_normal(normal) | |
| mesh.surface_add_vertex(p3) | |
| # End drawing | |
| mesh.surface_end() | |
| pass | |
| func CalcuateNormals(): | |
| normal_array.clear() | |
| tangent_array.clear() | |
| #var helper | |
| for i in range(point_count): | |
| var tangent := Vector3(0,0,0) | |
| var normal := Vector3(0,0,0) | |
| var temp_helper_vector := Vector3(0,0,0) | |
| #first point | |
| if i == 0: | |
| tangent = (points[i + 1] - points[i]).normalized() | |
| #last point | |
| elif i == point_count - 1: | |
| tangent = (points[i] - points[i-1]).normalized() | |
| #between point | |
| else: | |
| tangent = (points[i+1] - points[i]).normalized() + ( | |
| points[i] - points[i - 1]).normalized() | |
| if i == 0: | |
| temp_helper_vector = -Vector3.FORWARD if ( | |
| tangent.dot(Vector3.UP) > 0.5) else Vector3.UP | |
| normal = temp_helper_vector.cross(tangent).normalized() | |
| else: | |
| var tangent_prev = tangent_array[i-1] | |
| var normal_prev = normal_array[i-1] | |
| var bitangent = tangent_prev.cross(tangent) | |
| if bitangent.length() == 0: | |
| normal = normal_prev | |
| else: | |
| var bitangent_dir =bitangent.normalized() | |
| var theta = acos(tangent_prev.dot(tangent)) | |
| var rotate_matrix = Basis(bitangent_dir, theta) | |
| normal = (rotate_matrix * normal_prev).normalized() | |
| tangent_array.append(tangent) | |
| normal_array.append(normal) | |
| #pass | |
| # |