diff --git a/docs/plugins/markers.md b/docs/plugins/markers.md
index f717e96b4..610a0d9a7 100644
--- a/docs/plugins/markers.md
+++ b/docs/plugins/markers.md
@@ -122,9 +122,9 @@ const viewer = new Viewer({
// polygon marker
id: 'polygon',
polygon: [
- [6.2208, 0.0906], [0.0443, 0.1028], [0.2322, 0.0849], [0.4531, 0.0387],
+ [6.2208, 0.0906], [0.0443, 0.1028], [0.2322, 0.0849], [0.4531, 0.0387],
[0.5022, -0.0056], [0.4587, -0.0396], [0.252, -0.0453], [0.0434, -0.0575],
- [6.1302, -0.0623], [6.0094, -0.0169], [6.0471, 0.032], [6.2208, 0.0906],
+ [6.1302, -0.0623], [6.0094, -0.0169], [6.0471, 0.0320], [6.2208, 0.0906],
],
svgStyle: {
fill: 'rgba(200, 0, 0, 0.2)',
@@ -247,43 +247,53 @@ You can try markers live in [the playground](../playground.md).
One, and only one, of these options is required for each marker.
-| Name | Type | Description |
-| ---------------- | ---------------------------------------- | ----------------------------------------------------------------------------- |
-| `image` | `string` | Path to an image file. Requires `width` and `height` to be defined. |
-| `imageLayer` | `string` | Path to an image file. |
-| `videoLayer` | `string` | Path to a video file. |
-| `html` | `string` | HTML content of the marker. It is recommended to define `width` and `height`. |
-| `element` | `HTMLElement` | Existing DOM element. |
-| `square` | `integer` | Size of the square. |
-| `rect` | `integer[2]`
`{width:int,height:int}` | Size of the rectangle. |
-| `circle` | `integer` | Radius of the circle. |
-| `ellipse` | `integer[2]`
`{rx:int,ry:int}` | Radiuses of the ellipse. |
-| `path` | `string` | Definition of the path (0,0 will be placed at the defined `position`). |
-| `polygon` | `double[2][]`
`string[2][]` | Array of points defining the polygon in spherical coordinates. |
-| `polygonPixels` | `integer[2][]` | Same as `polygon` but in pixel coordinates on the panorama image. |
-| `polyline` | `double[2][]`
`string[2][]` | Same as `polygon` but generates a polyline. |
-| `polylinePixels` | `integer[2][]` | Same as `polygonPixels` but generates a polyline. |
-
-**Examples :**
+#### `image`
+
+- type: `string`
+
+Path to an image file. Requires `size` to be defined.
+
+```js{3}
+{
+ id: 'marker-1',
+ image: 'pin-red.png',
+ position: { yaw: 0, pitch: 0 },
+ size: { width: 32, height: 32 },
+}
+```
+
+#### `imageLayer`
+
+- type: `string`
+
+Path to an image file.
+
+::: tip "Layers" positionning
+There is two ways to position `imageLayer` and `videoLayer` markers:
+
+- `position` (one value) + `size` + `anchor` (optional) + `orientation` (optional)
+- `position` with four values defining the corners of the image/video
+
+[Check the demo](../demos/markers/layers.md)
+:::
+
+```js{3}
+{
+ id: 'marker-1',
+ imageLayer: 'pin-red.png',
+ position: { yaw: 0, pitch: 0 },
+ size: { width: 32, height: 32 },
+}
-```js
{
- image: 'pin-red.png',
- imageLayer: 'pin-blue.png',
- videoLayer: 'intro.mp4',
- html: 'Click here',
- element: document.querySelector('#my-marker'),
- square: 10,
- rect: [10, 5],
- rect: {width: 10, height: 5},
- circle: 10,
- ellipse: [10, 5],
- ellipse: {rx: 10, ry: 5},
- path: 'M 0 0 L 60 60 L 60 0 L 0 60 L 0 0',
- polygon: [[0.2, 0.4], [0.9, 1.1], [1.5, 0.7]],
- polygonPixels: [[100, 200], [150, 300], [300, 200]],
- polyline: [[0.2, 0.4], [0.9, 1.1]],
- polylinePixels: [[100, 200], [150, 300]],
+ id: 'marker-2',
+ imageLayer: 'pin-red.png',
+ position: [
+ { yaw: -0.2, pitch: 0.2 },
+ { yaw: 0.2, pitch: 0.2 },
+ { yaw: 0.2, pitch: -0.2 },
+ { yaw: -0.2, pitch: -0.2 },
+ ],
}
```
@@ -292,6 +302,50 @@ Both allows to display an image but the difference is in the rendering technique
And `image` marker is rendered flat above the viewer but and `imageLayer` is rendered inside the panorama itself, this allows for more natural movements and scaling.
:::
+#### `videoLayer`
+
+- type: `string`
+
+Path to a video file. It is positionned exactly like `imageLayer`. It can be used with the [`chromaKey`](#chromakey) option.
+
+```js{3}
+{
+ id: 'marker-1',
+ videoLayer: 'intro.mp4',
+ position: { yaw: 0, pitch: 0 },
+ size: { width: 600, height: 400 },
+}
+```
+
+#### `html`
+
+- type: `string`
+
+HTML content of the marker. It is recommended to define th `size`.
+
+```js{3}
+{
+ id: 'marker-1',
+ html: 'Click here',
+ position: { yaw: 0, pitch: 0 },
+ size: { width: 100, height: 30 },
+}
+```
+
+#### `element`
+
+- type: `HTMLElement`
+
+Existing DOM element.
+
+```js{3}
+{
+ id: 'marker-1',
+ element: document.querySelector('#my-marker'),
+ position: { yaw: 0, pitch: 0 },
+}
+```
+
::: tip Custom element markers
The `element` marker accepts [Web Components](https://developer.mozilla.org/docs/Web/API/Web_components/Using_custom_elements).
If your component has an `updateMarker()` method it will be called by the plugin on each render with a bunch of properties:
@@ -305,14 +359,149 @@ If your component has an `updateMarker()` method it will be called by the plugin
[Check the demo](../demos/markers/custom-element.md)
:::
-::: tip "Layers" positionning
-There is two ways to position `imageLayer` and `videoLayer` markers:
+#### `polygon`
-- `position` (one value) + `size` + `anchor` (optional) + `orientation` (optional)
-- `position` with four values defining the corners of the image/video
+- type: `number[2][] | number[2][][] | string[2][] | string[2][][]`
+
+Array of points defining the polygon in spherical coordinates (degrees or radians).
+The polygon can have one or more holes by defined them in a nested array (the syntax is [similar to GeoJSON](https://geojson.readthedocs.io/en/latest/#polygon)).
+
+```js{3,8-11}
+{
+ id: 'marker-1',
+ polygon: [[0.2, 0.4], [0.9, 1.1], [1.5, 0.7]];
+}
+
+{
+ id: 'marker-2',
+ polygon: [
+ [[0.2, 0.4], [0.9, 1.1], [1.5, 0.7]],
+ [[0.3, 0.5], [1.4, 0.8], [0.8, 1.0]], // holes coordinates must be in reverse order
+ ],
+}
+```
+
+#### `polygonPixels`
+
+- type: `number[2][] | number[2][][]`
+
+Same as `polygon` but in pixel coordinates on the panorama image.
+
+```js{3}
+{
+ id: 'marker-1',
+ polygonPixels: [[100, 200], [150, 300], [300, 200]],
+}
+```
+
+#### `polyline`
+
+- type: `number[2][] | string[2][]`
+
+Same as `polygon` but generates a polyline.
+
+```js{3}
+{
+ id: 'marker-1',
+ polyline: [[0.2, 0.4], [0.9, 1.1]],
+}
+```
+
+#### `polylinePixels`
+
+- type: `number[2][] | string[2][]`
+
+Same as `polygonPixels` but generates a polyline.
+
+```js{3}
+{
+ id: 'marker-1',
+ polylinePixels: [[100, 200], [150, 300]],
+}
+```
+
+#### `square`
+
+- type: `integer`
+
+Size of the square.
+
+```js{3}
+{
+ id: 'marker-1',
+ square: 10,
+ position: { yaw: 0, pitch: 0 },
+}
+```
+
+#### `rect`
+
+- type: `integer[2] | { width: integer, height: integer }`
+
+Size of the rectangle.
+
+```js{3,9}
+{
+ id: 'marker-1',
+ rect: [10, 5],
+ position: { yaw: 0, pitch: 0 },
+}
+
+{
+ id: 'marker-2',
+ rect: { width: 10, height: 5 },
+ position: { yaw: 0, pitch: 0 },
+}
+```
+
+#### `circle`
+
+- type: `integer`
+
+Radius of the circle.
+
+```js{3}
+{
+ id: 'marker-1',
+ circle: 10,
+ position: { yaw: 0, pitch: 0 },
+}
+```
+
+#### `ellipse`
+
+- type: `integer[2] | { rx: integer, ry: integer }`;
+
+Radiuses of the ellipse.
+
+```js{3,9}
+{
+ id: 'marker-1',
+ ellipse: [10, 5],
+ position: { yaw: 0, pitch: 0 },
+}
+
+{
+ id: 'marker-2',
+ ellipse: { rx: 10, ry: 5 },
+ position: { yaw: 0, pitch: 0 },
+}
+```
+
+#### `path`
+
+- type: `string`
+
+Definition of the path (0,0 will be placed at the defined `position`).
+
+```js{3}
+{
+ id: 'marker-1',
+ path: 'M0,0 L60,60 L60,0 L0,60 L0,0',
+ position: { yaw: 0, pitch: 0 },
+}
+```
-[Check the demo](../demos/markers/layers.md)
-:::
### Options
diff --git a/examples/plugin-markers.html b/examples/plugin-markers.html
index c74a42063..93765b552 100644
--- a/examples/plugin-markers.html
+++ b/examples/plugin-markers.html
@@ -200,7 +200,7 @@ Lorem ipsum
polygon: [
[6.0848, 0.1806], [0.1022, 0.3166], [0.6333, 0.3836], [1.2396, 0.5112],
[1.7922, 0.5544], [2.7635, 0.7853], [3.3688, 0.4723], [3.6902, 0.6161],
- [4.2981, 0.6942], [4.6605, 0.6618], [5.4694, 0.188], [5.7125, 0.1302],
+ [4.2981, 0.6942], [4.6605, 0.6618], [5.4694, 0.1880], [5.7125, 0.1302],
],
hideList: true,
});
@@ -210,8 +210,13 @@ Lorem ipsum
id: 'polygon-1',
// prettier-ignore
polygonPixels: [
- [2941, 1413], [3042, 1402], [3041, 1555], [2854, 1559],
- [2739, 1516], [2775, 1469], [2941, 1413],
+ [
+ [2941, 1413], [3042, 1402], [3041, 1555], [2854, 1559],
+ [2739, 1516], [2775, 1469], [2941, 1413],
+ ],
+ [
+ [2900, 1450], [2900, 1500], [2950, 1500], [2950, 1450],
+ ],
],
svgStyle: {
fill: 'rgba(255,0,0,0.2)',
@@ -219,7 +224,7 @@ Lorem ipsum
strokeWidth: '2px',
},
tooltip: {
- content: 'Simple polygon',
+ content: 'Polygon with hole',
position: 'bottom right',
},
});
@@ -248,8 +253,8 @@ Lorem ipsum
id: 'polyline',
// prettier-ignore
polylinePixels: [
- 2478, 1635, 2184, 1747, 1674, 1953, 1166, 1852,
- 709, 1669, 301, 1519, 94, 1399, 34, 1356,
+ [2478, 1635], [2184, 1747], [1674, 1953], [1166, 1852],
+ [709, 1669], [301, 1519], [94, 1399], [34, 1356],
],
svgStyle: {
stroke: 'rgba(80, 150, 50, 0.8)',
diff --git a/packages/markers-plugin/src/markers/Marker3D.ts b/packages/markers-plugin/src/markers/Marker3D.ts
index b14ff5258..65042eb83 100644
--- a/packages/markers-plugin/src/markers/Marker3D.ts
+++ b/packages/markers-plugin/src/markers/Marker3D.ts
@@ -190,6 +190,10 @@ export class Marker3D extends Marker {
}
}, { once: true });
+ if (video.autoplay) {
+ video.play();
+ }
+
this.definition = this.config.videoLayer;
} else {
material.alpha = this.config.opacity;
diff --git a/packages/markers-plugin/src/markers/MarkerPolygon.ts b/packages/markers-plugin/src/markers/MarkerPolygon.ts
index 242e9668a..fc8619e00 100644
--- a/packages/markers-plugin/src/markers/MarkerPolygon.ts
+++ b/packages/markers-plugin/src/markers/MarkerPolygon.ts
@@ -11,12 +11,15 @@ import { AbstractDomMarker } from './AbstractDomMarker';
* @internal
*/
export class MarkerPolygon extends AbstractDomMarker {
+
+ private positions3D: Vector3[][];
+
constructor(viewer: Viewer, plugin: MarkersPlugin, config: MarkerConfig) {
super(viewer, plugin, config);
}
override createElement(): void {
- this.element = document.createElementNS(SVG_NS, this.isPolygon ? 'polygon' : 'polyline');
+ this.element = document.createElementNS(SVG_NS, 'path');
this.element[MARKER_DATA] = this;
}
@@ -45,16 +48,34 @@ export class MarkerPolygon extends AbstractDomMarker {
return this.type === MarkerType.polyline || this.type === MarkerType.polylinePixels;
}
+ private get coords(): [number, number][][] {
+ return this.definition;
+ }
+
override render(): Point {
- const positions = this.__getPolyPositions();
- const isVisible = positions.length > (this.isPolygon ? 2 : 1);
+ const positions = this.__getAllPolyPositions();
+ const isVisible = positions[0].length > (this.isPolygon ? 2 : 1);
if (isVisible) {
const position = this.viewer.dataHelper.sphericalCoordsToViewerCoords(this.state.position);
- const points = positions.map((pos) => pos.x - position.x + ',' + (pos.y - position.y)).join(' ');
+ const points = positions
+ .filter((innerPos, i) => {
+ return innerPos.length > 0 && (this.isPolygon || i === 0);
+ })
+ .map((innerPos) => {
+ let innerPoints = 'M';
+ innerPoints += innerPos
+ .map((pos) => `${pos.x - position.x},${pos.y - position.y}`)
+ .join('L');
+ if (this.isPolygon) {
+ innerPoints += 'Z';
+ }
+ return innerPoints;
+ })
+ .join(' ');
- this.domElement.setAttributeNS(null, 'points', points);
+ this.domElement.setAttributeNS(null, 'd', points);
this.domElement.setAttributeNS(null, 'transform', `translate(${position.x} ${position.y})`);
return position;
@@ -89,7 +110,7 @@ export class MarkerPolygon extends AbstractDomMarker {
}
// fold arrays: [1,2,3,4] => [[1,2],[3,4]]
- const actualPoly: any = this.config[this.type];
+ let actualPoly: any = this.config[this.type];
if (!Array.isArray(actualPoly[0])) {
for (let i = 0; i < actualPoly.length; i++) {
// @ts-ignore
@@ -97,29 +118,47 @@ export class MarkerPolygon extends AbstractDomMarker {
}
}
+ if (!Array.isArray(actualPoly[0][0])) {
+ actualPoly = [actualPoly];
+ }
+
// convert texture coordinates to spherical coordinates
if (this.isPixels) {
- this.definition = (actualPoly as Array<[number, number]>).map((coord) => {
- const sphericalCoords = this.viewer.dataHelper.textureCoordsToSphericalCoords({
- textureX: coord[0],
- textureY: coord[1],
+ this.definition = (actualPoly as [number, number][][]).map((coords) => {
+ return coords.map((coord) => {
+ const sphericalCoords = this.viewer.dataHelper.textureCoordsToSphericalCoords({
+ textureX: coord[0],
+ textureY: coord[1],
+ });
+ return [sphericalCoords.yaw, sphericalCoords.pitch];
});
- return [sphericalCoords.yaw, sphericalCoords.pitch];
});
}
// clean angles
else {
- this.definition = (actualPoly as Array<[number | string, number | string]>).map((coord) => {
- return [utils.parseAngle(coord[0]), utils.parseAngle(coord[1], true)];
+ this.definition = (actualPoly as [number, number][][] | [string, string][][]).map((coords) => {
+ return coords.map((coord) => {
+ return [utils.parseAngle(coord[0]), utils.parseAngle(coord[1], true)];
+ });
});
}
- const centroid = this.isPolygon ? getPolygonCenter(this.definition) : getPolylineCenter(this.definition);
+ const centroid = this.isPolygon ? getPolygonCenter(this.coords[0]) : getPolylineCenter(this.coords[0]);
this.state.position = { yaw: centroid[0], pitch: centroid[1] };
// compute x/y/z positions
- this.state.positions3D = (this.definition as Array<[number, number]>).map((coord) => {
- return this.viewer.dataHelper.sphericalCoordsToVector3({ yaw: coord[0], pitch: coord[1] });
+ this.positions3D = this.coords.map((coords) => {
+ return coords.map((coord) => {
+ return this.viewer.dataHelper.sphericalCoordsToVector3({ yaw: coord[0], pitch: coord[1] });
+ });
+ });
+
+ this.state.positions3D = this.positions3D[0];
+ }
+
+ private __getAllPolyPositions(): Point[][] {
+ return this.positions3D.map(positions => {
+ return this.__getPolyPositions(positions);
});
}
@@ -127,11 +166,11 @@ export class MarkerPolygon extends AbstractDomMarker {
* Computes viewer coordinates of each point of a polygon/polyline
* It handles points behind the camera by creating intermediary points suitable for the projector
*/
- private __getPolyPositions(): Point[] {
- const nbVectors = this.state.positions3D.length;
+ private __getPolyPositions(positions: Vector3[]): Point[] {
+ const nbVectors = positions.length;
// compute if each vector is visible
- const positions3D = this.state.positions3D.map((vector) => {
+ const positions3D = positions.map((vector) => {
return {
vector: vector,
visible: vector.dot(this.viewer.state.direction) > 0,
diff --git a/packages/markers-plugin/src/model.ts b/packages/markers-plugin/src/model.ts
index e869f8561..2ca8a9d0f 100644
--- a/packages/markers-plugin/src/model.ts
+++ b/packages/markers-plugin/src/model.ts
@@ -64,12 +64,18 @@ export type MarkerConfig = {
* Array of points defining the polygon in spherical coordinates
*/
// eslint-disable-next-line @typescript-eslint/array-type
- polygon?: [number, number][] | [string, string][] | number[] | string[];
+ polygon?:
+ | [number, number][]
+ | [number, number][][]
+ | [string, string][]
+ | [string, string][][];
/**
* Array of points defining the polygon in pixel coordinates on the panorama image
*/
// eslint-disable-next-line @typescript-eslint/array-type
- polygonPixels?: [number, number][] | number[];
+ polygonPixels?:
+ | [number, number][]
+ | [number, number][][];
/**
* Array of points defining the polyline in spherical coordinates
*/