Skip to content

Instantly share code, notes, and snippets.

@Lightnet
Created June 23, 2025 05:35
Show Gist options
  • Save Lightnet/d842c786094979f10aa550390bbc1021 to your computer and use it in GitHub Desktop.
Save Lightnet/d842c786094979f10aa550390bbc1021 to your computer and use it in GitHub Desktop.
circle uv test godot 4
@tool
extends MeshInstance3D
# this is for ImmediateMesh
# works uv align but scale does not work.
# Rope properties
@export var rope_width: float = 0.1 # Radius of the rope
@export var resoulution: int = 8 # Number of vertices around the rope's circumference
@export var uv_scale: Vector2 = Vector2(1.0, 1.0) # UV scaling factor
@export var texture_height_to_width: float = 1.0 # Texture aspect ratio (height/width)
@export var show_debug_cubes: bool = true # Toggle to show/hide debug cubes
@export var use_face_colors: bool = true # Toggle to enable/disable per-face colors
@export var disable_face_logging: bool = false # Toggle to disable per-face logging
# Mesh data arrays
var vertex_array: Array = []
var uv1_array: Array = []
var normal_array: Array = []
var tangent_array: Array = []
var index_array: Array = []
var color_array: Array = [] # For per-vertex colors
# Rope points
var points: Array = []
@export var point_count: int = 0
var player_position: Vector3 = Vector3.ZERO
var grapple_hook_position: Vector3 = Vector3(0, 0, 1) # Short segment for testing
# Debug cubes
var debug_cubes: Array = [] # Store references to debug cube nodes
var cube_mesh: BoxMesh = BoxMesh.new() # Placeholder cube mesh
func _ready():
# Initialize test points for a single segment
initialize_test_points()
# Setup cube mesh
setup_debug_cubes()
func _process(delta: float) -> void:
generate_mesh()
func setup_debug_cubes():
# Configure cube mesh
cube_mesh.size = Vector3(0.05, 0.05, 0.05) # Small cubes for visibility
func clear_debug_cubes():
# Remove all existing debug cubes
for cube in debug_cubes:
if is_instance_valid(cube):
cube.queue_free()
debug_cubes.clear()
func create_debug_cube(position: Vector3, uv: Vector2, face_id: int, face_color: Color) -> MeshInstance3D:
# Create a new cube instance
var cube = MeshInstance3D.new()
cube.mesh = cube_mesh
var material = StandardMaterial3D.new()
material.albedo_color = face_color
material.flags_unshaded = true
cube.material_override = material
cube.global_position = position
# Add to scene tree
get_tree().root.add_child(cube)
# Store UV and face ID as metadata
cube.set_meta("uv", uv)
cube.set_meta("face_id", face_id)
# Add to debug_cubes array
debug_cubes.append(cube)
return cube
func initialize_test_points():
# Create a single rope segment (two points)
points.clear()
points.append(player_position) # Start at (0, 0, 0)
points.append(grapple_hook_position) # End at (0, 0, 1)
point_count = points.size()
func generate_mesh():
# Safeguard against invalid setup
if points.is_empty() or point_count < 2 or resoulution < 3:
print("GenerateMesh: Invalid setup (empty points, point_count < 2, or resoulution < 3)")
return
vertex_array.clear()
uv1_array.clear()
normal_array.clear()
tangent_array.clear()
index_array.clear()
color_array.clear()
# Clear existing debug cubes
if show_debug_cubes:
clear_debug_cubes()
# Calculate normals and tangents
calculate_normals()
# Clear existing mesh
mesh.clear_surfaces()
# Begin mesh construction
mesh.surface_begin(Mesh.PRIMITIVE_TRIANGLES)
# Total rope length for UV scaling
var total_rope_length = (grapple_hook_position - player_position).length()
if total_rope_length == 0:
total_rope_length = 0.001 # Prevent division by zero
# Circumference for UV aspect ratio
var circumference = 2.0 * PI * rope_width
var uv_u_scale = circumference * texture_height_to_width # ≈0.6283
# Generate vertices, UVs, colors, and indices
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()
# UV V-coordinate: normalized distance along rope
var distance_from_start = (center - player_position).length()
var uv1_v = distance_from_start / total_rope_length
# Generate vertices for the segment
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)
# UV U-coordinate: normalize to [0, uv_u_scale]
var uv1_u = (float(c) / resoulution) * uv_u_scale
var uv = Vector2(uv1_u, uv1_v) * uv_scale
uv1_array.append(uv)
# Placeholder color
color_array.append(Color.WHITE)
# Generate triangle indices
if p < point_count - 1:
var start_index = resoulution * p
var i1 = start_index + c
var i2 = start_index + c + resoulution
var i3 = start_index + (c + 1) % resoulution
index_array.append(i1)
index_array.append(i2)
index_array.append(i3)
var i4 = start_index + (c + 1) % resoulution
var i5 = start_index + c + resoulution
var i6 = start_index + ((c + 1) % resoulution) + resoulution
index_array.append(i4)
index_array.append(i5)
index_array.append(i6)
# Add triangles to ImmediateMesh with per-face colors and debug cubes
var face_id: int = 0
var max_faces = index_array.size() / 3
for i in range(max_faces):
var i1 = index_array[3 * i]
var i2 = index_array[3 * i + 1]
var i3 = index_array[3 * i + 2]
var p1 = vertex_array[i1]
var p2 = vertex_array[i2]
var p3 = vertex_array[i3]
var uv1 = uv1_array[i1]
var uv2 = uv1_array[i2]
var uv3 = uv1_array[i3]
# Apply UV fix for faces 0, 1, 2, 14, 15
if face_id == max_faces - 1: # Face 15
uv1.x = uv_u_scale
uv3.x = uv_u_scale
elif face_id == max_faces - 2: # Face 14
uv3.x = uv_u_scale
elif face_id == 1: # Face 1
uv1.x = uv_u_scale / resoulution # ≈0.0785
uv3.x = uv_u_scale / resoulution # ≈0.0785
elif face_id == 0: # Face 0
uv3.x = uv_u_scale / resoulution # ≈0.0785
elif face_id == 2: # Face 2
uv3.x = 2 * uv_u_scale / resoulution # ≈0.157
#uv3.x = 1 * uv_u_scale / resoulution # ≈0.157
pass
# Calculate triangle normal
var edge1 = p2 - p1
var edge2 = p3 - p1
var normal = edge1.cross(edge2).normalized()
# Check for degenerate triangle
var area = edge1.cross(edge2).length() / 2.0
var is_degenerate = area < 0.0001
# Assign unique color to this face
var face_color = Color.from_hsv(float(face_id) / max_faces, 0.8, 0.8) if use_face_colors else Color.WHITE
# Update vertex colors for this face
color_array[i1] = face_color
color_array[i2] = face_color
color_array[i3] = face_color
# Log face information for all faces to verify
if not disable_face_logging:
print("Face ", face_id, ":")
print(" Indices: ", [i1, i2, i3])
print(" Vertex 1: ", p1, ", UV: ", uv1)
print(" Vertex 2: ", p2, ", UV: ", uv2)
print(" Vertex 3: ", p3, ", UV: ", uv3)
print(" UV u differences: ", [abs(uv2.x - uv1.x), abs(uv3.x - uv1.x), abs(uv3.x - uv2.x)])
print(" Triangle area: ", area, ", Degenerate: ", is_degenerate)
print(" Color: ", face_color)
if show_debug_cubes:
var cube1 = create_debug_cube(p1, uv1, face_id, face_color)
var cube2 = create_debug_cube(p2, uv2, face_id, face_color)
var cube3 = create_debug_cube(p3, uv3, face_id, face_color)
print(" Cube 1 ID: ", cube1.get_instance_id(), ", Position: ", p1)
print(" Cube 2 ID: ", cube2.get_instance_id(), ", Position: ", p2)
print(" Cube 3 ID: ", cube3.get_instance_id(), ", Position: ", p3)
else:
create_debug_cube(p1, uv1, face_id, face_color)
create_debug_cube(p2, uv2, face_id, face_color)
create_debug_cube(p3, uv3, face_id, face_color)
else:
if show_debug_cubes:
create_debug_cube(p1, uv1, face_id, face_color)
create_debug_cube(p2, uv2, face_id, face_color)
create_debug_cube(p3, uv3, face_id, face_color)
# Add vertices with normals, UVs, and colors
mesh.surface_set_normal(normal)
mesh.surface_set_uv(uv1)
mesh.surface_set_color(face_color)
mesh.surface_add_vertex(p1)
mesh.surface_set_normal(normal)
mesh.surface_set_uv(uv2)
mesh.surface_set_color(face_color)
mesh.surface_add_vertex(p2)
mesh.surface_set_normal(normal)
mesh.surface_set_uv(uv3)
mesh.surface_set_color(face_color)
mesh.surface_add_vertex(p3)
face_id += 1
# End mesh
mesh.surface_end()
func calculate_normals():
normal_array.clear()
tangent_array.clear()
for i in range(point_count):
var tangent: Vector3 = Vector3.ZERO
var normal: Vector3 = Vector3.ZERO
var temp_helper_vector: Vector3 = Vector3.ZERO
# 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()).normalized()
if i == 0:
temp_helper_vector = Vector3.UP if tangent.dot(Vector3.UP) < 0.5 else -Vector3.FORWARD
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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment