@womp/mesh-editor 0.1.0

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.
@@ -0,0 +1,27 @@
1
+ interface SculptNormalBuffers {
2
+ positions: Float32Array;
3
+ indices: Uint16Array | Uint32Array | null;
4
+ triangleOffsets: Uint32Array;
5
+ triangleIndices: Uint32Array;
6
+ triangleNormals: Float32Array;
7
+ normals: Float32Array;
8
+ triangleCapacity: number;
9
+ vertexCapacity: number;
10
+ }
11
+ export declare class SculptNormalWorkerPool {
12
+ private readonly workers;
13
+ private readonly source;
14
+ private readonly positions;
15
+ private readonly triangleNormals;
16
+ private readonly normals;
17
+ private readonly taskTriangles;
18
+ private readonly taskVertices;
19
+ private readonly pendingTasks;
20
+ private nextTaskId;
21
+ constructor(buffers: SculptNormalBuffers, workerCount: number);
22
+ get worker_count(): number;
23
+ private run_phase;
24
+ refresh(affectedTriangles: number[], affectedVertices: number[]): Promise<void>;
25
+ dispose(): void;
26
+ }
27
+ export {};
@@ -0,0 +1,138 @@
1
+ function shared_uint32_array(length) {
2
+ return new Uint32Array(new SharedArrayBuffer(length * Uint32Array.BYTES_PER_ELEMENT));
3
+ }
4
+ function shared_float32_copy(source) {
5
+ const copy = new Float32Array(new SharedArrayBuffer(source.byteLength));
6
+ copy.set(source);
7
+ return copy;
8
+ }
9
+ function shared_uint32_copy(source) {
10
+ const copy = shared_uint32_array(source.length);
11
+ copy.set(source);
12
+ return copy;
13
+ }
14
+ function shared_index_copy(source) {
15
+ if (source === null) {
16
+ return null;
17
+ }
18
+ if (source instanceof Uint16Array) {
19
+ const copy = new Uint16Array(new SharedArrayBuffer(source.byteLength));
20
+ copy.set(source);
21
+ return copy;
22
+ }
23
+ return shared_uint32_copy(source);
24
+ }
25
+ export class SculptNormalWorkerPool {
26
+ workers;
27
+ source;
28
+ positions;
29
+ triangleNormals;
30
+ normals;
31
+ taskTriangles;
32
+ taskVertices;
33
+ pendingTasks;
34
+ nextTaskId;
35
+ constructor(buffers, workerCount) {
36
+ this.workers = [];
37
+ this.source = buffers;
38
+ this.positions = shared_float32_copy(buffers.positions);
39
+ this.triangleNormals = shared_float32_copy(buffers.triangleNormals);
40
+ this.normals = shared_float32_copy(buffers.normals);
41
+ const indices = shared_index_copy(buffers.indices);
42
+ const triangleOffsets = shared_uint32_copy(buffers.triangleOffsets);
43
+ const triangleIndices = shared_uint32_copy(buffers.triangleIndices);
44
+ this.taskTriangles = shared_uint32_array(buffers.triangleCapacity);
45
+ this.taskVertices = shared_uint32_array(buffers.vertexCapacity);
46
+ this.pendingTasks = new Map();
47
+ this.nextTaskId = 1;
48
+ for (let workerIndex = 0; workerIndex < workerCount; workerIndex += 1) {
49
+ const worker = new Worker(new URL("./sculpt_normal_worker.js", import.meta.url), { type: "module" });
50
+ worker.onmessage = (event) => {
51
+ const taskId = Number(event.data?.taskId);
52
+ const pending = this.pendingTasks.get(taskId);
53
+ if (pending === undefined) {
54
+ return;
55
+ }
56
+ pending.remaining -= 1;
57
+ if (pending.remaining === 0) {
58
+ this.pendingTasks.delete(taskId);
59
+ pending.resolve();
60
+ }
61
+ };
62
+ worker.onerror = (event) => {
63
+ const error = new Error(event.message || "Sculpt normal worker failed.");
64
+ for (const pending of this.pendingTasks.values()) {
65
+ pending.reject(error);
66
+ }
67
+ this.pendingTasks.clear();
68
+ };
69
+ worker.postMessage({
70
+ type: "configure",
71
+ positions: this.positions.buffer,
72
+ indices: indices?.buffer ?? null,
73
+ indexBytes: indices?.BYTES_PER_ELEMENT ?? 0,
74
+ triangleOffsets: triangleOffsets.buffer,
75
+ triangleIndices: triangleIndices.buffer,
76
+ triangleNormals: this.triangleNormals.buffer,
77
+ normals: this.normals.buffer,
78
+ taskTriangles: this.taskTriangles.buffer,
79
+ taskVertices: this.taskVertices.buffer,
80
+ });
81
+ this.workers.push(worker);
82
+ }
83
+ }
84
+ get worker_count() {
85
+ return this.workers.length;
86
+ }
87
+ run_phase(phase, itemCount) {
88
+ if (itemCount === 0 || this.workers.length === 0) {
89
+ return Promise.resolve();
90
+ }
91
+ const activeWorkerCount = Math.min(this.workers.length, itemCount);
92
+ const taskId = this.nextTaskId;
93
+ this.nextTaskId += 1;
94
+ return new Promise((resolve, reject) => {
95
+ this.pendingTasks.set(taskId, { remaining: activeWorkerCount, resolve, reject });
96
+ for (let workerIndex = 0; workerIndex < activeWorkerCount; workerIndex += 1) {
97
+ const start = Math.floor(itemCount * workerIndex / activeWorkerCount);
98
+ const end = Math.floor(itemCount * (workerIndex + 1) / activeWorkerCount);
99
+ this.workers[workerIndex].postMessage({ type: "run", taskId, phase, start, end });
100
+ }
101
+ });
102
+ }
103
+ async refresh(affectedTriangles, affectedVertices) {
104
+ for (const vertexIndex of affectedVertices) {
105
+ const offset = vertexIndex * 3;
106
+ this.positions[offset + 0] = this.source.positions[offset + 0];
107
+ this.positions[offset + 1] = this.source.positions[offset + 1];
108
+ this.positions[offset + 2] = this.source.positions[offset + 2];
109
+ }
110
+ this.taskTriangles.set(affectedTriangles);
111
+ this.taskVertices.set(affectedVertices);
112
+ await this.run_phase("triangles", affectedTriangles.length);
113
+ await this.run_phase("vertices", affectedVertices.length);
114
+ for (const triangleIndex of affectedTriangles) {
115
+ const offset = triangleIndex * 3;
116
+ this.source.triangleNormals[offset + 0] = this.triangleNormals[offset + 0];
117
+ this.source.triangleNormals[offset + 1] = this.triangleNormals[offset + 1];
118
+ this.source.triangleNormals[offset + 2] = this.triangleNormals[offset + 2];
119
+ }
120
+ for (const vertexIndex of affectedVertices) {
121
+ const offset = vertexIndex * 3;
122
+ this.source.normals[offset + 0] = this.normals[offset + 0];
123
+ this.source.normals[offset + 1] = this.normals[offset + 1];
124
+ this.source.normals[offset + 2] = this.normals[offset + 2];
125
+ }
126
+ }
127
+ dispose() {
128
+ const error = new Error("Sculpt normal worker pool was disposed.");
129
+ for (const pending of this.pendingTasks.values()) {
130
+ pending.reject(error);
131
+ }
132
+ this.pendingTasks.clear();
133
+ for (const worker of this.workers) {
134
+ worker.terminate();
135
+ }
136
+ this.workers.length = 0;
137
+ }
138
+ }
@@ -0,0 +1,6 @@
1
+ import type { EditableMesh } from "./types.js";
2
+ export declare function editable_mesh_from_three_geometry(geometry: any, name?: string): EditableMesh;
3
+ export declare function editable_meshes_from_three_object(root: any, name?: string): EditableMesh[];
4
+ export declare function editable_meshes_from_three_gltf_scene(root: any, associations: any, name?: string): EditableMesh[];
5
+ export declare function editable_mesh_from_three_object(root: any, name?: string): EditableMesh;
6
+ export declare function editable_mesh_to_three_geometry(THREE: any, mesh: EditableMesh): any;
@@ -0,0 +1,334 @@
1
+ import { clone_editable_mesh, clone_editable_materials, create_default_editable_material, validate_editable_mesh } from "./mesh_data.js";
2
+ function array_from_attribute(attribute, itemSize) {
3
+ if (attribute === undefined || attribute === null) {
4
+ return null;
5
+ }
6
+ const output = new Float32Array(attribute.count * itemSize);
7
+ for (let index = 0; index < attribute.count; index += 1) {
8
+ for (let item = 0; item < itemSize; item += 1) {
9
+ output[(index * itemSize) + item] = attribute.getComponent(index, item);
10
+ }
11
+ }
12
+ return output;
13
+ }
14
+ function append_array(target, source) {
15
+ for (let index = 0; index < source.length; index += 1) {
16
+ target.push(source[index]);
17
+ }
18
+ }
19
+ function append_attribute_or_default(target, attribute, itemSize, vertexCount, fallback) {
20
+ if (attribute !== undefined && attribute !== null) {
21
+ for (let index = 0; index < attribute.count; index += 1) {
22
+ for (let item = 0; item < itemSize; item += 1) {
23
+ target.push(attribute.getComponent(index, item));
24
+ }
25
+ }
26
+ return;
27
+ }
28
+ for (let index = 0; index < vertexCount; index += 1) {
29
+ append_array(target, fallback);
30
+ }
31
+ }
32
+ function transformed_geometry_copy(geometry, matrixWorld) {
33
+ const source = geometry.clone();
34
+ if (matrixWorld !== undefined && matrixWorld !== null) {
35
+ source.applyMatrix4(matrixWorld);
36
+ }
37
+ return source;
38
+ }
39
+ function geometry_indices(geometry, vertexOffset) {
40
+ const indexAttribute = geometry.index;
41
+ const indexCount = indexAttribute === null ? geometry.getAttribute("position").count : indexAttribute.count;
42
+ const indices = new Array(indexCount);
43
+ for (let index = 0; index < indexCount; index += 1) {
44
+ indices[index] = vertexOffset + (indexAttribute === null ? index : indexAttribute.getX(index));
45
+ }
46
+ return indices;
47
+ }
48
+ function compact_index_array(indices, vertexCount) {
49
+ return vertexCount <= 65535 ? new Uint16Array(indices) : new Uint32Array(indices);
50
+ }
51
+ function three_materials_from_object(object) {
52
+ if (Array.isArray(object.material)) {
53
+ return object.material.length > 0 ? object.material : [null];
54
+ }
55
+ return [object.material || null];
56
+ }
57
+ function pixels_from_texture_image(image) {
58
+ if (image === undefined || image === null) {
59
+ return null;
60
+ }
61
+ const width = Number(image.width || image.videoWidth || 0);
62
+ const height = Number(image.height || image.videoHeight || 0);
63
+ if (Number.isInteger(width) === false || Number.isInteger(height) === false || width <= 0 || height <= 0) {
64
+ return null;
65
+ }
66
+ if (image.data !== undefined &&
67
+ image.data !== null &&
68
+ (image.data instanceof Uint8Array || image.data instanceof Uint8ClampedArray) &&
69
+ image.data.length === width * height * 4) {
70
+ return { width, height, pixels: new Uint8Array(image.data) };
71
+ }
72
+ const canvas = document.createElement("canvas");
73
+ canvas.width = width;
74
+ canvas.height = height;
75
+ const context = canvas.getContext("2d");
76
+ if (context === null) {
77
+ return null;
78
+ }
79
+ try {
80
+ context.drawImage(image, 0, 0, width, height);
81
+ return { width, height, pixels: new Uint8Array(context.getImageData(0, 0, width, height).data) };
82
+ }
83
+ catch (_error) {
84
+ return null;
85
+ }
86
+ }
87
+ function editable_texture_index_for_three_texture(texture, textures, textureIndexBySource) {
88
+ if (texture === undefined || texture === null) {
89
+ return null;
90
+ }
91
+ const existing = textureIndexBySource.get(texture);
92
+ if (existing !== undefined) {
93
+ return existing;
94
+ }
95
+ const editableTexture = pixels_from_texture_image(texture.image);
96
+ if (editableTexture === null) {
97
+ return null;
98
+ }
99
+ const index = textures.length;
100
+ textures.push(editableTexture);
101
+ textureIndexBySource.set(texture, index);
102
+ return index;
103
+ }
104
+ function editable_material_from_three_material(material, textures, textureIndexBySource) {
105
+ const editableMaterial = create_default_editable_material();
106
+ if (material === undefined || material === null) {
107
+ return editableMaterial;
108
+ }
109
+ if (material.color !== undefined && material.color !== null) {
110
+ editableMaterial.baseColorFactor[0] = Number(material.color.r ?? editableMaterial.baseColorFactor[0]);
111
+ editableMaterial.baseColorFactor[1] = Number(material.color.g ?? editableMaterial.baseColorFactor[1]);
112
+ editableMaterial.baseColorFactor[2] = Number(material.color.b ?? editableMaterial.baseColorFactor[2]);
113
+ }
114
+ editableMaterial.baseColorFactor[3] = Number(material.opacity ?? editableMaterial.baseColorFactor[3]);
115
+ if (material.emissive !== undefined && material.emissive !== null) {
116
+ editableMaterial.emissiveFactor[0] = Number(material.emissive.r ?? editableMaterial.emissiveFactor[0]);
117
+ editableMaterial.emissiveFactor[1] = Number(material.emissive.g ?? editableMaterial.emissiveFactor[1]);
118
+ editableMaterial.emissiveFactor[2] = Number(material.emissive.b ?? editableMaterial.emissiveFactor[2]);
119
+ }
120
+ editableMaterial.roughnessFactor = Number(material.roughness ?? editableMaterial.roughnessFactor);
121
+ editableMaterial.metalnessFactor = Number(material.metalness ?? editableMaterial.metalnessFactor);
122
+ editableMaterial.baseColorTextureIndex = editable_texture_index_for_three_texture(material.map, textures, textureIndexBySource);
123
+ return editableMaterial;
124
+ }
125
+ export function editable_mesh_from_three_geometry(geometry, name = "uploaded mesh") {
126
+ const source = transformed_geometry_copy(geometry, null);
127
+ const positionAttribute = source.getAttribute("position");
128
+ if (positionAttribute === undefined || positionAttribute === null) {
129
+ source.dispose();
130
+ throw new Error("Uploaded mesh has no position attribute.");
131
+ }
132
+ const positions = array_from_attribute(positionAttribute, 3);
133
+ if (positions === null) {
134
+ source.dispose();
135
+ throw new Error("Uploaded mesh has no position attribute.");
136
+ }
137
+ const normals = array_from_attribute(source.getAttribute("normal"), 3);
138
+ const uvs = array_from_attribute(source.getAttribute("uv"), 2);
139
+ const colors = array_from_attribute(source.getAttribute("color"), 3);
140
+ const indices = compact_index_array(geometry_indices(source, 0), positionAttribute.count);
141
+ const groups = source.groups.length > 0
142
+ ? source.groups.map((group) => ({
143
+ start: group.start,
144
+ count: group.count,
145
+ materialIndex: group.materialIndex,
146
+ }))
147
+ : [{ start: 0, count: indices.length, materialIndex: 0 }];
148
+ const maxMaterialIndex = groups.reduce((maxIndex, group) => Math.max(maxIndex, group.materialIndex), 0);
149
+ const mesh = {
150
+ name,
151
+ positions,
152
+ normals,
153
+ indices,
154
+ uvs,
155
+ colors,
156
+ materialGroups: groups,
157
+ sourceParts: [],
158
+ materials: clone_editable_materials(null, maxMaterialIndex + 1),
159
+ textures: [],
160
+ };
161
+ validate_editable_mesh(mesh);
162
+ source.dispose();
163
+ return mesh;
164
+ }
165
+ function editable_mesh_from_three_mesh_object(object, fallbackName) {
166
+ return editable_mesh_from_three_mesh_objects([object], object.name || fallbackName);
167
+ }
168
+ export function editable_meshes_from_three_object(root, name = "uploaded mesh") {
169
+ root.updateWorldMatrix(true, true);
170
+ const meshes = [];
171
+ let meshIndex = 0;
172
+ root.traverse((object) => {
173
+ if (object.isMesh && object.geometry) {
174
+ meshIndex += 1;
175
+ meshes.push(editable_mesh_from_three_mesh_object(object, `${name} part ${meshIndex}`));
176
+ }
177
+ });
178
+ if (meshes.length === 0) {
179
+ throw new Error("Uploaded file did not contain a mesh.");
180
+ }
181
+ return meshes;
182
+ }
183
+ export function editable_meshes_from_three_gltf_scene(root, associations, name = "uploaded mesh") {
184
+ root.updateWorldMatrix(true, true);
185
+ const meshes = [];
186
+ let meshIndex = 0;
187
+ root.traverse((object) => {
188
+ const mapping = associations?.get?.(object);
189
+ if (mapping === undefined || mapping === null || mapping.nodes === undefined || mapping.meshes === undefined) {
190
+ return;
191
+ }
192
+ meshIndex += 1;
193
+ const meshObjects = [];
194
+ collect_gltf_node_mesh_objects(object, associations, meshObjects);
195
+ if (meshObjects.length > 0) {
196
+ meshes.push(editable_mesh_from_three_mesh_objects(meshObjects, object.name || `${name} part ${meshIndex}`));
197
+ }
198
+ });
199
+ if (meshes.length === 0) {
200
+ return editable_meshes_from_three_object(root, name);
201
+ }
202
+ return meshes;
203
+ }
204
+ function collect_gltf_node_mesh_objects(object, associations, target) {
205
+ if (object.isMesh && object.geometry) {
206
+ target.push(object);
207
+ }
208
+ const children = object.children || [];
209
+ for (const child of children) {
210
+ const mapping = associations?.get?.(child);
211
+ if (mapping !== undefined && mapping !== null && mapping.nodes !== undefined && mapping.meshes !== undefined) {
212
+ continue;
213
+ }
214
+ collect_gltf_node_mesh_objects(child, associations, target);
215
+ }
216
+ }
217
+ export function editable_mesh_from_three_object(root, name = "uploaded mesh") {
218
+ root.updateWorldMatrix(true, true);
219
+ const objects = [];
220
+ root.traverse((object) => {
221
+ if (object.isMesh && object.geometry) {
222
+ objects.push(object);
223
+ }
224
+ });
225
+ return editable_mesh_from_three_mesh_objects(objects, name);
226
+ }
227
+ function editable_mesh_from_three_mesh_objects(objects, name) {
228
+ const sources = [];
229
+ for (const object of objects) {
230
+ sources.push({
231
+ geometry: transformed_geometry_copy(object.geometry, object.matrixWorld),
232
+ materials: three_materials_from_object(object),
233
+ });
234
+ }
235
+ if (sources.length === 0) {
236
+ throw new Error("Uploaded file did not contain a mesh.");
237
+ }
238
+ const positions = [];
239
+ const normals = [];
240
+ const uvs = [];
241
+ const colors = [];
242
+ const indices = [];
243
+ const groups = [];
244
+ const materials = [];
245
+ const textures = [];
246
+ const textureIndexBySource = new Map();
247
+ let vertexOffset = 0;
248
+ let indexOffset = 0;
249
+ let materialOffset = 0;
250
+ let hasUvs = false;
251
+ let hasColors = false;
252
+ let hasNormals = true;
253
+ try {
254
+ for (const sourceInfo of sources) {
255
+ const source = sourceInfo.geometry;
256
+ const positionAttribute = source.getAttribute("position");
257
+ if (positionAttribute === undefined || positionAttribute === null) {
258
+ throw new Error("Uploaded mesh has no position attribute.");
259
+ }
260
+ const uvAttribute = source.getAttribute("uv");
261
+ const colorAttribute = source.getAttribute("color");
262
+ const normalAttribute = source.getAttribute("normal");
263
+ hasUvs = hasUvs || (uvAttribute !== undefined && uvAttribute !== null);
264
+ hasColors = hasColors || (colorAttribute !== undefined && colorAttribute !== null);
265
+ hasNormals = hasNormals && normalAttribute !== undefined && normalAttribute !== null;
266
+ append_attribute_or_default(positions, positionAttribute, 3, positionAttribute.count, [0, 0, 0]);
267
+ append_attribute_or_default(normals, normalAttribute, 3, positionAttribute.count, [0, 1, 0]);
268
+ append_attribute_or_default(uvs, uvAttribute, 2, positionAttribute.count, [0, 0]);
269
+ append_attribute_or_default(colors, colorAttribute, 3, positionAttribute.count, [1, 1, 1]);
270
+ const sourceIndices = geometry_indices(source, vertexOffset);
271
+ append_array(indices, sourceIndices);
272
+ for (const material of sourceInfo.materials) {
273
+ materials.push(editable_material_from_three_material(material, textures, textureIndexBySource));
274
+ }
275
+ const sourceGroups = source.groups.length > 0
276
+ ? source.groups
277
+ : [{ start: 0, count: sourceIndices.length, materialIndex: 0 }];
278
+ for (const group of sourceGroups) {
279
+ const groupMaterialIndex = Math.min(Math.max(Number(group.materialIndex || 0), 0), sourceInfo.materials.length - 1);
280
+ groups.push({
281
+ start: indexOffset + group.start,
282
+ count: group.count,
283
+ materialIndex: materialOffset + groupMaterialIndex,
284
+ });
285
+ }
286
+ vertexOffset += positionAttribute.count;
287
+ indexOffset += sourceIndices.length;
288
+ materialOffset += Math.max(sourceInfo.materials.length, 1);
289
+ }
290
+ }
291
+ finally {
292
+ for (const sourceInfo of sources) {
293
+ sourceInfo.geometry.dispose();
294
+ }
295
+ }
296
+ const mesh = {
297
+ name,
298
+ positions: new Float32Array(positions),
299
+ normals: hasNormals ? new Float32Array(normals) : null,
300
+ indices: compact_index_array(indices, vertexOffset),
301
+ uvs: hasUvs ? new Float32Array(uvs) : null,
302
+ colors: hasColors ? new Float32Array(colors) : null,
303
+ materialGroups: groups,
304
+ sourceParts: [],
305
+ materials: clone_editable_materials(materials, materialOffset),
306
+ textures,
307
+ };
308
+ validate_editable_mesh(mesh);
309
+ return mesh;
310
+ }
311
+ export function editable_mesh_to_three_geometry(THREE, mesh) {
312
+ const copy = clone_editable_mesh(mesh);
313
+ const geometry = new THREE.BufferGeometry();
314
+ geometry.setAttribute("position", new THREE.BufferAttribute(copy.positions, 3));
315
+ if (copy.normals !== null) {
316
+ geometry.setAttribute("normal", new THREE.BufferAttribute(copy.normals, 3));
317
+ }
318
+ if (copy.uvs !== null) {
319
+ geometry.setAttribute("uv", new THREE.BufferAttribute(copy.uvs, 2));
320
+ }
321
+ if (copy.colors !== null) {
322
+ geometry.setAttribute("color", new THREE.BufferAttribute(copy.colors, 3));
323
+ }
324
+ if (copy.indices !== null) {
325
+ geometry.setIndex(new THREE.BufferAttribute(copy.indices, 1));
326
+ }
327
+ for (const group of copy.materialGroups) {
328
+ geometry.addGroup(group.start, group.count, group.materialIndex);
329
+ }
330
+ if (copy.normals === null) {
331
+ geometry.computeVertexNormals();
332
+ }
333
+ return geometry;
334
+ }
@@ -0,0 +1,44 @@
1
+ export interface EditableMeshMaterialGroup {
2
+ start: number;
3
+ count: number;
4
+ materialIndex: number;
5
+ }
6
+ export interface EditableMeshSourcePart {
7
+ name: string;
8
+ start: number;
9
+ count: number;
10
+ nodeTransform: number[][];
11
+ inverseNodeTransform: number[][];
12
+ }
13
+ export interface EditableTexture {
14
+ width: number;
15
+ height: number;
16
+ pixels: Uint8Array;
17
+ }
18
+ export interface EditableMaterial {
19
+ baseColorTextureIndex: number | null;
20
+ baseColorFactor: [number, number, number, number];
21
+ emissiveFactor: [number, number, number, number];
22
+ roughnessFactor: number;
23
+ metalnessFactor: number;
24
+ }
25
+ export interface EditableMesh {
26
+ name: string;
27
+ positions: Float32Array;
28
+ normals: Float32Array | null;
29
+ indices: Uint16Array | Uint32Array | null;
30
+ uvs: Float32Array | null;
31
+ colors: Float32Array | null;
32
+ materialGroups: EditableMeshMaterialGroup[];
33
+ sourceParts: EditableMeshSourcePart[];
34
+ materials: EditableMaterial[];
35
+ textures: EditableTexture[];
36
+ }
37
+ export interface EditableMeshBounds {
38
+ min: [number, number, number];
39
+ max: [number, number, number];
40
+ center: [number, number, number];
41
+ radius: number;
42
+ }
43
+ export type Vec3 = [number, number, number];
44
+ export type Vec2 = [number, number];
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import * as DEFAULT_THREE from "three";
2
+ import type { EditableMesh } from "../mesh_editor/types.js";
3
+ export interface OpenEditSessionOptions {
4
+ title?: string;
5
+ source_id?: unknown;
6
+ editable_mesh: EditableMesh;
7
+ three?: typeof DEFAULT_THREE;
8
+ container?: HTMLElement;
9
+ root_class?: string;
10
+ symmetry_enabled?: boolean;
11
+ symmetry_vertical?: boolean;
12
+ symmetry_horizontal?: boolean;
13
+ on_commit?: (result: EditSessionCommitResult) => void | Promise<void>;
14
+ }
15
+ export type EditSessionCommitResult = {
16
+ action: "commit";
17
+ mode: "edit";
18
+ mesh: EditableMesh;
19
+ };
20
+ export type EditSessionResult = {
21
+ action: "cancel";
22
+ } | EditSessionCommitResult;
23
+ export declare function open_edit_session(options: OpenEditSessionOptions): Promise<EditSessionResult>;