copper3d 3.6.2 → 3.6.5
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/dist/Loader/copperNrrdLoader.js +1 -1
- package/dist/Loader/copperNrrdLoader.js.map +1 -1
- package/dist/Scene/copperScene.d.ts +4 -4
- package/dist/Scene/copperScene.js +4 -4
- package/dist/Scene/copperScene.js.map +1 -1
- package/dist/Utils/segmentation/NrrdTools.d.ts +41 -2
- package/dist/Utils/segmentation/NrrdTools.js +30 -2
- package/dist/Utils/segmentation/NrrdTools.js.map +1 -1
- package/dist/Utils/segmentation/tools/AiAssistTool.d.ts +108 -5
- package/dist/Utils/segmentation/tools/AiAssistTool.js +408 -25
- package/dist/Utils/segmentation/tools/AiAssistTool.js.map +1 -1
- package/dist/Utils/surfaceAnnotation/MeshGraph.d.ts +12 -10
- package/dist/Utils/surfaceAnnotation/MeshGraph.js +14 -12
- package/dist/Utils/surfaceAnnotation/MeshGraph.js.map +1 -1
- package/dist/Utils/surfaceAnnotation/SurfaceAnnotator.d.ts +48 -28
- package/dist/Utils/surfaceAnnotation/SurfaceAnnotator.js +76 -50
- package/dist/Utils/surfaceAnnotation/SurfaceAnnotator.js.map +1 -1
- package/dist/Utils/surfaceAnnotation/annotationStore.d.ts +4 -4
- package/dist/Utils/surfaceAnnotation/annotationStore.js +4 -4
- package/dist/Utils/surfaceAnnotation/annotationStore.js.map +1 -1
- package/dist/Utils/surfaceAnnotation/contourRender.d.ts +1 -1
- package/dist/Utils/surfaceAnnotation/contourRender.js +16 -11
- package/dist/Utils/surfaceAnnotation/contourRender.js.map +1 -1
- package/dist/Utils/surfaceAnnotation/geodesicContour.d.ts +11 -10
- package/dist/Utils/surfaceAnnotation/geodesicContour.js +16 -15
- package/dist/Utils/surfaceAnnotation/geodesicContour.js.map +1 -1
- package/dist/Utils/surfaceAnnotation/pointMarkers.d.ts +3 -2
- package/dist/Utils/surfaceAnnotation/pointMarkers.js +6 -2
- package/dist/Utils/surfaceAnnotation/pointMarkers.js.map +1 -1
- package/dist/Utils/surfaceAnnotation/raycastSurface.d.ts +3 -2
- package/dist/Utils/surfaceAnnotation/raycastSurface.js +71 -5
- package/dist/Utils/surfaceAnnotation/raycastSurface.js.map +1 -1
- package/dist/Utils/surfaceAnnotation/strokeContour.d.ts +9 -6
- package/dist/Utils/surfaceAnnotation/strokeContour.js +15 -10
- package/dist/Utils/surfaceAnnotation/strokeContour.js.map +1 -1
- package/dist/Utils/surfaceAnnotation/types.d.ts +5 -5
- package/dist/Utils/surfaceAnnotation/types.js +2 -2
- package/dist/Utils/surfaceAnnotation/types.js.map +1 -1
- package/dist/Utils/texture2d.js +4 -2
- package/dist/Utils/texture2d.js.map +1 -1
- package/dist/Utils/workers/reformatSaveDataWorker.js +2 -2
- package/dist/Utils/workers/reformatSaveDataWorker.js.map +1 -1
- package/dist/bundle.esm.js +684 -157
- package/dist/bundle.umd.js +684 -157
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/index.js.map +1 -1
- package/dist/lib/three-vignette.js +8 -2
- package/dist/lib/three-vignette.js.map +1 -1
- package/dist/types/Scene/copperScene.d.ts +4 -4
- package/dist/types/Utils/segmentation/NrrdTools.d.ts +41 -2
- package/dist/types/Utils/segmentation/tools/AiAssistTool.d.ts +108 -5
- package/dist/types/Utils/surfaceAnnotation/MeshGraph.d.ts +12 -10
- package/dist/types/Utils/surfaceAnnotation/SurfaceAnnotator.d.ts +48 -28
- package/dist/types/Utils/surfaceAnnotation/annotationStore.d.ts +4 -4
- package/dist/types/Utils/surfaceAnnotation/contourRender.d.ts +1 -1
- package/dist/types/Utils/surfaceAnnotation/geodesicContour.d.ts +11 -10
- package/dist/types/Utils/surfaceAnnotation/pointMarkers.d.ts +3 -2
- package/dist/types/Utils/surfaceAnnotation/raycastSurface.d.ts +3 -2
- package/dist/types/Utils/surfaceAnnotation/strokeContour.d.ts +9 -6
- package/dist/types/Utils/surfaceAnnotation/types.d.ts +5 -5
- package/dist/types/index.d.ts +1 -1
- package/package.json +2 -1
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Build a vertex adjacency graph from a BufferGeometry's index, used for the shortest path in
|
|
4
|
+
* mode B (geodesic). Vertex coordinates are stored in local space; query points must be converted
|
|
5
|
+
* to local first (mesh.worldToLocal).
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* Note: the geometry must be indexed. modelLoader welds duplicate vertices with mergeVertices so
|
|
8
|
+
* that vertices at the same surface position are shared, otherwise the adjacency graph won't be connected.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* Performance: O(V²) Dijkstra + O(V) nearest-vertex lookup. Acceptable for a single click on tens
|
|
11
|
+
* of thousands of vertices; if too large it can later be sped up with a binary heap + spatial grid
|
|
12
|
+
* (left as a future upgrade).
|
|
11
13
|
*/
|
|
12
14
|
export declare class MeshGraph {
|
|
13
15
|
private positions;
|
|
@@ -18,16 +20,16 @@ export declare class MeshGraph {
|
|
|
18
20
|
private px;
|
|
19
21
|
private py;
|
|
20
22
|
private pz;
|
|
21
|
-
/**
|
|
23
|
+
/** Nearest vertex (pass in local coordinates). */
|
|
22
24
|
nearestVertex(localPoint: THREE.Vector3): number;
|
|
23
25
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
+
* Shortest path between two vertices (sequence of vertex indices including endpoints).
|
|
27
|
+
* Binary-heap Dijkstra, O(E log V). Falls back to [start,end] when disconnected.
|
|
26
28
|
*/
|
|
27
29
|
shortestPath(startV: number, endV: number): number[];
|
|
28
30
|
vertexWorld(i: number, matrixWorld: THREE.Matrix4): THREE.Vector3;
|
|
29
31
|
vertexNormalWorld(i: number, mesh: THREE.Mesh): THREE.Vector3;
|
|
30
|
-
/**
|
|
32
|
+
/** Vertex local coordinates + local normal (the graph geometry is already local space). */
|
|
31
33
|
vertexLocal(i: number): {
|
|
32
34
|
x: number;
|
|
33
35
|
y: number;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import * as THREE from "three";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Build a vertex adjacency graph from a BufferGeometry's index, used for the shortest path in
|
|
4
|
+
* mode B (geodesic). Vertex coordinates are stored in local space; query points must be converted
|
|
5
|
+
* to local first (mesh.worldToLocal).
|
|
5
6
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* Note: the geometry must be indexed. modelLoader welds duplicate vertices with mergeVertices so
|
|
8
|
+
* that vertices at the same surface position are shared, otherwise the adjacency graph won't be connected.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* Performance: O(V²) Dijkstra + O(V) nearest-vertex lookup. Acceptable for a single click on tens
|
|
11
|
+
* of thousands of vertices; if too large it can later be sped up with a binary heap + spatial grid
|
|
12
|
+
* (left as a future upgrade).
|
|
11
13
|
*/
|
|
12
14
|
export class MeshGraph {
|
|
13
15
|
constructor(geometry) {
|
|
@@ -35,7 +37,7 @@ export class MeshGraph {
|
|
|
35
37
|
}
|
|
36
38
|
}
|
|
37
39
|
else {
|
|
38
|
-
//
|
|
40
|
+
// Non-indexed fallback (poor connectivity, only to avoid crashing).
|
|
39
41
|
for (let i = 0; i + 2 < this.vertexCount; i += 3) {
|
|
40
42
|
addEdge(i, i + 1);
|
|
41
43
|
addEdge(i + 1, i + 2);
|
|
@@ -52,7 +54,7 @@ export class MeshGraph {
|
|
|
52
54
|
pz(i) {
|
|
53
55
|
return this.positions[i * 3 + 2];
|
|
54
56
|
}
|
|
55
|
-
/**
|
|
57
|
+
/** Nearest vertex (pass in local coordinates). */
|
|
56
58
|
nearestVertex(localPoint) {
|
|
57
59
|
let best = -1;
|
|
58
60
|
let bestD = Infinity;
|
|
@@ -69,8 +71,8 @@ export class MeshGraph {
|
|
|
69
71
|
return best;
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
+
* Shortest path between two vertices (sequence of vertex indices including endpoints).
|
|
75
|
+
* Binary-heap Dijkstra, O(E log V). Falls back to [start,end] when disconnected.
|
|
74
76
|
*/
|
|
75
77
|
shortestPath(startV, endV) {
|
|
76
78
|
if (startV === endV)
|
|
@@ -127,7 +129,7 @@ export class MeshGraph {
|
|
|
127
129
|
const nm = new THREE.Matrix3().getNormalMatrix(mesh.matrixWorld);
|
|
128
130
|
return n.applyMatrix3(nm).normalize();
|
|
129
131
|
}
|
|
130
|
-
/**
|
|
132
|
+
/** Vertex local coordinates + local normal (the graph geometry is already local space). */
|
|
131
133
|
vertexLocal(i) {
|
|
132
134
|
return {
|
|
133
135
|
x: this.px(i),
|
|
@@ -140,7 +142,7 @@ export class MeshGraph {
|
|
|
140
142
|
};
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
|
-
/**
|
|
145
|
+
/** Minimal binary min-heap (ordered by priority, stores vertex indices). Used for Dijkstra. */
|
|
144
146
|
class MinHeap {
|
|
145
147
|
constructor() {
|
|
146
148
|
this.ids = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeshGraph.js","sourceRoot":"","sources":["../../../src/Utils/surfaceAnnotation/MeshGraph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B
|
|
1
|
+
{"version":3,"file":"MeshGraph.js","sourceRoot":"","sources":["../../../src/Utils/surfaceAnnotation/MeshGraph.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,26 +9,34 @@ export interface SurfaceAnnotatorOptions {
|
|
|
9
9
|
enabled: boolean;
|
|
10
10
|
};
|
|
11
11
|
mesh: THREE.Mesh;
|
|
12
|
-
/**
|
|
12
|
+
/** Computed from the mesh geometry's bounding-box diagonal when omitted. */
|
|
13
13
|
bboxDiagonal?: number;
|
|
14
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* Outward offset of the annotation line from the surface (world units), defaults to bboxDiagonal*0.002.
|
|
16
|
+
* Providing it separately decouples it from the sampling spacing (minGap/maxJump, still derived from
|
|
17
|
+
* bboxDiagonal) — useful on bumpy/voxel surfaces where a larger offset is needed to float the line
|
|
18
|
+
* above the relief without also coarsening the sampling.
|
|
19
|
+
*/
|
|
20
|
+
epsilon?: number;
|
|
21
|
+
/** Freehand contour color, defaults to #e5006e. */
|
|
15
22
|
freehandColor?: string;
|
|
16
|
-
/**
|
|
23
|
+
/** Geodesic contour color, defaults to #ffa24e. */
|
|
17
24
|
geodesicColor?: string;
|
|
18
|
-
/**
|
|
25
|
+
/** Point-marker color, defaults to #ffd166. */
|
|
19
26
|
pointColor?: string;
|
|
20
|
-
/**
|
|
27
|
+
/** Line width (pixels), defaults to 3. */
|
|
21
28
|
lineWidth?: number;
|
|
22
|
-
/**
|
|
29
|
+
/** Marker sphere radius, defaults to bboxDiagonal*0.006. */
|
|
23
30
|
markerRadius?: number;
|
|
24
31
|
onModeChange?: (m: AnnotationMode) => void;
|
|
25
|
-
/**
|
|
32
|
+
/** Callback when the annotation list changes (add/remove/undo/clear). */
|
|
26
33
|
onChange?: (annotations: Annotation[]) => void;
|
|
27
34
|
}
|
|
28
35
|
/**
|
|
29
|
-
*
|
|
30
|
-
* Enter
|
|
31
|
-
*
|
|
36
|
+
* Main surface-annotation controller (Phase 4): four modes navigate / freehand / geodesic / point,
|
|
37
|
+
* Enter closes the loop, and data is managed by AnnotationStore (multiple color-labeled strips,
|
|
38
|
+
* undo/delete/clear, export).
|
|
39
|
+
* The render layer reconciles the scene against store.list(); undo/delete automatically add/remove three objects.
|
|
32
40
|
*/
|
|
33
41
|
export declare class SurfaceAnnotator {
|
|
34
42
|
private o;
|
|
@@ -56,7 +64,7 @@ export declare class SurfaceAnnotator {
|
|
|
56
64
|
private hoveredGeoMarker;
|
|
57
65
|
private _projV;
|
|
58
66
|
constructor(opts: SurfaceAnnotatorOptions);
|
|
59
|
-
/**
|
|
67
|
+
/** Update the pixel resolution of all fat lines on window resize, otherwise line width distorts. */
|
|
60
68
|
private onResize;
|
|
61
69
|
private get freehandColor();
|
|
62
70
|
private get geodesicColor();
|
|
@@ -64,13 +72,13 @@ export declare class SurfaceAnnotator {
|
|
|
64
72
|
getMode(): AnnotationMode;
|
|
65
73
|
setMode(m: AnnotationMode): void;
|
|
66
74
|
getStore(): AnnotationStore;
|
|
67
|
-
/**
|
|
75
|
+
/** Snapshot of the current annotation list. */
|
|
68
76
|
getAnnotations(): Annotation[];
|
|
69
77
|
undo(): void;
|
|
70
78
|
clearAll(): void;
|
|
71
79
|
deleteAnnotation(id: string): void;
|
|
72
80
|
selectAnnotation(id: string | null): void;
|
|
73
|
-
/**
|
|
81
|
+
/** Redraw the corresponding three object after a color change. */
|
|
74
82
|
refreshAnnotation(id: string): void;
|
|
75
83
|
exportJSON(modelName: string, opts?: ExportOptions): {
|
|
76
84
|
model: string;
|
|
@@ -87,42 +95,54 @@ export declare class SurfaceAnnotator {
|
|
|
87
95
|
}[];
|
|
88
96
|
};
|
|
89
97
|
private applyCameraGating;
|
|
90
|
-
/**
|
|
98
|
+
/** Reconcile the scene against store.list(): add missing objects, remove deleted ones (no dispose, kept for undo restore). */
|
|
91
99
|
private reconcile;
|
|
92
100
|
private applySelection;
|
|
93
101
|
private disposeObject;
|
|
94
102
|
private nextId;
|
|
95
103
|
private hit;
|
|
96
104
|
/**
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
105
|
+
* Whether the event target is the WebGL canvas inside the container.
|
|
106
|
+
* Only accept the canvas: panels (GUIDE / control panel / the ✕ buttons in the annotation list)
|
|
107
|
+
* are all children of the container, so checking contains alone would treat clicks on the panels
|
|
108
|
+
* as drawing on the model — which "leaks through" the delete buttons and makes ✕ hard to click.
|
|
109
|
+
* Responding only to pointer events on the canvas fully isolates the UI from the drawing surface.
|
|
101
110
|
*/
|
|
102
111
|
private insideContainer;
|
|
103
112
|
private onPointerDown;
|
|
104
|
-
/**
|
|
113
|
+
/**
|
|
114
|
+
* Pick an in-progress anchor: return the index of the nearest anchor (screen pixel distance <
|
|
115
|
+
* tolerance), or -1 on a miss.
|
|
116
|
+
*
|
|
117
|
+
* Uses "screen-space pixel distance" instead of ray/sphere intersection: the anchor spheres are
|
|
118
|
+
* tiny, so strict ray/sphere hits require pixel-perfect aiming and most clicks miss — and a miss
|
|
119
|
+
* falls through to the "add anchor" branch, so trying to delete a point ends up adding one. That
|
|
120
|
+
* was the root cause of the old "deleting a point takes several clicks, must hit the sphere dead
|
|
121
|
+
* center" problem. Projecting each anchor to the screen and taking the nearest within tolerance
|
|
122
|
+
* makes deletion stable and reliable. The tolerance is enlarged relative to the sphere's screen
|
|
123
|
+
* radius to make it easy to hit.
|
|
124
|
+
*/
|
|
105
125
|
private pickGeoMarker;
|
|
106
|
-
/**
|
|
126
|
+
/** Set the currently hovered anchor (-1 = none): update highlight scale, cursor, and the "✕ (cancel)" floating badge. */
|
|
107
127
|
private setGeoHover;
|
|
108
|
-
/**
|
|
128
|
+
/** Lazily create the floating ✕ badge (appended inside the container, pointer-events:none so it doesn't block clicks). */
|
|
109
129
|
private ensureGeoBadge;
|
|
110
|
-
/**
|
|
130
|
+
/** Position the ✕ badge at the anchor's screen projection (dead center, i.e. where the cursor hovers, to avoid ambiguity). */
|
|
111
131
|
private positionGeoBadge;
|
|
112
|
-
/**
|
|
132
|
+
/** Rebuild the visible anchor spheres from the current anchors (slightly larger than placed points, so they're easy to see and click to cancel). */
|
|
113
133
|
private rebuildGeoMarkers;
|
|
114
|
-
/**
|
|
134
|
+
/** Redraw the in-progress geodesic from the current anchors (not closed); remove the line when fewer than two points. */
|
|
115
135
|
private redrawGeoLine;
|
|
116
|
-
/**
|
|
136
|
+
/** Discard the in-progress geodesic: remove and dispose the line and all anchors. */
|
|
117
137
|
private clearActiveGeo;
|
|
118
|
-
/**
|
|
138
|
+
/** Remove all anchor spheres of the in-progress geodesic (called after committing: only the line remains). */
|
|
119
139
|
private removeGeoMarkers;
|
|
120
140
|
private onPointerMove;
|
|
121
141
|
private onPointerUp;
|
|
122
142
|
private closeLastContour;
|
|
123
|
-
/**
|
|
143
|
+
/** Compute the geodesic path a→b along the mesh surface (local vertices), dropping the first point that coincides with a. a/b are already local. */
|
|
124
144
|
private surfacePathBetween;
|
|
125
|
-
/**
|
|
145
|
+
/** Finish the current geodesic: close it into a ring and commit it as one contour. */
|
|
126
146
|
private finishGeodesic;
|
|
127
147
|
private isTypingTarget;
|
|
128
148
|
private onKeyDown;
|
|
@@ -11,13 +11,14 @@ import { mergeVertices } from "three/examples/jsm/utils/BufferGeometryUtils";
|
|
|
11
11
|
const LINE_W = 3;
|
|
12
12
|
const LINE_W_SEL = 6;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
* Enter
|
|
16
|
-
*
|
|
14
|
+
* Main surface-annotation controller (Phase 4): four modes navigate / freehand / geodesic / point,
|
|
15
|
+
* Enter closes the loop, and data is managed by AnnotationStore (multiple color-labeled strips,
|
|
16
|
+
* undo/delete/clear, export).
|
|
17
|
+
* The render layer reconciles the scene against store.list(); undo/delete automatically add/remove three objects.
|
|
17
18
|
*/
|
|
18
19
|
export class SurfaceAnnotator {
|
|
19
20
|
constructor(opts) {
|
|
20
|
-
var _a, _b;
|
|
21
|
+
var _a, _b, _c;
|
|
21
22
|
this.mode = "navigate";
|
|
22
23
|
this.spaceHeld = false;
|
|
23
24
|
this.pointerDown = false;
|
|
@@ -25,12 +26,12 @@ export class SurfaceAnnotator {
|
|
|
25
26
|
this.managed = new Set();
|
|
26
27
|
this.seq = 0;
|
|
27
28
|
this.selectedId = null;
|
|
28
|
-
this.activeGeoMarkers = []; //
|
|
29
|
+
this.activeGeoMarkers = []; // visible anchors of the in-progress geodesic
|
|
29
30
|
this.geoRay = new THREE.Raycaster();
|
|
30
31
|
this.geoNdc = new THREE.Vector2();
|
|
31
32
|
this.hoveredGeoMarker = -1;
|
|
32
33
|
this._projV = new THREE.Vector3();
|
|
33
|
-
/**
|
|
34
|
+
/** Update the pixel resolution of all fat lines on window resize, otherwise line width distorts. */
|
|
34
35
|
this.onResize = () => {
|
|
35
36
|
const w = this.o.container.clientWidth;
|
|
36
37
|
const h = this.o.container.clientHeight;
|
|
@@ -49,7 +50,7 @@ export class SurfaceAnnotator {
|
|
|
49
50
|
if (this.spaceHeld)
|
|
50
51
|
return;
|
|
51
52
|
if (e.button !== 0)
|
|
52
|
-
return; //
|
|
53
|
+
return; // left button only
|
|
53
54
|
if (!this.insideContainer(e))
|
|
54
55
|
return;
|
|
55
56
|
this.pointerDown = true;
|
|
@@ -84,7 +85,7 @@ export class SurfaceAnnotator {
|
|
|
84
85
|
return;
|
|
85
86
|
}
|
|
86
87
|
if (this.mode === "geodesic") {
|
|
87
|
-
//
|
|
88
|
+
// First check whether an existing anchor was clicked → cancel that point (any point can be canceled).
|
|
88
89
|
const pick = this.pickGeoMarker(e);
|
|
89
90
|
if (pick >= 0 && this.activeGeo) {
|
|
90
91
|
this.activeGeo.removeAnchorAt(pick);
|
|
@@ -96,7 +97,7 @@ export class SurfaceAnnotator {
|
|
|
96
97
|
}
|
|
97
98
|
return;
|
|
98
99
|
}
|
|
99
|
-
//
|
|
100
|
+
// Otherwise drop a new anchor on the surface.
|
|
100
101
|
const h = this.hit(e);
|
|
101
102
|
if (!h)
|
|
102
103
|
return;
|
|
@@ -114,7 +115,7 @@ export class SurfaceAnnotator {
|
|
|
114
115
|
this.setGeoHover(-1);
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
117
|
-
//
|
|
118
|
+
// In geodesic mode, show the "✕" hint when hovering an anchor sphere (that point can be clicked to cancel).
|
|
118
119
|
if (this.mode === "geodesic" && !this.pointerDown) {
|
|
119
120
|
this.setGeoHover(this.insideContainer(e) ? this.pickGeoMarker(e) : -1);
|
|
120
121
|
}
|
|
@@ -179,7 +180,7 @@ export class SurfaceAnnotator {
|
|
|
179
180
|
return;
|
|
180
181
|
}
|
|
181
182
|
if (e.key === "Enter") {
|
|
182
|
-
//
|
|
183
|
+
// Geodesic in progress → finish and close it; otherwise close the most recent freehand line.
|
|
183
184
|
if (this.activeGeo)
|
|
184
185
|
this.finishGeodesic();
|
|
185
186
|
else
|
|
@@ -204,24 +205,25 @@ export class SurfaceAnnotator {
|
|
|
204
205
|
this.o = opts;
|
|
205
206
|
const meshGeo = opts.mesh.geometry;
|
|
206
207
|
if (!meshGeo.getAttribute("normal"))
|
|
207
|
-
meshGeo.computeVertexNormals(); //
|
|
208
|
+
meshGeo.computeVertexNormals(); // rendering needs normals
|
|
208
209
|
meshGeo.computeBoundingBox();
|
|
209
210
|
const diag = (_a = opts.bboxDiagonal) !== null && _a !== void 0 ? _a : meshGeo.boundingBox.getSize(new THREE.Vector3()).length();
|
|
210
211
|
this.markerRadius = (_b = opts.markerRadius) !== null && _b !== void 0 ? _b : diag * 0.0032;
|
|
211
|
-
this.epsilon = diag * 0.002;
|
|
212
|
+
this.epsilon = (_c = opts.epsilon) !== null && _c !== void 0 ? _c : diag * 0.002;
|
|
212
213
|
this.minGap = diag * 0.004;
|
|
213
214
|
this.maxJump = diag * 0.05;
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
//
|
|
215
|
+
// Geodesic connectivity: weld a separate "position-only" geometry for MeshGraph to use —
|
|
216
|
+
// leaving the rendered mesh untouched (keeping its normals/UV/texture). Position-only ensures
|
|
217
|
+
// vertices at the same surface position get merged by mergeVertices (otherwise per-face
|
|
218
|
+
// normals/UV would block the merge, breaking the graph → the closed loop cuts through the model).
|
|
217
219
|
const posOnly = new THREE.BufferGeometry();
|
|
218
220
|
posOnly.setAttribute("position", meshGeo.getAttribute("position").clone());
|
|
219
221
|
const graphGeo = mergeVertices(posOnly);
|
|
220
222
|
graphGeo.computeVertexNormals();
|
|
221
223
|
this.graph = new MeshGraph(graphGeo);
|
|
222
224
|
this.store.subscribe(() => this.reconcile());
|
|
223
|
-
//
|
|
224
|
-
//
|
|
225
|
+
// Listen on window in the capture phase (capture=true): receive every pointer event before
|
|
226
|
+
// copper's TrackballControls, so its setPointerCapture / event routing doesn't drop moves mid-drag.
|
|
225
227
|
window.addEventListener("pointerdown", this.onPointerDown, true);
|
|
226
228
|
window.addEventListener("pointermove", this.onPointerMove, true);
|
|
227
229
|
window.addEventListener("pointerup", this.onPointerUp, true);
|
|
@@ -242,13 +244,13 @@ export class SurfaceAnnotator {
|
|
|
242
244
|
var _a;
|
|
243
245
|
return (_a = this.o.pointColor) !== null && _a !== void 0 ? _a : "#ffd166";
|
|
244
246
|
}
|
|
245
|
-
// ----
|
|
247
|
+
// ---- Public API (called from Vue) ----
|
|
246
248
|
getMode() {
|
|
247
249
|
return this.mode;
|
|
248
250
|
}
|
|
249
251
|
setMode(m) {
|
|
250
252
|
var _a, _b;
|
|
251
|
-
//
|
|
253
|
+
// When leaving geodesic mode, discard the in-progress geodesic not yet committed with Enter (clear leftover anchors and line).
|
|
252
254
|
if (m !== "geodesic" && this.activeGeo)
|
|
253
255
|
this.clearActiveGeo();
|
|
254
256
|
this.mode = m;
|
|
@@ -258,13 +260,13 @@ export class SurfaceAnnotator {
|
|
|
258
260
|
getStore() {
|
|
259
261
|
return this.store;
|
|
260
262
|
}
|
|
261
|
-
/**
|
|
263
|
+
/** Snapshot of the current annotation list. */
|
|
262
264
|
getAnnotations() {
|
|
263
265
|
return this.store.list();
|
|
264
266
|
}
|
|
265
267
|
undo() {
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
+
// Geodesic in progress → undo the most recent anchor edit (both adding and canceling a point can be rolled back);
|
|
269
|
+
// otherwise undo the most recently committed annotation.
|
|
268
270
|
if (this.activeGeo && this.activeGeo.canUndo()) {
|
|
269
271
|
this.activeGeo.undoEdit();
|
|
270
272
|
if (this.activeGeo.anchorCount === 0)
|
|
@@ -298,7 +300,7 @@ export class SurfaceAnnotator {
|
|
|
298
300
|
this.selectedId = id;
|
|
299
301
|
this.applySelection();
|
|
300
302
|
}
|
|
301
|
-
/**
|
|
303
|
+
/** Redraw the corresponding three object after a color change. */
|
|
302
304
|
refreshAnnotation(id) {
|
|
303
305
|
const a = this.store.get(id);
|
|
304
306
|
if (!a || !a.object3D)
|
|
@@ -314,12 +316,12 @@ export class SurfaceAnnotator {
|
|
|
314
316
|
exportJSON(modelName, opts) {
|
|
315
317
|
return this.store.toJSON(modelName, this.o.mesh, opts);
|
|
316
318
|
}
|
|
317
|
-
// ----
|
|
319
|
+
// ---- Internal ----
|
|
318
320
|
applyCameraGating() {
|
|
319
|
-
//
|
|
321
|
+
// Holding Space for temporary rotation takes priority; otherwise enable the camera only in navigate mode.
|
|
320
322
|
this.o.controls.enabled = this.spaceHeld || this.mode === "navigate";
|
|
321
323
|
}
|
|
322
|
-
/**
|
|
324
|
+
/** Reconcile the scene against store.list(): add missing objects, remove deleted ones (no dispose, kept for undo restore). */
|
|
323
325
|
reconcile() {
|
|
324
326
|
var _a, _b;
|
|
325
327
|
const present = new Set();
|
|
@@ -369,29 +371,53 @@ export class SurfaceAnnotator {
|
|
|
369
371
|
return raycastSurface(this.o.camera, this.o.container, this.o.mesh, e.clientX, e.clientY);
|
|
370
372
|
}
|
|
371
373
|
/**
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
374
|
+
* Whether the event target is the WebGL canvas inside the container.
|
|
375
|
+
* Only accept the canvas: panels (GUIDE / control panel / the ✕ buttons in the annotation list)
|
|
376
|
+
* are all children of the container, so checking contains alone would treat clicks on the panels
|
|
377
|
+
* as drawing on the model — which "leaks through" the delete buttons and makes ✕ hard to click.
|
|
378
|
+
* Responding only to pointer events on the canvas fully isolates the UI from the drawing surface.
|
|
376
379
|
*/
|
|
377
380
|
insideContainer(e) {
|
|
378
381
|
const t = e.target;
|
|
379
382
|
return !!t && t.tagName === "CANVAS" && this.o.container.contains(t);
|
|
380
383
|
}
|
|
381
|
-
/**
|
|
384
|
+
/**
|
|
385
|
+
* Pick an in-progress anchor: return the index of the nearest anchor (screen pixel distance <
|
|
386
|
+
* tolerance), or -1 on a miss.
|
|
387
|
+
*
|
|
388
|
+
* Uses "screen-space pixel distance" instead of ray/sphere intersection: the anchor spheres are
|
|
389
|
+
* tiny, so strict ray/sphere hits require pixel-perfect aiming and most clicks miss — and a miss
|
|
390
|
+
* falls through to the "add anchor" branch, so trying to delete a point ends up adding one. That
|
|
391
|
+
* was the root cause of the old "deleting a point takes several clicks, must hit the sphere dead
|
|
392
|
+
* center" problem. Projecting each anchor to the screen and taking the nearest within tolerance
|
|
393
|
+
* makes deletion stable and reliable. The tolerance is enlarged relative to the sphere's screen
|
|
394
|
+
* radius to make it easy to hit.
|
|
395
|
+
*/
|
|
382
396
|
pickGeoMarker(e) {
|
|
383
397
|
if (!this.activeGeoMarkers.length)
|
|
384
398
|
return -1;
|
|
385
399
|
const rect = this.o.container.getBoundingClientRect();
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
400
|
+
const px = e.clientX - rect.left;
|
|
401
|
+
const py = e.clientY - rect.top;
|
|
402
|
+
const TOL = 16; // hit tolerance (pixels), larger than the sphere itself for forgiving clicks
|
|
403
|
+
let best = -1;
|
|
404
|
+
let bestDist = TOL;
|
|
405
|
+
const v = this._projV;
|
|
406
|
+
for (let i = 0; i < this.activeGeoMarkers.length; i++) {
|
|
407
|
+
v.copy(this.activeGeoMarkers[i].position).project(this.o.camera);
|
|
408
|
+
if (v.z > 1)
|
|
409
|
+
continue; // projects behind the camera, skip
|
|
410
|
+
const sx = (v.x * 0.5 + 0.5) * rect.width;
|
|
411
|
+
const sy = (-v.y * 0.5 + 0.5) * rect.height;
|
|
412
|
+
const d = Math.hypot(sx - px, sy - py);
|
|
413
|
+
if (d < bestDist) {
|
|
414
|
+
bestDist = d;
|
|
415
|
+
best = i;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
return best;
|
|
393
419
|
}
|
|
394
|
-
/**
|
|
420
|
+
/** Set the currently hovered anchor (-1 = none): update highlight scale, cursor, and the "✕ (cancel)" floating badge. */
|
|
395
421
|
setGeoHover(idx) {
|
|
396
422
|
if (idx === this.hoveredGeoMarker) {
|
|
397
423
|
if (idx >= 0)
|
|
@@ -415,7 +441,7 @@ export class SurfaceAnnotator {
|
|
|
415
441
|
this.o.container.style.cursor = "";
|
|
416
442
|
}
|
|
417
443
|
}
|
|
418
|
-
/**
|
|
444
|
+
/** Lazily create the floating ✕ badge (appended inside the container, pointer-events:none so it doesn't block clicks). */
|
|
419
445
|
ensureGeoBadge() {
|
|
420
446
|
if (this.geoHoverBadge)
|
|
421
447
|
return this.geoHoverBadge;
|
|
@@ -443,7 +469,7 @@ export class SurfaceAnnotator {
|
|
|
443
469
|
this.geoHoverBadge = b;
|
|
444
470
|
return b;
|
|
445
471
|
}
|
|
446
|
-
/**
|
|
472
|
+
/** Position the ✕ badge at the anchor's screen projection (dead center, i.e. where the cursor hovers, to avoid ambiguity). */
|
|
447
473
|
positionGeoBadge(marker) {
|
|
448
474
|
if (!marker)
|
|
449
475
|
return;
|
|
@@ -455,7 +481,7 @@ export class SurfaceAnnotator {
|
|
|
455
481
|
b.style.left = `${x}px`;
|
|
456
482
|
b.style.top = `${y}px`;
|
|
457
483
|
}
|
|
458
|
-
/**
|
|
484
|
+
/** Rebuild the visible anchor spheres from the current anchors (slightly larger than placed points, so they're easy to see and click to cancel). */
|
|
459
485
|
rebuildGeoMarkers() {
|
|
460
486
|
this.setGeoHover(-1);
|
|
461
487
|
for (const m of this.activeGeoMarkers) {
|
|
@@ -471,7 +497,7 @@ export class SurfaceAnnotator {
|
|
|
471
497
|
this.activeGeoMarkers.push(marker);
|
|
472
498
|
}
|
|
473
499
|
}
|
|
474
|
-
/**
|
|
500
|
+
/** Redraw the in-progress geodesic from the current anchors (not closed); remove the line when fewer than two points. */
|
|
475
501
|
redrawGeoLine() {
|
|
476
502
|
if (!this.activeGeo)
|
|
477
503
|
return;
|
|
@@ -492,7 +518,7 @@ export class SurfaceAnnotator {
|
|
|
492
518
|
updateContourLine(this.activeGeoLine, verts, false, this.epsilon, this.o.mesh);
|
|
493
519
|
}
|
|
494
520
|
}
|
|
495
|
-
/**
|
|
521
|
+
/** Discard the in-progress geodesic: remove and dispose the line and all anchors. */
|
|
496
522
|
clearActiveGeo() {
|
|
497
523
|
this.setGeoHover(-1);
|
|
498
524
|
if (this.activeGeoLine) {
|
|
@@ -507,7 +533,7 @@ export class SurfaceAnnotator {
|
|
|
507
533
|
this.activeGeoMarkers = [];
|
|
508
534
|
this.activeGeo = undefined;
|
|
509
535
|
}
|
|
510
|
-
/**
|
|
536
|
+
/** Remove all anchor spheres of the in-progress geodesic (called after committing: only the line remains). */
|
|
511
537
|
removeGeoMarkers() {
|
|
512
538
|
this.setGeoHover(-1);
|
|
513
539
|
for (const m of this.activeGeoMarkers) {
|
|
@@ -520,7 +546,7 @@ export class SurfaceAnnotator {
|
|
|
520
546
|
const last = this.lastFreehand;
|
|
521
547
|
if (!last || last.closed || last.vertices.length < 3 || !last.object3D)
|
|
522
548
|
return;
|
|
523
|
-
//
|
|
549
|
+
// Use a geodesic along the surface to connect the last point back to the first, so a straight chord doesn't "shortcut" through the model interior and get occluded.
|
|
524
550
|
const tail = last.vertices[last.vertices.length - 1];
|
|
525
551
|
const head = last.vertices[0];
|
|
526
552
|
const closing = this.surfacePathBetween(tail, head);
|
|
@@ -528,14 +554,14 @@ export class SurfaceAnnotator {
|
|
|
528
554
|
last.closed = true;
|
|
529
555
|
updateContourLine(last.object3D, last.vertices, true, this.epsilon, this.o.mesh);
|
|
530
556
|
}
|
|
531
|
-
/**
|
|
557
|
+
/** Compute the geodesic path a→b along the mesh surface (local vertices), dropping the first point that coincides with a. a/b are already local. */
|
|
532
558
|
surfacePathBetween(a, b) {
|
|
533
559
|
const va = this.graph.nearestVertex(new THREE.Vector3(a.x, a.y, a.z));
|
|
534
560
|
const vb = this.graph.nearestVertex(new THREE.Vector3(b.x, b.y, b.z));
|
|
535
561
|
const path = this.graph.shortestPath(va, vb);
|
|
536
562
|
return path.slice(1).map((i) => this.graph.vertexLocal(i));
|
|
537
563
|
}
|
|
538
|
-
/**
|
|
564
|
+
/** Finish the current geodesic: close it into a ring and commit it as one contour. */
|
|
539
565
|
finishGeodesic() {
|
|
540
566
|
if (!this.activeGeo || !this.activeGeoLine)
|
|
541
567
|
return;
|
|
@@ -559,7 +585,7 @@ export class SurfaceAnnotator {
|
|
|
559
585
|
this.o.scene.remove(this.activeGeoLine);
|
|
560
586
|
this.disposeObject(this.activeGeoLine);
|
|
561
587
|
}
|
|
562
|
-
//
|
|
588
|
+
// After committing, clear the visible anchors and leave only the surface-hugging line.
|
|
563
589
|
this.removeGeoMarkers();
|
|
564
590
|
this.activeGeo = undefined;
|
|
565
591
|
this.activeGeoLine = undefined;
|