@nice2dev/ui-3d 1.3.14 → 1.3.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/cjs/collaborative/collaborativeScene.js +160 -206
  3. package/dist/cjs/core/editorAsk.js +51 -41
  4. package/dist/cjs/core/i18n.js +13 -14
  5. package/dist/cjs/core/threeCompat.js +29 -42
  6. package/dist/cjs/dance/DanceBridge.js +148 -177
  7. package/dist/cjs/dance/DanceScoreEngine.js +209 -221
  8. package/dist/cjs/dance/PoseDetector.js +302 -275
  9. package/dist/cjs/index.js +110 -113
  10. package/dist/cjs/material/MaterialEditor.module.css.js +242 -5
  11. package/dist/cjs/material/NiceMaterialEditor.js +1213 -757
  12. package/dist/cjs/material/materialEditorTypes.js +35 -49
  13. package/dist/cjs/material/materialEditorUtils.js +684 -747
  14. package/dist/cjs/material/materialNodeDefinitions.js +1411 -1459
  15. package/dist/cjs/model/ModelEditor.js +90 -40
  16. package/dist/cjs/model/ModelEditor.module.css.js +257 -5
  17. package/dist/cjs/model/ModelEditorLeftPanel.js +126 -47
  18. package/dist/cjs/model/ModelEditorMenuBar.js +359 -32
  19. package/dist/cjs/model/ModelEditorRightPanel.js +647 -131
  20. package/dist/cjs/model/ModelEditorSubComponents.js +277 -112
  21. package/dist/cjs/model/ModelEditorTimeline.js +232 -34
  22. package/dist/cjs/model/ModelEditorToolbar.js +170 -14
  23. package/dist/cjs/model/ModelEditorViewport.js +231 -39
  24. package/dist/cjs/model/ModelViewer.js +388 -63
  25. package/dist/cjs/model/ModelViewer.module.css.js +77 -5
  26. package/dist/cjs/model/NiceArmatureEditor.js +473 -274
  27. package/dist/cjs/model/NiceMorphTargetEditor.js +333 -227
  28. package/dist/cjs/model/NiceOctree.js +372 -320
  29. package/dist/cjs/model/NicePhysicsSimulation.js +528 -298
  30. package/dist/cjs/model/NiceProceduralGeometry.js +673 -300
  31. package/dist/cjs/model/NiceTerrainEditor.js +595 -385
  32. package/dist/cjs/model/NiceWeightPainter.js +384 -261
  33. package/dist/cjs/model/NiceXRPreview.js +385 -308
  34. package/dist/cjs/model/cadModeUtils.js +103 -106
  35. package/dist/cjs/model/editorShortcuts.js +171 -194
  36. package/dist/cjs/model/modelEditorTypes.js +100 -118
  37. package/dist/cjs/model/modelEditorUtils.js +1061 -1146
  38. package/dist/cjs/model/simsModeUtils.js +1023 -1053
  39. package/dist/cjs/model/useModelEditor.js +1977 -1933
  40. package/dist/cjs/model/useModelViewer.js +633 -665
  41. package/dist/cjs/nice2dev-ui-3d.css +1 -1
  42. package/dist/cjs/particle/NiceParticleEditor.js +1186 -571
  43. package/dist/cjs/particle/ParticleEditor.module.css.js +122 -5
  44. package/dist/cjs/particle/particleEditorTypes.js +62 -70
  45. package/dist/cjs/particle/particleEditorUtils.js +998 -1013
  46. package/dist/cjs/rendering/NiceCascadedShadows.js +381 -259
  47. package/dist/cjs/rendering/NiceRenderExport.js +424 -337
  48. package/dist/cjs/rendering/NiceSSAO.js +454 -337
  49. package/dist/cjs/rendering/NiceSSR.js +387 -261
  50. package/dist/cjs/rendering/NiceWebGPURenderer.js +359 -230
  51. package/dist/cjs/uv/NiceUVEditor.js +787 -550
  52. package/dist/cjs/uv/UVEditor.module.css.js +53 -5
  53. package/dist/cjs/uv/uvEditorTypes.js +59 -76
  54. package/dist/cjs/uv/uvEditorUtils.js +501 -575
  55. package/dist/esm/collaborative/collaborativeScene.js +163 -205
  56. package/dist/esm/core/editorAsk.js +53 -40
  57. package/dist/esm/core/i18n.js +15 -13
  58. package/dist/esm/core/threeCompat.js +18 -26
  59. package/dist/esm/dance/DanceBridge.js +151 -176
  60. package/dist/esm/dance/DanceScoreEngine.js +211 -220
  61. package/dist/esm/dance/PoseDetector.js +304 -274
  62. package/dist/esm/index.js +258 -36
  63. package/dist/esm/material/MaterialEditor.module.css.js +242 -3
  64. package/dist/esm/material/NiceMaterialEditor.js +1215 -756
  65. package/dist/esm/material/materialEditorTypes.js +45 -48
  66. package/dist/esm/material/materialEditorUtils.js +713 -746
  67. package/dist/esm/material/materialNodeDefinitions.js +1415 -1458
  68. package/dist/esm/model/ModelEditor.js +91 -39
  69. package/dist/esm/model/ModelEditor.module.css.js +257 -3
  70. package/dist/esm/model/ModelEditorLeftPanel.js +126 -45
  71. package/dist/esm/model/ModelEditorMenuBar.js +359 -30
  72. package/dist/esm/model/ModelEditorRightPanel.js +634 -114
  73. package/dist/esm/model/ModelEditorSubComponents.js +265 -94
  74. package/dist/esm/model/ModelEditorTimeline.js +232 -32
  75. package/dist/esm/model/ModelEditorToolbar.js +170 -12
  76. package/dist/esm/model/ModelEditorViewport.js +231 -37
  77. package/dist/esm/model/ModelViewer.js +389 -62
  78. package/dist/esm/model/ModelViewer.module.css.js +77 -3
  79. package/dist/esm/model/NiceArmatureEditor.js +462 -258
  80. package/dist/esm/model/NiceMorphTargetEditor.js +322 -211
  81. package/dist/esm/model/NiceOctree.js +361 -304
  82. package/dist/esm/model/NicePhysicsSimulation.js +517 -282
  83. package/dist/esm/model/NiceProceduralGeometry.js +667 -284
  84. package/dist/esm/model/NiceTerrainEditor.js +584 -369
  85. package/dist/esm/model/NiceWeightPainter.js +373 -245
  86. package/dist/esm/model/NiceXRPreview.js +374 -292
  87. package/dist/esm/model/cadModeUtils.js +95 -88
  88. package/dist/esm/model/editorShortcuts.js +172 -193
  89. package/dist/esm/model/modelEditorTypes.js +90 -100
  90. package/dist/esm/model/modelEditorUtils.js +1056 -1128
  91. package/dist/esm/model/simsModeUtils.js +1021 -1035
  92. package/dist/esm/model/useModelEditor.js +1946 -1920
  93. package/dist/esm/model/useModelViewer.js +619 -647
  94. package/dist/esm/nice2dev-ui-3d.css +1 -1
  95. package/dist/esm/particle/NiceParticleEditor.js +1188 -570
  96. package/dist/esm/particle/ParticleEditor.module.css.js +122 -3
  97. package/dist/esm/particle/particleEditorTypes.js +69 -69
  98. package/dist/esm/particle/particleEditorUtils.js +1027 -1012
  99. package/dist/esm/rendering/NiceCascadedShadows.js +370 -243
  100. package/dist/esm/rendering/NiceRenderExport.js +413 -321
  101. package/dist/esm/rendering/NiceSSAO.js +450 -328
  102. package/dist/esm/rendering/NiceSSR.js +383 -252
  103. package/dist/esm/rendering/NiceWebGPURenderer.js +326 -214
  104. package/dist/esm/uv/NiceUVEditor.js +788 -549
  105. package/dist/esm/uv/UVEditor.module.css.js +53 -3
  106. package/dist/esm/uv/uvEditorTypes.js +68 -75
  107. package/dist/esm/uv/uvEditorUtils.js +515 -557
  108. package/package.json +7 -12
@@ -1,638 +1,596 @@
1
- import * as THREE from 'three';
2
-
3
- /**
4
- * UV Editor Utilities
5
- *
6
- * Core algorithms and utilities for UV mapping operations.
7
- * @module @nice2dev/ui-3d
8
- */
9
- /* ═══════════════════════════════════════════
10
- ID Generation
11
- ═══════════════════════════════════════════ */
1
+ import * as THREE from "three";
12
2
  let idCounter = 0;
13
3
  const generateId = (prefix) => `${prefix}_${Date.now()}_${++idCounter}`;
14
- /* ═══════════════════════════════════════════
15
- UV Math Utilities
16
- ═══════════════════════════════════════════ */
17
4
  const uvDistance = (a, b) => Math.sqrt((a.u - b.u) ** 2 + (a.v - b.v) ** 2);
18
5
  const uvLerp = (a, b, t) => ({
19
- u: a.u + (b.u - a.u) * t,
20
- v: a.v + (b.v - a.v) * t,
6
+ u: a.u + (b.u - a.u) * t,
7
+ v: a.v + (b.v - a.v) * t
21
8
  });
22
9
  const uvAdd = (a, b) => ({
23
- u: a.u + b.u,
24
- v: a.v + b.v,
10
+ u: a.u + b.u,
11
+ v: a.v + b.v
25
12
  });
26
13
  const uvSub = (a, b) => ({
27
- u: a.u - b.u,
28
- v: a.v - b.v,
14
+ u: a.u - b.u,
15
+ v: a.v - b.v
29
16
  });
30
17
  const uvScale = (p, s) => ({
31
- u: p.u * s,
32
- v: p.v * s,
18
+ u: p.u * s,
19
+ v: p.v * s
33
20
  });
34
21
  const uvRotate = (p, angle, center = { u: 0.5, v: 0.5 }) => {
35
- const cos = Math.cos(angle);
36
- const sin = Math.sin(angle);
37
- const du = p.u - center.u;
38
- const dv = p.v - center.v;
39
- return {
40
- u: center.u + du * cos - dv * sin,
41
- v: center.v + du * sin + dv * cos,
42
- };
22
+ const cos = Math.cos(angle);
23
+ const sin = Math.sin(angle);
24
+ const du = p.u - center.u;
25
+ const dv = p.v - center.v;
26
+ return {
27
+ u: center.u + du * cos - dv * sin,
28
+ v: center.v + du * sin + dv * cos
29
+ };
43
30
  };
44
31
  const uvCross = (a, b) => a.u * b.v - a.v * b.u;
45
- /* ═══════════════════════════════════════════
46
- Bounds Utilities
47
- ═══════════════════════════════════════════ */
48
32
  const createEmptyBounds = () => ({
49
- minU: Infinity,
50
- maxU: -Infinity,
51
- minV: Infinity,
52
- maxV: -Infinity,
33
+ minU: Infinity,
34
+ maxU: -Infinity,
35
+ minV: Infinity,
36
+ maxV: -Infinity
53
37
  });
54
38
  const expandBounds = (bounds, point) => ({
55
- minU: Math.min(bounds.minU, point.u),
56
- maxU: Math.max(bounds.maxU, point.u),
57
- minV: Math.min(bounds.minV, point.v),
58
- maxV: Math.max(bounds.maxV, point.v),
39
+ minU: Math.min(bounds.minU, point.u),
40
+ maxU: Math.max(bounds.maxU, point.u),
41
+ minV: Math.min(bounds.minV, point.v),
42
+ maxV: Math.max(bounds.maxV, point.v)
59
43
  });
60
44
  const mergeBounds = (a, b) => ({
61
- minU: Math.min(a.minU, b.minU),
62
- maxU: Math.max(a.maxU, b.maxU),
63
- minV: Math.min(a.minV, b.minV),
64
- maxV: Math.max(a.maxV, b.maxV),
45
+ minU: Math.min(a.minU, b.minU),
46
+ maxU: Math.max(a.maxU, b.maxU),
47
+ minV: Math.min(a.minV, b.minV),
48
+ maxV: Math.max(a.maxV, b.maxV)
65
49
  });
66
50
  const getBoundsCenter = (bounds) => ({
67
- u: (bounds.minU + bounds.maxU) / 2,
68
- v: (bounds.minV + bounds.maxV) / 2,
51
+ u: (bounds.minU + bounds.maxU) / 2,
52
+ v: (bounds.minV + bounds.maxV) / 2
69
53
  });
70
54
  const getBoundsSize = (bounds) => ({
71
- u: bounds.maxU - bounds.minU,
72
- v: bounds.maxV - bounds.minV,
55
+ u: bounds.maxU - bounds.minU,
56
+ v: bounds.maxV - bounds.minV
73
57
  });
74
58
  const getBoundsArea = (bounds) => (bounds.maxU - bounds.minU) * (bounds.maxV - bounds.minV);
75
- const boundsContains = (bounds, point) => point.u >= bounds.minU &&
76
- point.u <= bounds.maxU &&
77
- point.v >= bounds.minV &&
78
- point.v <= bounds.maxV;
59
+ const boundsContains = (bounds, point) => point.u >= bounds.minU && point.u <= bounds.maxU && point.v >= bounds.minV && point.v <= bounds.maxV;
79
60
  const boundsOverlap = (a, b) => a.minU <= b.maxU && a.maxU >= b.minU && a.minV <= b.maxV && a.maxV >= b.minV;
80
- /* ═══════════════════════════════════════════
81
- Geometry Extraction
82
- ═══════════════════════════════════════════ */
83
- function extractUVMeshData(geometry, channel = 'uv') {
84
- const uvAttr = geometry.getAttribute(channel);
85
- const posAttr = geometry.getAttribute('position');
86
- const indexAttr = geometry.getIndex();
87
- if (!uvAttr || !posAttr) {
88
- return null;
89
- }
90
- const vertices = new Map();
91
- const edges = new Map();
92
- const faces = new Map();
93
- // Extract vertices
94
- for (let i = 0; i < uvAttr.count; i++) {
95
- const id = generateId('v');
96
- vertices.set(id, {
97
- id,
98
- position: { u: uvAttr.getX(i), v: uvAttr.getY(i) },
99
- index: i,
100
- pinned: false,
101
- selected: false,
102
- vertexIndex: i,
103
- });
61
+ function extractUVMeshData(geometry, channel = "uv") {
62
+ const uvAttr = geometry.getAttribute(channel);
63
+ const posAttr = geometry.getAttribute("position");
64
+ const indexAttr = geometry.getIndex();
65
+ if (!uvAttr || !posAttr) {
66
+ return null;
67
+ }
68
+ const vertices = /* @__PURE__ */ new Map();
69
+ const edges = /* @__PURE__ */ new Map();
70
+ const faces = /* @__PURE__ */ new Map();
71
+ for (let i = 0; i < uvAttr.count; i++) {
72
+ const id = generateId("v");
73
+ vertices.set(id, {
74
+ id,
75
+ position: { u: uvAttr.getX(i), v: uvAttr.getY(i) },
76
+ index: i,
77
+ pinned: false,
78
+ selected: false,
79
+ vertexIndex: i
80
+ });
81
+ }
82
+ const vertexIds = Array.from(vertices.keys());
83
+ const edgeMap = /* @__PURE__ */ new Map();
84
+ const faceCount = indexAttr ? indexAttr.count / 3 : posAttr.count / 3;
85
+ for (let i = 0; i < faceCount; i++) {
86
+ const faceVertexIds = [];
87
+ const indices = [];
88
+ for (let j = 0; j < 3; j++) {
89
+ const idx = indexAttr ? indexAttr.getX(i * 3 + j) : i * 3 + j;
90
+ indices.push(idx);
91
+ faceVertexIds.push(vertexIds[idx]);
104
92
  }
105
- // Extract faces and edges
106
- const vertexIds = Array.from(vertices.keys());
107
- const edgeMap = new Map(); // "v1_v2" -> edgeId
108
- const faceCount = indexAttr ? indexAttr.count / 3 : posAttr.count / 3;
109
- for (let i = 0; i < faceCount; i++) {
110
- const faceVertexIds = [];
111
- const indices = [];
112
- for (let j = 0; j < 3; j++) {
113
- const idx = indexAttr ? indexAttr.getX(i * 3 + j) : i * 3 + j;
114
- indices.push(idx);
115
- faceVertexIds.push(vertexIds[idx]);
116
- }
117
- // Calculate face area in UV space
118
- const v0 = vertices.get(faceVertexIds[0]).position;
119
- const v1 = vertices.get(faceVertexIds[1]).position;
120
- const v2 = vertices.get(faceVertexIds[2]).position;
121
- const uvArea = Math.abs(uvCross(uvSub(v1, v0), uvSub(v2, v0))) / 2;
122
- // Calculate face area in 3D space
123
- const p0 = new THREE.Vector3().fromBufferAttribute(posAttr, indices[0]);
124
- const p1 = new THREE.Vector3().fromBufferAttribute(posAttr, indices[1]);
125
- const p2 = new THREE.Vector3().fromBufferAttribute(posAttr, indices[2]);
126
- const e1 = p1.clone().sub(p0);
127
- const e2 = p2.clone().sub(p0);
128
- const area3D = e1.cross(e2).length() / 2;
129
- const faceId = generateId('f');
130
- faces.set(faceId, {
131
- id: faceId,
132
- vertices: faceVertexIds,
133
- area: uvArea,
134
- area3D,
135
- distortion: area3D > 0 ? uvArea / area3D : 1,
136
- selected: false,
137
- faceIndex: i,
93
+ const v0 = vertices.get(faceVertexIds[0]).position;
94
+ const v1 = vertices.get(faceVertexIds[1]).position;
95
+ const v2 = vertices.get(faceVertexIds[2]).position;
96
+ const uvArea = Math.abs(uvCross(uvSub(v1, v0), uvSub(v2, v0))) / 2;
97
+ const p0 = new THREE.Vector3().fromBufferAttribute(posAttr, indices[0]);
98
+ const p1 = new THREE.Vector3().fromBufferAttribute(posAttr, indices[1]);
99
+ const p2 = new THREE.Vector3().fromBufferAttribute(posAttr, indices[2]);
100
+ const e1 = p1.clone().sub(p0);
101
+ const e2 = p2.clone().sub(p0);
102
+ const area3D = e1.cross(e2).length() / 2;
103
+ const faceId = generateId("f");
104
+ faces.set(faceId, {
105
+ id: faceId,
106
+ vertices: faceVertexIds,
107
+ area: uvArea,
108
+ area3D,
109
+ distortion: area3D > 0 ? uvArea / area3D : 1,
110
+ selected: false,
111
+ faceIndex: i
112
+ });
113
+ for (let j = 0; j < 3; j++) {
114
+ const v1Id = faceVertexIds[j];
115
+ const v2Id = faceVertexIds[(j + 1) % 3];
116
+ const edgeKey = [v1Id, v2Id].sort().join("_");
117
+ if (!edgeMap.has(edgeKey)) {
118
+ const edgeId = generateId("e");
119
+ const v1Pos = vertices.get(v1Id).position;
120
+ const v2Pos = vertices.get(v2Id).position;
121
+ edges.set(edgeId, {
122
+ id: edgeId,
123
+ startVertex: v1Id,
124
+ endVertex: v2Id,
125
+ seam: false,
126
+ selected: false,
127
+ length: uvDistance(v1Pos, v2Pos)
138
128
  });
139
- // Create edges
140
- for (let j = 0; j < 3; j++) {
141
- const v1Id = faceVertexIds[j];
142
- const v2Id = faceVertexIds[(j + 1) % 3];
143
- const edgeKey = [v1Id, v2Id].sort().join('_');
144
- if (!edgeMap.has(edgeKey)) {
145
- const edgeId = generateId('e');
146
- const v1Pos = vertices.get(v1Id).position;
147
- const v2Pos = vertices.get(v2Id).position;
148
- edges.set(edgeId, {
149
- id: edgeId,
150
- startVertex: v1Id,
151
- endVertex: v2Id,
152
- seam: false,
153
- selected: false,
154
- length: uvDistance(v1Pos, v2Pos),
155
- });
156
- edgeMap.set(edgeKey, edgeId);
157
- }
158
- }
129
+ edgeMap.set(edgeKey, edgeId);
130
+ }
159
131
  }
160
- // Compute islands
161
- const islands = computeIslands(vertices, edges, faces);
162
- return {
163
- vertices,
164
- edges,
165
- faces,
166
- islands,
167
- geometryId: geometry.uuid,
168
- dirty: false,
169
- };
132
+ }
133
+ const islands = computeIslands(vertices, edges, faces);
134
+ return {
135
+ vertices,
136
+ edges,
137
+ faces,
138
+ islands,
139
+ geometryId: geometry.uuid,
140
+ dirty: false
141
+ };
170
142
  }
171
- /* ═══════════════════════════════════════════
172
- Island Detection
173
- ═══════════════════════════════════════════ */
174
143
  function computeIslands(vertices, edges, faces) {
175
- const islands = new Map();
176
- const visited = new Set();
177
- const faceToIsland = new Map();
178
- // Build adjacency graph (faces connected by non-seam edges)
179
- const adjacency = new Map();
180
- for (const face of faces.values()) {
181
- adjacency.set(face.id, new Set());
144
+ const islands = /* @__PURE__ */ new Map();
145
+ const visited = /* @__PURE__ */ new Set();
146
+ const faceToIsland = /* @__PURE__ */ new Map();
147
+ const adjacency = /* @__PURE__ */ new Map();
148
+ for (const face of faces.values()) {
149
+ adjacency.set(face.id, /* @__PURE__ */ new Set());
150
+ }
151
+ const vertexToFaces = /* @__PURE__ */ new Map();
152
+ for (const face of faces.values()) {
153
+ for (const vId of face.vertices) {
154
+ if (!vertexToFaces.has(vId)) {
155
+ vertexToFaces.set(vId, /* @__PURE__ */ new Set());
156
+ }
157
+ vertexToFaces.get(vId).add(face.id);
182
158
  }
183
- // Find adjacent faces (sharing vertices)
184
- const vertexToFaces = new Map();
185
- for (const face of faces.values()) {
186
- for (const vId of face.vertices) {
187
- if (!vertexToFaces.has(vId)) {
188
- vertexToFaces.set(vId, new Set());
189
- }
190
- vertexToFaces.get(vId).add(face.id);
159
+ }
160
+ for (const face of faces.values()) {
161
+ for (const vId of face.vertices) {
162
+ const connectedFaces = vertexToFaces.get(vId);
163
+ for (const otherFaceId of connectedFaces) {
164
+ if (otherFaceId !== face.id) {
165
+ adjacency.get(face.id).add(otherFaceId);
191
166
  }
167
+ }
192
168
  }
193
- for (const face of faces.values()) {
194
- for (const vId of face.vertices) {
195
- const connectedFaces = vertexToFaces.get(vId);
196
- for (const otherFaceId of connectedFaces) {
197
- if (otherFaceId !== face.id) {
198
- adjacency.get(face.id).add(otherFaceId);
199
- }
200
- }
169
+ }
170
+ const floodFill = (startFaceId) => {
171
+ const stack = [startFaceId];
172
+ const islandFaces = [];
173
+ while (stack.length > 0) {
174
+ const faceId = stack.pop();
175
+ if (visited.has(faceId)) {
176
+ continue;
177
+ }
178
+ visited.add(faceId);
179
+ islandFaces.push(faceId);
180
+ for (const neighborId of adjacency.get(faceId) ?? []) {
181
+ if (!visited.has(neighborId)) {
182
+ stack.push(neighborId);
201
183
  }
184
+ }
202
185
  }
203
- // Flood fill to find islands
204
- const floodFill = (startFaceId) => {
205
- var _a;
206
- const stack = [startFaceId];
207
- const islandFaces = [];
208
- while (stack.length > 0) {
209
- const faceId = stack.pop();
210
- if (visited.has(faceId)) {
211
- continue;
212
- }
213
- visited.add(faceId);
214
- islandFaces.push(faceId);
215
- for (const neighborId of (_a = adjacency.get(faceId)) !== null && _a !== void 0 ? _a : []) {
216
- if (!visited.has(neighborId)) {
217
- stack.push(neighborId);
218
- }
219
- }
220
- }
221
- return islandFaces;
222
- };
223
- for (const face of faces.values()) {
224
- if (visited.has(face.id)) {
225
- continue;
226
- }
227
- const islandFaces = floodFill(face.id);
228
- const islandId = generateId('i');
229
- // Gather island vertices and edges
230
- const islandVertices = new Set();
231
- const islandEdges = new Set();
232
- for (const faceId of islandFaces) {
233
- faceToIsland.set(faceId, islandId);
234
- const f = faces.get(faceId);
235
- for (const vId of f.vertices) {
236
- islandVertices.add(vId);
237
- }
238
- }
239
- for (const edge of edges.values()) {
240
- if (islandVertices.has(edge.startVertex) && islandVertices.has(edge.endVertex)) {
241
- islandEdges.add(edge.id);
242
- }
243
- }
244
- // Calculate bounds
245
- let bounds = createEmptyBounds();
246
- let totalArea = 0;
247
- let centerU = 0, centerV = 0;
248
- for (const vId of islandVertices) {
249
- const v = vertices.get(vId);
250
- bounds = expandBounds(bounds, v.position);
251
- centerU += v.position.u;
252
- centerV += v.position.v;
253
- }
254
- for (const faceId of islandFaces) {
255
- totalArea += faces.get(faceId).area;
256
- }
257
- const vertexCount = islandVertices.size;
258
- islands.set(islandId, {
259
- id: islandId,
260
- faces: islandFaces,
261
- vertices: Array.from(islandVertices),
262
- edges: Array.from(islandEdges),
263
- bounds,
264
- area: totalArea,
265
- selected: false,
266
- center: { u: centerU / vertexCount, v: centerV / vertexCount },
267
- rotation: 0,
268
- scale: { u: 1, v: 1 },
269
- });
186
+ return islandFaces;
187
+ };
188
+ for (const face of faces.values()) {
189
+ if (visited.has(face.id)) {
190
+ continue;
270
191
  }
271
- return islands;
272
- }
273
- /* ═══════════════════════════════════════════
274
- Projection Functions
275
- ═══════════════════════════════════════════ */
276
- function applyProjection(geometry, settings, selectedFaces) {
277
- const posAttr = geometry.getAttribute('position');
278
- let uvAttr = geometry.getAttribute('uv');
279
- if (!uvAttr) {
280
- uvAttr = new THREE.BufferAttribute(new Float32Array(posAttr.count * 2), 2);
281
- geometry.setAttribute('uv', uvAttr);
192
+ const islandFaces = floodFill(face.id);
193
+ const islandId = generateId("i");
194
+ const islandVertices = /* @__PURE__ */ new Set();
195
+ const islandEdges = /* @__PURE__ */ new Set();
196
+ for (const faceId of islandFaces) {
197
+ faceToIsland.set(faceId, islandId);
198
+ const f = faces.get(faceId);
199
+ for (const vId of f.vertices) {
200
+ islandVertices.add(vId);
201
+ }
282
202
  }
283
- const indices = selectedFaces !== null && selectedFaces !== void 0 ? selectedFaces : Array.from({ length: posAttr.count }, (_, i) => i);
284
- switch (settings.type) {
285
- case 'planar':
286
- applyPlanarProjection(geometry, settings, indices);
287
- break;
288
- case 'cylindrical':
289
- applyCylindricalProjection(geometry, settings, indices);
290
- break;
291
- case 'spherical':
292
- applySphericalProjection(geometry, settings, indices);
293
- break;
294
- case 'box':
295
- applyBoxProjection(geometry, settings, indices);
296
- break;
297
- default:
298
- applyPlanarProjection(geometry, { ...settings, axis: 'z'}, indices);
203
+ for (const edge of edges.values()) {
204
+ if (islandVertices.has(edge.startVertex) && islandVertices.has(edge.endVertex)) {
205
+ islandEdges.add(edge.id);
206
+ }
299
207
  }
300
- uvAttr.needsUpdate = true;
208
+ let bounds = createEmptyBounds();
209
+ let totalArea = 0;
210
+ let centerU = 0, centerV = 0;
211
+ for (const vId of islandVertices) {
212
+ const v = vertices.get(vId);
213
+ bounds = expandBounds(bounds, v.position);
214
+ centerU += v.position.u;
215
+ centerV += v.position.v;
216
+ }
217
+ for (const faceId of islandFaces) {
218
+ totalArea += faces.get(faceId).area;
219
+ }
220
+ const vertexCount = islandVertices.size;
221
+ islands.set(islandId, {
222
+ id: islandId,
223
+ faces: islandFaces,
224
+ vertices: Array.from(islandVertices),
225
+ edges: Array.from(islandEdges),
226
+ bounds,
227
+ area: totalArea,
228
+ selected: false,
229
+ center: { u: centerU / vertexCount, v: centerV / vertexCount },
230
+ rotation: 0,
231
+ scale: { u: 1, v: 1 }
232
+ });
233
+ }
234
+ return islands;
235
+ }
236
+ function applyProjection(geometry, settings, selectedFaces) {
237
+ const posAttr = geometry.getAttribute("position");
238
+ let uvAttr = geometry.getAttribute("uv");
239
+ if (!uvAttr) {
240
+ uvAttr = new THREE.BufferAttribute(new Float32Array(posAttr.count * 2), 2);
241
+ geometry.setAttribute("uv", uvAttr);
242
+ }
243
+ const indices = selectedFaces ?? Array.from({ length: posAttr.count }, (_, i) => i);
244
+ switch (settings.type) {
245
+ case "planar":
246
+ applyPlanarProjection(geometry, settings, indices);
247
+ break;
248
+ case "cylindrical":
249
+ applyCylindricalProjection(geometry, settings, indices);
250
+ break;
251
+ case "spherical":
252
+ applySphericalProjection(geometry, settings, indices);
253
+ break;
254
+ case "box":
255
+ applyBoxProjection(geometry, settings, indices);
256
+ break;
257
+ default:
258
+ applyPlanarProjection(
259
+ geometry,
260
+ { ...settings, axis: "z" },
261
+ indices
262
+ );
263
+ }
264
+ uvAttr.needsUpdate = true;
301
265
  }
302
266
  function applyPlanarProjection(geometry, settings, indices) {
303
- const posAttr = geometry.getAttribute('position');
304
- const uvAttr = geometry.getAttribute('uv');
305
- const { scale, offset, axis } = settings;
306
- for (const i of indices) {
307
- const x = posAttr.getX(i);
308
- const y = posAttr.getY(i);
309
- const z = posAttr.getZ(i);
310
- let u, v;
311
- switch (axis) {
312
- case 'x':
313
- u = (z - offset.z) * scale.z;
314
- v = (y - offset.y) * scale.y;
315
- break;
316
- case 'y':
317
- u = (x - offset.x) * scale.x;
318
- v = (z - offset.z) * scale.z;
319
- break;
320
- case 'z':
321
- default:
322
- u = (x - offset.x) * scale.x;
323
- v = (y - offset.y) * scale.y;
324
- break;
325
- }
326
- uvAttr.setXY(i, u, v);
267
+ const posAttr = geometry.getAttribute("position");
268
+ const uvAttr = geometry.getAttribute("uv");
269
+ const { scale, offset, axis } = settings;
270
+ for (const i of indices) {
271
+ const x = posAttr.getX(i);
272
+ const y = posAttr.getY(i);
273
+ const z = posAttr.getZ(i);
274
+ let u, v;
275
+ switch (axis) {
276
+ case "x":
277
+ u = (z - offset.z) * scale.z;
278
+ v = (y - offset.y) * scale.y;
279
+ break;
280
+ case "y":
281
+ u = (x - offset.x) * scale.x;
282
+ v = (z - offset.z) * scale.z;
283
+ break;
284
+ case "z":
285
+ default:
286
+ u = (x - offset.x) * scale.x;
287
+ v = (y - offset.y) * scale.y;
288
+ break;
327
289
  }
290
+ uvAttr.setXY(i, u, v);
291
+ }
328
292
  }
329
293
  function applyCylindricalProjection(geometry, settings, indices) {
330
- const posAttr = geometry.getAttribute('position');
331
- const uvAttr = geometry.getAttribute('uv');
332
- const { scale, offset, axis } = settings;
333
- for (const i of indices) {
334
- const x = posAttr.getX(i) - offset.x;
335
- const y = posAttr.getY(i) - offset.y;
336
- const z = posAttr.getZ(i) - offset.z;
337
- let u, v;
338
- switch (axis) {
339
- case 'x':
340
- u = (Math.atan2(z, y) / (2 * Math.PI) + 0.5) * scale.x;
341
- v = x * scale.y;
342
- break;
343
- case 'y':
344
- u = (Math.atan2(x, z) / (2 * Math.PI) + 0.5) * scale.x;
345
- v = y * scale.y;
346
- break;
347
- case 'z':
348
- default:
349
- u = (Math.atan2(y, x) / (2 * Math.PI) + 0.5) * scale.x;
350
- v = z * scale.y;
351
- break;
352
- }
353
- uvAttr.setXY(i, u, v);
294
+ const posAttr = geometry.getAttribute("position");
295
+ const uvAttr = geometry.getAttribute("uv");
296
+ const { scale, offset, axis } = settings;
297
+ for (const i of indices) {
298
+ const x = posAttr.getX(i) - offset.x;
299
+ const y = posAttr.getY(i) - offset.y;
300
+ const z = posAttr.getZ(i) - offset.z;
301
+ let u, v;
302
+ switch (axis) {
303
+ case "x":
304
+ u = (Math.atan2(z, y) / (2 * Math.PI) + 0.5) * scale.x;
305
+ v = x * scale.y;
306
+ break;
307
+ case "y":
308
+ u = (Math.atan2(x, z) / (2 * Math.PI) + 0.5) * scale.x;
309
+ v = y * scale.y;
310
+ break;
311
+ case "z":
312
+ default:
313
+ u = (Math.atan2(y, x) / (2 * Math.PI) + 0.5) * scale.x;
314
+ v = z * scale.y;
315
+ break;
354
316
  }
317
+ uvAttr.setXY(i, u, v);
318
+ }
355
319
  }
356
320
  function applySphericalProjection(geometry, settings, indices) {
357
- const posAttr = geometry.getAttribute('position');
358
- const uvAttr = geometry.getAttribute('uv');
359
- const { scale, offset } = settings;
360
- for (const i of indices) {
361
- const x = posAttr.getX(i) - offset.x;
362
- const y = posAttr.getY(i) - offset.y;
363
- const z = posAttr.getZ(i) - offset.z;
364
- const r = Math.sqrt(x * x + y * y + z * z);
365
- const theta = Math.acos(y / r);
366
- const phi = Math.atan2(z, x);
367
- const u = (phi / (2 * Math.PI) + 0.5) * scale.x;
368
- const v = (theta / Math.PI) * scale.y;
369
- uvAttr.setXY(i, u, v);
370
- }
321
+ const posAttr = geometry.getAttribute("position");
322
+ const uvAttr = geometry.getAttribute("uv");
323
+ const { scale, offset } = settings;
324
+ for (const i of indices) {
325
+ const x = posAttr.getX(i) - offset.x;
326
+ const y = posAttr.getY(i) - offset.y;
327
+ const z = posAttr.getZ(i) - offset.z;
328
+ const r = Math.sqrt(x * x + y * y + z * z);
329
+ const theta = Math.acos(y / r);
330
+ const phi = Math.atan2(z, x);
331
+ const u = (phi / (2 * Math.PI) + 0.5) * scale.x;
332
+ const v = theta / Math.PI * scale.y;
333
+ uvAttr.setXY(i, u, v);
334
+ }
371
335
  }
372
336
  function applyBoxProjection(geometry, settings, indices) {
373
- var _a, _b, _c;
374
- const posAttr = geometry.getAttribute('position');
375
- const normalAttr = geometry.getAttribute('normal');
376
- const uvAttr = geometry.getAttribute('uv');
377
- if (!normalAttr) {
378
- geometry.computeVertexNormals();
379
- }
380
- const { scale, offset } = settings;
381
- for (const i of indices) {
382
- const x = posAttr.getX(i) - offset.x;
383
- const y = posAttr.getY(i) - offset.y;
384
- const z = posAttr.getZ(i) - offset.z;
385
- const nx = Math.abs((_a = normalAttr === null || normalAttr === void 0 ? void 0 : normalAttr.getX(i)) !== null && _a !== void 0 ? _a : 0);
386
- const ny = Math.abs((_b = normalAttr === null || normalAttr === void 0 ? void 0 : normalAttr.getY(i)) !== null && _b !== void 0 ? _b : 1);
387
- const nz = Math.abs((_c = normalAttr === null || normalAttr === void 0 ? void 0 : normalAttr.getZ(i)) !== null && _c !== void 0 ? _c : 0);
388
- let u, v;
389
- if (nx >= ny && nx >= nz) {
390
- // Project on YZ plane
391
- u = z * scale.z;
392
- v = y * scale.y;
393
- }
394
- else if (ny >= nx && ny >= nz) {
395
- // Project on XZ plane
396
- u = x * scale.x;
397
- v = z * scale.z;
398
- }
399
- else {
400
- // Project on XY plane
401
- u = x * scale.x;
402
- v = y * scale.y;
403
- }
404
- uvAttr.setXY(i, u, v);
337
+ const posAttr = geometry.getAttribute("position");
338
+ const normalAttr = geometry.getAttribute("normal");
339
+ const uvAttr = geometry.getAttribute("uv");
340
+ if (!normalAttr) {
341
+ geometry.computeVertexNormals();
342
+ }
343
+ const { scale, offset } = settings;
344
+ for (const i of indices) {
345
+ const x = posAttr.getX(i) - offset.x;
346
+ const y = posAttr.getY(i) - offset.y;
347
+ const z = posAttr.getZ(i) - offset.z;
348
+ const nx = Math.abs(normalAttr?.getX(i) ?? 0);
349
+ const ny = Math.abs(normalAttr?.getY(i) ?? 1);
350
+ const nz = Math.abs(normalAttr?.getZ(i) ?? 0);
351
+ let u, v;
352
+ if (nx >= ny && nx >= nz) {
353
+ u = z * scale.z;
354
+ v = y * scale.y;
355
+ } else if (ny >= nx && ny >= nz) {
356
+ u = x * scale.x;
357
+ v = z * scale.z;
358
+ } else {
359
+ u = x * scale.x;
360
+ v = y * scale.y;
405
361
  }
362
+ uvAttr.setXY(i, u, v);
363
+ }
406
364
  }
407
- /* ═══════════════════════════════════════════
408
- UV Packing
409
- ═══════════════════════════════════════════ */
410
365
  function packIslands(meshData, options) {
411
- const transforms = new Map();
412
- const islands = Array.from(meshData.islands.values());
413
- if (islands.length === 0) {
414
- return { transforms, utilization: 0, success: true, iterations: 0 };
415
- }
416
- // Sort islands by area (largest first) for better packing
417
- if (options.areaPriority) {
418
- islands.sort((a, b) => b.area - a.area);
419
- }
420
- // Simple shelf-based packing algorithm
421
- const padding = options.padding / options.resolution;
422
- const margin = options.margin;
423
- let currentX = margin;
424
- let currentY = margin;
425
- let rowHeight = 0;
426
- let maxX = 0;
427
- let maxY = 0;
428
- for (const island of islands) {
429
- const size = getBoundsSize(island.bounds);
430
- const width = size.u + padding * 2;
431
- const height = size.v + padding * 2;
432
- // Check if fits in current row
433
- if (currentX + width > 1 - margin) {
434
- // Move to next row
435
- currentX = margin;
436
- currentY += rowHeight + padding;
437
- rowHeight = 0;
438
- }
439
- // Calculate translation to move island to current position
440
- const translation = {
441
- u: currentX + padding - island.bounds.minU,
442
- v: currentY + padding - island.bounds.minV,
443
- };
444
- transforms.set(island.id, {
445
- translation,
446
- rotation: 0,
447
- scale: 1,
448
- });
449
- currentX += width;
450
- rowHeight = Math.max(rowHeight, height);
451
- maxX = Math.max(maxX, currentX);
452
- maxY = Math.max(maxY, currentY + height);
366
+ const transforms = /* @__PURE__ */ new Map();
367
+ const islands = Array.from(meshData.islands.values());
368
+ if (islands.length === 0) {
369
+ return { transforms, utilization: 0, success: true, iterations: 0 };
370
+ }
371
+ if (options.areaPriority) {
372
+ islands.sort((a, b) => b.area - a.area);
373
+ }
374
+ const padding = options.padding / options.resolution;
375
+ const margin = options.margin;
376
+ let currentX = margin;
377
+ let currentY = margin;
378
+ let rowHeight = 0;
379
+ let maxX = 0;
380
+ let maxY = 0;
381
+ for (const island of islands) {
382
+ const size = getBoundsSize(island.bounds);
383
+ const width = size.u + padding * 2;
384
+ const height = size.v + padding * 2;
385
+ if (currentX + width > 1 - margin) {
386
+ currentX = margin;
387
+ currentY += rowHeight + padding;
388
+ rowHeight = 0;
453
389
  }
454
- // Calculate utilization
455
- const totalIslandArea = islands.reduce((sum, i) => sum + i.area, 0);
456
- const usedArea = maxX * maxY;
457
- const utilization = usedArea > 0 ? totalIslandArea / usedArea : 0;
458
- return {
459
- transforms,
460
- utilization,
461
- success: maxX <= 1 && maxY <= 1,
462
- iterations: 1,
390
+ const translation = {
391
+ u: currentX + padding - island.bounds.minU,
392
+ v: currentY + padding - island.bounds.minV
463
393
  };
394
+ transforms.set(island.id, {
395
+ translation,
396
+ rotation: 0,
397
+ scale: 1
398
+ });
399
+ currentX += width;
400
+ rowHeight = Math.max(rowHeight, height);
401
+ maxX = Math.max(maxX, currentX);
402
+ maxY = Math.max(maxY, currentY + height);
403
+ }
404
+ const totalIslandArea = islands.reduce((sum, i) => sum + i.area, 0);
405
+ const usedArea = maxX * maxY;
406
+ const utilization = usedArea > 0 ? totalIslandArea / usedArea : 0;
407
+ return {
408
+ transforms,
409
+ utilization,
410
+ success: maxX <= 1 && maxY <= 1,
411
+ iterations: 1
412
+ };
464
413
  }
465
414
  function applyIslandTransforms(meshData, transforms) {
466
- for (const [islandId, transform] of transforms) {
467
- const island = meshData.islands.get(islandId);
468
- if (!island) {
469
- continue;
470
- }
471
- const center = island.center;
472
- for (const vertexId of island.vertices) {
473
- const vertex = meshData.vertices.get(vertexId);
474
- if (!vertex) {
475
- continue;
476
- }
477
- let pos = vertex.position;
478
- // Apply rotation around center
479
- if (transform.rotation !== 0) {
480
- pos = uvRotate(pos, transform.rotation, center);
481
- }
482
- // Apply scale from center
483
- if (transform.scale !== 1) {
484
- pos = {
485
- u: center.u + (pos.u - center.u) * transform.scale,
486
- v: center.v + (pos.v - center.v) * transform.scale,
487
- };
488
- }
489
- // Apply translation
490
- pos = uvAdd(pos, transform.translation);
491
- vertex.position = pos;
492
- }
493
- // Update island bounds
494
- let bounds = createEmptyBounds();
495
- for (const vertexId of island.vertices) {
496
- bounds = expandBounds(bounds, meshData.vertices.get(vertexId).position);
497
- }
498
- island.bounds = bounds;
499
- island.center = getBoundsCenter(bounds);
415
+ for (const [islandId, transform] of transforms) {
416
+ const island = meshData.islands.get(islandId);
417
+ if (!island) {
418
+ continue;
500
419
  }
501
- meshData.dirty = true;
420
+ const center = island.center;
421
+ for (const vertexId of island.vertices) {
422
+ const vertex = meshData.vertices.get(vertexId);
423
+ if (!vertex) {
424
+ continue;
425
+ }
426
+ let pos = vertex.position;
427
+ if (transform.rotation !== 0) {
428
+ pos = uvRotate(pos, transform.rotation, center);
429
+ }
430
+ if (transform.scale !== 1) {
431
+ pos = {
432
+ u: center.u + (pos.u - center.u) * transform.scale,
433
+ v: center.v + (pos.v - center.v) * transform.scale
434
+ };
435
+ }
436
+ pos = uvAdd(pos, transform.translation);
437
+ vertex.position = pos;
438
+ }
439
+ let bounds = createEmptyBounds();
440
+ for (const vertexId of island.vertices) {
441
+ bounds = expandBounds(bounds, meshData.vertices.get(vertexId).position);
442
+ }
443
+ island.bounds = bounds;
444
+ island.center = getBoundsCenter(bounds);
445
+ }
446
+ meshData.dirty = true;
502
447
  }
503
- /* ═══════════════════════════════════════════
504
- UV Relaxation
505
- ═══════════════════════════════════════════ */
506
448
  function relaxUVs(meshData, options, selectedVertices) {
507
- const verticesToRelax = selectedVertices !== null && selectedVertices !== void 0 ? selectedVertices : new Set(meshData.vertices.keys());
508
- const affectedVertices = [];
509
- for (let iter = 0; iter < options.iterations; iter++) {
510
- for (const vertexId of verticesToRelax) {
511
- const vertex = meshData.vertices.get(vertexId);
512
- if (!vertex || (options.preservePinned && vertex.pinned)) {
513
- continue;
514
- }
515
- // Find connected vertices
516
- const neighbors = [];
517
- for (const edge of meshData.edges.values()) {
518
- if (edge.startVertex === vertexId) {
519
- neighbors.push(meshData.vertices.get(edge.endVertex).position);
520
- }
521
- else if (edge.endVertex === vertexId) {
522
- neighbors.push(meshData.vertices.get(edge.startVertex).position);
523
- }
524
- }
525
- if (neighbors.length === 0) {
526
- continue;
527
- }
528
- // Move towards average of neighbors (Laplacian smoothing)
529
- const avg = {
530
- u: neighbors.reduce((s, n) => s + n.u, 0) / neighbors.length,
531
- v: neighbors.reduce((s, n) => s + n.v, 0) / neighbors.length,
532
- };
533
- const factor = 0.5; // Relaxation factor
534
- vertex.position = uvLerp(vertex.position, avg, factor);
535
- if (!affectedVertices.includes(vertexId)) {
536
- affectedVertices.push(vertexId);
537
- }
449
+ const verticesToRelax = selectedVertices ?? new Set(meshData.vertices.keys());
450
+ const affectedVertices = [];
451
+ for (let iter = 0; iter < options.iterations; iter++) {
452
+ for (const vertexId of verticesToRelax) {
453
+ const vertex = meshData.vertices.get(vertexId);
454
+ if (!vertex || options.preservePinned && vertex.pinned) {
455
+ continue;
456
+ }
457
+ const neighbors = [];
458
+ for (const edge of meshData.edges.values()) {
459
+ if (edge.startVertex === vertexId) {
460
+ neighbors.push(meshData.vertices.get(edge.endVertex).position);
461
+ } else if (edge.endVertex === vertexId) {
462
+ neighbors.push(meshData.vertices.get(edge.startVertex).position);
538
463
  }
464
+ }
465
+ if (neighbors.length === 0) {
466
+ continue;
467
+ }
468
+ const avg = {
469
+ u: neighbors.reduce((s, n) => s + n.u, 0) / neighbors.length,
470
+ v: neighbors.reduce((s, n) => s + n.v, 0) / neighbors.length
471
+ };
472
+ const factor = 0.5;
473
+ vertex.position = uvLerp(vertex.position, avg, factor);
474
+ if (!affectedVertices.includes(vertexId)) {
475
+ affectedVertices.push(vertexId);
476
+ }
539
477
  }
540
- meshData.dirty = true;
541
- return {
542
- success: true,
543
- affectedVertices,
544
- };
478
+ }
479
+ meshData.dirty = true;
480
+ return {
481
+ success: true,
482
+ affectedVertices
483
+ };
545
484
  }
546
- /* ═══════════════════════════════════════════
547
- Selection Utilities
548
- ═══════════════════════════════════════════ */
549
485
  function selectVerticesInRect(meshData, rect, additive = false) {
550
- const selected = [];
551
- for (const [id, vertex] of meshData.vertices) {
552
- if (boundsContains(rect, vertex.position)) {
553
- vertex.selected = true;
554
- selected.push(id);
555
- }
556
- else if (!additive) {
557
- vertex.selected = false;
558
- }
486
+ const selected = [];
487
+ for (const [id, vertex] of meshData.vertices) {
488
+ if (boundsContains(rect, vertex.position)) {
489
+ vertex.selected = true;
490
+ selected.push(id);
491
+ } else if (!additive) {
492
+ vertex.selected = false;
559
493
  }
560
- return selected;
494
+ }
495
+ return selected;
561
496
  }
562
497
  function selectIsland(meshData, islandId, additive = false) {
563
- if (!additive) {
564
- for (const v of meshData.vertices.values()) {
565
- v.selected = false;
566
- }
567
- for (const e of meshData.edges.values()) {
568
- e.selected = false;
569
- }
570
- for (const f of meshData.faces.values()) {
571
- f.selected = false;
572
- }
573
- for (const i of meshData.islands.values()) {
574
- i.selected = false;
575
- }
498
+ if (!additive) {
499
+ for (const v of meshData.vertices.values()) {
500
+ v.selected = false;
576
501
  }
577
- const island = meshData.islands.get(islandId);
578
- if (!island) {
579
- return;
502
+ for (const e of meshData.edges.values()) {
503
+ e.selected = false;
580
504
  }
581
- island.selected = true;
582
- for (const vId of island.vertices) {
583
- const v = meshData.vertices.get(vId);
584
- if (v) {
585
- v.selected = true;
586
- }
505
+ for (const f of meshData.faces.values()) {
506
+ f.selected = false;
587
507
  }
588
- for (const eId of island.edges) {
589
- const e = meshData.edges.get(eId);
590
- if (e) {
591
- e.selected = true;
592
- }
508
+ for (const i of meshData.islands.values()) {
509
+ i.selected = false;
593
510
  }
594
- for (const fId of island.faces) {
595
- const f = meshData.faces.get(fId);
596
- if (f) {
597
- f.selected = true;
598
- }
511
+ }
512
+ const island = meshData.islands.get(islandId);
513
+ if (!island) {
514
+ return;
515
+ }
516
+ island.selected = true;
517
+ for (const vId of island.vertices) {
518
+ const v = meshData.vertices.get(vId);
519
+ if (v) {
520
+ v.selected = true;
521
+ }
522
+ }
523
+ for (const eId of island.edges) {
524
+ const e = meshData.edges.get(eId);
525
+ if (e) {
526
+ e.selected = true;
527
+ }
528
+ }
529
+ for (const fId of island.faces) {
530
+ const f = meshData.faces.get(fId);
531
+ if (f) {
532
+ f.selected = true;
599
533
  }
534
+ }
600
535
  }
601
536
  function getSelectedVertices(meshData) {
602
- return Array.from(meshData.vertices.values()).filter((v) => v.selected);
537
+ return Array.from(meshData.vertices.values()).filter((v) => v.selected);
603
538
  }
604
539
  function getSelectedIslands(meshData) {
605
- return Array.from(meshData.islands.values()).filter((i) => i.selected);
540
+ return Array.from(meshData.islands.values()).filter((i) => i.selected);
606
541
  }
607
- /* ═══════════════════════════════════════════
608
- Seam Operations
609
- ═══════════════════════════════════════════ */
610
542
  function markSeam(meshData, edgeId, isSeam) {
611
- const edge = meshData.edges.get(edgeId);
612
- if (edge) {
613
- edge.seam = isSeam;
614
- meshData.dirty = true;
615
- }
543
+ const edge = meshData.edges.get(edgeId);
544
+ if (edge) {
545
+ edge.seam = isSeam;
546
+ meshData.dirty = true;
547
+ }
616
548
  }
617
549
  function clearAllSeams(meshData) {
618
- for (const edge of meshData.edges.values()) {
619
- edge.seam = false;
620
- }
621
- meshData.dirty = true;
550
+ for (const edge of meshData.edges.values()) {
551
+ edge.seam = false;
552
+ }
553
+ meshData.dirty = true;
622
554
  }
623
- /* ═══════════════════════════════════════════
624
- Sync Back to Geometry
625
- ═══════════════════════════════════════════ */
626
- function syncToGeometry(meshData, geometry, channel = 'uv') {
627
- const uvAttr = geometry.getAttribute(channel);
628
- if (!uvAttr) {
629
- return;
630
- }
631
- for (const vertex of meshData.vertices.values()) {
632
- uvAttr.setXY(vertex.index, vertex.position.u, vertex.position.v);
633
- }
634
- uvAttr.needsUpdate = true;
635
- meshData.dirty = false;
555
+ function syncToGeometry(meshData, geometry, channel = "uv") {
556
+ const uvAttr = geometry.getAttribute(channel);
557
+ if (!uvAttr) {
558
+ return;
559
+ }
560
+ for (const vertex of meshData.vertices.values()) {
561
+ uvAttr.setXY(vertex.index, vertex.position.u, vertex.position.v);
562
+ }
563
+ uvAttr.needsUpdate = true;
564
+ meshData.dirty = false;
636
565
  }
637
-
638
- export { applyIslandTransforms, applyProjection, boundsContains, boundsOverlap, clearAllSeams, computeIslands, createEmptyBounds, expandBounds, extractUVMeshData, generateId, getBoundsArea, getBoundsCenter, getBoundsSize, getSelectedIslands, getSelectedVertices, markSeam, mergeBounds, packIslands, relaxUVs, selectIsland, selectVerticesInRect, syncToGeometry, uvAdd, uvCross, uvDistance, uvLerp, uvRotate, uvScale, uvSub };
566
+ export {
567
+ applyIslandTransforms,
568
+ applyProjection,
569
+ boundsContains,
570
+ boundsOverlap,
571
+ clearAllSeams,
572
+ computeIslands,
573
+ createEmptyBounds,
574
+ expandBounds,
575
+ extractUVMeshData,
576
+ generateId,
577
+ getBoundsArea,
578
+ getBoundsCenter,
579
+ getBoundsSize,
580
+ getSelectedIslands,
581
+ getSelectedVertices,
582
+ markSeam,
583
+ mergeBounds,
584
+ packIslands,
585
+ relaxUVs,
586
+ selectIsland,
587
+ selectVerticesInRect,
588
+ syncToGeometry,
589
+ uvAdd,
590
+ uvCross,
591
+ uvDistance,
592
+ uvLerp,
593
+ uvRotate,
594
+ uvScale,
595
+ uvSub
596
+ };