Skip to content

Instantly share code, notes, and snippets.

@Lightnet
Created June 24, 2025 17:32
Show Gist options
  • Save Lightnet/36856ae0a4a5a56608772bf96aa839c5 to your computer and use it in GitHub Desktop.
Save Lightnet/36856ae0a4a5a56608772bf96aa839c5 to your computer and use it in GitHub Desktop.
sample test rope circle uv texture.
@tool
extends MeshInstance3D
@export var rope_length: float = 5.0 # Total length of the rope
@export var segments: int = 20 # Number of segments
@export var radius: float = 0.1 # Thickness of the rope
@export var sides: int = 8 # Number of sides for the rope's cross-section
func _ready() -> void:
generate_rope()
func _process(_delta: float) -> void:
if Engine.is_editor_hint(): # Update in editor for @tool
generate_rope()
func generate_rope() -> void:
# Create or get the ImmediateMesh
if mesh == null:
mesh = ImmediateMesh.new()
else:
mesh.clear_surfaces()
# Begin drawing the mesh
mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES)
# Generate points along a simple straight line (modify for curves if needed)
var points: Array[Vector3] = []
for i in range(segments + 1):
var t: float = float(i) / segments
points.append(Vector3(0, -t * rope_length, 0)) # Straight down
# Generate the rope mesh by creating a tube along the points
for i in range(segments):
var p0: Vector3 = points[i]
var p1: Vector3 = points[i + 1]
# Calculate the direction and basis vectors for the cross-section
var dir: Vector3 = (p1 - p0).normalized()
var right: Vector3 = dir.cross(Vector3.UP).normalized()
if right == Vector3.ZERO: # Handle edge case for vertical direction
right = dir.cross(Vector3.FORWARD).normalized()
var up: Vector3 = dir.cross(right).normalized()
# Create vertices for the current and next cross-section
for j in range(sides):
var angle0: float = j * TAU / sides
var angle1: float = (j + 1) * TAU / sides
# Vertices for the current segment's cross-section
var v0: Vector3 = p0 + radius * (cos(angle0) * right + sin(angle0) * up)
var v1: Vector3 = p0 + radius * (cos(angle1) * right + sin(angle1) * up)
var v2: Vector3 = p1 + radius * (cos(angle0) * right + sin(angle0) * up)
var v3: Vector3 = p1 + radius * (cos(angle1) * right + sin(angle1) * up)
# Normals (pointing outward from the rope's surface)
var n0: Vector3 = (cos(angle0) * right + sin(angle0) * up).normalized()
var n1: Vector3 = (cos(angle1) * right + sin(angle1) * up).normalized()
# UVs (simple cylindrical mapping)
var uv0: Vector2 = Vector2(float(j) / sides, float(i) / segments)
var uv1: Vector2 = Vector2(float(j + 1) / sides, float(i) / segments)
var uv2: Vector2 = Vector2(float(j) / sides, float(i + 1) / segments)
var uv3: Vector2 = Vector2(float(j + 1) / sides, float(i + 1) / segments)
# Add triangles with correct counterclockwise winding order for front face
# Triangle 1: v0, v2, v1 (counterclockwise when viewed from outside)
mesh.surface_set_normal(n0)
mesh.surface_set_uv(uv0)
mesh.surface_add_vertex(v0)
mesh.surface_set_normal(n0)
mesh.surface_set_uv(uv2)
mesh.surface_add_vertex(v2)
mesh.surface_set_normal(n1)
mesh.surface_set_uv(uv1)
mesh.surface_add_vertex(v1)
# Triangle 2: v1, v2, v3 (counterclockwise when viewed from outside)
mesh.surface_set_normal(n1)
mesh.surface_set_uv(uv1)
mesh.surface_add_vertex(v1)
mesh.surface_set_normal(n0)
mesh.surface_set_uv(uv2)
mesh.surface_add_vertex(v2)
mesh.surface_set_normal(n1)
mesh.surface_set_uv(uv3)
mesh.surface_add_vertex(v3)
# End the surface
mesh.surface_end()
# Assign a material (optional, for visibility in editor)
if material_override == null:
var mat = StandardMaterial3D.new()
mat.albedo_color = Color.GRAY
mat.cull_mode = StandardMaterial3D.CULL_BACK # Ensure backface culling
material_override = mat
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment