@smarterplan/ngx-smarterplan-core 1.4.14 → 1.4.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +24 -24
  2. package/esm2022/lib/components/csv-export/csv-export.component.mjs +3 -3
  3. package/esm2022/lib/components/loader/loader.component.mjs +3 -3
  4. package/esm2022/lib/components/menu-bar/avatar/avatar.component.mjs +1 -1
  5. package/esm2022/lib/components/menu-bar/menu-bar.component.mjs +3 -3
  6. package/esm2022/lib/components/menu-bar/navigation-bar/navigation-bar.component.mjs +3 -3
  7. package/esm2022/lib/components/modal-switch-visit/modal-switch-visit.component.mjs +3 -3
  8. package/esm2022/lib/components/search-bar/search-bar.component.mjs +3 -3
  9. package/esm2022/lib/components/support-modal/support-modal.component.mjs +3 -3
  10. package/esm2022/lib/config.mjs +1 -1
  11. package/esm2022/lib/helpers.service.mjs +46 -1
  12. package/esm2022/lib/matterport-extensions/nest-thermostat/CanvasImage.mjs +1 -1
  13. package/esm2022/lib/matterport-extensions/nest-thermostat/CanvasRenderer.mjs +1 -1
  14. package/esm2022/lib/matterport-extensions/nest-thermostat/NestThermostat.mjs +1 -1
  15. package/esm2022/lib/matterport-extensions/nest-thermostat/PlaneRenderer.mjs +1 -1
  16. package/esm2022/lib/matterport-extensions/scene-component/SceneComponent.mjs +1 -1
  17. package/esm2022/lib/matterport-extensions/security-camera/SecurityCamera.mjs +22 -22
  18. package/esm2022/lib/matterport-extensions/tv-player/TvPlayer.mjs +1 -1
  19. package/esm2022/lib/matterport-extensions/view-frustum-mesh/ViewFrustumMesh.mjs +22 -22
  20. package/esm2022/lib/mattertagData.mjs +1 -1
  21. package/esm2022/lib/ngx-smarterplan-core.module.mjs +1 -1
  22. package/esm2022/lib/ngx-smarterplan-core.service.mjs +1 -1
  23. package/esm2022/lib/pipes/duration-to-string.pipe.mjs +1 -1
  24. package/esm2022/lib/pipes/format-date-number-to-digits.pipe.mjs +1 -1
  25. package/esm2022/lib/pipes/safe-url.pipe.mjs +1 -1
  26. package/esm2022/lib/pipes/time-date-to-local-string.pipe.mjs +1 -1
  27. package/esm2022/lib/pipes/username-from-id.pipe.mjs +1 -1
  28. package/esm2022/lib/services/amplify-cache.service.mjs +1 -1
  29. package/esm2022/lib/services/base-tab.service.mjs +1 -1
  30. package/esm2022/lib/services/baseVisibility.service.mjs +1 -1
  31. package/esm2022/lib/services/content.service.mjs +1 -1
  32. package/esm2022/lib/services/filter.service.mjs +1 -1
  33. package/esm2022/lib/services/intervention.service.mjs +1 -1
  34. package/esm2022/lib/services/locale.service.mjs +1 -1
  35. package/esm2022/lib/services/matterport-import.service.mjs +1 -1
  36. package/esm2022/lib/services/matterport-measurement.service.mjs +1 -1
  37. package/esm2022/lib/services/matterport-navigation.service.mjs +1 -1
  38. package/esm2022/lib/services/matterport-object3d.service.mjs +1 -1
  39. package/esm2022/lib/services/matterport-pointer.service.mjs +6 -6
  40. package/esm2022/lib/services/matterport-tag-copy.service.mjs +448 -62
  41. package/esm2022/lib/services/matterport-tag.service.mjs +1 -1
  42. package/esm2022/lib/services/matterport.service.mjs +17 -17
  43. package/esm2022/lib/services/models/affectation.service.mjs +1 -1
  44. package/esm2022/lib/services/models/base-object.service.mjs +1 -1
  45. package/esm2022/lib/services/models/capture.service.mjs +1 -1
  46. package/esm2022/lib/services/models/comment.service.mjs +1 -1
  47. package/esm2022/lib/services/models/domain.service.mjs +20 -1
  48. package/esm2022/lib/services/models/equipment.service.mjs +4 -2
  49. package/esm2022/lib/services/models/event.service.mjs +1 -1
  50. package/esm2022/lib/services/models/feature.service.mjs +1 -7
  51. package/esm2022/lib/services/models/hashtag.service.mjs +1 -1
  52. package/esm2022/lib/services/models/layer.service.mjs +1 -1
  53. package/esm2022/lib/services/models/measurement.service.mjs +1 -1
  54. package/esm2022/lib/services/models/mission.service.mjs +17 -1
  55. package/esm2022/lib/services/models/navigation.service.mjs +1 -1
  56. package/esm2022/lib/services/models/node.service.mjs +1 -1
  57. package/esm2022/lib/services/models/object3D.service.mjs +1 -1
  58. package/esm2022/lib/services/models/operation.service.mjs +1 -1
  59. package/esm2022/lib/services/models/organisation.service.mjs +1 -1
  60. package/esm2022/lib/services/models/plan.service.mjs +1 -1
  61. package/esm2022/lib/services/models/poi.service.mjs +27 -27
  62. package/esm2022/lib/services/models/profile.service.mjs +1 -1
  63. package/esm2022/lib/services/models/property.service.mjs +1 -1
  64. package/esm2022/lib/services/models/space.service.mjs +25 -6
  65. package/esm2022/lib/services/models/template.service.mjs +1 -1
  66. package/esm2022/lib/services/models/ticket.service.mjs +1 -1
  67. package/esm2022/lib/services/models/visit.service.mjs +17 -5
  68. package/esm2022/lib/services/models/zone.service.mjs +1 -1
  69. package/esm2022/lib/services/navigator.service.mjs +1 -1
  70. package/esm2022/lib/services/s3.service.mjs +18 -1
  71. package/esm2022/lib/services/search.service.mjs +1 -1
  72. package/esm2022/lib/services/support.service.mjs +1 -1
  73. package/esm2022/lib/services/tag.service.mjs +16 -16
  74. package/esm2022/lib/services/threejs-import.service.mjs +151 -0
  75. package/esm2022/lib/services/threejs.service.mjs +1306 -0
  76. package/esm2022/lib/services/user.service.mjs +1 -1
  77. package/esm2022/lib/services/utils/matterport-transform.util.mjs +1 -1
  78. package/esm2022/lib/services/validators.service.mjs +1 -1
  79. package/esm2022/lib/services/viewer.service.mjs +263 -5
  80. package/esm2022/lib/services/zone-drawer.service.mjs +1 -1
  81. package/esm2022/lib/services/zoneChange.service.mjs +1 -1
  82. package/esm2022/lib/tokens/matterport-sdk-key.token.mjs +1 -1
  83. package/esm2022/lib/types.service.mjs +2 -1
  84. package/esm2022/lib/validators/email.directive.mjs +1 -1
  85. package/esm2022/lib/validators/no-empty.directive.mjs +1 -1
  86. package/esm2022/lib/validators/number.directive.mjs +1 -1
  87. package/esm2022/lib/validators/text.directive.mjs +1 -1
  88. package/esm2022/public-api.mjs +3 -1
  89. package/fesm2022/smarterplan-ngx-smarterplan-core.mjs +2759 -551
  90. package/fesm2022/smarterplan-ngx-smarterplan-core.mjs.map +1 -1
  91. package/lib/helpers.service.d.ts +43 -0
  92. package/lib/services/matterport-tag-copy.service.d.ts +137 -17
  93. package/lib/services/models/domain.service.d.ts +8 -0
  94. package/lib/services/models/feature.service.d.ts +0 -6
  95. package/lib/services/models/mission.service.d.ts +5 -0
  96. package/lib/services/models/space.service.d.ts +15 -1
  97. package/lib/services/models/visit.service.d.ts +9 -1
  98. package/lib/services/s3.service.d.ts +8 -0
  99. package/lib/services/threejs-import.service.d.ts +31 -0
  100. package/lib/services/threejs.service.d.ts +423 -0
  101. package/lib/services/viewer.service.d.ts +114 -1
  102. package/lib/types.service.d.ts +7 -1
  103. package/package.json +1 -1
  104. package/public-api.d.ts +2 -0
  105. package/src/assets/vs-app.json +67 -67
@@ -0,0 +1,423 @@
1
+ import { NgZone } from '@angular/core';
2
+ import { Subject } from 'rxjs';
3
+ import { ThreeJsAlignment } from '../helpers.service';
4
+ import { PoiType } from '../types.service';
5
+ import * as i0 from "@angular/core";
6
+ /**
7
+ * A tag to draw in the Three.js scene, mirroring a Mattertag.
8
+ *
9
+ * Positions are in the model's own coordinate space - the same numbers stored in POI.coordinate -
10
+ * not scene space. The service reparents tags under the model so they inherit the centering and
11
+ * scaling applied at load, which means callers never deal with that transform.
12
+ */
13
+ export interface ThreeJsTag {
14
+ /** The tagged element's id (POI.elementID), used to update or remove the tag later. */
15
+ id: string;
16
+ position: {
17
+ x: number;
18
+ y: number;
19
+ z: number;
20
+ };
21
+ /** Drives both the click route and type filtering, so it travels with the tag. */
22
+ poiType?: PoiType | null;
23
+ /** Icon drawn on the tag. Falls back to a plain disc when absent or it fails to load. */
24
+ iconUrl?: string | null;
25
+ /** Tint for the fallback disc, as a CSS colour. */
26
+ color?: string;
27
+ }
28
+ /** What a click on a tag reports: enough to route to the element's detail page. */
29
+ export interface ThreeJsTagClick {
30
+ elementId: string;
31
+ poiType: PoiType | null;
32
+ }
33
+ /**
34
+ * A spot picked on the model for a new tag.
35
+ *
36
+ * Both vectors are in the model's own coordinate space, which is what POI.coordinate stores. Scene
37
+ * coordinates deliberately never leave this service: the model is scaled to a fixed size on load, so
38
+ * a scene-space position would silently mean somewhere else the moment the .glb is re-exported.
39
+ */
40
+ export interface ThreeJsTagPlacement {
41
+ position: {
42
+ x: number;
43
+ y: number;
44
+ z: number;
45
+ };
46
+ /** Surface normal at the picked point - Matterport stores the same thing in POI.metadata. */
47
+ normal: {
48
+ x: number;
49
+ y: number;
50
+ z: number;
51
+ };
52
+ }
53
+ /** One tag seen in both spaces: where it is stored, and where it really sits on the model. */
54
+ export interface ThreeJsAlignmentPair {
55
+ source: {
56
+ x: number;
57
+ y: number;
58
+ z: number;
59
+ };
60
+ target: {
61
+ x: number;
62
+ y: number;
63
+ z: number;
64
+ };
65
+ }
66
+ /**
67
+ * Solves the transform that maps stored tag coordinates onto the loaded model.
68
+ *
69
+ * Takes exactly the correspondences a person can produce by hand: three tags, each pointed at twice
70
+ * - once where it currently sits, once where it belongs. That is the minimum that pins down all
71
+ * three parts of the answer. One pair fixes only translation; two leave the rotation about the line
72
+ * between them free; three resolve it.
73
+ *
74
+ * Returns null when the points are collinear or degenerate, because a wrong alignment applied
75
+ * silently is worse than none.
76
+ */
77
+ export declare function solveThreeJsAlignment(pairs: ThreeJsAlignmentPair[]): ThreeJsAlignment | null;
78
+ export interface ObjAdded {
79
+ id: string;
80
+ name: string;
81
+ file: string;
82
+ position: {
83
+ x: number;
84
+ y: number;
85
+ z: number;
86
+ };
87
+ rotation: {
88
+ x: number;
89
+ y: number;
90
+ z: number;
91
+ };
92
+ scale: {
93
+ x: number;
94
+ y: number;
95
+ z: number;
96
+ };
97
+ }
98
+ export declare class ThreeJsService {
99
+ private ngZone;
100
+ private scene;
101
+ private camera;
102
+ private renderer;
103
+ private controls;
104
+ private animationFrameId;
105
+ private raycaster;
106
+ private mouse;
107
+ private isDraggingCamera;
108
+ private isDraggingTimer;
109
+ private clickableObjects;
110
+ objectHovered$: Subject<{
111
+ id: string;
112
+ event: MouseEvent;
113
+ }>;
114
+ objectClicked$: Subject<string>;
115
+ /** Emits when the user clicks a tag, mirroring Matterport's tag click. */
116
+ tagClicked$: Subject<ThreeJsTagClick>;
117
+ private tagGroup;
118
+ private tagSprites;
119
+ private canvasResizeObserver?;
120
+ /** Aborts the in-flight model download. Parsing cannot be interrupted, only not started. */
121
+ private loadAbortController?;
122
+ private modelRoot?;
123
+ /** True while the next click on the model places a tag instead of orbiting. */
124
+ private placingTag;
125
+ /** Position and normal of the last placed tag, in model space, awaiting commit to a POI. */
126
+ private pendingTagPlacement;
127
+ /** Emits once a tag is dropped on the model, so the UI can confirm and persist it. */
128
+ tagPlaced$: Subject<ThreeJsTagPlacement>;
129
+ /** Translucent marker shown under the cursor while placing. */
130
+ private placementPreview?;
131
+ private aligning;
132
+ private alignmentPairs;
133
+ private alignmentSource;
134
+ /** Drives the alignment walkthrough's UI: how many pairs are done and what it wants next. */
135
+ alignmentProgress$: Subject<{
136
+ pairs: number;
137
+ awaiting: 'tag' | 'point' | null;
138
+ tagId?: string;
139
+ }>;
140
+ /** In-flight camera move started by goToTag, advanced by the render loop. */
141
+ private cameraTween;
142
+ /** Emits true while a heavy OBJ file is being parsed (in a worker, when available), false once done. */
143
+ modelLoading$: Subject<boolean>;
144
+ /**
145
+ * Download progress for GLB models, as a 0-1 fraction, or null when the total size is unknown.
146
+ *
147
+ * A null means the server did not send Content-Length (chunked responses, some proxies) and no
148
+ * percentage can be derived; show an indeterminate indicator rather than guessing at one.
149
+ */
150
+ modelLoadProgress$: Subject<number>;
151
+ /**
152
+ * Emits when a load actually stopped short, which is not the same as asking it to.
153
+ *
154
+ * cancelModelLoad() can arrive after parse() has taken the main thread, and parse cannot be
155
+ * interrupted - the model finishes loading regardless. Only this tells the UI which of the two
156
+ * happened, so it does not offer to reload something that is already on screen.
157
+ */
158
+ modelLoadCancelled$: Subject<void>;
159
+ private objParserWorker?;
160
+ private objParserPendingRequests;
161
+ constructor(ngZone: NgZone);
162
+ /**
163
+ * Registers a Web Worker used to parse heavy .obj files off the main thread.
164
+ * Must be instantiated by the consuming app (e.g. `new Worker(new URL('./obj-parser.worker', import.meta.url))`),
165
+ * since a library packaged with ng-packagr cannot bundle worker files itself.
166
+ * When no worker is provided, loadObjModel falls back to parsing on the main thread.
167
+ */
168
+ setObjParserWorker(worker: Worker): void;
169
+ private requestObjParseInWorker;
170
+ /** Rebuilds a THREE.Mesh from the raw typed arrays sent back by the parser worker. */
171
+ private meshFromParsedData;
172
+ init(canvas: HTMLCanvasElement): void;
173
+ /**
174
+ * Path (served by the app) holding basis_transcoder.js/.wasm, which KTX2Loader needs to turn
175
+ * Basis Universal textures into whatever compressed format the local GPU actually supports.
176
+ */
177
+ private ktx2TranscoderPath;
178
+ /** Overrides where the Basis transcoder files are served from. */
179
+ setKtx2TranscoderPath(path: string): void;
180
+ /**
181
+ * Loads a base 3D model in GLTF/GLB format - the preferred format for large models.
182
+ * Unlike .obj, geometry arrives as binary typed arrays (no text parsing to offload to a worker)
183
+ * and stays indexed, so far less data reaches the GPU. KTX2 textures are supported too: they
184
+ * stay compressed in VRAM instead of being decoded to raw pixels like JPEG/PNG.
185
+ */
186
+ loadGLTFModel(url: string, cameraPosition?: {
187
+ x: number;
188
+ y: number;
189
+ z: number;
190
+ }, rotation?: {
191
+ x: number;
192
+ y: number;
193
+ z: number;
194
+ }): void;
195
+ /**
196
+ * Stops the model download in progress, if any.
197
+ *
198
+ * Only the download can be interrupted: parse() runs synchronously and holds the main thread until
199
+ * it finishes, so once it has begun there is nothing to cancel. In practice the download is the
200
+ * long part, which is where someone reaches for this.
201
+ */
202
+ cancelModelLoad(): void;
203
+ /**
204
+ * Abandons the load in progress without reporting it as cancelled.
205
+ *
206
+ * Dropping the controller is what makes the difference: onError treats an abort as user-initiated
207
+ * only while the controller that aborted is still the current one. Tearing down or rebuilding the
208
+ * scene is not something the person asked for, so it must not surface as "loading cancelled".
209
+ */
210
+ private discardCurrentLoad;
211
+ /**
212
+ * Resolves once the browser has painted at least one frame.
213
+ *
214
+ * A requestAnimationFrame callback runs *before* that frame is painted, so a single one is not
215
+ * enough to guarantee pending DOM changes are on screen; the second fires after the first frame
216
+ * has gone out. Falls back to a timeout where rAF isn't available (SSR, tests).
217
+ */
218
+ private afterNextPaint;
219
+ /**
220
+ * Downloads a file, reporting progress on modelLoadProgress$ as it arrives.
221
+ *
222
+ * GLTFLoader.load() would report progress through XHR, but this path deliberately uses fetch() +
223
+ * parse() instead (see loadGLTFModel for why), so progress is read off the response stream here.
224
+ * That is also the more useful number: on a model of this size the download dominates, and the
225
+ * stream reports it as it happens rather than in one jump at the end.
226
+ */
227
+ private fetchWithProgress;
228
+ /**
229
+ * Fits the camera's clipping planes to how big the model actually is.
230
+ *
231
+ * Depth precision depends on the far/near ratio, and the fixed 0.1 / 10000 the camera starts with
232
+ * is a ratio of 100000 for a model only ~10 units across. That wastes most of the depth buffer on
233
+ * empty space and lets nearly coincident surfaces - which captured meshes have everywhere -
234
+ * flicker against each other.
235
+ */
236
+ private adjustClippingPlanes;
237
+ /**
238
+ * Swaps the PBR materials GLTFLoader builds for the plain Phong ones the .obj path produces.
239
+ *
240
+ * MeshStandardMaterial keeps a specular reflection even at metalness 0, since every dielectric
241
+ * reflects some light, and Fresnel drives that reflection towards white as a surface turns away
242
+ * from the viewer. On an aerial view of a facade most surfaces sit at exactly those grazing
243
+ * angles, which paints white highlights over window frames and roof edges - the same model
244
+ * loaded as .obj never shows them, because MTLLoader builds Phong materials and OBJLoader zeroes
245
+ * their specular when the .mtl declares Ns 0.
246
+ *
247
+ * Captured models carry their lighting in the texture and want no specular response at all, so
248
+ * matching the .obj path here is both the fix and the more faithful choice.
249
+ */
250
+ private convertToPhongMaterials;
251
+ /**
252
+ * Renders every material double-sided.
253
+ *
254
+ * Reality capture meshes routinely contain triangles with reversed winding - an artefact of the
255
+ * reconstruction, not of the file. With the default FrontSide those triangles are culled, leaving
256
+ * scattered holes that show the scene background through the model. Against a light background
257
+ * they read as white specks with no discernible pattern.
258
+ *
259
+ * Drawing both sides costs a little fill rate but is what makes captured geometry render as
260
+ * solid; other viewers of this kind of content do the same unconditionally.
261
+ */
262
+ private forceDoubleSidedMaterials;
263
+ /**
264
+ * Computes real vertex normals for any geometry that arrived without them.
265
+ *
266
+ * A glTF without a NORMAL attribute is spec-compliant, and GLTFLoader handles it by switching the
267
+ * material to flatShading, which makes the shader derive the normal per pixel from screen-space
268
+ * derivatives. That derivation collapses on the degenerate, needle-thin triangles that reality
269
+ * capture meshes are full of: the face has almost no area, the derived normal is meaningless, and
270
+ * lighting blows those fragments out to white specks - densest exactly where fine detail is.
271
+ *
272
+ * Computing normals from the geometry instead is robust to those triangles, since a degenerate
273
+ * face contributes almost nothing to the average and its neighbours dominate.
274
+ */
275
+ private ensureVertexNormals;
276
+ /**
277
+ * Configures how every texture in a model is sampled: maximum anisotropy, and no mipmapping.
278
+ *
279
+ * Photogrammetry atlases pack their UV islands with unused gutter pixels between them. Mipmapping
280
+ * averages neighbouring pixels at each level, so those gutters bleed further into the islands the
281
+ * smaller the level gets - and viewing the whole model at once uses the smallest levels, painting
282
+ * bright gutter pixels over the edges of fine detail. Sampling the full-resolution image avoids
283
+ * that, at the cost of some shimmer on minified surfaces.
284
+ *
285
+ * Note that anisotropy only takes effect while mipmapping is on, so with the settings below it is
286
+ * inert; it is set anyway so the sampling stays correct for anyone who re-enables mipmaps here.
287
+ */
288
+ private tuneTextureSampling;
289
+ /**
290
+ * Loads an OBJ model with MTL.
291
+ * The heavy text-parsing step runs in a Web Worker when one has been registered via
292
+ * setObjParserWorker (see there for why the app, not this library, provides it); this keeps
293
+ * the main thread free while large architecture models (millions of vertices) are parsed.
294
+ * Falls back to parsing on the main thread otherwise.
295
+ */
296
+ loadObjModel(mtlUrl: string, objUrl: string, cameraPosition?: {
297
+ x: number;
298
+ y: number;
299
+ z: number;
300
+ }, rotation?: {
301
+ x: number;
302
+ y: number;
303
+ z: number;
304
+ }): void;
305
+ /** Centers, scales and adds a loaded object to the scene, then frames the camera on it. */
306
+ private placeLoadedObjectInScene;
307
+ /**
308
+ * Keeps the drawing buffer and camera matched to the canvas as it is resized.
309
+ *
310
+ * renderer.setSize() writes explicit pixel width/height into the canvas's style, overriding the
311
+ * 100%/100% it starts with. Without this, the canvas keeps whatever size it had at startup: hiding
312
+ * the top menu grows the container, the canvas does not follow, and the strip that opens up shows
313
+ * the page behind it instead of the scene.
314
+ *
315
+ * The parent is observed rather than the canvas, because resizing the canvas is what this reacts
316
+ * to - watching the element we resize would retrigger the observer.
317
+ */
318
+ private observeCanvasResize;
319
+ /**
320
+ * Starts the alignment walkthrough: pick a tag, then click where it really is, three times.
321
+ *
322
+ * Existing tags are the correspondences - they already carry the stored coordinate, so the person
323
+ * only has to supply the matching spot on the model. No new records are written; nothing is
324
+ * applied until solveAlignmentFromPairs() succeeds.
325
+ */
326
+ startTagAlignment(): void;
327
+ cancelTagAlignment(): void;
328
+ isAligningTags(): boolean;
329
+ /**
330
+ * Solves and applies the alignment collected so far, returning it so the caller can persist it.
331
+ *
332
+ * Returns null when the three tags turn out to be collinear, in which case nothing is applied and
333
+ * the walkthrough stays open for the person to pick a better third point.
334
+ */
335
+ solveAlignmentFromPairs(): ThreeJsAlignment | null;
336
+ /** Handles a click while the alignment walkthrough is running. */
337
+ private handleAlignmentClick;
338
+ /** Arms tag placement: the next click on the model drops a tag instead of being ignored. */
339
+ startTagPlacement(): void;
340
+ /** Leaves placement mode without placing anything. */
341
+ cancelTagPlacement(): void;
342
+ /**
343
+ * Shows a translucent tag under the cursor while placing, so the spot is visible before commiting.
344
+ *
345
+ * Matterport does this with a tag that follows the pointer; the equivalent here is a preview
346
+ * sprite that gets removed the moment the real tag takes its place.
347
+ */
348
+ private updatePlacementPreview;
349
+ private removePlacementPreview;
350
+ /** Shows or hides one tag, leaving it registered so it can be brought back without reloading. */
351
+ setTagVisibility(elementId: string, visible: boolean): void;
352
+ /**
353
+ * Shows only tags of the given types, hiding the rest.
354
+ *
355
+ * Passing null shows everything - the "no filter" state, distinct from an empty array, which is a
356
+ * filter that happens to match nothing.
357
+ */
358
+ setVisibleTagTypes(types: PoiType[] | null): void;
359
+ /**
360
+ * Moves the camera to look at a tag, mirroring Matterport's goToTag.
361
+ *
362
+ * Animated rather than teleported: jumping the camera loses the viewer's sense of where they were
363
+ * relative to the model. The approach distance follows the model's size so it frames sensibly
364
+ * whatever the units are.
365
+ */
366
+ goToTag(elementId: string): void;
367
+ /** Advances the camera animation started by goToTag, if one is running. */
368
+ private stepCameraTween;
369
+ isPlacingTag(): boolean;
370
+ /** The spot picked by the last placement, for whoever commits it to a POI. */
371
+ getPendingTagPlacement(): ThreeJsTagPlacement | null;
372
+ /**
373
+ * Turns a click into a point on the model, in model space.
374
+ *
375
+ * Returns null when the click missed the geometry, which is a normal outcome - the user clicked
376
+ * the sky - and leaves placement mode armed so they can simply try again.
377
+ */
378
+ private pickPlacementOnModel;
379
+ /**
380
+ * Sets the transform mapping captured tag coordinates onto this model's space.
381
+ *
382
+ * Every tag comes from the same source space, so this is one transform on the group rather than
383
+ * per-tag maths - which also means it applies retroactively to tags already placed, and can be
384
+ * adjusted live while someone dials the numbers in.
385
+ *
386
+ * Passing null resets to identity, i.e. tag coordinates are already in model space.
387
+ */
388
+ setTagAlignment(alignment: ThreeJsAlignment | null): void;
389
+ /**
390
+ * Adds a tag, or moves/restyles the one already registered for this element.
391
+ *
392
+ * Upsert rather than a declarative "here is the full list" call, because tags arrive per type
393
+ * (equipment, then tickets, then measurements...) from separate subscriptions - replacing the set
394
+ * on each of those would wipe the types that came before.
395
+ */
396
+ upsertTag(tag: ThreeJsTag): void;
397
+ /** Removes the tag registered for an element, if there is one. */
398
+ removeTag(elementId: string): void;
399
+ /** Removes every tag from the scene. */
400
+ clearTags(): void;
401
+ /** Loads an icon into an existing tag, leaving the fallback disc in place if it fails. */
402
+ private applyTagIcon;
403
+ /** Draws the default tag marker: a filled disc with a white ring, so it reads on any background. */
404
+ private createTagTexture;
405
+ /**
406
+ * Sizes a tag relative to the model.
407
+ *
408
+ * A sprite inherits the model's scale and the alignment's, so a fixed size would come out tiny on
409
+ * a large model and huge on a small one. Dividing by the group's world scale keeps tags the same
410
+ * size on screen whatever the model's units are.
411
+ */
412
+ private scaleTagForModel;
413
+ /**
414
+ * Adds secondary interactive objects (e.g., POIs, sensors) to the scene.
415
+ */
416
+ updateObjsToScene(objsAdded: ObjAdded[]): void;
417
+ private onMouseMove;
418
+ private onMouseClick;
419
+ private animate;
420
+ destroy(): void;
421
+ static ɵfac: i0.ɵɵFactoryDeclaration<ThreeJsService, never>;
422
+ static ɵprov: i0.ɵɵInjectableDeclaration<ThreeJsService>;
423
+ }
@@ -1,5 +1,6 @@
1
1
  import { Subject } from 'rxjs';
2
2
  import { MatterportService } from './matterport.service';
3
+ import { ThreeJsService } from './threejs.service';
3
4
  import { BaseTagService } from './tag.service';
4
5
  import { DbObjectType, POI, PoiType, Comment, Equipment, Ticket, Feature, SpModule } from '../types.service';
5
6
  import { PoiService } from './models/poi.service';
@@ -8,6 +9,7 @@ export declare class ViewerService {
8
9
  private readonly sdkKey;
9
10
  private poiService;
10
11
  private matterportService;
12
+ private threeJsService;
11
13
  private viewerMode;
12
14
  currentModel3D: string;
13
15
  private tourUrl;
@@ -50,7 +52,7 @@ export declare class ViewerService {
50
52
  };
51
53
  };
52
54
  private isObjectLoaded;
53
- constructor(sdkKey: string, poiService: PoiService, matterportService: MatterportService);
55
+ constructor(sdkKey: string, poiService: PoiService, matterportService: MatterportService, threeJsService: ThreeJsService);
54
56
  checkMode(indexMode: string): boolean;
55
57
  setMode(indexMode: string): void;
56
58
  setTourUrl(options: {
@@ -82,7 +84,55 @@ export declare class ViewerService {
82
84
  */
83
85
  addObjectTagsToViewer(objects: DbObjectType[]): Promise<void>;
84
86
  actionAddObjectFromPOI(object: DbObjectType, poi: POI): Promise<any>;
87
+ /**
88
+ * Draws a POI as a tag in the Three.js scene.
89
+ *
90
+ * Both viewers are fed by the same subscriptions on the same POI records, so a tag created in
91
+ * either one shows up in the other without any syncing of its own - this is just the Three.js end
92
+ * of the pipeline that already drives Matterport.
93
+ */
94
+ private addThreeJsTagFromPOI;
95
+ /** Moves the Three.js camera to frame a tag, mirroring Matterport's goToTag. */
96
+ goToThreeJsTag(elementId: string): void;
97
+ /** Shows only tags of the given POI types; null clears the filter. */
98
+ setVisibleThreeJsTagTypes(types: PoiType[] | null): void;
99
+ /**
100
+ * Alignment walkthrough: pick a tag, then click where it really sits, three times over.
101
+ *
102
+ * Uses tags that already exist, so nothing is created and nothing is written until the caller
103
+ * saves the result. See threeJsAlignmentProgress$ for what the UI should be asking for.
104
+ */
105
+ startThreeJsTagAlignment(): void;
106
+ cancelThreeJsTagAlignment(): void;
107
+ get threeJsAlignmentProgress$(): Subject<{
108
+ pairs: number;
109
+ awaiting: "tag" | "point";
110
+ tagId?: string;
111
+ }>;
112
+ /**
113
+ * Solves the alignment and applies it to the scene, returning it as the JSON to store in the
114
+ * capture's metadata. Null means the three tags were collinear and nothing was applied.
115
+ */
116
+ solveThreeJsTagAlignment(existingMetadata?: string): {
117
+ alignment: any;
118
+ metadata: string;
119
+ } | null;
120
+ /** Shows or hides a single Three.js tag. */
121
+ setThreeJsTagVisibility(elementId: string, visible: boolean): void;
85
122
  action_delete_element(element: DbObjectType): Promise<void>;
123
+ /** Emits when a Three.js model load actually stopped short, rather than merely being asked to. */
124
+ get threeJsModelLoadCancelled$(): Subject<void>;
125
+ /** Stops the Three.js model download in progress. */
126
+ cancelThreeJsModelLoad(): void;
127
+ /** Emits the element id of a tag clicked in the Three.js scene. */
128
+ get threeJsTagClicked$(): Subject<import("./threejs.service").ThreeJsTagClick>;
129
+ /**
130
+ * Applies the visit's stored tag alignment, mapping captured tag coordinates onto this model.
131
+ *
132
+ * Safe to call before the model or any tag exists: it is a transform on the group both end up
133
+ * under, so it applies to whatever arrives later.
134
+ */
135
+ setThreeJsTagAlignmentFromMetadata(metadata: string): void;
86
136
  /**
87
137
  * Enable tag placement menu to add a new element depending on its type (TICKET, DATA, EQUIPMENT etc)
88
138
  */
@@ -104,6 +154,17 @@ export declare class ViewerService {
104
154
  action_toolbox_mesure(): void;
105
155
  action_toolbox_cancel_mesure(): void;
106
156
  action_update_poi(poi: POI, isValid: boolean, element: DbObjectType): Promise<POI>;
157
+ /**
158
+ * Writes the spot picked in the Three.js scene into the POI, mirroring the Matterport branch.
159
+ *
160
+ * The coordinate goes in the same field and the same space Matterport writes, which is what makes
161
+ * a tag created here show up in the right place there - and the other way round.
162
+ */
163
+ private commitThreeJsTagPlacement;
164
+ /** Emits when a tag is dropped on the model in the Three.js scene. */
165
+ get threeJsTagPlaced$(): Subject<import("./threejs.service").ThreeJsTagPlacement>;
166
+ /** Leaves Three.js tag placement mode without placing anything. */
167
+ cancelThreeJsTagPlacement(): void;
107
168
  editOnlyTagStyleForPoi(poi: POI, object: DbObjectType): void;
108
169
  addIconAndSetForPoi(poi: POI, iconPath: string): void;
109
170
  changeToRegistredIconForPoi(poi: POI, iconName: string): void;
@@ -114,6 +175,58 @@ export declare class ViewerService {
114
175
  * @param element
115
176
  */
116
177
  action_move_poi_visually(poi: POI, element: Ticket | Feature | Equipment | Comment): Promise<void>;
178
+ initThreeJs(canvas: HTMLCanvasElement): void;
179
+ /**
180
+ * Loads a 3D model into the Three.js scene (GLTF).
181
+ * Only works when the viewer is in 'threejs' mode.
182
+ */
183
+ loadGLTFModel(url: string, cameraPosition?: {
184
+ x: number;
185
+ y: number;
186
+ z: number;
187
+ }, rotation?: {
188
+ x: number;
189
+ y: number;
190
+ z: number;
191
+ }): void;
192
+ /**
193
+ * Loads a GLB stored in S3, given its key (e.g. `visits/<id>/model.glb`).
194
+ *
195
+ * Resolves the key to a signed URL first, then loads it like any other. The signed URL only
196
+ * covers authentication - the bucket still needs a CORS rule allowing GET from the app's origin,
197
+ * or the browser blocks the download before it starts.
198
+ *
199
+ * `placementMetadata` is the capture's stored placement; when absent or unusable the viewer falls
200
+ * back to framing the model automatically.
201
+ */
202
+ loadGLTFModelFromS3(key: string, placementMetadata?: string): Promise<void>;
203
+ /**
204
+ * Loads a 3D model into the Three.js scene (OBJ).
205
+ */
206
+ loadObjModel(mtlUrl: string, objUrl: string, cameraPosition?: {
207
+ x: number;
208
+ y: number;
209
+ z: number;
210
+ }, rotation?: {
211
+ x: number;
212
+ y: number;
213
+ z: number;
214
+ }): void;
215
+ get threeJsObjectHovered$(): Subject<{
216
+ id: string;
217
+ event: MouseEvent;
218
+ }>;
219
+ get threeJsObjectClicked$(): Subject<string>;
220
+ /** Emits true while a heavy .obj file is being parsed (in a worker, when available), false once done. */
221
+ get threeJsModelLoading$(): Subject<boolean>;
222
+ /** Download progress for GLB models as a 0-1 fraction, or null when the size isn't known upfront. */
223
+ get threeJsModelLoadProgress$(): Subject<number>;
224
+ /**
225
+ * Registers the Web Worker used to parse heavy .obj files off the main thread.
226
+ * Must be created by the app (see obj-parser.worker.ts), since this library can't bundle worker files.
227
+ */
228
+ setObjParserWorker(worker: Worker): void;
229
+ addThreeJsObjects(objs: any[]): void;
117
230
  initMatterportSdk(spaceID: string, model3D: string, tagService: BaseTagService, module?: SpModule): Promise<void>;
118
231
  removeForbiddenSweeps(forbiddenSweeps: string[]): void;
119
232
  setObject3DLoadedInPosition(objectLoaded: boolean): void;
@@ -127,7 +127,8 @@ export declare enum EmailStatus {
127
127
  export declare enum CaptureViewer {
128
128
  CUPIX = "CUPIX",
129
129
  MATTERPORT = "MATTERPORT",
130
- SMARTERPLAN_V1 = "SMARTERPLAN_V1"
130
+ SMARTERPLAN_V1 = "SMARTERPLAN_V1",
131
+ THREEJS = "THREEJS"
131
132
  }
132
133
  export declare enum InventoryStatus {
133
134
  SUCCESS = "SUCCESS",
@@ -595,6 +596,11 @@ export type Visit = {
595
596
  node: Node;
596
597
  name?: string | null;
597
598
  nmbImagesImported: number;
599
+ /**
600
+ * Free-form metadata from the capture. Named after its source to keep it distinct from `name`,
601
+ * which comes from node.metadata. Three.js visits store their initial placement here.
602
+ */
603
+ captureMetadata?: string | null;
598
604
  };
599
605
  export type Hashtag = {
600
606
  name?: string | null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smarterplan/ngx-smarterplan-core",
3
- "version": "1.4.14",
3
+ "version": "1.4.16",
4
4
  "dependencies": {
5
5
  "tslib": "^2.5.0"
6
6
  },
package/public-api.d.ts CHANGED
@@ -6,6 +6,7 @@ export * from './lib/services/tag.service';
6
6
  export * from './lib/services/baseVisibility.service';
7
7
  export * from './lib/services/content.service';
8
8
  export * from './lib/services/viewer.service';
9
+ export * from './lib/services/threejs.service';
9
10
  export * from './lib/services/navigator.service';
10
11
  export * from './lib/services/zoneChange.service';
11
12
  export * from './lib/config';
@@ -18,6 +19,7 @@ export * from './lib/services/filter.service';
18
19
  export * from './lib/services/search.service';
19
20
  export * from './lib/services/zone-drawer.service';
20
21
  export * from './lib/services/matterport-import.service';
22
+ export * from './lib/services/threejs-import.service';
21
23
  export * from './lib/services/matterport-tag-copy.service';
22
24
  export * from './lib/mattertagData';
23
25
  export * from './lib/services/utils/matterport-transform.util';