@combeenation/3d-viewer 18.0.0 → 18.1.0-beta2
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/lib-cjs/buildinfo.json +1 -1
- package/dist/lib-cjs/commonjs.tsconfig.tsbuildinfo +1 -1
- package/dist/lib-cjs/internal/screenshot-helper.d.ts +14 -0
- package/dist/lib-cjs/internal/screenshot-helper.js +100 -0
- package/dist/lib-cjs/internal/screenshot-helper.js.map +1 -0
- package/dist/lib-cjs/manager/camera-manager.d.ts +9 -1
- package/dist/lib-cjs/manager/camera-manager.js +18 -18
- package/dist/lib-cjs/manager/camera-manager.js.map +1 -1
- package/dist/lib-cjs/manager/html-anchor-manager.d.ts +1 -1
- package/dist/lib-cjs/manager/html-anchor-manager.js +36 -10
- package/dist/lib-cjs/manager/html-anchor-manager.js.map +1 -1
- package/package.json +11 -11
- package/src/internal/screenshot-helper.ts +102 -0
- package/src/manager/camera-manager.ts +26 -25
- package/src/manager/html-anchor-manager.ts +44 -8
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
ViewerEvent,
|
|
13
13
|
} from '../index';
|
|
14
14
|
import { isNodeExcluded } from '../internal/geometry-helper';
|
|
15
|
+
import { createScreenshotCanvas, trimCanvas } from '../internal/screenshot-helper';
|
|
15
16
|
|
|
16
17
|
export type CameraPosition = {
|
|
17
18
|
alpha: number;
|
|
@@ -68,6 +69,11 @@ export type ScreenshotSettings = {
|
|
|
68
69
|
* Excludes ALL html anchor groups if set to `true`.
|
|
69
70
|
*/
|
|
70
71
|
excludeHtmlAnchorGroups?: string[] | true;
|
|
72
|
+
/**
|
|
73
|
+
* Removes all bounding transparent pixels, so that final image size exactly fits the scene content.\
|
|
74
|
+
* As a result the final image size might be smaller than the values defined on the `width` and `height` input.
|
|
75
|
+
*/
|
|
76
|
+
cropTransparentArea?: boolean;
|
|
71
77
|
/**
|
|
72
78
|
* "MIME type" of the returned screenshot image, defaults to `image/png`
|
|
73
79
|
*
|
|
@@ -79,7 +85,10 @@ export type ScreenshotSettings = {
|
|
|
79
85
|
mimeType?: string;
|
|
80
86
|
/** If file name is given, the screenshot image will be downloaded and the base64 string will NOT be returned! */
|
|
81
87
|
fileName?: string;
|
|
82
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Expert settings to tweak the screenshot image, see [Babylon.js](https://doc.babylonjs.com/typedoc/functions/BABYLON.CreateScreenshotUsingRenderTarget) docs for further information.\
|
|
90
|
+
* Defaults to `8` for improved screenshot quality.
|
|
91
|
+
*/
|
|
83
92
|
samples?: number;
|
|
84
93
|
/** Expert settings to tweak the screenshot image, see [Babylon.js](https://doc.babylonjs.com/typedoc/functions/BABYLON.CreateScreenshotUsingRenderTarget) docs for further information */
|
|
85
94
|
antialiasing?: boolean;
|
|
@@ -299,6 +308,7 @@ export class CameraManager {
|
|
|
299
308
|
autofocusScene,
|
|
300
309
|
exclude,
|
|
301
310
|
excludeHtmlAnchorGroups,
|
|
311
|
+
cropTransparentArea,
|
|
302
312
|
fileName,
|
|
303
313
|
samples,
|
|
304
314
|
antialiasing,
|
|
@@ -344,7 +354,8 @@ export class CameraManager {
|
|
|
344
354
|
screenshotCam,
|
|
345
355
|
{ width: screenshotSize.imageWidth, height: screenshotSize.imageHeight },
|
|
346
356
|
mimeType,
|
|
347
|
-
|
|
357
|
+
// default to "8", since Babylon.js default "1" is really ugly
|
|
358
|
+
samples ?? 8,
|
|
348
359
|
antialiasing,
|
|
349
360
|
undefined,
|
|
350
361
|
renderSprites,
|
|
@@ -365,32 +376,22 @@ export class CameraManager {
|
|
|
365
376
|
excludeHtmlAnchorGroups === true
|
|
366
377
|
? []
|
|
367
378
|
: this.viewer.htmlAnchorManager.getHtmlAnchorKeys(undefined, excludeHtmlAnchorGroups, true);
|
|
368
|
-
if (htmlAnchorKeys.length) {
|
|
379
|
+
if (htmlAnchorKeys.length || cropTransparentArea) {
|
|
369
380
|
// html anchors are not included in the main screenshot, as the html elements are located outside of the canvas
|
|
370
381
|
// the idea is to create a dedicated canvas for these elements and merge the result with the main screenshot into
|
|
371
382
|
// a combined canvas
|
|
372
|
-
const screenshotHtmlCanvas =
|
|
373
|
-
screenshotSize,
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
);
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
const screenshotCombinedCanvas = document.createElement('canvas');
|
|
386
|
-
screenshotCombinedCanvas.width = screenshotSize.imageWidth;
|
|
387
|
-
screenshotCombinedCanvas.height = screenshotSize.imageHeight;
|
|
388
|
-
|
|
389
|
-
// draw main and html screenshot on a new canvas and get the base64 string from it
|
|
390
|
-
const context = screenshotCombinedCanvas.getContext('2d')!;
|
|
391
|
-
context.drawImage(screenshot3dImg, 0, 0, screenshotSize.imageWidth, screenshotSize.imageHeight);
|
|
392
|
-
context.drawImage(screenshotHtmlCanvas, 0, 0, screenshotSize.imageWidth, screenshotSize.imageHeight);
|
|
393
|
-
imageStr = screenshotCombinedCanvas.toDataURL(mimeType);
|
|
383
|
+
const screenshotHtmlCanvas = htmlAnchorKeys.length
|
|
384
|
+
? await this.viewer.htmlAnchorManager.createScreenshotCanvas(screenshotSize, screenshotCam, htmlAnchorKeys)
|
|
385
|
+
: undefined;
|
|
386
|
+
|
|
387
|
+
const screenshotCombinedCanvas = await createScreenshotCanvas(imageStr3d, screenshotSize, screenshotHtmlCanvas);
|
|
388
|
+
if (cropTransparentArea) {
|
|
389
|
+
const screenshotTrimmedCanvas = trimCanvas(screenshotCombinedCanvas);
|
|
390
|
+
imageStr = screenshotTrimmedCanvas.toDataURL(mimeType);
|
|
391
|
+
screenshotTrimmedCanvas.remove();
|
|
392
|
+
} else {
|
|
393
|
+
imageStr = screenshotCombinedCanvas.toDataURL(mimeType);
|
|
394
|
+
}
|
|
394
395
|
|
|
395
396
|
screenshotCombinedCanvas.remove();
|
|
396
397
|
} else {
|
|
@@ -4,8 +4,10 @@ import {
|
|
|
4
4
|
Camera,
|
|
5
5
|
DimensionLineManager,
|
|
6
6
|
MeshBuilder,
|
|
7
|
+
Ray,
|
|
7
8
|
ScreenshotSize,
|
|
8
9
|
StandardMaterial,
|
|
10
|
+
Tags,
|
|
9
11
|
TransformNode,
|
|
10
12
|
Vector3,
|
|
11
13
|
Viewer,
|
|
@@ -82,9 +84,13 @@ export class HtmlAnchorManager {
|
|
|
82
84
|
parentHtmlElement,
|
|
83
85
|
anchorMesh,
|
|
84
86
|
viewer.scene.activeCamera!,
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
// it's import to use `clientWidth` and `clientHeight` as it makes a difference on mobile devices that have
|
|
88
|
+
// hardware scaling activated
|
|
89
|
+
// => `width`/`height` return the upscaled values, but the basis html element for the anchors remains unscaled
|
|
90
|
+
canvas.clientWidth,
|
|
91
|
+
canvas.clientHeight,
|
|
87
92
|
1,
|
|
93
|
+
false,
|
|
88
94
|
options
|
|
89
95
|
);
|
|
90
96
|
});
|
|
@@ -97,9 +103,7 @@ export class HtmlAnchorManager {
|
|
|
97
103
|
* mesh within the scene.
|
|
98
104
|
*/
|
|
99
105
|
public addHtmlAnchor(name: string, htmlElement: HTMLElement, position: Vector3, options?: HtmlAnchorOptions): void {
|
|
100
|
-
|
|
101
|
-
if (!canvasParentHtmlElement) {
|
|
102
|
-
console.warn(`No parent for desired html anchor available`);
|
|
106
|
+
if (!this.viewer.canvas) {
|
|
103
107
|
return;
|
|
104
108
|
}
|
|
105
109
|
|
|
@@ -120,7 +124,10 @@ export class HtmlAnchorManager {
|
|
|
120
124
|
if (!enablePointerEvents) {
|
|
121
125
|
parentHtmlElement.style.pointerEvents = 'none';
|
|
122
126
|
}
|
|
123
|
-
|
|
127
|
+
// it's important to insert the html anchor elements right after the viewer canvas
|
|
128
|
+
// otherwise the elements could be in front of other sibling nodes, like the viewer control loading mask
|
|
129
|
+
// => see CB-10496
|
|
130
|
+
this.viewer.canvas.insertAdjacentElement('afterend', parentHtmlElement);
|
|
124
131
|
|
|
125
132
|
// NOTE: creates a sphere with fixed size, which could be problematic in scene with "strange" dimensions
|
|
126
133
|
// add a property for the sphere size if required
|
|
@@ -206,7 +213,10 @@ export class HtmlAnchorManager {
|
|
|
206
213
|
htmlContainer.append(clonedHtmlNode);
|
|
207
214
|
|
|
208
215
|
// reposition that cloned html element, so that it fits to the desired camera position
|
|
209
|
-
|
|
216
|
+
// `clientHeight`: makes difference on mobile devices that have hardware scaling activated
|
|
217
|
+
// => `height` returns the upscaled value, but the basis html element for the anchors remains
|
|
218
|
+
// unscaled
|
|
219
|
+
const baseScale = size.canvasHeight / this.viewer.canvas!.clientHeight;
|
|
210
220
|
this._updateHtmlAnchor(
|
|
211
221
|
clonedHtmlNode,
|
|
212
222
|
anchorMesh,
|
|
@@ -214,6 +224,7 @@ export class HtmlAnchorManager {
|
|
|
214
224
|
size.canvasWidth,
|
|
215
225
|
size.canvasHeight,
|
|
216
226
|
baseScale,
|
|
227
|
+
true,
|
|
217
228
|
options
|
|
218
229
|
);
|
|
219
230
|
});
|
|
@@ -271,6 +282,7 @@ export class HtmlAnchorManager {
|
|
|
271
282
|
width: number,
|
|
272
283
|
height: number,
|
|
273
284
|
baseScale: number,
|
|
285
|
+
useRayHitTestForOcclusionCheck: boolean,
|
|
274
286
|
options?: HtmlAnchorOptions
|
|
275
287
|
): void {
|
|
276
288
|
const { hideIfOccluded, scaleWithCameraDistance } = options ?? {};
|
|
@@ -315,7 +327,31 @@ export class HtmlAnchorManager {
|
|
|
315
327
|
vertexScreenCoords.x + elementXOffset < width &&
|
|
316
328
|
vertexScreenCoords.y - elementYOffset > 0 &&
|
|
317
329
|
vertexScreenCoords.y + elementYOffset < height;
|
|
318
|
-
|
|
330
|
+
|
|
331
|
+
let isOccluded = false;
|
|
332
|
+
if (hideIfOccluded) {
|
|
333
|
+
if (useRayHitTestForOcclusionCheck) {
|
|
334
|
+
// ray test has to be used for synchronous occlusion checks
|
|
335
|
+
// this is the case for screenshots, where the screenshot camera might be on a different position than the scene
|
|
336
|
+
// camera
|
|
337
|
+
// default occlusion query is too slow and there is no way to know when the occlusion check is finished, so we
|
|
338
|
+
// fall back to this alternative technology
|
|
339
|
+
// however, this tech has higher performance impact, so we don't use it for the cyclic occlusion check
|
|
340
|
+
const meshCenter = anchorMesh.getBoundingInfo().boundingBox.centerWorld;
|
|
341
|
+
const camDirection = meshCenter.subtract(camera.position);
|
|
342
|
+
const ray = new Ray(camera.position, camDirection);
|
|
343
|
+
const hit = this.viewer.scene.pickWithRay(
|
|
344
|
+
ray,
|
|
345
|
+
mesh => !Tags.MatchesQuery(mesh, DimensionLineManager.DIMENSION_LINE_KEY),
|
|
346
|
+
false
|
|
347
|
+
);
|
|
348
|
+
isOccluded = hit?.pickedMesh !== anchorMesh;
|
|
349
|
+
} else {
|
|
350
|
+
// use default occlusion check from GPU
|
|
351
|
+
isOccluded = anchorMesh.isOccluded;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
319
355
|
parentHtmlElement.style.opacity = isInViewport && !isOccluded ? '1' : '0';
|
|
320
356
|
}
|
|
321
357
|
|