diff options
| -rw-r--r-- | BakeVertexData.py | 91 | ||||
| -rw-r--r-- | DecodeVertexData.cs | 252 |
2 files changed, 188 insertions, 155 deletions
diff --git a/BakeVertexData.py b/BakeVertexData.py index dd8e3f5..3eecf05 100644 --- a/BakeVertexData.py +++ b/BakeVertexData.py @@ -778,7 +778,7 @@ class MESH_OT_merge_by_distance_per_submesh(BaseSubmeshOperator): layout.prop(self, "merge_distance") -class MESH_OT_bake_vertex_and_rotation_combined(BaseSubmeshOperator): +class MESH_OT_bake_origin_and_orientation_combined(BaseSubmeshOperator): bl_idname = "mesh.bake_submesh_origin_and_orientation" bl_label = "Bake Submesh Data" bl_description = "Bake vertex vectors and orientation quaternions" @@ -800,27 +800,38 @@ class MESH_OT_bake_vertex_and_rotation_combined(BaseSubmeshOperator): use_cache: BoolProperty( name="Cache Identical Submeshes", - description="Cache calculations for identical submeshes", + description="Cache calculations for identical submeshes to avoid recomputing basis and scale", default=True ) - def calculate_island_data(self, mesh, island_indices): - """Calculate center and scale for an island""" + def calculate_island_center(self, mesh, island_indices): + """Calculate center for an island""" if not island_indices: - return None, 1.0 + return None center = mathutils.Vector((0.0, 0.0, 0.0)) for idx in island_indices: center += mesh.vertices[idx].co center /= len(island_indices) - max_dist = max((abs(c - center[i]) - for idx in island_indices - for i, c in enumerate(mesh.vertices[idx].co)), - default=0) - - scale = 1.0 / max_dist if max_dist > 0 else 1.0 - return center, scale + return center + + def calculate_island_scale(self, mesh, island_indices, center, basis_inv): + """Calculate scale using L-infinity norm in rotated basis""" + if not island_indices: + return 1.0 + + max_coord = 0.0 + for idx in island_indices: + # Transform to local rotated basis + offset = mesh.vertices[idx].co - center + local_pos = basis_inv @ offset + + # L-infinity norm: max of absolute values + max_coord = max(max_coord, abs(local_pos.x), abs(local_pos.y), abs(local_pos.z)) + + scale = 1.0 / max_coord if max_coord > 0 else 1.0 + return scale def build_basis_from_faces(self, mesh, face_indices, epsilon): """Build orthonormal basis from face normals""" @@ -857,18 +868,19 @@ class MESH_OT_bake_vertex_and_rotation_combined(BaseSubmeshOperator): return matrix - def create_submesh_signature(self, mesh, island_indices, center, scale): - """Create signature for caching""" + def create_submesh_signature(self, mesh, island_indices, center): + """Create signature for caching - based on relative positions only""" tolerance = 0.0001 - local_positions = [] + relative_positions = [] for idx in island_indices: - local_pos = (mesh.vertices[idx].co - center) * scale - rounded = tuple(round(local_pos[i] / tolerance) * tolerance for i in range(3)) - local_positions.append(rounded) + relative_pos = mesh.vertices[idx].co - center + # Round to tolerance to handle floating point precision + rounded = tuple(round(relative_pos[i] / tolerance) * tolerance for i in range(3)) + relative_positions.append(rounded) - local_positions.sort() - return (len(island_indices), tuple(local_positions)) + relative_positions.sort() + return (len(island_indices), tuple(relative_positions)) def process_object(self, context, obj): """Process a single object and return (success, stats)""" @@ -913,40 +925,45 @@ class MESH_OT_bake_vertex_and_rotation_combined(BaseSubmeshOperator): # Process each island world_matrix = obj.matrix_world world_inv = world_matrix.inverted() + + # Cache for storing computed basis, scale, and quaternion for identical submeshes + # Key: geometry signature, Value: (scale, basis, quaternion, basis_inverse) submesh_cache = {} if self.use_cache else None for island in islands: - center, scale = self.calculate_island_data(mesh, island) + center = self.calculate_island_center(mesh, island) if center is None: continue - # Check cache + # Check cache first (if enabled) + cache_hit = False if self.use_cache: - signature = self.create_submesh_signature(mesh, island, center, scale) + signature = self.create_submesh_signature(mesh, island, center) if signature in submesh_cache: scale, basis, quat, basis_inv = submesh_cache[signature] - else: - island_faces = [f for f in selected_faces - if all(v in island for v in mesh.polygons[f].vertices)] - basis = self.build_basis_from_faces(mesh, island_faces, self.normal_epsilon) - quat = basis.to_quaternion() - quat.normalize() - if quat.w < 0: - quat.negate() - quat = correction @ quat - basis_inv = basis.inverted() + cache_hit = True - submesh_cache[signature] = (scale, basis, quat, basis_inv) - else: + # Only calculate if not in cache + if not cache_hit: + # Calculate basis (compute-intensive) island_faces = [f for f in selected_faces if all(v in island for v in mesh.polygons[f].vertices)] basis = self.build_basis_from_faces(mesh, island_faces, self.normal_epsilon) + basis_inv = basis.inverted() + + # Calculate scale using L-infinity norm in rotated basis (compute-intensive) + scale = self.calculate_island_scale(mesh, island, center, basis_inv) + + # Calculate quaternion quat = basis.to_quaternion() quat.normalize() if quat.w < 0: quat.negate() quat = correction @ quat - basis_inv = basis.inverted() + + # Store in cache if enabled + if self.use_cache: + submesh_cache[signature] = (scale, basis, quat, basis_inv) # Transform vertices center_world = world_matrix @ center @@ -1034,7 +1051,7 @@ classes = [ MESH_OT_deduplicate_submeshes, MESH_OT_pack_uv_islands_by_submesh_z, MESH_OT_merge_by_distance_per_submesh, - MESH_OT_bake_vertex_and_rotation_combined, + MESH_OT_bake_origin_and_orientation_combined, MESH_PT_bake_vertex_panel ] diff --git a/DecodeVertexData.cs b/DecodeVertexData.cs index 32933ce..d248773 100644 --- a/DecodeVertexData.cs +++ b/DecodeVertexData.cs @@ -1,184 +1,200 @@ using UnityEngine; using System.Collections.Generic; +using System.Linq; 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 Color correctedVectorColor = new Color(1f, 0.5f, 0.2f, 0.7f); - - [Header("Orientation Visualization")] + [Header("Display Settings")] + [SerializeField] private int maxVertices = 100; + [SerializeField] private float vectorScale = 0.3f; + + [Header("Edge Visualization")] + [SerializeField] private bool showEdges = true; + [SerializeField] private int edgeSubdivisions = 2; + + [Header("Orientation")] [SerializeField] private bool showOrientations = true; - [SerializeField] private bool showAllAxes = true; - [SerializeField] private float orientationVectorLength = 1.0f; - + [SerializeField] private float orientationScale = 1.0f; + [Header("UV Channels")] [SerializeField] private int quaternionXYChannel = 1; [SerializeField] private int quaternionZWChannel = 2; - + [Header("Colors")] + [SerializeField] private Color vectorColor = new Color(0.5f, 0.8f, 1f); + [SerializeField] private Color correctedVectorColor = new Color(1f, 0.5f, 0.2f); [SerializeField] private Color forwardColor = Color.blue; - [SerializeField] private Color rightColor = Color.red; - [SerializeField] private Color upColor = Color.green; private void OnDrawGizmos() { var meshFilter = GetComponent<MeshFilter>(); - if (meshFilter == null) return; - + if (!meshFilter || !meshFilter.sharedMesh) return; + var mesh = meshFilter.sharedMesh; - if (mesh == null) return; - var vertices = mesh.vertices; - var vertexColors = mesh.colors; - - if (vertexColors != null && vertexColors.Length > 0) + var colors = mesh.colors; + + // Draw vertex vectors from colors + if (colors != null && colors.Length > 0) { - DrawInterpolatedEdges(mesh, vertices, vertexColors); + DrawVertexVectors(mesh, vertices, colors); } - + + // Draw orientations from UVs if (showOrientations) { DrawOrientations(mesh, vertices); } } - - void DrawInterpolatedEdges(Mesh mesh, Vector3[] vertices, Color[] vertexColors) + + void DrawVertexVectors(Mesh mesh, Vector3[] vertices, Color[] colors) { - var triangles = mesh.triangles; - HashSet<(int, int)> drawnEdges = new HashSet<(int, int)>(); - Vector2[] uvXY = GetUVData(mesh, quaternionXYChannel); Vector2[] uvZW = GetUVData(mesh, quaternionZWChannel); - bool hasQuaternions = uvXY != null && uvZW != null && uvXY.Length > 0 && uvZW.Length > 0; - - for (int i = 0; i < triangles.Length; i += 3) + bool hasQuaternions = uvXY != null && uvZW != null; + + int vertexStep = Mathf.Max(1, vertices.Length / maxVertices); + + // Draw vectors at vertices + for (int i = 0; i < vertices.Length; i += vertexStep) + { + if (i >= colors.Length) break; + + Vector3 worldPos = transform.TransformPoint(vertices[i]); + Vector3 decodedVector = DecodeVectorFromColor(colors[i]); + + // Basic vector + Gizmos.color = vectorColor; + DrawVector(worldPos, transform.TransformDirection(decodedVector), vectorScale); + + // Quaternion-corrected vector + if (hasQuaternions && i < uvXY.Length && i < uvZW.Length) + { + Quaternion quat = GetQuaternionFromUV(uvXY[i], uvZW[i]); + Vector3 corrected = quat * decodedVector; + + Gizmos.color = correctedVectorColor; + DrawVector(worldPos, transform.TransformDirection(corrected), vectorScale); + } + } + + // Draw edge interpolations + if (showEdges && edgeSubdivisions > 0) + { + DrawEdgeInterpolations(mesh, vertices, colors, uvXY, uvZW); + } + } + + void DrawEdgeInterpolations(Mesh mesh, Vector3[] vertices, Color[] colors, Vector2[] uvXY, Vector2[] uvZW) + { + var triangles = mesh.triangles; + HashSet<(int, int)> drawnEdges = new HashSet<(int, int)>(); + bool hasQuaternions = uvXY != null && uvZW != null; + + for (int i = 0; i < triangles.Length && drawnEdges.Count < maxVertices/2; i += 3) { for (int j = 0; j < 3; j++) { int v1 = triangles[i + j]; int v2 = triangles[i + ((j + 1) % 3)]; - + var edge = v1 < v2 ? (v1, v2) : (v2, v1); - if (drawnEdges.Contains(edge)) continue; - drawnEdges.Add(edge); - + if (!drawnEdges.Add(edge)) continue; + if (v1 >= vertices.Length || v2 >= vertices.Length || - v1 >= vertexColors.Length || v2 >= vertexColors.Length) - continue; - - bool canUseQuaternions = hasQuaternions && - v1 < uvXY.Length && v2 < uvXY.Length && - v1 < uvZW.Length && v2 < uvZW.Length; - - for (int k = 0; k <= edgeSubdivisions; k++) + v1 >= colors.Length || v2 >= colors.Length) continue; + + // Draw subdivisions along edge + for (int k = 1; k < edgeSubdivisions; k++) { float t = k / (float)edgeSubdivisions; - - Vector3 localPos = Vector3.Lerp(vertices[v1], vertices[v2], t); - Vector3 worldPos = transform.TransformPoint(localPos); - - Color interpolatedColor = Color.Lerp(vertexColors[v1], vertexColors[v2], t); - Vector3 decodedVector = DecodeVectorFromColor(interpolatedColor); - - Gizmos.color = edgeVectorColor; - Vector3 worldVector = transform.TransformDirection(decodedVector); - DrawVector(worldPos, worldVector, edgeGizmoScale); - - if (canUseQuaternions) + Vector3 pos = Vector3.Lerp(vertices[v1], vertices[v2], t); + Color col = Color.Lerp(colors[v1], colors[v2], t); + + Vector3 worldPos = transform.TransformPoint(pos); + Vector3 vec = DecodeVectorFromColor(col); + + // Basic vector + Gizmos.color = vectorColor * 0.7f; // Slightly dimmer for edge points + DrawVector(worldPos, transform.TransformDirection(vec), vectorScale * 0.8f); + + // Quaternion-corrected vector + if (hasQuaternions && v1 < uvXY.Length && v2 < uvXY.Length && + v1 < uvZW.Length && v2 < uvZW.Length) { - Quaternion q1 = GetQuaternionFromUV(uvXY[v1], uvZW[v1]); - Quaternion q2 = GetQuaternionFromUV(uvXY[v2], uvZW[v2]); - // Slerp is more correct, but lerp is what we'll actually get in the shader. - Quaternion interpolatedQuat = Quaternion.Lerp(q1, q2, t); - - Vector3 correctedVector = interpolatedQuat * decodedVector; - Vector3 worldCorrectedVector = transform.TransformDirection(correctedVector); - - Gizmos.color = correctedVectorColor; - DrawVector(worldPos, worldCorrectedVector, edgeGizmoScale); + Vector2 interpXY = Vector2.Lerp(uvXY[v1], uvXY[v2], t); + Vector2 interpZW = Vector2.Lerp(uvZW[v1], uvZW[v2], t); + Quaternion interpQuat = GetQuaternionFromUV(interpXY, interpZW); + Vector3 corrected = interpQuat * vec; + + Gizmos.color = correctedVectorColor * 0.7f; // Slightly dimmer for edge points + DrawVector(worldPos, transform.TransformDirection(corrected), vectorScale * 0.8f); } } } } } - - void DrawVector(Vector3 origin, Vector3 direction, float scale) - { - Vector3 end = origin + direction * scale; - Gizmos.DrawSphere(end, 0.02f); - Gizmos.DrawLine(origin, end); - } - - Quaternion GetQuaternionFromUV(Vector2 xy, Vector2 zw) - { - float x = xy.x; - float y = xy.y; - float z = zw.x; - float w = zw.y; - - return new Quaternion(x, y, z, w).normalized; - } - + void DrawOrientations(Mesh mesh, Vector3[] vertices) { Vector2[] uvXY = GetUVData(mesh, quaternionXYChannel); Vector2[] uvZW = GetUVData(mesh, quaternionZWChannel); - - if (uvXY == null || uvZW == null || uvXY.Length == 0 || uvZW.Length == 0) return; - - int vertexCount = Mathf.Min(vertices.Length, uvXY.Length, uvZW.Length); - - for (int vertIdx = 0; vertIdx < vertexCount; vertIdx++) + + if (uvXY == null || uvZW == null) return; + + int vertexStep = Mathf.Max(1, vertices.Length / maxVertices); + + for (int i = 0; i < vertices.Length; i += vertexStep) { - Quaternion quat = GetQuaternionFromUV(uvXY[vertIdx], uvZW[vertIdx]); - - Vector3 worldPos = transform.TransformPoint(vertices[vertIdx]); - + if (i >= uvXY.Length || i >= uvZW.Length) break; + + Vector3 worldPos = transform.TransformPoint(vertices[i]); + Quaternion quat = GetQuaternionFromUV(uvXY[i], uvZW[i]); + + // Draw forward direction + Gizmos.color = forwardColor; Vector3 forward = transform.TransformDirection(quat * Vector3.forward); - DrawArrow(worldPos, forward, forwardColor, orientationVectorLength); - - if (showAllAxes) - { - Vector3 right = transform.TransformDirection(quat * Vector3.right); - Vector3 up = transform.TransformDirection(quat * Vector3.up); - DrawArrow(worldPos, right, rightColor, orientationVectorLength * 0.8f); - DrawArrow(worldPos, up, upColor, orientationVectorLength * 0.8f); - } + DrawArrow(worldPos, forward, orientationScale); } } - - void DrawArrow(Vector3 origin, Vector3 direction, Color color, float length) + + void DrawVector(Vector3 origin, Vector3 direction, float scale) + { + Vector3 end = origin + direction * scale; + Gizmos.DrawLine(origin, end); + Gizmos.DrawSphere(end, 0.02f); + } + + void DrawArrow(Vector3 origin, Vector3 direction, float length) { - Gizmos.color = color; - Vector3 end = origin + direction * length; Gizmos.DrawLine(origin, end); - + + // Simple arrowhead Vector3 right = Vector3.Cross(direction, Vector3.up).normalized; if (right.magnitude < 0.01f) right = Vector3.Cross(direction, Vector3.right).normalized; - + Vector3 arrowBack = -direction * length * 0.2f; Vector3 arrowSide = right * length * 0.1f; - + Gizmos.DrawLine(end, end + arrowBack + arrowSide); Gizmos.DrawLine(end, end + arrowBack - arrowSide); - - Gizmos.DrawSphere(origin, 0.05f); } - - n (-1 to 1), aVectorontains scale factor. - /// </summary> - + + Quaternion GetQuaternionFromUV(Vector2 xy, Vector2 zw) + { + return new Quaternion(xy.x, xy.y, zw.x, zw.y).normalized; + } + + 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; + color.r * 2.0f - 1.0f, + color.g * 2.0f - 1.0f, + color.b * 2.0f - 1.0f) / color.a; } - + Vector2[] GetUVData(Mesh mesh, int channel) { switch (channel) |
