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