@combeenation/3d-viewer 18.0.0-beta1 → 18.0.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/index.d.ts +6 -0
- package/dist/lib-cjs/index.js +6 -0
- package/dist/lib-cjs/index.js.map +1 -1
- package/dist/lib-cjs/internal/cloning-helper.js +1 -1
- package/dist/lib-cjs/internal/cloning-helper.js.map +1 -1
- package/dist/lib-cjs/manager/camera-manager.d.ts +23 -2
- package/dist/lib-cjs/manager/camera-manager.js +90 -25
- package/dist/lib-cjs/manager/camera-manager.js.map +1 -1
- package/dist/lib-cjs/manager/debug-manager.d.ts +1 -1
- package/dist/lib-cjs/manager/debug-manager.js +2 -2
- package/dist/lib-cjs/manager/debug-manager.js.map +1 -1
- package/dist/lib-cjs/manager/dimension-line-manager.d.ts +126 -0
- package/dist/lib-cjs/manager/dimension-line-manager.js +138 -0
- package/dist/lib-cjs/manager/dimension-line-manager.js.map +1 -0
- package/dist/lib-cjs/manager/html-anchor-manager.d.ts +93 -0
- package/dist/lib-cjs/manager/html-anchor-manager.js +228 -0
- package/dist/lib-cjs/manager/html-anchor-manager.js.map +1 -0
- package/dist/lib-cjs/manager/model-manager.d.ts +0 -1
- package/dist/lib-cjs/manager/model-manager.js +0 -1
- package/dist/lib-cjs/manager/model-manager.js.map +1 -1
- package/dist/lib-cjs/manager/parameter-manager.d.ts +1 -1
- package/dist/lib-cjs/manager/scene-manager.js +9 -2
- package/dist/lib-cjs/manager/scene-manager.js.map +1 -1
- package/dist/lib-cjs/viewer.d.ts +6 -2
- package/dist/lib-cjs/viewer.js +13 -1
- package/dist/lib-cjs/viewer.js.map +1 -1
- package/package.json +2 -1
- package/src/index.ts +6 -0
- package/src/internal/cloning-helper.ts +1 -1
- package/src/manager/camera-manager.ts +152 -40
- package/src/manager/debug-manager.ts +2 -2
- package/src/manager/dimension-line-manager.ts +255 -0
- package/src/manager/html-anchor-manager.ts +332 -0
- package/src/manager/model-manager.ts +0 -1
- package/src/manager/parameter-manager.ts +1 -1
- package/src/manager/scene-manager.ts +11 -2
- package/src/viewer.ts +17 -1
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import { Color3, LinesMesh, MeshBuilder, Tags, TransformNode, Vector3, Viewer } from '../index';
|
|
2
|
+
import { merge } from 'lodash-es';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* General options that are used for each dimension line
|
|
6
|
+
*/
|
|
7
|
+
export type DefaultDimensionLineOptions = {
|
|
8
|
+
/**
|
|
9
|
+
* Default: `Color3.Black()`
|
|
10
|
+
*/
|
|
11
|
+
lineColor: Color3;
|
|
12
|
+
/**
|
|
13
|
+
* Default: 50mm
|
|
14
|
+
*/
|
|
15
|
+
closingLineHeight: number;
|
|
16
|
+
/**
|
|
17
|
+
* Height of dimension label
|
|
18
|
+
*
|
|
19
|
+
* Default: 24px
|
|
20
|
+
*/
|
|
21
|
+
labelHeight: number;
|
|
22
|
+
/**
|
|
23
|
+
* Corner radius of dimension label
|
|
24
|
+
*
|
|
25
|
+
* Default: 1/2 of `labelHeight`, to create a half circle
|
|
26
|
+
*/
|
|
27
|
+
borderRadius?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Text padding in x direction
|
|
30
|
+
*
|
|
31
|
+
* Default: 1/2 of `labelHeight`
|
|
32
|
+
*/
|
|
33
|
+
textPadding?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Default: Arial
|
|
36
|
+
*/
|
|
37
|
+
fontFamily: string;
|
|
38
|
+
/**
|
|
39
|
+
* Text color of dimension label
|
|
40
|
+
*
|
|
41
|
+
* Default: black
|
|
42
|
+
*/
|
|
43
|
+
textColor: string;
|
|
44
|
+
/**
|
|
45
|
+
* Color of border
|
|
46
|
+
*
|
|
47
|
+
* Default: black
|
|
48
|
+
*/
|
|
49
|
+
borderColor: string;
|
|
50
|
+
/**
|
|
51
|
+
* Background color of dimension label
|
|
52
|
+
*
|
|
53
|
+
* Default: white
|
|
54
|
+
*/
|
|
55
|
+
backgroundColor: string;
|
|
56
|
+
/**
|
|
57
|
+
* `true`: html elements can be occluded by other meshes
|
|
58
|
+
* `false`: html elements will always be shown in front of the scene
|
|
59
|
+
*
|
|
60
|
+
* Default: `false`
|
|
61
|
+
*/
|
|
62
|
+
hideIfOccluded?: boolean;
|
|
63
|
+
/**
|
|
64
|
+
* `true`: html element size is relative to camera distance, just like a "normal" 3d object
|
|
65
|
+
* `false`: html element size remains constant
|
|
66
|
+
*
|
|
67
|
+
* Default: `false`
|
|
68
|
+
*/
|
|
69
|
+
scaleWithCameraDistance?: boolean;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Specific options for each dimension line
|
|
74
|
+
*/
|
|
75
|
+
export type DimensionLineOptions = Partial<DefaultDimensionLineOptions> & {
|
|
76
|
+
/**
|
|
77
|
+
* Optional parent node of dimension line group, can be used for easier calculation of start and end point
|
|
78
|
+
*
|
|
79
|
+
* Default: no parent node set
|
|
80
|
+
*/
|
|
81
|
+
parentNode?: TransformNode;
|
|
82
|
+
/**
|
|
83
|
+
* Optional direction of the closing lines on start and end.\
|
|
84
|
+
* Use built-in vector functions like `Vector3.Left()` or `Vector3.Forward()` for simple directions of use a custom
|
|
85
|
+
* normal vector like `Vector3(1, 0.5 ,0)`.
|
|
86
|
+
*
|
|
87
|
+
* Default: x or y direction is taken, depending on the normal vector between start and end point
|
|
88
|
+
*/
|
|
89
|
+
closingLineDirection?: Vector3;
|
|
90
|
+
/**
|
|
91
|
+
* Optional label text
|
|
92
|
+
*
|
|
93
|
+
* Default: dimension line size is calculated from `startPoint` and `endPoint` and suffixed with "mm" (e.g. "1200 mm")
|
|
94
|
+
*/
|
|
95
|
+
labelText?: string;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Manager for dimension lines, which can be used to display certain dimensions like object sizes in the scene
|
|
100
|
+
*/
|
|
101
|
+
export class DimensionLineManager {
|
|
102
|
+
public static readonly DIMENSION_LINE_KEY = '$dimLine';
|
|
103
|
+
|
|
104
|
+
protected _defaultLineOptions: DefaultDimensionLineOptions = {
|
|
105
|
+
lineColor: Color3.Black(),
|
|
106
|
+
closingLineHeight: 50,
|
|
107
|
+
labelHeight: 24,
|
|
108
|
+
fontFamily: 'Arial',
|
|
109
|
+
textColor: 'black',
|
|
110
|
+
borderColor: 'black',
|
|
111
|
+
backgroundColor: 'white',
|
|
112
|
+
hideIfOccluded: false,
|
|
113
|
+
scaleWithCameraDistance: false,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// map with all dimension lines that are currently visible in the scene
|
|
117
|
+
// holds data of all disposable objects for a clean up
|
|
118
|
+
protected _dimensionLineObjs: {
|
|
119
|
+
[name: string]: {
|
|
120
|
+
linesMesh: LinesMesh;
|
|
121
|
+
htmlLabelName: string;
|
|
122
|
+
};
|
|
123
|
+
} = {};
|
|
124
|
+
|
|
125
|
+
/** @internal */
|
|
126
|
+
public constructor(protected viewer: Viewer) {}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Define dimension line options that are used for each line by default.\
|
|
130
|
+
* Options can be overwritten by each line individually.
|
|
131
|
+
*/
|
|
132
|
+
public setDefaultDimensionLineOptions(defaultLineOptions: Partial<DefaultDimensionLineOptions>): void {
|
|
133
|
+
merge(this._defaultLineOptions, defaultLineOptions);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Create dimension line between two points
|
|
138
|
+
*/
|
|
139
|
+
public addDimensionLine(name: string, startPoint: Vector3, endPoint: Vector3, options?: DimensionLineOptions): void {
|
|
140
|
+
if (this._dimensionLineObjs[name]) {
|
|
141
|
+
console.warn(`Dimension line "${name}" already exists`);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const {
|
|
146
|
+
parentNode,
|
|
147
|
+
closingLineDirection: closingLineDirectionIn,
|
|
148
|
+
labelText: labelTextIn,
|
|
149
|
+
...inputDefaultOptions
|
|
150
|
+
} = options ?? {};
|
|
151
|
+
|
|
152
|
+
const resDefaultOptions = { ...this._defaultLineOptions };
|
|
153
|
+
merge(resDefaultOptions, inputDefaultOptions);
|
|
154
|
+
const {
|
|
155
|
+
lineColor,
|
|
156
|
+
closingLineHeight,
|
|
157
|
+
labelHeight,
|
|
158
|
+
textPadding,
|
|
159
|
+
borderRadius,
|
|
160
|
+
fontFamily,
|
|
161
|
+
textColor,
|
|
162
|
+
borderColor,
|
|
163
|
+
backgroundColor,
|
|
164
|
+
hideIfOccluded,
|
|
165
|
+
scaleWithCameraDistance,
|
|
166
|
+
} = resDefaultOptions;
|
|
167
|
+
|
|
168
|
+
let closingLineDirection = closingLineDirectionIn;
|
|
169
|
+
if (!closingLineDirection) {
|
|
170
|
+
// evaluate closing line direction if not given
|
|
171
|
+
const normal = endPoint.subtract(startPoint);
|
|
172
|
+
const yIsMainDirection = Math.abs(normal.y) > Math.abs(normal.x) && Math.abs(normal.y) > Math.abs(normal.z);
|
|
173
|
+
closingLineDirection = yIsMainDirection ? Vector3.Right() : Vector3.Up();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// calculate and create main and closing lines
|
|
177
|
+
const lineCenter = Vector3.Center(startPoint, endPoint);
|
|
178
|
+
const relStartPt = startPoint.subtract(lineCenter);
|
|
179
|
+
const relEndPt = endPoint.subtract(lineCenter);
|
|
180
|
+
const closingLineOffsetVector = closingLineDirection.normalizeToNew().scale(closingLineHeight / 2 / 1000);
|
|
181
|
+
const closingLineStartPts = [relStartPt.add(closingLineOffsetVector), relStartPt.subtract(closingLineOffsetVector)];
|
|
182
|
+
const closingLineEndPts = [relEndPt.add(closingLineOffsetVector), relEndPt.subtract(closingLineOffsetVector)];
|
|
183
|
+
|
|
184
|
+
const linesMesh = MeshBuilder.CreateLineSystem(`${DimensionLineManager.DIMENSION_LINE_KEY}_${name}`, {
|
|
185
|
+
lines: [[relStartPt, relEndPt], closingLineStartPts, closingLineEndPts],
|
|
186
|
+
});
|
|
187
|
+
linesMesh.position = lineCenter;
|
|
188
|
+
linesMesh.color = lineColor;
|
|
189
|
+
// rendering group id 1 reserved for html anchor meshes, which are required for occlusion checking
|
|
190
|
+
linesMesh.renderingGroupId = hideIfOccluded ? 0 : 2;
|
|
191
|
+
// tag can be used to exclude dimension lines from autofocus or gltf export
|
|
192
|
+
Tags.AddTagsTo(linesMesh, DimensionLineManager.DIMENSION_LINE_KEY);
|
|
193
|
+
if (parentNode) {
|
|
194
|
+
linesMesh.parent = parentNode;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
let labelText = labelTextIn;
|
|
198
|
+
if (labelText === undefined) {
|
|
199
|
+
// create default text if not explicitely overwritten by the function
|
|
200
|
+
const length = Vector3.Distance(startPoint, endPoint);
|
|
201
|
+
const lengthRounded = Math.round(length * 1000 * 100) / 100;
|
|
202
|
+
labelText = `${lengthRounded} mm`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// create default html element
|
|
206
|
+
const span = document.createElement('span');
|
|
207
|
+
span.textContent = labelText;
|
|
208
|
+
span.style.display = 'block';
|
|
209
|
+
span.style.height = `${labelHeight}px`;
|
|
210
|
+
span.style.lineHeight = `${labelHeight}px`;
|
|
211
|
+
span.style.fontFamily = fontFamily;
|
|
212
|
+
span.style.fontSize = `${(labelHeight * 2) / 3}px`;
|
|
213
|
+
span.style.border = `1px solid ${borderColor}`;
|
|
214
|
+
span.style.paddingLeft = `${textPadding ?? labelHeight / 2}px`;
|
|
215
|
+
span.style.paddingRight = `${textPadding ?? labelHeight / 2}px`;
|
|
216
|
+
span.style.borderRadius = `${borderRadius ?? labelHeight / 2}px`;
|
|
217
|
+
span.style.color = textColor;
|
|
218
|
+
span.style.background = backgroundColor;
|
|
219
|
+
|
|
220
|
+
const htmlLabelName = `${DimensionLineManager.DIMENSION_LINE_KEY}_${name}`;
|
|
221
|
+
this.viewer.htmlAnchorManager.addHtmlAnchor(name, span, Vector3.Zero(), {
|
|
222
|
+
parentNode: linesMesh,
|
|
223
|
+
group: DimensionLineManager.DIMENSION_LINE_KEY,
|
|
224
|
+
hideIfOccluded,
|
|
225
|
+
scaleWithCameraDistance,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
this._dimensionLineObjs[name] = { linesMesh, htmlLabelName };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Remove dimension line and disposes all associated ressources
|
|
233
|
+
*/
|
|
234
|
+
public removeDimensionLine(name: string): void {
|
|
235
|
+
const dimensionLine = this._dimensionLineObjs[name];
|
|
236
|
+
if (!dimensionLine) {
|
|
237
|
+
console.warn(`Dimension line "${name}" does not exist`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
this.viewer.htmlAnchorManager.removeHtmlAnchor(name);
|
|
242
|
+
dimensionLine.linesMesh.dispose(false, true);
|
|
243
|
+
|
|
244
|
+
delete this._dimensionLineObjs[name];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Remove all dimension lines that have been created with the `DimensionLineManager`
|
|
249
|
+
*/
|
|
250
|
+
public removeAllDimensionLines(): void {
|
|
251
|
+
Object.keys(this._dimensionLineObjs).forEach(dimLineName => {
|
|
252
|
+
this.removeDimensionLine(dimLineName);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AbstractMesh,
|
|
3
|
+
ArcRotateCamera,
|
|
4
|
+
Camera,
|
|
5
|
+
DimensionLineManager,
|
|
6
|
+
MeshBuilder,
|
|
7
|
+
ScreenshotSize,
|
|
8
|
+
StandardMaterial,
|
|
9
|
+
TransformNode,
|
|
10
|
+
Vector3,
|
|
11
|
+
Viewer,
|
|
12
|
+
Viewport,
|
|
13
|
+
} from '..';
|
|
14
|
+
import html2canvas from 'html2canvas';
|
|
15
|
+
|
|
16
|
+
export type HtmlAnchorOptions = {
|
|
17
|
+
/**
|
|
18
|
+
* Associated anchor mesh will be created underneath this parent node.\
|
|
19
|
+
* Can be used to make anchor position calculation easier in nested scene structures.
|
|
20
|
+
*/
|
|
21
|
+
parentNode?: TransformNode;
|
|
22
|
+
/**
|
|
23
|
+
* Can be used to filter affected html anchors in {@link HtmlAnchorManager.removeAllHtmlAnchors}
|
|
24
|
+
*/
|
|
25
|
+
group?: string;
|
|
26
|
+
/**
|
|
27
|
+
* `true`: html elements can be occluded by other meshes
|
|
28
|
+
* `false`: html elements will always be shown in front of the scene
|
|
29
|
+
*
|
|
30
|
+
* Default: `false`
|
|
31
|
+
*/
|
|
32
|
+
hideIfOccluded?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* `true`: html element size is relative to camera distance, just like a "normal" 3d object
|
|
35
|
+
* `false`: html element size remains constant
|
|
36
|
+
*
|
|
37
|
+
* Default: `false`
|
|
38
|
+
*/
|
|
39
|
+
scaleWithCameraDistance?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* Activates/deactivates `pointer-events` CSS class of anchor element.\
|
|
42
|
+
* Set this to `true` if the html element should be interactive (e.g. button)
|
|
43
|
+
*
|
|
44
|
+
* Default: `false`
|
|
45
|
+
*/
|
|
46
|
+
enablePointerEvents?: boolean;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Manager for mapping html elements to 3d positions in the scene.\
|
|
51
|
+
* Common use cases are:
|
|
52
|
+
* - **Hotspot buttons**: Interactively add/remove elements in the scene or just show some info on hovering
|
|
53
|
+
* - **Input fields**: Interactively change the size of an element
|
|
54
|
+
* - **Info label**: Just shows some information on a certain position within the scene
|
|
55
|
+
*
|
|
56
|
+
* Html anchors are also used as dimension lables in the {@link DimensionLineManager}
|
|
57
|
+
*/
|
|
58
|
+
export class HtmlAnchorManager {
|
|
59
|
+
protected static readonly _HTML_ANCHOR_KEY = '$htmlAnchor';
|
|
60
|
+
|
|
61
|
+
protected _htmlAnchors: {
|
|
62
|
+
[name: string]: {
|
|
63
|
+
parentHtmlElement: HTMLDivElement;
|
|
64
|
+
anchorMesh: AbstractMesh;
|
|
65
|
+
options?: HtmlAnchorOptions;
|
|
66
|
+
};
|
|
67
|
+
} = {};
|
|
68
|
+
|
|
69
|
+
protected _anchorMeshMaterial: StandardMaterial | null = null;
|
|
70
|
+
|
|
71
|
+
/** @internal */
|
|
72
|
+
public constructor(protected viewer: Viewer) {
|
|
73
|
+
const canvas = viewer.canvas;
|
|
74
|
+
if (!canvas) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// html element positions need to be updated after each camera render call
|
|
79
|
+
viewer.scene.onAfterRenderCameraObservable.add(() => {
|
|
80
|
+
Object.values(this._htmlAnchors).forEach(({ parentHtmlElement, anchorMesh, options }) => {
|
|
81
|
+
this._updateHtmlAnchor(
|
|
82
|
+
parentHtmlElement,
|
|
83
|
+
anchorMesh,
|
|
84
|
+
viewer.scene.activeCamera!,
|
|
85
|
+
canvas.width,
|
|
86
|
+
canvas.height,
|
|
87
|
+
1,
|
|
88
|
+
options
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Assign a html element to a certain position in the scene.\
|
|
96
|
+
* The html elements 2d position will be updated on each camera render, so that the element appears like a "normal"
|
|
97
|
+
* mesh within the scene.
|
|
98
|
+
*/
|
|
99
|
+
public addHtmlAnchor(name: string, htmlElement: HTMLElement, position: Vector3, options?: HtmlAnchorOptions): void {
|
|
100
|
+
const canvasParentHtmlElement = this.viewer.canvas?.parentElement;
|
|
101
|
+
if (!canvasParentHtmlElement) {
|
|
102
|
+
console.warn(`No parent for desired html anchor available`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (this._htmlAnchors[name]) {
|
|
107
|
+
console.warn(`Html anchor "${name}" already exists`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const { parentNode, enablePointerEvents } = options ?? {};
|
|
112
|
+
|
|
113
|
+
// create a parent for the input html element, which will receive the updated style and transform data
|
|
114
|
+
// in this way the original input element remains untouched
|
|
115
|
+
const parentHtmlElement = document.createElement('div');
|
|
116
|
+
parentHtmlElement.dataset.anchorName = name;
|
|
117
|
+
parentHtmlElement.style.position = 'absolute';
|
|
118
|
+
parentHtmlElement.style.margin = '0';
|
|
119
|
+
parentHtmlElement.appendChild(htmlElement);
|
|
120
|
+
if (!enablePointerEvents) {
|
|
121
|
+
parentHtmlElement.style.pointerEvents = 'none';
|
|
122
|
+
}
|
|
123
|
+
canvasParentHtmlElement.appendChild(parentHtmlElement);
|
|
124
|
+
|
|
125
|
+
// NOTE: creates a sphere with fixed size, which could be problematic in scene with "strange" dimensions
|
|
126
|
+
// add a property for the sphere size if required
|
|
127
|
+
const anchorMesh = MeshBuilder.CreateSphere(`${HtmlAnchorManager._HTML_ANCHOR_KEY}_${name}`, { diameter: 0.01 });
|
|
128
|
+
anchorMesh.position = position;
|
|
129
|
+
anchorMesh.parent = parentNode ?? null;
|
|
130
|
+
// anchor mesh will be invisible, we only need it for positioning and occlusion check
|
|
131
|
+
anchorMesh.material = this._getOrCreateAnchorMeshMaterial();
|
|
132
|
+
anchorMesh.occlusionType = AbstractMesh.OCCLUSION_TYPE_OPTIMISTIC;
|
|
133
|
+
anchorMesh.occlusionQueryAlgorithmType = AbstractMesh.OCCLUSION_ALGORITHM_TYPE_CONSERVATIVE;
|
|
134
|
+
// it's important that the occlusion check mesh is rendered after all "normal" meshes, which should be taken into
|
|
135
|
+
// account for the occlusion check
|
|
136
|
+
anchorMesh.renderingGroupId = 1;
|
|
137
|
+
|
|
138
|
+
this._htmlAnchors[name] = { parentHtmlElement, anchorMesh, options };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Remove html anchor element and disposes all associated ressources
|
|
143
|
+
*/
|
|
144
|
+
public removeHtmlAnchor(name: string): void {
|
|
145
|
+
const htmlAnchor = this._htmlAnchors[name];
|
|
146
|
+
if (!htmlAnchor) {
|
|
147
|
+
console.warn(`Html anchor "${name}" does not exist`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
htmlAnchor.parentHtmlElement.remove();
|
|
152
|
+
htmlAnchor.anchorMesh.dispose();
|
|
153
|
+
|
|
154
|
+
delete this._htmlAnchors[name];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Removes html anchor elements, as defined by the `groups` input
|
|
159
|
+
*
|
|
160
|
+
* @param groups if set, only html anchors which are part of these groups will be removed\
|
|
161
|
+
* if left `undefined`, ALL html anchors will be removed
|
|
162
|
+
*/
|
|
163
|
+
public removeAllHtmlAnchors(groups?: string[]): void {
|
|
164
|
+
const anchorKeysToRemove = this.getHtmlAnchorKeys(
|
|
165
|
+
groups,
|
|
166
|
+
undefined,
|
|
167
|
+
// system groups should not be remove, as there is a dedicated workflow
|
|
168
|
+
// (e.g. `DimensionLineManager.removeDimensionLine`) to do this
|
|
169
|
+
false
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
anchorKeysToRemove.forEach(name => this.removeHtmlAnchor(name));
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Creates a screenshot of defined html anchors and returns the associated canvas.\
|
|
177
|
+
* This function is used @internal from the main screenshot function.
|
|
178
|
+
*/
|
|
179
|
+
public async createScreenshotCanvas(
|
|
180
|
+
size: ScreenshotSize,
|
|
181
|
+
camera: ArcRotateCamera,
|
|
182
|
+
htmlAnchorKeys: string[]
|
|
183
|
+
): Promise<HTMLCanvasElement> {
|
|
184
|
+
const canvasParentHtmlElement = this.viewer.canvas?.parentElement;
|
|
185
|
+
if (!canvasParentHtmlElement) {
|
|
186
|
+
console.warn(`No parent for html anchors available`);
|
|
187
|
+
return new HTMLCanvasElement();
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const htmlContainer = document.createElement('div');
|
|
191
|
+
htmlContainer.style.width = `${size.canvasWidth}px`;
|
|
192
|
+
htmlContainer.style.height = `${size.canvasHeight}px`;
|
|
193
|
+
|
|
194
|
+
// the `html2canvas` library requires the html to be present in the DOM
|
|
195
|
+
// that's why we create it top level and move it out of the viewport
|
|
196
|
+
// @reviewer: maybe there is a smoother method of doing that?
|
|
197
|
+
document.body.append(htmlContainer);
|
|
198
|
+
htmlContainer.style.position = 'absolute';
|
|
199
|
+
htmlContainer.style.top = `${-size.canvasHeight}px`;
|
|
200
|
+
|
|
201
|
+
htmlAnchorKeys.forEach(key => {
|
|
202
|
+
const { parentHtmlElement, anchorMesh, options } = this._htmlAnchors[key];
|
|
203
|
+
|
|
204
|
+
// create clones of html anchors, so that the position of the original elements remains untouched
|
|
205
|
+
const clonedHtmlNode = parentHtmlElement.cloneNode(true) as HTMLDivElement;
|
|
206
|
+
htmlContainer.append(clonedHtmlNode);
|
|
207
|
+
|
|
208
|
+
// reposition that cloned html element, so that it fits to the desired camera position
|
|
209
|
+
const baseScale = size.canvasHeight / this.viewer.canvas!.height;
|
|
210
|
+
this._updateHtmlAnchor(
|
|
211
|
+
clonedHtmlNode,
|
|
212
|
+
anchorMesh,
|
|
213
|
+
camera,
|
|
214
|
+
size.canvasWidth,
|
|
215
|
+
size.canvasHeight,
|
|
216
|
+
baseScale,
|
|
217
|
+
options
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const screenshotHtmlCanvas = await html2canvas(htmlContainer, {
|
|
222
|
+
width: size.imageWidth,
|
|
223
|
+
height: size.imageHeight,
|
|
224
|
+
// apply difference of canvas and image size as offsets
|
|
225
|
+
x: (size.canvasWidth - size.imageWidth) / 2,
|
|
226
|
+
y: (size.canvasHeight - size.imageHeight) / 2,
|
|
227
|
+
logging: false,
|
|
228
|
+
backgroundColor: null,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
htmlContainer.remove();
|
|
232
|
+
|
|
233
|
+
return screenshotHtmlCanvas;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Receive html anchor keys that pass the `includeGroups` and `excludeGroups` filter
|
|
238
|
+
* @param allowSystemGroups defines if html anchors created by the viewer should be considered (e.g. dimension line
|
|
239
|
+
* labels)
|
|
240
|
+
* @internal
|
|
241
|
+
*/
|
|
242
|
+
public getHtmlAnchorKeys(includeGroups?: string[], excludeGroups?: string[], allowSystemGroups?: boolean): string[] {
|
|
243
|
+
const anchorKeys = Object.keys(this._htmlAnchors).filter(name => {
|
|
244
|
+
const group = this._htmlAnchors[name].options?.group;
|
|
245
|
+
const passIncludeGroupFilter =
|
|
246
|
+
includeGroups === undefined || (group !== undefined && includeGroups.includes(group));
|
|
247
|
+
const passExcludeGroupFilter =
|
|
248
|
+
excludeGroups === undefined || group === undefined || !excludeGroups.includes(group);
|
|
249
|
+
// ATM only dimension labels are system html anchors
|
|
250
|
+
const passSystemGroupFilter = allowSystemGroups || group !== DimensionLineManager.DIMENSION_LINE_KEY;
|
|
251
|
+
|
|
252
|
+
return passIncludeGroupFilter && passExcludeGroupFilter && passSystemGroupFilter;
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return anchorKeys;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if input mesh is a html anchor mesh, created by this manager.\
|
|
260
|
+
* This check is done via the material, as all html anchor meshes have the generic `_anchorMeshMaterial` set.
|
|
261
|
+
* @internal
|
|
262
|
+
*/
|
|
263
|
+
public isHtmlAnchorMesh(mesh: AbstractMesh): boolean {
|
|
264
|
+
return mesh.material === this._anchorMeshMaterial;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
protected _updateHtmlAnchor(
|
|
268
|
+
parentHtmlElement: HTMLDivElement,
|
|
269
|
+
anchorMesh: AbstractMesh,
|
|
270
|
+
camera: Camera,
|
|
271
|
+
width: number,
|
|
272
|
+
height: number,
|
|
273
|
+
baseScale: number,
|
|
274
|
+
options?: HtmlAnchorOptions
|
|
275
|
+
): void {
|
|
276
|
+
const { hideIfOccluded, scaleWithCameraDistance } = options ?? {};
|
|
277
|
+
|
|
278
|
+
const viewMatrix = camera.getViewMatrix();
|
|
279
|
+
const projectionMatrix = camera.getProjectionMatrix();
|
|
280
|
+
const transformMatrix = viewMatrix.multiply(projectionMatrix);
|
|
281
|
+
|
|
282
|
+
// convert into 2d space
|
|
283
|
+
const vertexScreenCoords = Vector3.Project(
|
|
284
|
+
Vector3.Zero(),
|
|
285
|
+
anchorMesh.getWorldMatrix(),
|
|
286
|
+
transformMatrix,
|
|
287
|
+
new Viewport(0, 0, width, height)
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
// base scale is used if width and height don't equal the viewer canvas, which is the case for screenshots
|
|
291
|
+
let scale = baseScale;
|
|
292
|
+
if (scaleWithCameraDistance) {
|
|
293
|
+
const meshWorldPos = anchorMesh.getAbsolutePosition();
|
|
294
|
+
// calculate camera world position manually, as there is no help function like for meshes
|
|
295
|
+
const camWorldMatrix = camera.computeWorldMatrix();
|
|
296
|
+
const camWorldPos = camWorldMatrix.getTranslation();
|
|
297
|
+
const distance = Vector3.Distance(meshWorldPos, camWorldPos);
|
|
298
|
+
const frustumSlopeY = Math.tan(camera.fov / 2);
|
|
299
|
+
// if the distance from camera to mesh gets larger, the html elements scaling will be decreased
|
|
300
|
+
// we also consider the cameras FOV, so scale 1 means, that the resulting vertical frustum of the camera
|
|
301
|
+
// distance equals 1
|
|
302
|
+
// ...this is probably too small in some scenarios, so we might have to add a general scale setting here
|
|
303
|
+
scale *= 1 / (distance * frustumSlopeY);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const elementXOffset = (parentHtmlElement.offsetWidth * scale) / 2;
|
|
307
|
+
const elementYOffset = (parentHtmlElement.offsetHeight * scale) / 2;
|
|
308
|
+
// finally move and scale the HTML element
|
|
309
|
+
parentHtmlElement.style.transform = `translate3d(calc(${vertexScreenCoords.x}px - 50%), calc(${vertexScreenCoords.y}px - 50%), 0px) scale(${scale})`;
|
|
310
|
+
|
|
311
|
+
// check for occlusion if desired
|
|
312
|
+
// we use the opacity setting, which could also be animated nicely if we want
|
|
313
|
+
const isInViewport =
|
|
314
|
+
vertexScreenCoords.x - elementXOffset > 0 &&
|
|
315
|
+
vertexScreenCoords.x + elementXOffset < width &&
|
|
316
|
+
vertexScreenCoords.y - elementYOffset > 0 &&
|
|
317
|
+
vertexScreenCoords.y + elementYOffset < height;
|
|
318
|
+
const isOccluded = hideIfOccluded && anchorMesh.isOccluded;
|
|
319
|
+
parentHtmlElement.style.opacity = isInViewport && !isOccluded ? '1' : '0';
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
protected _getOrCreateAnchorMeshMaterial(): StandardMaterial {
|
|
323
|
+
if (this._anchorMeshMaterial) {
|
|
324
|
+
return this._anchorMeshMaterial;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
this._anchorMeshMaterial = new StandardMaterial('$matAnchorMesh');
|
|
328
|
+
this._anchorMeshMaterial.alpha = 0;
|
|
329
|
+
this.viewer.scene.removeMaterial(this._anchorMeshMaterial);
|
|
330
|
+
return this._anchorMeshMaterial;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
@@ -97,7 +97,7 @@ export type ParameterObserver = (payload: ParameterObserverPayload) => Promise<v
|
|
|
97
97
|
|
|
98
98
|
/**
|
|
99
99
|
* Payload of parameter observer.\
|
|
100
|
-
* Contains current data of parameter entry, which can be
|
|
100
|
+
* Contains current data of parameter entry, which can be useful for implementing the dedicated observer
|
|
101
101
|
*/
|
|
102
102
|
export type ParameterObserverPayload = {
|
|
103
103
|
subject: ParameterSubject;
|
|
@@ -294,10 +294,19 @@ export class SceneManager {
|
|
|
294
294
|
const hasInfiniteDistance = mesh.infiniteDistance;
|
|
295
295
|
// ignore meshes with "BackgroundMaterial" - usually a ground or skybox
|
|
296
296
|
const hasBackgroundMaterial = mesh.material instanceof BackgroundMaterial;
|
|
297
|
+
// ignore dummy meshes for html anchor occlusion check
|
|
298
|
+
const isHtmlAnchorMesh = this.viewer.htmlAnchorManager.isHtmlAnchorMesh(mesh);
|
|
297
299
|
// ignore excluded meshes
|
|
298
300
|
const isExcluded = excludeGeometry ? isNodeExcluded(mesh, excludeGeometry) : false;
|
|
299
301
|
|
|
300
|
-
return
|
|
302
|
+
return (
|
|
303
|
+
isEnabled &&
|
|
304
|
+
hasValidBBoxInfo &&
|
|
305
|
+
!hasInfiniteDistance &&
|
|
306
|
+
!hasBackgroundMaterial &&
|
|
307
|
+
!isHtmlAnchorMesh &&
|
|
308
|
+
!isExcluded
|
|
309
|
+
);
|
|
301
310
|
})
|
|
302
311
|
.reduce(
|
|
303
312
|
(accBBoxMinMax, curMesh, idx) => {
|
|
@@ -399,7 +408,7 @@ export class SceneManager {
|
|
|
399
408
|
// camera
|
|
400
409
|
if (cameraSettings.create) {
|
|
401
410
|
const camera = new ArcRotateCamera(
|
|
402
|
-
`${SceneManager._DEFAULT_SCENE_ASSET_NAME}
|
|
411
|
+
`${SceneManager._DEFAULT_SCENE_ASSET_NAME}_camera`,
|
|
403
412
|
cameraSettings.initialPosition.alpha,
|
|
404
413
|
cameraSettings.initialPosition.beta,
|
|
405
414
|
cameraSettings.initialPosition.radius,
|