diff options
| author | yum <yum.food.vr@gmail.com> | 2025-06-08 14:34:22 -0700 |
|---|---|---|
| committer | yum <yum.food.vr@gmail.com> | 2025-06-08 14:34:22 -0700 |
| commit | 5e75dcd4260b1d64b9360acb2f2887fe63230eb0 (patch) | |
| tree | 68b2de38b48794e763e31cb0b35e35efd08e9377 | |
| parent | 7cbb2c12028db11d811554c0fd8d1e6e79c4810d (diff) | |
switch to new baking method where we also add a rotating matrix
| -rw-r--r-- | BakeVertexData.py | 707 | ||||
| -rw-r--r-- | DecodeVertexVectors.cs | 104 |
2 files changed, 456 insertions, 355 deletions
diff --git a/BakeVertexData.py b/BakeVertexData.py index 7493aef..2e2554a 100644 --- a/BakeVertexData.py +++ b/BakeVertexData.py @@ -15,252 +15,6 @@ from bpy.props import BoolProperty, FloatProperty, IntProperty from bpy.types import Panel, Operator -class MESH_OT_bake_vertex_vectors(Operator): - bl_idname = "mesh.bake_vertex_vectors" - bl_label = "Bake Vertex Vectors" - bl_description = "Bake selected vertices with automatic center and scale calculation" - bl_options = {'REGISTER', 'UNDO'} - - contiguous_mode: BoolProperty( - name="Contiguous Groups", - description="Process each contiguous group of vertices separately with its own center and scale", - default=False - ) - - @classmethod - def poll(cls, context): - obj = context.active_object - return (obj is not None and - obj.type == 'MESH' and - context.mode == 'EDIT_MESH') - - def get_vertex_islands(self, mesh, selected_indices): - """Find contiguous groups of vertices using edge connectivity""" - adjacency = {idx: set() for idx in selected_indices} - - for edge in mesh.edges: - v0, v1 = edge.vertices - if v0 in selected_indices and v1 in selected_indices: - adjacency[v0].add(v1) - adjacency[v1].add(v0) - - islands = [] - visited = set() - - for start_idx in selected_indices: - if start_idx in visited: - continue - - island = set() - queue = [start_idx] - - while queue: - current = queue.pop(0) - if current in visited: - continue - - visited.add(current) - island.add(current) - - for neighbor in adjacency[current]: - if neighbor not in visited: - queue.append(neighbor) - - islands.append(island) - - return islands - - def get_island_info(self, mesh, island_indices): - """Calculate center and scale for a single island""" - verts = [] - for idx in island_indices: - verts.append(mesh.vertices[idx].co) - - if not verts: - return None, None, None - - center = mathutils.Vector((0, 0, 0)) - for co in verts: - center += co - center /= len(verts) - - max_dist = 0.0 - for co in verts: - vec = co - center - max_component = max(abs(vec.x), abs(vec.y), abs(vec.z)) - max_dist = max(max_dist, max_component) - - if max_dist > 0: - scale = 1.0 / max_dist - else: - scale = 1.0 - - return center, max_dist, scale - - def execute(self, context): - obj = context.active_object - mesh = obj.data - - try: - bpy.ops.object.mode_set(mode='OBJECT') - except Exception as e: - self.report({'ERROR'}, f"Failed to switch to object mode: {str(e)}") - return {'CANCELLED'} - - selected_indices = {v.index for v in mesh.vertices if v.select} - - if not selected_indices: - self.report({'WARNING'}, "No vertices selected") - try: - bpy.ops.object.mode_set(mode='EDIT') - except: - pass - return {'CANCELLED'} - - # Check if mesh has faces - if not mesh.polygons: - self.report({'ERROR'}, "Mesh has no faces. Vertex colors require faces to store data.") - try: - bpy.ops.object.mode_set(mode='EDIT') - except: - pass - return {'CANCELLED'} - - if not mesh.vertex_colors: - mesh.vertex_colors.new(name="BakedVectors") - - color_layer = mesh.vertex_colors.active - if not color_layer: - self.report({'ERROR'}, "Failed to create vertex color layer") - try: - bpy.ops.object.mode_set(mode='EDIT') - except: - pass - return {'CANCELLED'} - - source_matrix = obj.matrix_world - - if self.contiguous_mode: - # Build vertex to polygon mapping for efficiency - vertex_to_polys = {} - for poly_idx, poly in enumerate(mesh.polygons): - for vertex_idx in poly.vertices: - if vertex_idx in selected_indices: - if vertex_idx not in vertex_to_polys: - vertex_to_polys[vertex_idx] = [] - vertex_to_polys[vertex_idx].append(poly_idx) - - islands = self.get_vertex_islands(mesh, selected_indices) - total_updated = 0 - - for island_indices in islands: - center, max_dist, scale = self.get_island_info(mesh, island_indices) - if center is None: - continue - - center_world = source_matrix @ center - - # Collect all polygons that contain vertices from this island - relevant_polys = set() - for vertex_idx in island_indices: - if vertex_idx in vertex_to_polys: - relevant_polys.update(vertex_to_polys[vertex_idx]) - - # Only process relevant polygons - for poly_idx in relevant_polys: - poly = mesh.polygons[poly_idx] - for loop_idx in poly.loop_indices: - vertex_idx = mesh.loops[loop_idx].vertex_index - - if vertex_idx in island_indices: - vertex = mesh.vertices[vertex_idx] - - vertex_world = source_matrix @ vertex.co - vector_world = center_world - vertex_world - vector_object = source_matrix.inverted_safe().to_3x3() @ vector_world - vector_scaled = vector_object * scale - - color = mathutils.Vector(( - (vector_scaled.x + 1.0) * 0.5, - (vector_scaled.y + 1.0) * 0.5, - (vector_scaled.z + 1.0) * 0.5, - scale - )) - - color_layer.data[loop_idx].color = color - total_updated += 1 - - mesh.update() - - try: - bpy.ops.object.mode_set(mode='EDIT') - except Exception as e: - self.report({'WARNING'}, f"Could not return to edit mode: {str(e)}") - - self.report({'INFO'}, f"Baked {len(islands)} contiguous groups ({len(selected_indices)} vertices)") - return {'FINISHED'} - - else: - verts = [] - for idx in selected_indices: - verts.append(mesh.vertices[idx].co) - - center = mathutils.Vector((0, 0, 0)) - for co in verts: - center += co - center /= len(verts) - - max_dist = 0.0 - for co in verts: - vec = co - center - max_component = max(abs(vec.x), abs(vec.y), abs(vec.z)) - max_dist = max(max_dist, max_component) - - if max_dist > 0: - scale = 1.0 / max_dist - else: - scale = 1.0 - - center_world = source_matrix @ center - - updated_count = 0 - for poly in mesh.polygons: - for loop_idx in poly.loop_indices: - vertex_idx = mesh.loops[loop_idx].vertex_index - - if vertex_idx in selected_indices: - vertex = mesh.vertices[vertex_idx] - - vertex_world = source_matrix @ vertex.co - vector_world = center_world - vertex_world - vector_object = source_matrix.inverted_safe().to_3x3() @ vector_world - vector_scaled = vector_object * scale - - color = mathutils.Vector(( - (vector_scaled.x + 1.0) * 0.5, - (vector_scaled.y + 1.0) * 0.5, - (vector_scaled.z + 1.0) * 0.5, - scale - )) - - color_layer.data[loop_idx].color = color - updated_count += 1 - - mesh.update() - - try: - bpy.ops.object.mode_set(mode='EDIT') - except Exception as e: - self.report({'WARNING'}, f"Could not return to edit mode: {str(e)}") - - self.report({'INFO'}, f"Baked {len(selected_indices)} vertices with scale {scale:.3f}") - return {'FINISHED'} - - def draw(self, context): - layout = self.layout - layout.prop(self, "contiguous_mode") - - class MESH_OT_select_all_linked_submeshes(Operator): bl_idname = "mesh.select_all_linked_submeshes" bl_label = "Select All Linked Submeshes" @@ -357,6 +111,10 @@ class MESH_OT_select_all_linked_submeshes(Operator): self.report({'INFO'}, f"Expanded selection in {affected_islands} submeshes ({expanded_count} new vertices)") return {'FINISHED'} + def draw(self, context): + layout = self.layout + layout.prop(self, "contiguous_mode") + class MESH_OT_select_linked_across_boundaries(Operator): bl_idname = "mesh.select_linked_across_boundaries" @@ -1320,7 +1078,7 @@ class MESH_OT_merge_by_distance_in_submeshes(Operator): queue = deque([start_idx]) while queue: - current = queue.popleft() + current = queue.pop(0) if current in visited: continue @@ -1448,6 +1206,449 @@ class MESH_OT_merge_by_distance_in_submeshes(Operator): layout.label(text="Merges within submeshes only", icon='INFO') +class MESH_OT_bake_vertex_and_rotation_combined(Operator): + bl_idname = "mesh.bake_vertex_and_rotation_combined" + bl_label = "Bake Vectors & Rotation Combined" + bl_description = "Bake vertex vectors relative to orthonormal basis and orientation quaternions in one operation" + bl_options = {'REGISTER', 'UNDO'} + + contiguous_mode: BoolProperty( + name="Contiguous Groups", + description="Process each contiguous group of vertices separately with its own center and scale", + default=False + ) + + normal_epsilon: FloatProperty( + name="Normal Epsilon", + description="Minimum angle difference (radians) between normals to consider them unique", + default=0.1, + min=0.01, + max=1.0, + precision=3 + ) + + @classmethod + def poll(cls, context): + obj = context.active_object + return (obj is not None and + obj.type == 'MESH' and + context.mode == 'EDIT_MESH') + + def get_vertex_islands(self, mesh, selected_indices): + """Find contiguous groups of vertices using edge connectivity""" + adjacency = {idx: set() for idx in selected_indices} + + for edge in mesh.edges: + v0, v1 = edge.vertices + if v0 in selected_indices and v1 in selected_indices: + adjacency[v0].add(v1) + adjacency[v1].add(v0) + + islands = [] + visited = set() + + for start_idx in selected_indices: + if start_idx in visited: + continue + + island = set() + queue = [start_idx] + + while queue: + current = queue.pop(0) + if current in visited: + continue + + visited.add(current) + island.add(current) + + for neighbor in adjacency[current]: + if neighbor not in visited: + queue.append(neighbor) + + islands.append(island) + + return islands + + def get_submesh_faces_and_normals(self, mesh, island_indices): + """Get all faces and their normals for vertices in the island""" + faces_data = [] + + # Build vertex to faces mapping for efficiency + vertex_to_faces = {} + for face_idx, face in enumerate(mesh.polygons): + for vertex_idx in face.vertices: + if vertex_idx in island_indices: + if vertex_idx not in vertex_to_faces: + vertex_to_faces[vertex_idx] = [] + vertex_to_faces[vertex_idx].append(face_idx) + + # Collect unique faces that belong to this island + island_faces = set() + for vertex_idx in island_indices: + if vertex_idx in vertex_to_faces: + for face_idx in vertex_to_faces[vertex_idx]: + face = mesh.polygons[face_idx] + # Check if all vertices of this face are in the island + if all(v in island_indices for v in face.vertices): + island_faces.add(face_idx) + + # Calculate face data + for face_idx in island_faces: + face = mesh.polygons[face_idx] + faces_data.append({ + 'normal': face.normal.copy(), + 'area': face.area, + 'index': face_idx + }) + + return faces_data + + def build_orthonormal_basis_from_faces(self, faces_data, epsilon): + """Build orthonormal basis from face normals""" + if not faces_data: + # Default to standard basis if no faces + return mathutils.Matrix.Identity(3) + + # Sort faces by area (largest first) + faces_data.sort(key=lambda x: x['area'], reverse=True) + + # Find up to 3 non-parallel normals + basis_normals = [] + epsilon_cos = math.cos(epsilon) + + for face_data in faces_data: + normal = face_data['normal'].normalized() + + # Check if this normal is sufficiently different from existing ones + is_unique = True + for existing_normal in basis_normals: + dot_product = abs(normal.dot(existing_normal)) + if dot_product > epsilon_cos: + is_unique = False + break + + if is_unique: + basis_normals.append(normal) + if len(basis_normals) >= 3: + break + + # Build orthonormal basis + if len(basis_normals) == 0: + # No faces, use default + return mathutils.Matrix.Identity(3) + + # First basis vector is the largest face normal + x_axis = basis_normals[0].normalized() + + if len(basis_normals) >= 2: + # Use second normal to define y-axis + y_candidate = basis_normals[1] + # Make it orthogonal to x + y_axis = (y_candidate - y_candidate.project(x_axis)).normalized() + + # If y_axis is too small (normals were almost parallel), find a different vector + if y_axis.length < 0.1: + # Create arbitrary perpendicular vector + if abs(x_axis.z) < 0.9: + y_axis = mathutils.Vector((0, 0, 1)).cross(x_axis).normalized() + else: + y_axis = mathutils.Vector((1, 0, 0)).cross(x_axis).normalized() + else: + # Only one unique normal, create arbitrary perpendicular + if abs(x_axis.z) < 0.9: + y_axis = mathutils.Vector((0, 0, 1)).cross(x_axis).normalized() + else: + y_axis = mathutils.Vector((1, 0, 0)).cross(x_axis).normalized() + + # Third axis from cross product + z_axis = x_axis.cross(y_axis).normalized() + + # Build rotation matrix (columns are the basis vectors) + matrix = mathutils.Matrix(( + (x_axis.x, y_axis.x, z_axis.x), + (x_axis.y, y_axis.y, z_axis.y), + (x_axis.z, y_axis.z, z_axis.z) + )) + + # Ensure right-handed coordinate system + if matrix.determinant() < 0: + z_axis = -z_axis + matrix = mathutils.Matrix(( + (x_axis.x, y_axis.x, z_axis.x), + (x_axis.y, y_axis.y, z_axis.y), + (x_axis.z, y_axis.z, z_axis.z) + )) + + return matrix + + def get_island_info(self, mesh, island_indices): + """Calculate center and scale for a single island""" + verts = [] + for idx in island_indices: + verts.append(mesh.vertices[idx].co) + + if not verts: + return None, None, None + + center = mathutils.Vector((0, 0, 0)) + for co in verts: + center += co + center /= len(verts) + + max_dist = 0.0 + for co in verts: + vec = co - center + max_component = max(abs(vec.x), abs(vec.y), abs(vec.z)) + max_dist = max(max_dist, max_component) + + if max_dist > 0: + scale = 1.0 / max_dist + else: + scale = 1.0 + + return center, max_dist, scale + + def execute(self, context): + obj = context.active_object + mesh = obj.data + + try: + bpy.ops.object.mode_set(mode='OBJECT') + except Exception as e: + self.report({'ERROR'}, f"Failed to switch to object mode: {str(e)}") + return {'CANCELLED'} + + selected_indices = {v.index for v in mesh.vertices if v.select} + + if not selected_indices: + self.report({'WARNING'}, "No vertices selected") + try: + bpy.ops.object.mode_set(mode='EDIT') + except: + pass + return {'CANCELLED'} + + # Check if mesh has faces + if not mesh.polygons: + self.report({'ERROR'}, "Mesh has no faces. Both vertex colors and quaternion baking require faces.") + try: + bpy.ops.object.mode_set(mode='EDIT') + except: + pass + return {'CANCELLED'} + + # Create vertex color layer + if not mesh.vertex_colors: + mesh.vertex_colors.new(name="BakedVectors") + color_layer = mesh.vertex_colors.active + if not color_layer: + self.report({'ERROR'}, "Failed to create vertex color layer") + try: + bpy.ops.object.mode_set(mode='EDIT') + except: + pass + return {'CANCELLED'} + + # Remove existing UV maps and create new ones for quaternions + uv_names = ["BakedOriginAngle0", "BakedOriginAngle1"] + for uv_name in uv_names: + if uv_name in mesh.uv_layers: + mesh.uv_layers.remove(mesh.uv_layers[uv_name]) + + uv_layer0 = mesh.uv_layers.new(name="BakedOriginAngle0") + uv_layer1 = mesh.uv_layers.new(name="BakedOriginAngle1") + + if not uv_layer0 or not uv_layer1: + self.report({'ERROR'}, "Failed to create UV layers") + try: + bpy.ops.object.mode_set(mode='EDIT') + except: + pass + return {'CANCELLED'} + + source_matrix = obj.matrix_world + + if self.contiguous_mode: + # Build vertex to polygon mapping for efficiency + vertex_to_polys = {} + for poly_idx, poly in enumerate(mesh.polygons): + for vertex_idx in poly.vertices: + if vertex_idx in selected_indices: + if vertex_idx not in vertex_to_polys: + vertex_to_polys[vertex_idx] = [] + vertex_to_polys[vertex_idx].append(poly_idx) + + islands = self.get_vertex_islands(mesh, selected_indices) + total_updated = 0 + + # Process each island separately + for island_indices in islands: + # Get vector data for this island + center, max_dist, scale = self.get_island_info(mesh, island_indices) + if center is None: + continue + + # Get face data and build orthonormal basis for this island + faces_data = self.get_submesh_faces_and_normals(mesh, island_indices) + basis_matrix = self.build_orthonormal_basis_from_faces(faces_data, self.normal_epsilon) + + # Convert basis to quaternion + quaternion = basis_matrix.to_quaternion() + quaternion.normalize() + + if quaternion.w < 0: + quaternion.negate() + + center_world = source_matrix @ center + + # Collect all polygons that contain vertices from this island + relevant_polys = set() + for vertex_idx in island_indices: + if vertex_idx in vertex_to_polys: + relevant_polys.update(vertex_to_polys[vertex_idx]) + + # Only process relevant polygons + for poly_idx in relevant_polys: + poly = mesh.polygons[poly_idx] + for loop_idx in poly.loop_indices: + vertex_idx = mesh.loops[loop_idx].vertex_index + + if vertex_idx in island_indices: + vertex = mesh.vertices[vertex_idx] + + # Calculate vector from vertex to center in world space + vertex_world = source_matrix @ vertex.co + vector_world = vertex_world - center_world + + # Transform to object space + vector_object = source_matrix.inverted_safe().to_3x3() @ vector_world + + # Transform vector to local orthonormal basis coordinates + # Use transpose of basis matrix to transform from object to local + vector_local = basis_matrix.transposed() @ vector_object + + # Apply scale + vector_scaled = vector_local * scale + + # Store transformed vector in vertex colors + color = mathutils.Vector(( + (vector_scaled.x + 1.0) * 0.5, + (vector_scaled.y + 1.0) * 0.5, + (vector_scaled.z + 1.0) * 0.5, + scale + )) + color_layer.data[loop_idx].color = color + + # Store quaternion in UV coordinates + uv_layer0.data[loop_idx].uv = mathutils.Vector((quaternion.x, quaternion.y)) + uv_layer1.data[loop_idx].uv = mathutils.Vector((quaternion.z, quaternion.w)) + + total_updated += 1 + + mesh.update() + + try: + bpy.ops.object.mode_set(mode='EDIT') + except Exception as e: + self.report({'WARNING'}, f"Could not return to edit mode: {str(e)}") + + self.report({'INFO'}, f"Baked vectors & quaternions for {len(islands)} contiguous groups ({len(selected_indices)} vertices)") + return {'FINISHED'} + + else: + # Single group mode - process all selected vertices as one group + + # Get vector data + verts = [] + for idx in selected_indices: + verts.append(mesh.vertices[idx].co) + + center = mathutils.Vector((0, 0, 0)) + for co in verts: + center += co + center /= len(verts) + + max_dist = 0.0 + for co in verts: + vec = co - center + max_component = max(abs(vec.x), abs(vec.y), abs(vec.z)) + max_dist = max(max_dist, max_component) + + if max_dist > 0: + scale = 1.0 / max_dist + else: + scale = 1.0 + + # Get face data and build orthonormal basis for all selected vertices + faces_data = self.get_submesh_faces_and_normals(mesh, selected_indices) + basis_matrix = self.build_orthonormal_basis_from_faces(faces_data, self.normal_epsilon) + + # Convert basis to quaternion + quaternion = basis_matrix.to_quaternion() + quaternion.normalize() + + if quaternion.w < 0: + quaternion.negate() + + center_world = source_matrix @ center + + updated_count = 0 + for poly in mesh.polygons: + for loop_idx in poly.loop_indices: + vertex_idx = mesh.loops[loop_idx].vertex_index + + if vertex_idx in selected_indices: + vertex = mesh.vertices[vertex_idx] + + # Calculate vector from vertex to center in world space + vertex_world = source_matrix @ vertex.co + vector_world = vertex_world - center_world + + # Transform to object space + vector_object = source_matrix.inverted_safe().to_3x3() @ vector_world + + # Transform vector to local orthonormal basis coordinates + vector_local = basis_matrix.transposed() @ vector_object + + # Apply scale + vector_scaled = vector_local * scale + + # Store transformed vector in vertex colors + color = mathutils.Vector(( + (vector_scaled.x + 1.0) * 0.5, + (vector_scaled.y + 1.0) * 0.5, + (vector_scaled.z + 1.0) * 0.5, + scale + )) + color_layer.data[loop_idx].color = color + + # Store quaternion in UV coordinates + uv_layer0.data[loop_idx].uv = mathutils.Vector((quaternion.x, quaternion.y)) + uv_layer1.data[loop_idx].uv = mathutils.Vector((quaternion.z, quaternion.w)) + + updated_count += 1 + + mesh.update() + + try: + bpy.ops.object.mode_set(mode='EDIT') + except Exception as e: + self.report({'WARNING'}, f"Could not return to edit mode: {str(e)}") + + self.report({'INFO'}, f"Baked vectors & quaternion for {len(selected_indices)} vertices with scale {scale:.3f}") + return {'FINISHED'} + + def draw(self, context): + layout = self.layout + layout.prop(self, "contiguous_mode") + layout.prop(self, "normal_epsilon") + layout.label(text="Vectors relative to orthonormal basis", icon='INFO') + layout.label(text="Stores quaternion in UV maps:", icon='INFO') + layout.label(text=" BakedOriginAngle0: X, Y components") + layout.label(text=" BakedOriginAngle1: Z, W components") + + class MESH_PT_bake_vertex_panel(Panel): bl_label = "Bake Vertex Vectors" bl_idname = "MESH_PT_bake_vertex_vectors" @@ -1469,7 +1670,9 @@ class MESH_PT_bake_vertex_panel(Panel): col.operator("mesh.merge_by_distance_in_submeshes", icon='AUTOMERGE_ON') col.operator("mesh.pack_uv_islands_by_submesh", icon='UV') col.separator() - col.operator("mesh.bake_vertex_vectors", icon='EXPORT') + + # Combined operator + col.operator("mesh.bake_vertex_and_rotation_combined", icon='EXPORT', text="Bake Vectors & Rotation") box = col.box() box.label(text="Info:", icon='INFO') @@ -1478,8 +1681,10 @@ class MESH_PT_bake_vertex_panel(Panel): box.label(text="Deduplicate: Remove duplicate selected submeshes") box.label(text="Merge: Merge vertices within submeshes") box.label(text="Pack UV Islands: Sort UV islands by submesh Z position") - box.label(text="Bake: Auto-scale selected vertices") + box.label(text="Bake Vectors & Rotation: Bake vectors relative to") + box.label(text=" orthonormal basis & orientation quaternions") box.label(text="Toggle Contiguous Groups for separate islands") + box.label(text="Vectors stored in vertex colors, quaternions in UV maps") box.label(text="Scale factor stored in alpha channel") mesh = obj.data @@ -1493,7 +1698,7 @@ class MESH_PT_bake_vertex_panel(Panel): classes = [ - MESH_OT_bake_vertex_vectors, + MESH_OT_bake_vertex_and_rotation_combined, MESH_OT_select_all_linked_submeshes, MESH_OT_select_linked_across_boundaries, MESH_OT_deduplicate_submeshes, @@ -1510,7 +1715,7 @@ def menu_func(self, context): self.layout.operator("mesh.deduplicate_submeshes", icon='DUPLICATE') self.layout.operator("mesh.merge_by_distance_in_submeshes", icon='AUTOMERGE_ON') self.layout.operator("mesh.pack_uv_islands_by_submesh", icon='UV') - self.layout.operator("mesh.bake_vertex_vectors", icon='EXPORT') + self.layout.operator("mesh.bake_vertex_and_rotation_combined", icon='EXPORT') def register(): diff --git a/DecodeVertexVectors.cs b/DecodeVertexVectors.cs deleted file mode 100644 index 6dd1627..0000000 --- a/DecodeVertexVectors.cs +++ /dev/null @@ -1,104 +0,0 @@ -using UnityEngine; -using System.Collections.Generic; - -public class DecodeVertexVectors : MonoBehaviour -{ - [Header("Edge Interpolation")] - [SerializeField] private int edgeSubdivisions = 5; - [SerializeField] private float edgeGizmoScale = 0.3f; - [SerializeField] private Color edgeVectorColor = new Color(0.5f, 0.8f, 1f, 0.7f); - [SerializeField] private float edgeLineThickness = 2f; - - private Vector3[] decodedVertexVectors; - - void Start() - { - var mesh = GetComponent<MeshFilter>().mesh; - var vertexColors = mesh.colors; - - if (vertexColors.Length == 0) - { - Debug.LogError("No vertex colors found on mesh!"); - return; - } - - decodedVertexVectors = new Vector3[vertexColors.Length]; - - for (int i = 0; i < vertexColors.Length; i++) - { - decodedVertexVectors[i] = DecodeVectorFromColor(vertexColors[i]); - } - } - - Vector3 DecodeVectorFromColor(Color color) - { - return new Vector3( - -(color.r * 2.0f - 1.0f), - (color.g * 2.0f - 1.0f), - (color.b * 2.0f - 1.0f)) / color.a; - } - - private void OnDrawGizmos() - { - var meshFilter = GetComponent<MeshFilter>(); - if (meshFilter == null) return; - - var mesh = meshFilter.sharedMesh; - if (mesh == null) return; - - var vertices = mesh.vertices; - var vertexColors = mesh.colors; - - DrawInterpolatedEdges(mesh, vertices, vertexColors); - } - - void DrawInterpolatedEdges(Mesh mesh, Vector3[] vertices, Color[] vertexColors) - { - var triangles = mesh.triangles; - HashSet<(int, int)> drawnEdges = new HashSet<(int, int)>(); - - Gizmos.color = edgeVectorColor; - - for (int i = 0; i < triangles.Length; i += 3) - { - // Draw edges for each triangle - for (int j = 0; j < 3; j++) - { - int v1 = triangles[i + j]; - int v2 = triangles[i + ((j + 1) % 3)]; - - // Ensure we don't draw the same edge twice - var edge = v1 < v2 ? (v1, v2) : (v2, v1); - if (drawnEdges.Contains(edge)) continue; - drawnEdges.Add(edge); - - if (v1 >= vertices.Length || v2 >= vertices.Length || - v1 >= vertexColors.Length || v2 >= vertexColors.Length) - continue; - - // Interpolate along the edge - for (int k = 0; k <= edgeSubdivisions; k++) - { - float t = k / (float)edgeSubdivisions; - - // Interpolate position - Vector3 localPos = Vector3.Lerp(vertices[v1], vertices[v2], t); - Vector3 worldPos = transform.TransformPoint(localPos); - - // Interpolate color - Color interpolatedColor = Color.Lerp(vertexColors[v1], vertexColors[v2], t); - - // Decode vector from interpolated color - Vector3 decodedVector = DecodeVectorFromColor(interpolatedColor); - Vector3 worldVector = transform.TransformDirection(decodedVector); - - // Draw the interpolated vector - Vector3 start = worldPos; - Vector3 end = worldPos + worldVector * edgeGizmoScale; - Gizmos.DrawSphere(end, 0.02f); - Gizmos.DrawLine(start, end); - } - } - } - } -} |
