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