/*! * Photo Sphere Viewer / Markers Plugin 5.11.1 * @copyright 2015-2024 Damien "Mistic" Sorel * @licence MIT (https://opensource.org/licenses/MIT) */ "use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var src_exports = {}; __export(src_exports, { MarkersPlugin: () => MarkersPlugin, events: () => events_exports }); module.exports = __toCommonJS(src_exports); var import_core17 = require("@photo-sphere-viewer/core"); // src/events.ts var events_exports = {}; __export(events_exports, { EnterMarkerEvent: () => EnterMarkerEvent, GotoMarkerDoneEvent: () => GotoMarkerDoneEvent, HideMarkersEvent: () => HideMarkersEvent, LeaveMarkerEvent: () => LeaveMarkerEvent, MarkerVisibilityEvent: () => MarkerVisibilityEvent, MarkersPluginEvent: () => MarkersPluginEvent, RenderMarkersListEvent: () => RenderMarkersListEvent, SelectMarkerEvent: () => SelectMarkerEvent, SelectMarkerListEvent: () => SelectMarkerListEvent, SetMarkersEvent: () => SetMarkersEvent, ShowMarkersEvent: () => ShowMarkersEvent, UnselectMarkerEvent: () => UnselectMarkerEvent }); var import_core = require("@photo-sphere-viewer/core"); var MarkersPluginEvent = class extends import_core.TypedEvent { }; var _MarkerVisibilityEvent = class _MarkerVisibilityEvent extends MarkersPluginEvent { /** @internal */ constructor(marker, visible) { super(_MarkerVisibilityEvent.type); this.marker = marker; this.visible = visible; } }; _MarkerVisibilityEvent.type = "marker-visibility"; var MarkerVisibilityEvent = _MarkerVisibilityEvent; var _GotoMarkerDoneEvent = class _GotoMarkerDoneEvent extends MarkersPluginEvent { /** @internal */ constructor(marker) { super(_GotoMarkerDoneEvent.type); this.marker = marker; } }; _GotoMarkerDoneEvent.type = "goto-marker-done"; var GotoMarkerDoneEvent = _GotoMarkerDoneEvent; var _LeaveMarkerEvent = class _LeaveMarkerEvent extends MarkersPluginEvent { /** @internal */ constructor(marker) { super(_LeaveMarkerEvent.type); this.marker = marker; } }; _LeaveMarkerEvent.type = "leave-marker"; var LeaveMarkerEvent = _LeaveMarkerEvent; var _EnterMarkerEvent = class _EnterMarkerEvent extends MarkersPluginEvent { /** @internal */ constructor(marker) { super(_EnterMarkerEvent.type); this.marker = marker; } }; _EnterMarkerEvent.type = "enter-marker"; var EnterMarkerEvent = _EnterMarkerEvent; var _SelectMarkerEvent = class _SelectMarkerEvent extends MarkersPluginEvent { /** @internal */ constructor(marker, doubleClick, rightClick) { super(_SelectMarkerEvent.type); this.marker = marker; this.doubleClick = doubleClick; this.rightClick = rightClick; } }; _SelectMarkerEvent.type = "select-marker"; var SelectMarkerEvent = _SelectMarkerEvent; var _SelectMarkerListEvent = class _SelectMarkerListEvent extends MarkersPluginEvent { /** @internal */ constructor(marker) { super(_SelectMarkerListEvent.type); this.marker = marker; } }; _SelectMarkerListEvent.type = "select-marker-list"; var SelectMarkerListEvent = _SelectMarkerListEvent; var _UnselectMarkerEvent = class _UnselectMarkerEvent extends MarkersPluginEvent { /** @internal */ constructor(marker) { super(_UnselectMarkerEvent.type); this.marker = marker; } }; _UnselectMarkerEvent.type = "unselect-marker"; var UnselectMarkerEvent = _UnselectMarkerEvent; var _HideMarkersEvent = class _HideMarkersEvent extends MarkersPluginEvent { /** @internal */ constructor() { super(_HideMarkersEvent.type); } }; _HideMarkersEvent.type = "hide-markers"; var HideMarkersEvent = _HideMarkersEvent; var _SetMarkersEvent = class _SetMarkersEvent extends MarkersPluginEvent { /** @internal */ constructor(markers) { super(_SetMarkersEvent.type); this.markers = markers; } }; _SetMarkersEvent.type = "set-markers"; var SetMarkersEvent = _SetMarkersEvent; var _ShowMarkersEvent = class _ShowMarkersEvent extends MarkersPluginEvent { /** @internal */ constructor() { super(_ShowMarkersEvent.type); } }; _ShowMarkersEvent.type = "show-markers"; var ShowMarkersEvent = _ShowMarkersEvent; var _RenderMarkersListEvent = class _RenderMarkersListEvent extends MarkersPluginEvent { /** @internal */ constructor(markers) { super(_RenderMarkersListEvent.type); this.markers = markers; } }; _RenderMarkersListEvent.type = "render-markers-list"; var RenderMarkersListEvent = _RenderMarkersListEvent; // src/MarkersButton.ts var import_core2 = require("@photo-sphere-viewer/core"); // src/icons/pin.svg var pin_default = '\n'; // src/MarkersButton.ts var MarkersButton = class extends import_core2.AbstractButton { constructor(navbar) { super(navbar, { className: "psv-markers-button", icon: pin_default, hoverScale: true, collapsable: true, tabbable: true }); this.plugin = this.viewer.getPlugin("markers"); if (this.plugin) { this.plugin.addEventListener(ShowMarkersEvent.type, this); this.plugin.addEventListener(HideMarkersEvent.type, this); this.toggleActive(true); } } destroy() { if (this.plugin) { this.plugin.removeEventListener(ShowMarkersEvent.type, this); this.plugin.removeEventListener(HideMarkersEvent.type, this); } super.destroy(); } isSupported() { return !!this.plugin; } handleEvent(e) { if (e instanceof ShowMarkersEvent) { this.toggleActive(true); } else if (e instanceof HideMarkersEvent) { this.toggleActive(false); } } onClick() { this.plugin.toggleAllMarkers(); } }; MarkersButton.id = "markers"; // src/MarkersListButton.ts var import_core4 = require("@photo-sphere-viewer/core"); // src/constants.ts var import_core3 = require("@photo-sphere-viewer/core"); // src/icons/pin-list.svg var pin_list_default = '\n'; // src/constants.ts var SVG_NS = "http://www.w3.org/2000/svg"; var MARKER_DATA = "psvMarker"; var MARKER_DATA_KEY = import_core3.utils.dasherize(MARKER_DATA); var ID_PANEL_MARKER = "marker"; var ID_PANEL_MARKERS_LIST = "markersList"; var DEFAULT_HOVER_SCALE = { amount: 2, duration: 100, easing: "linear" }; var MARKERS_LIST_TEMPLATE = (markers, title) => `

${pin_list_default} ${title}

`; // src/MarkersListButton.ts var MarkersListButton = class extends import_core4.AbstractButton { constructor(navbar) { super(navbar, { className: " psv-markers-list-button", icon: pin_list_default, hoverScale: true, collapsable: true, tabbable: true }); this.plugin = this.viewer.getPlugin("markers"); if (this.plugin) { this.viewer.addEventListener(import_core4.events.ShowPanelEvent.type, this); this.viewer.addEventListener(import_core4.events.HidePanelEvent.type, this); } } destroy() { this.viewer.removeEventListener(import_core4.events.ShowPanelEvent.type, this); this.viewer.removeEventListener(import_core4.events.HidePanelEvent.type, this); super.destroy(); } isSupported() { return !!this.plugin; } handleEvent(e) { if (e instanceof import_core4.events.ShowPanelEvent) { this.toggleActive(e.panelId === ID_PANEL_MARKERS_LIST); } else if (e instanceof import_core4.events.HidePanelEvent) { this.toggleActive(false); } } onClick() { this.plugin.toggleMarkersList(); } }; MarkersListButton.id = "markersList"; // src/MarkersPlugin.ts var import_core16 = require("@photo-sphere-viewer/core"); // src/CSS3DContainer.ts var import_core5 = require("@photo-sphere-viewer/core"); var import_three2 = require("three"); // ../../node_modules/three/examples/jsm/renderers/CSS3DRenderer.js var import_three = require("three"); var _position = new import_three.Vector3(); var _quaternion = new import_three.Quaternion(); var _scale = new import_three.Vector3(); var CSS3DObject = class extends import_three.Object3D { constructor(element = document.createElement("div")) { super(); this.isCSS3DObject = true; this.element = element; this.element.style.position = "absolute"; this.element.style.pointerEvents = "auto"; this.element.style.userSelect = "none"; this.element.setAttribute("draggable", false); this.addEventListener("removed", function() { this.traverse(function(object) { if (object.element instanceof Element && object.element.parentNode !== null) { object.element.parentNode.removeChild(object.element); } }); }); } copy(source, recursive) { super.copy(source, recursive); this.element = source.element.cloneNode(true); return this; } }; var _matrix = new import_three.Matrix4(); var _matrix2 = new import_three.Matrix4(); var CSS3DRenderer = class { constructor(parameters = {}) { const _this = this; let _width, _height; let _widthHalf, _heightHalf; const cache = { camera: { style: "" }, objects: /* @__PURE__ */ new WeakMap() }; const domElement = parameters.element !== void 0 ? parameters.element : document.createElement("div"); domElement.style.overflow = "hidden"; this.domElement = domElement; const viewElement = document.createElement("div"); viewElement.style.transformOrigin = "0 0"; viewElement.style.pointerEvents = "none"; domElement.appendChild(viewElement); const cameraElement = document.createElement("div"); cameraElement.style.transformStyle = "preserve-3d"; viewElement.appendChild(cameraElement); this.getSize = function() { return { width: _width, height: _height }; }; this.render = function(scene, camera) { const fov = camera.projectionMatrix.elements[5] * _heightHalf; if (camera.view && camera.view.enabled) { viewElement.style.transform = `translate( ${-camera.view.offsetX * (_width / camera.view.width)}px, ${-camera.view.offsetY * (_height / camera.view.height)}px )`; viewElement.style.transform += `scale( ${camera.view.fullWidth / camera.view.width}, ${camera.view.fullHeight / camera.view.height} )`; } else { viewElement.style.transform = ""; } if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld(); if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld(); let tx, ty; if (camera.isOrthographicCamera) { tx = -(camera.right + camera.left) / 2; ty = (camera.top + camera.bottom) / 2; } const scaleByViewOffset = camera.view && camera.view.enabled ? camera.view.height / camera.view.fullHeight : 1; const cameraCSSMatrix = camera.isOrthographicCamera ? `scale( ${scaleByViewOffset} )scale(` + fov + ")translate(" + epsilon(tx) + "px," + epsilon(ty) + "px)" + getCameraCSSMatrix(camera.matrixWorldInverse) : `scale( ${scaleByViewOffset} )translateZ(` + fov + "px)" + getCameraCSSMatrix(camera.matrixWorldInverse); const perspective = camera.isPerspectiveCamera ? "perspective(" + fov + "px) " : ""; const style = perspective + cameraCSSMatrix + "translate(" + _widthHalf + "px," + _heightHalf + "px)"; if (cache.camera.style !== style) { cameraElement.style.transform = style; cache.camera.style = style; } renderObject(scene, scene, camera, cameraCSSMatrix); }; this.setSize = function(width, height) { _width = width; _height = height; _widthHalf = _width / 2; _heightHalf = _height / 2; domElement.style.width = width + "px"; domElement.style.height = height + "px"; viewElement.style.width = width + "px"; viewElement.style.height = height + "px"; cameraElement.style.width = width + "px"; cameraElement.style.height = height + "px"; }; function epsilon(value) { return Math.abs(value) < 1e-10 ? 0 : value; } function getCameraCSSMatrix(matrix) { const elements = matrix.elements; return "matrix3d(" + epsilon(elements[0]) + "," + epsilon(-elements[1]) + "," + epsilon(elements[2]) + "," + epsilon(elements[3]) + "," + epsilon(elements[4]) + "," + epsilon(-elements[5]) + "," + epsilon(elements[6]) + "," + epsilon(elements[7]) + "," + epsilon(elements[8]) + "," + epsilon(-elements[9]) + "," + epsilon(elements[10]) + "," + epsilon(elements[11]) + "," + epsilon(elements[12]) + "," + epsilon(-elements[13]) + "," + epsilon(elements[14]) + "," + epsilon(elements[15]) + ")"; } function getObjectCSSMatrix(matrix) { const elements = matrix.elements; const matrix3d = "matrix3d(" + epsilon(elements[0]) + "," + epsilon(elements[1]) + "," + epsilon(elements[2]) + "," + epsilon(elements[3]) + "," + epsilon(-elements[4]) + "," + epsilon(-elements[5]) + "," + epsilon(-elements[6]) + "," + epsilon(-elements[7]) + "," + epsilon(elements[8]) + "," + epsilon(elements[9]) + "," + epsilon(elements[10]) + "," + epsilon(elements[11]) + "," + epsilon(elements[12]) + "," + epsilon(elements[13]) + "," + epsilon(elements[14]) + "," + epsilon(elements[15]) + ")"; return "translate(-50%,-50%)" + matrix3d; } function hideObject(object) { if (object.isCSS3DObject) object.element.style.display = "none"; for (let i = 0, l = object.children.length; i < l; i++) { hideObject(object.children[i]); } } function renderObject(object, scene, camera, cameraCSSMatrix) { if (object.visible === false) { hideObject(object); return; } if (object.isCSS3DObject) { const visible = object.layers.test(camera.layers) === true; const element = object.element; element.style.display = visible === true ? "" : "none"; if (visible === true) { object.onBeforeRender(_this, scene, camera); let style; if (object.isCSS3DSprite) { _matrix.copy(camera.matrixWorldInverse); _matrix.transpose(); if (object.rotation2D !== 0) _matrix.multiply(_matrix2.makeRotationZ(object.rotation2D)); object.matrixWorld.decompose(_position, _quaternion, _scale); _matrix.setPosition(_position); _matrix.scale(_scale); _matrix.elements[3] = 0; _matrix.elements[7] = 0; _matrix.elements[11] = 0; _matrix.elements[15] = 1; style = getObjectCSSMatrix(_matrix); } else { style = getObjectCSSMatrix(object.matrixWorld); } const cachedObject = cache.objects.get(object); if (cachedObject === void 0 || cachedObject.style !== style) { element.style.transform = style; const objectData = { style }; cache.objects.set(object, objectData); } if (element.parentNode !== cameraElement) { cameraElement.appendChild(element); } object.onAfterRender(_this, scene, camera); } } for (let i = 0, l = object.children.length; i < l; i++) { renderObject(object.children[i], scene, camera, cameraCSSMatrix); } } } }; // src/CSS3DContainer.ts var CSS3DContainer = class { constructor(viewer) { this.viewer = viewer; this.element = document.createElement("div"); this.element.className = "psv-markers-css3d-container"; this.renderer = new CSS3DRenderer({ element: this.element }); this.scene = new import_three2.Scene(); this.intersectionObserver = new IntersectionObserver((entries) => { entries.forEach((entry) => { const marker = entry.target[MARKER_DATA]; if (marker.config.visible) { marker.viewportIntersection = entry.isIntersecting; } }); }, { root: this.element }); viewer.addEventListener(import_core5.events.ReadyEvent.type, this, { once: true }); viewer.addEventListener(import_core5.events.SizeUpdatedEvent.type, this); viewer.addEventListener(import_core5.events.RenderEvent.type, this); } handleEvent(e) { switch (e.type) { case import_core5.events.ReadyEvent.type: case import_core5.events.SizeUpdatedEvent.type: this.updateSize(); break; case import_core5.events.RenderEvent.type: this.render(); break; } } destroy() { this.viewer.removeEventListener(import_core5.events.ReadyEvent.type, this); this.viewer.removeEventListener(import_core5.events.SizeUpdatedEvent.type, this); this.viewer.removeEventListener(import_core5.events.RenderEvent.type, this); this.intersectionObserver.disconnect(); } updateSize() { const size = this.viewer.getSize(); this.renderer.setSize(size.width, size.height); } render() { this.renderer.render(this.scene, this.viewer.renderer.camera); } addObject(marker) { this.scene.add(marker.threeElement); this.intersectionObserver.observe(marker.domElement); } removeObject(marker) { this.scene.remove(marker.threeElement); this.intersectionObserver.unobserve(marker.domElement); } }; // src/MarkerType.ts var import_core6 = require("@photo-sphere-viewer/core"); var MarkerType = /* @__PURE__ */ ((MarkerType3) => { MarkerType3["image"] = "image"; MarkerType3["html"] = "html"; MarkerType3["element"] = "element"; MarkerType3["imageLayer"] = "imageLayer"; MarkerType3["videoLayer"] = "videoLayer"; MarkerType3["elementLayer"] = "elementLayer"; MarkerType3["polygon"] = "polygon"; MarkerType3["polygonPixels"] = "polygonPixels"; MarkerType3["polyline"] = "polyline"; MarkerType3["polylinePixels"] = "polylinePixels"; MarkerType3["square"] = "square"; MarkerType3["rect"] = "rect"; MarkerType3["circle"] = "circle"; MarkerType3["ellipse"] = "ellipse"; MarkerType3["path"] = "path"; return MarkerType3; })(MarkerType || {}); function getMarkerType(config, allowNone = false) { const found = []; Object.keys(MarkerType).forEach((type) => { if (config[type]) { found.push(type); } }); if (found.length === 0 && !allowNone) { throw new import_core6.PSVError(`missing marker content, either ${Object.keys(MarkerType).join(", ")}`); } else if (found.length > 1) { throw new import_core6.PSVError(`multiple marker content, either ${Object.keys(MarkerType).join(", ")}`); } return found[0]; } // src/markers/AbstractStandardMarker.ts var import_core9 = require("@photo-sphere-viewer/core"); var import_three3 = require("three"); // src/markers/AbstractDomMarker.ts var import_core8 = require("@photo-sphere-viewer/core"); // src/markers/Marker.ts var import_core7 = require("@photo-sphere-viewer/core"); var Marker = class { constructor(viewer, plugin, config) { this.viewer = viewer; this.plugin = plugin; /** @internal */ this.state = { anchor: null, visible: false, staticTooltip: false, position: null, position2D: null, positions3D: null, size: null }; if (!config.id) { throw new import_core7.PSVError("missing marker id"); } this.type = getMarkerType(config); this.createElement(); this.update(config); } get id() { return this.config.id; } get data() { return this.config.data; } get domElement() { return null; } get threeElement() { return null; } get video() { return null; } /** * @internal */ destroy() { delete this.viewer; delete this.plugin; delete this.element; this.hideTooltip(); } /** * Checks if it is a 3D marker (imageLayer, videoLayer) */ is3d() { return false; } /** * Checks if it is a normal marker (image, html, element) */ isNormal() { return false; } /** * Checks if it is a polygon/polyline marker */ isPoly() { return false; } /** * Checks if it is an SVG marker */ isSvg() { return false; } /** * Checks if it is an CSS3D marker */ isCss3d() { return false; } /** * Updates the marker with new properties * @throws {@link PSVError} if the configuration is invalid * @internal */ update(config) { const newType = getMarkerType(config, true); if (newType !== void 0 && newType !== this.type) { throw new import_core7.PSVError(`cannot change marker ${config.id} type`); } if (import_core7.utils.isExtendedPosition(config)) { import_core7.utils.logWarn('Use the "position" property to configure the position of a marker'); config.position = this.viewer.dataHelper.cleanPosition(config); } if ("width" in config && "height" in config) { import_core7.utils.logWarn('Use the "size" property to configure the size of a marker'); config.size = { width: config["width"], height: config["height"] }; } this.config = import_core7.utils.deepmerge(this.config, config); if (typeof this.config.tooltip === "string") { this.config.tooltip = { content: this.config.tooltip }; } if (this.config.tooltip && !this.config.tooltip.trigger) { this.config.tooltip.trigger = "hover"; } if (import_core7.utils.isNil(this.config.visible)) { this.config.visible = true; } if (import_core7.utils.isNil(this.config.zIndex)) { this.config.zIndex = 1; } if (import_core7.utils.isNil(this.config.opacity)) { this.config.opacity = 1; } if (this.config.rotation) { const rot = this.config.rotation; if (typeof rot === "object") { this.config.rotation = { yaw: rot.yaw ? import_core7.utils.parseAngle(rot.yaw, true, false) : 0, pitch: rot.pitch ? import_core7.utils.parseAngle(rot.pitch, true, false) : 0, roll: rot.roll ? import_core7.utils.parseAngle(rot.roll, true, false) : 0 }; } else { this.config.rotation = { yaw: 0, pitch: 0, roll: import_core7.utils.parseAngle(rot, true, false) }; } } else { this.config.rotation = { yaw: 0, pitch: 0, roll: 0 }; } this.state.anchor = import_core7.utils.parsePoint(this.config.anchor); } /** * Returns the markers list content for the marker, it can be either : * - the `listContent` * - the `tooltip` * - the `html` * - the `id` * @internal */ getListContent() { if (this.config.listContent) { return this.config.listContent; } else if (this.config.tooltip?.content) { return this.config.tooltip.content; } else if (this.config.html) { return this.config.html; } else { return this.id; } } /** * Display the tooltip of this marker * @internal */ showTooltip(clientX, clientY, forceUpdate = false) { if (this.state.visible && this.config.tooltip?.content && this.state.position2D) { const config = { ...this.config.tooltip, style: { // prevents conflicts with tooltip tracking pointerEvents: this.state.staticTooltip ? "auto" : "none" }, data: this, top: 0, left: 0 }; if (this.isPoly() || this.is3d() || this.isCss3d()) { if (clientX || clientY) { const viewerPos = import_core7.utils.getPosition(this.viewer.container); config.top = clientY - viewerPos.y + 10; config.left = clientX - viewerPos.x; config.box = { // separate the tooltip from the cursor width: 20, height: 20 }; } else { config.top = this.state.position2D.y; config.left = this.state.position2D.x; } } else { const position = this.viewer.dataHelper.vector3ToViewerCoords(this.state.positions3D[0]); let width = this.state.size.width; let height = this.state.size.height; if (this.config.hoverScale && !this.state.staticTooltip) { width *= this.config.hoverScale.amount; height *= this.config.hoverScale.amount; } config.top = position.y - height * this.state.anchor.y + height / 2; config.left = position.x - width * this.state.anchor.x + width / 2; config.box = { width, height }; } if (this.tooltip) { if (forceUpdate) { this.tooltip.update(this.config.tooltip.content, config); } else { this.tooltip.move(config); } } else { this.tooltip = this.viewer.createTooltip(config); } } } /** * Hides the tooltip of this marker * @internal */ hideTooltip() { if (this.tooltip) { this.tooltip.hide(); this.tooltip = null; } } }; // src/markers/AbstractDomMarker.ts var AbstractDomMarker = class extends Marker { get domElement() { return this.element; } constructor(viewer, plugin, config) { super(viewer, plugin, config); } afterCreateElement() { this.element[MARKER_DATA] = this; } destroy() { delete this.element[MARKER_DATA]; super.destroy(); } update(config) { super.update(config); const element = this.domElement; element.id = `psv-marker-${this.config.id}`; element.setAttribute("class", "psv-marker"); if (this.state.visible) { element.classList.add("psv-marker--visible"); } if (this.config.tooltip) { element.classList.add("psv-marker--has-tooltip"); } if (this.config.content) { element.classList.add("psv-marker--has-content"); } if (this.config.className) { import_core8.utils.addClasses(element, this.config.className); } element.style.opacity = `${this.config.opacity}`; element.style.zIndex = `${30 + this.config.zIndex}`; if (this.config.style) { Object.assign(element.style, this.config.style); } } }; // src/markers/AbstractStandardMarker.ts var AbstractStandardMarker = class extends AbstractDomMarker { constructor(viewer, plugin, config) { super(viewer, plugin, config); } afterCreateElement() { super.afterCreateElement(); this.domElement.addEventListener("transitionend", () => { this.domElement.style.transition = ""; }); } render({ viewerPosition, zoomLevel, hoveringMarker }) { this.__updateSize(); const position = this.viewer.dataHelper.vector3ToViewerCoords(this.state.positions3D[0]); position.x -= this.state.size.width * this.state.anchor.x; position.y -= this.state.size.height * this.state.anchor.y; const isVisible = this.state.positions3D[0].dot(this.viewer.state.direction) > 0 && position.x + this.state.size.width >= 0 && position.x - this.state.size.width <= this.viewer.state.size.width && position.y + this.state.size.height >= 0 && position.y - this.state.size.height <= this.viewer.state.size.height; if (isVisible) { this.domElement.style.translate = `${position.x}px ${position.y}px 0px`; this.applyScale({ zoomLevel, viewerPosition, mouseover: this === hoveringMarker }); return position; } else { return null; } } update(config) { super.update(config); if (!import_core9.utils.isExtendedPosition(this.config.position)) { throw new import_core9.PSVError(`missing marker ${this.id} position`); } try { this.state.position = this.viewer.dataHelper.cleanPosition(this.config.position); } catch (e) { throw new import_core9.PSVError(`invalid marker ${this.id} position`, e); } this.state.positions3D = [this.viewer.dataHelper.sphericalCoordsToVector3(this.state.position)]; const element = this.domElement; element.classList.add("psv-marker--normal"); if (this.config.scale && Array.isArray(this.config.scale)) { this.config.scale = { zoom: this.config.scale }; } if (typeof this.config.hoverScale === "boolean") { this.config.hoverScale = this.config.hoverScale ? this.plugin.config.defaultHoverScale || DEFAULT_HOVER_SCALE : null; } else if (typeof this.config.hoverScale === "number") { this.config.hoverScale = { amount: this.config.hoverScale }; } else if (!this.config.hoverScale) { this.config.hoverScale = this.plugin.config.defaultHoverScale; } if (this.config.hoverScale) { this.config.hoverScale = { ...this.plugin.config.defaultHoverScale, ...this.config.hoverScale }; } element.style.rotate = this.config.rotation.roll !== 0 ? import_three3.MathUtils.radToDeg(this.config.rotation.roll) + "deg" : null; element.style.transformOrigin = `${this.state.anchor.x * 100}% ${this.state.anchor.y * 100}%`; } /** * Computes the real size of a marker * @description This is done by removing all it's transformations (if any) and making it visible * before querying its bounding rect */ __updateSize() { if (!this.needsUpdateSize) { return; } const element = this.domElement; const makeTransparent = !this.state.visible || !this.state.size; if (makeTransparent) { element.classList.add("psv-marker--transparent"); } if (this.isSvg()) { const rect = element.firstElementChild.getBoundingClientRect(); this.state.size = { width: rect.width, height: rect.height }; } else { this.state.size = { width: element.offsetWidth, height: element.offsetHeight }; } if (makeTransparent) { element.classList.remove("psv-marker--transparent"); } if (this.isSvg()) { element.style.width = this.state.size.width + "px"; element.style.height = this.state.size.height + "px"; } if (this.type !== "element" /* element */) { this.needsUpdateSize = false; } } /** * Computes and applies the scale to the marker */ applyScale({ zoomLevel, viewerPosition, mouseover }) { if (mouseover !== null && this.config.hoverScale) { this.domElement.style.transition = `scale ${this.config.hoverScale.duration}ms ${this.config.hoverScale.easing}`; } let scale = 1; if (typeof this.config.scale === "function") { scale = this.config.scale(zoomLevel, viewerPosition); } else if (this.config.scale) { if (Array.isArray(this.config.scale.zoom)) { const [min, max] = this.config.scale.zoom; scale *= min + (max - min) * import_core9.CONSTANTS.EASINGS.inQuad(zoomLevel / 100); } if (Array.isArray(this.config.scale.yaw)) { const [min, max] = this.config.scale.yaw; const halfFov = import_three3.MathUtils.degToRad(this.viewer.state.hFov) / 2; const arc = Math.abs(import_core9.utils.getShortestArc(this.state.position.yaw, viewerPosition.yaw)); scale *= max + (min - max) * import_core9.CONSTANTS.EASINGS.outQuad(Math.max(0, (halfFov - arc) / halfFov)); } } if (mouseover && this.config.hoverScale) { scale *= this.config.hoverScale.amount; } this.domElement.style.scale = `${scale}`; } }; // src/markers/Marker3D.ts var import_core11 = require("@photo-sphere-viewer/core"); var import_three6 = require("three"); // ../shared/ChromaKeyMaterial.ts var import_three4 = require("three"); // ../shared/shaders/chromaKey.fragment.glsl var chromaKey_fragment_default = "// https://www.8thwall.com/playground/chromakey-threejs\n\nuniform sampler2D map;\nuniform float alpha;\nuniform bool keying;\nuniform vec3 color;\nuniform float similarity;\nuniform float smoothness;\nuniform float spill;\n\nvarying vec2 vUv;\n\nvec2 RGBtoUV(vec3 rgb) {\n return vec2(\n rgb.r * -0.169 + rgb.g * -0.331 + rgb.b * 0.5 + 0.5,\n rgb.r * 0.5 + rgb.g * -0.419 + rgb.b * -0.081 + 0.5\n );\n}\n\nvoid main(void) {\n gl_FragColor = texture2D(map, vUv);\n\n if (keying) {\n float chromaDist = distance(RGBtoUV(gl_FragColor.rgb), RGBtoUV(color));\n\n float baseMask = chromaDist - similarity;\n float fullMask = pow(clamp(baseMask / smoothness, 0., 1.), 1.5);\n gl_FragColor.a *= fullMask * alpha;\n\n float spillVal = pow(clamp(baseMask / spill, 0., 1.), 1.5);\n float desat = clamp(gl_FragColor.r * 0.2126 + gl_FragColor.g * 0.7152 + gl_FragColor.b * 0.0722, 0., 1.);\n gl_FragColor.rgb = mix(vec3(desat, desat, desat), gl_FragColor.rgb, spillVal);\n } else {\n gl_FragColor.a *= alpha;\n }\n}\n"; // ../shared/shaders/chromaKey.vertex.glsl var chromaKey_vertex_default = "varying vec2 vUv;\nuniform vec2 repeat;\nuniform vec2 offset;\n\nvoid main() {\n vUv = uv * repeat + offset;\n gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}\n"; // ../shared/ChromaKeyMaterial.ts var ChromaKeyMaterial = class extends import_three4.ShaderMaterial { get map() { return this.uniforms.map.value; } set map(map) { this.uniforms.map.value = map; } set alpha(alpha) { this.uniforms.alpha.value = alpha; } get offset() { return this.uniforms.offset.value; } get repeat() { return this.uniforms.repeat.value; } set chromaKey(chromaKey) { this.uniforms.keying.value = chromaKey?.enabled === true; if (chromaKey?.enabled) { if (typeof chromaKey.color === "object" && "r" in chromaKey.color) { this.uniforms.color.value.set( chromaKey.color.r / 255, chromaKey.color.g / 255, chromaKey.color.b / 255 ); } else { this.uniforms.color.value.set(chromaKey.color ?? 65280); } this.uniforms.similarity.value = chromaKey.similarity ?? 0.2; this.uniforms.smoothness.value = chromaKey.smoothness ?? 0.2; } } constructor(params) { super({ transparent: true, depthTest: false, depthWrite: false, uniforms: { map: { value: params?.map }, repeat: { value: new import_three4.Vector2(1, 1) }, offset: { value: new import_three4.Vector2(0, 0) }, alpha: { value: params?.alpha ?? 1 }, keying: { value: false }, color: { value: new import_three4.Color(65280) }, similarity: { value: 0.2 }, smoothness: { value: 0.2 }, spill: { value: 0.1 } }, vertexShader: chromaKey_vertex_default, fragmentShader: chromaKey_fragment_default }); this.chromaKey = params?.chromaKey; } }; // ../shared/video-utils.ts function createVideo({ src, withCredentials, muted, autoplay }) { const video = document.createElement("video"); video.crossOrigin = withCredentials ? "use-credentials" : "anonymous"; video.loop = true; video.playsInline = true; video.autoplay = autoplay; video.muted = muted; video.preload = "metadata"; if (src instanceof MediaStream) { video.srcObject = src; } else { video.src = src; } return video; } // src/utils.ts var import_core10 = require("@photo-sphere-viewer/core"); var import_three5 = require("three"); function greatArcIntermediaryPoint(p1, p2, f) { const [\u03BB1, \u03C61] = p1; const [\u03BB2, \u03C62] = p2; const r = import_core10.utils.greatArcDistance(p1, p2); const a = Math.sin((1 - f) * r) / Math.sin(r); const b = Math.sin(f * r) / Math.sin(r); const x = a * Math.cos(\u03C61) * Math.cos(\u03BB1) + b * Math.cos(\u03C62) * Math.cos(\u03BB2); const y = a * Math.cos(\u03C61) * Math.sin(\u03BB1) + b * Math.cos(\u03C62) * Math.sin(\u03BB2); const z = a * Math.sin(\u03C61) + b * Math.sin(\u03C62); return [Math.atan2(y, x), Math.atan2(z, Math.sqrt(x * x + y * y))]; } function getPolygonCoherentPoints(points) { const workPoints = [points[0]]; let k = 0; for (let i = 1; i < points.length; i++) { const d = points[i - 1][0] - points[i][0]; if (d > Math.PI) { k += 1; } else if (d < -Math.PI) { k -= 1; } workPoints.push([points[i][0] + k * 2 * Math.PI, points[i][1]]); } return workPoints; } function getPolygonCenter(polygon) { const points = getPolygonCoherentPoints(polygon); const sum = points.reduce((intermediary, point) => [intermediary[0] + point[0], intermediary[1] + point[1]]); return [import_core10.utils.parseAngle(sum[0] / polygon.length), sum[1] / polygon.length]; } function getPolylineCenter(polyline) { const points = getPolygonCoherentPoints(polyline); let length = 0; const lengths = []; for (let i = 0; i < points.length - 1; i++) { const l = import_core10.utils.greatArcDistance(points[i], points[i + 1]) * import_core10.CONSTANTS.SPHERE_RADIUS; lengths.push(l); length += l; } let consumed = 0; for (let j = 0; j < points.length - 1; j++) { if (consumed + lengths[j] > length / 2) { const r = (length / 2 - consumed) / lengths[j]; return greatArcIntermediaryPoint(points[j], points[j + 1], r); } consumed += lengths[j]; } return points[Math.round(points.length / 2)]; } var C = new import_three5.Vector3(); var N = new import_three5.Vector3(); var V = new import_three5.Vector3(); var X = new import_three5.Vector3(); var Y = new import_three5.Vector3(); var A = new import_three5.Vector3(); function getGreatCircleIntersection(P1, P2, direction) { C.copy(direction).normalize(); N.crossVectors(P1, P2).normalize(); V.crossVectors(N, P1).normalize(); X.copy(P1).multiplyScalar(-C.dot(V)); Y.copy(V).multiplyScalar(C.dot(P1)); const H = new import_three5.Vector3().addVectors(X, Y).normalize(); A.crossVectors(H, C); return H.applyAxisAngle(A, 0.01).multiplyScalar(import_core10.CONSTANTS.SPHERE_RADIUS); } // src/markers/Marker3D.ts var Marker3D = class extends Marker { get threeElement() { return this.element; } get threeMesh() { return this.threeElement.children[0]; } get video() { if (this.type === "videoLayer" /* videoLayer */) { return this.threeMesh.material.map.image; } else { return null; } } constructor(viewer, plugin, config) { super(viewer, plugin, config); } is3d() { return true; } createElement() { const material = new ChromaKeyMaterial({ alpha: 0 }); const geometry = new import_three6.PlaneGeometry(1, 1); const mesh = new import_three6.Mesh(geometry, material); mesh.userData = { [MARKER_DATA]: this }; Object.defineProperty(mesh, "visible", { enumerable: true, get: function() { return this.userData[MARKER_DATA].config.visible; }, set: function(visible) { this.userData[MARKER_DATA].config.visible = visible; } }); this.element = new import_three6.Group().add(mesh); if (this.type === "videoLayer" /* videoLayer */) { this.viewer.needsContinuousUpdate(true); } } destroy() { delete this.threeMesh.userData[MARKER_DATA]; if (this.type === "videoLayer" /* videoLayer */) { this.video.pause(); this.viewer.needsContinuousUpdate(false); } super.destroy(); } render() { if (this.viewer.renderer.isObjectVisible(this.threeMesh)) { return this.viewer.dataHelper.sphericalCoordsToViewerCoords(this.state.position); } else { return null; } } update(config) { super.update(config); const mesh = this.threeMesh; const group = mesh.parent; const material = mesh.material; if (import_core11.utils.isExtendedPosition(this.config.position)) { try { this.state.position = this.viewer.dataHelper.cleanPosition(this.config.position); } catch (e) { throw new import_core11.PSVError(`invalid marker ${this.id} position`, e); } if (!this.config.size) { throw new import_core11.PSVError(`missing marker ${this.id} size`); } this.state.size = this.config.size; mesh.scale.set(this.config.size.width / 100, this.config.size.height / 100, 1); mesh.position.set(mesh.scale.x * (0.5 - this.state.anchor.x), mesh.scale.y * (this.state.anchor.y - 0.5), 0); mesh.rotation.set(0, 0, 0); this.viewer.dataHelper.sphericalCoordsToVector3(this.state.position, group.position); group.lookAt(0, group.position.y, 0); if (this.config.orientation) { import_core11.utils.logWarn(`Marker#orientation is deprecated, use "rotation.yaw" or "rotation.pitch" instead`); mesh.rotateZ(-this.config.rotation.roll); switch (this.config.orientation) { case "horizontal": group.rotateX(this.state.position.pitch < 0 ? -Math.PI / 2 : Math.PI / 2); break; case "vertical-left": group.rotateY(-Math.PI * 0.4); break; case "vertical-right": group.rotateY(Math.PI * 0.4); break; } } else { mesh.rotateY(-this.config.rotation.yaw); mesh.rotateX(-this.config.rotation.pitch); mesh.rotateZ(-this.config.rotation.roll); } const p = mesh.geometry.getAttribute("position"); this.state.positions3D = [0, 1, 3, 2].map((i) => { const v3 = new import_three6.Vector3(); v3.fromBufferAttribute(p, i); return mesh.localToWorld(v3); }); } else { if (this.config.position?.length !== 4) { throw new import_core11.PSVError(`missing marker ${this.id} position`); } let positions; try { positions = this.config.position.map((p2) => this.viewer.dataHelper.cleanPosition(p2)); } catch (e) { throw new import_core11.PSVError(`invalid marker ${this.id} position`, e); } const positions3D = positions.map((p2) => this.viewer.dataHelper.sphericalCoordsToVector3(p2)); const centroid = getPolygonCenter(positions.map(({ yaw, pitch }) => [yaw, pitch])); this.state.position = { yaw: centroid[0], pitch: centroid[1] }; this.state.positions3D = positions3D; const p = mesh.geometry.getAttribute("position"); [ positions3D[0], positions3D[1], positions3D[3], // not a mistake! positions3D[2] ].forEach((v, i) => { p.setX(i, v.x); p.setY(i, v.y); p.setZ(i, v.z); }); p.needsUpdate = true; this.__setTextureWrap(material); } switch (this.type) { case "videoLayer" /* videoLayer */: if (this.definition !== this.config.videoLayer) { material.map?.dispose(); const video = createVideo({ src: this.config.videoLayer, withCredentials: this.viewer.config.withCredentials, muted: true, autoplay: this.config.autoplay ?? true }); const texture = new import_three6.VideoTexture(video); material.map = texture; material.alpha = 0; video.addEventListener("loadedmetadata", () => { if (!this.viewer) { return; } material.alpha = this.config.opacity; if (!import_core11.utils.isExtendedPosition(this.config.position)) { mesh.material.userData[MARKER_DATA] = { width: video.videoWidth, height: video.videoHeight }; this.__setTextureWrap(material); } }, { once: true }); if (video.autoplay) { video.play(); } this.definition = this.config.videoLayer; } else { material.alpha = this.config.opacity; } break; case "imageLayer" /* imageLayer */: if (this.definition !== this.config.imageLayer) { material.map?.dispose(); const texture = new import_three6.Texture(); material.map = texture; material.alpha = 0; this.viewer.textureLoader.loadImage(this.config.imageLayer).then((image) => { if (!this.viewer) { return; } if (!import_core11.utils.isExtendedPosition(this.config.position)) { mesh.material.userData[MARKER_DATA] = { width: image.width, height: image.height }; this.__setTextureWrap(material); } texture.image = image; texture.anisotropy = 4; texture.needsUpdate = true; material.alpha = this.config.opacity; this.viewer.needsUpdate(); }); this.definition = this.config.imageLayer; } else { material.alpha = this.config.opacity; } break; } material.chromaKey = this.config.chromaKey; mesh.renderOrder = 1e3 + this.config.zIndex; mesh.geometry.boundingBox = null; } /** * For layers positionned by corners, applies offset to the texture in order to keep its proportions */ __setTextureWrap(material) { const imageSize = material.userData[MARKER_DATA]; if (!imageSize || !imageSize.height || !imageSize.width) { material.repeat.set(1, 1); material.offset.set(0, 0); return; } const positions = this.config.position.map((p) => { return this.viewer.dataHelper.cleanPosition(p); }); const w1 = import_core11.utils.greatArcDistance( [positions[0].yaw, positions[0].pitch], [positions[1].yaw, positions[1].pitch] ); const w2 = import_core11.utils.greatArcDistance( [positions[3].yaw, positions[3].pitch], [positions[2].yaw, positions[2].pitch] ); const h1 = import_core11.utils.greatArcDistance( [positions[1].yaw, positions[1].pitch], [positions[2].yaw, positions[2].pitch] ); const h2 = import_core11.utils.greatArcDistance( [positions[0].yaw, positions[0].pitch], [positions[3].yaw, positions[3].pitch] ); const layerRatio = (w1 + w2) / (h1 + h2); const imageRatio = imageSize.width / imageSize.height; let hMargin = 0; let vMargin = 0; if (layerRatio < imageRatio) { hMargin = imageRatio - layerRatio; } else { vMargin = 1 / imageRatio - 1 / layerRatio; } material.repeat.set(1 - hMargin, 1 - vMargin); material.offset.set(hMargin / 2, vMargin / 2); } }; // src/markers/MarkerCSS3D.ts var import_core12 = require("@photo-sphere-viewer/core"); var MarkerCSS3D = class extends AbstractDomMarker { constructor(viewer, plugin, config) { super(viewer, plugin, config); /** * @internal */ this.viewportIntersection = false; } get threeElement() { return this.object; } isCss3d() { return true; } createElement() { this.element = document.createElement("div"); this.object = new CSS3DObject(this.element); this.object.userData = { [MARKER_DATA]: this }; Object.defineProperty(this.object, "visible", { enumerable: true, get: function() { return this.userData[MARKER_DATA].config.visible; }, set: function(visible) { this.userData[MARKER_DATA].config.visible = visible; } }); this.afterCreateElement(); } destroy() { delete this.object.userData[MARKER_DATA]; delete this.object; super.destroy(); } render({ viewerPosition, zoomLevel }) { const element = this.domElement; this.state.size = { width: element.offsetWidth, height: element.offsetHeight }; const isVisible = this.state.positions3D[0].dot(this.viewer.state.direction) > 0 && this.viewportIntersection; if (isVisible) { const position = this.viewer.dataHelper.sphericalCoordsToViewerCoords(this.state.position); this.config.elementLayer.updateMarker?.({ marker: this, position, viewerPosition, zoomLevel, viewerSize: this.viewer.state.size }); return position; } else { return null; } } update(config) { super.update(config); if (!import_core12.utils.isExtendedPosition(this.config.position)) { throw new import_core12.PSVError(`missing marker ${this.id} position`); } try { this.state.position = this.viewer.dataHelper.cleanPosition(this.config.position); } catch (e) { throw new import_core12.PSVError(`invalid marker ${this.id} position`, e); } this.state.positions3D = [this.viewer.dataHelper.sphericalCoordsToVector3(this.state.position)]; const object = this.threeElement; const element = this.domElement; element.classList.add("psv-marker--css3d"); element.childNodes.forEach((n) => n.remove()); element.appendChild(this.config.elementLayer); this.config.elementLayer.style.display = "block"; object.position.copy(this.state.positions3D[0]).multiplyScalar(100); object.lookAt(0, this.state.positions3D[0].y * 100, 0); object.rotateY(-this.config.rotation.yaw); object.rotateX(-this.config.rotation.pitch); object.rotateZ(-this.config.rotation.roll); } }; // src/markers/MarkerNormal.ts var import_core13 = require("@photo-sphere-viewer/core"); var MarkerNormal = class extends AbstractStandardMarker { constructor(viewer, plugin, config) { super(viewer, plugin, config); } isNormal() { return true; } createElement() { this.element = document.createElement("div"); this.afterCreateElement(); } render(params) { const position = super.render(params); if (position && this.type === "element" /* element */) { this.config.element.updateMarker?.({ marker: this, position, viewerPosition: params.viewerPosition, zoomLevel: params.zoomLevel, viewerSize: this.viewer.state.size }); } return position; } update(config) { super.update(config); const element = this.domElement; if (this.config.image && !this.config.size) { throw new import_core13.PSVError(`missing marker ${this.id} size`); } if (this.config.size) { this.needsUpdateSize = false; this.state.size = this.config.size; element.style.width = this.config.size.width + "px"; element.style.height = this.config.size.height + "px"; } else { this.needsUpdateSize = true; } switch (this.type) { case "image" /* image */: this.definition = this.config.image; element.style.backgroundImage = `url("${this.config.image}")`; break; case "html" /* html */: this.definition = this.config.html; element.innerHTML = this.config.html; break; case "element" /* element */: if (this.definition !== this.config.element) { this.definition = this.config.element; element.childNodes.forEach((n) => n.remove()); element.appendChild(this.config.element); this.config.element.style.display = "block"; } break; } } }; // src/markers/MarkerPolygon.ts var import_core14 = require("@photo-sphere-viewer/core"); var MarkerPolygon = class extends AbstractDomMarker { constructor(viewer, plugin, config) { super(viewer, plugin, config); } createElement() { this.element = document.createElementNS(SVG_NS, "path"); this.element[MARKER_DATA] = this; } isPoly() { return true; } /** * Checks if it is a polygon/polyline using pixel coordinates */ get isPixels() { return this.type === "polygonPixels" /* polygonPixels */ || this.type === "polylinePixels" /* polylinePixels */; } /** * Checks if it is a polygon marker */ get isPolygon() { return this.type === "polygon" /* polygon */ || this.type === "polygonPixels" /* polygonPixels */; } /** * Checks if it is a polyline marker */ get isPolyline() { return this.type === "polyline" /* polyline */ || this.type === "polylinePixels" /* polylinePixels */; } get coords() { return this.definition; } render() { 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.filter((innerPos) => innerPos.length > 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, "d", points); this.domElement.setAttributeNS(null, "transform", `translate(${position.x} ${position.y})`); return position; } else { return null; } } update(config) { super.update(config); const element = this.domElement; element.classList.add("psv-marker--poly"); if (this.config.svgStyle) { Object.entries(this.config.svgStyle).forEach(([prop, value]) => { element.setAttributeNS(null, import_core14.utils.dasherize(prop), value); }); if (this.isPolyline && !this.config.svgStyle.fill) { element.setAttributeNS(null, "fill", "none"); } } else if (this.isPolygon) { element.setAttributeNS(null, "fill", "rgba(0,0,0,0.5)"); } else if (this.isPolyline) { element.setAttributeNS(null, "fill", "none"); element.setAttributeNS(null, "stroke", "rgb(0,0,0)"); } try { let actualPoly = this.config[this.type]; if (!Array.isArray(actualPoly[0])) { for (let i = 0; i < actualPoly.length; i++) { actualPoly.splice(i, 2, [actualPoly[i], actualPoly[i + 1]]); } } if (!Array.isArray(actualPoly[0][0])) { actualPoly = [actualPoly]; } if (this.isPolyline && actualPoly.length > 1) { throw new import_core14.PSVError(`polylines cannot have holes`); } if (this.isPixels) { this.definition = actualPoly.map((coords) => { return coords.map((coord) => { const sphericalCoords = this.viewer.dataHelper.textureCoordsToSphericalCoords({ textureX: coord[0], textureY: coord[1] }); return [sphericalCoords.yaw, sphericalCoords.pitch]; }); }); } else { this.definition = actualPoly.map((coords) => { return coords.map((coord) => { return [import_core14.utils.parseAngle(coord[0]), import_core14.utils.parseAngle(coord[1], true)]; }); }); } } catch (e) { throw new import_core14.PSVError(`invalid marker ${this.id} position`, e); } const centroid = this.isPolygon ? getPolygonCenter(this.coords[0]) : getPolylineCenter(this.coords[0]); this.state.position = { yaw: centroid[0], pitch: centroid[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]; } __getAllPolyPositions() { return this.positions3D.map((positions) => { return this.__getPolyPositions(positions); }); } /** * Computes viewer coordinates of each point of a polygon/polyline
* It handles points behind the camera by creating intermediary points suitable for the projector */ __getPolyPositions(positions) { const nbVectors = positions.length; const positions3D = positions.map((vector) => { return { vector, visible: vector.dot(this.viewer.state.direction) > 0 }; }); const toBeComputed = []; positions3D.forEach((pos, i) => { if (!pos.visible) { const neighbours = [ i === 0 ? positions3D[nbVectors - 1] : positions3D[i - 1], i === nbVectors - 1 ? positions3D[0] : positions3D[i + 1] ]; neighbours.forEach((neighbour) => { if (neighbour.visible) { toBeComputed.push({ visible: neighbour.vector, invisible: pos.vector, index: i }); } }); } }); toBeComputed.reverse().forEach((pair) => { positions3D.splice(pair.index, 0, { vector: getGreatCircleIntersection(pair.visible, pair.invisible, this.viewer.state.direction), visible: true }); }); return positions3D.filter((pos) => pos.visible).map((pos) => this.viewer.dataHelper.vector3ToViewerCoords(pos.vector)); } }; // src/markers/MarkerSvg.ts var import_core15 = require("@photo-sphere-viewer/core"); var MarkerSvg = class extends AbstractStandardMarker { get svgElement() { return this.domElement.firstElementChild; } constructor(viewer, plugin, config) { super(viewer, plugin, config); } isSvg() { return true; } createElement() { const svgType = this.type === "square" /* square */ ? "rect" : this.type; const elt = document.createElementNS(SVG_NS, svgType); this.element = document.createElementNS(SVG_NS, "svg"); this.element.appendChild(elt); this.afterCreateElement(); } update(config) { super.update(config); const svgElement = this.svgElement; this.needsUpdateSize = true; switch (this.type) { case "square" /* square */: this.definition = { x: 0, y: 0, width: this.config.square, height: this.config.square }; break; case "rect" /* rect */: if (Array.isArray(this.config.rect)) { this.definition = { x: 0, y: 0, width: this.config.rect[0], height: this.config.rect[1] }; } else { this.definition = { x: 0, y: 0, width: this.config.rect.width, height: this.config.rect.height }; } break; case "circle" /* circle */: this.definition = { cx: this.config.circle, cy: this.config.circle, r: this.config.circle }; break; case "ellipse" /* ellipse */: if (Array.isArray(this.config.ellipse)) { this.definition = { cx: this.config.ellipse[0], cy: this.config.ellipse[1], rx: this.config.ellipse[0], ry: this.config.ellipse[1] }; } else { this.definition = { cx: this.config.ellipse.rx, cy: this.config.ellipse.ry, rx: this.config.ellipse.rx, ry: this.config.ellipse.ry }; } break; case "path" /* path */: this.definition = { d: this.config.path }; break; } Object.entries(this.definition).forEach(([prop, value]) => { svgElement.setAttributeNS(null, prop, value); }); if (this.config.svgStyle) { Object.entries(this.config.svgStyle).forEach(([prop, value]) => { svgElement.setAttributeNS(null, import_core15.utils.dasherize(prop), value); }); } else { svgElement.setAttributeNS(null, "fill", "rgba(0,0,0,0.5)"); } } }; // src/MarkersPlugin.ts var getConfig = import_core16.utils.getConfigParser( { clickEventOnMarker: false, gotoMarkerSpeed: "8rpm", markers: null, defaultHoverScale: null }, { defaultHoverScale(defaultHoverScale) { if (!defaultHoverScale) { return null; } if (defaultHoverScale === true) { defaultHoverScale = DEFAULT_HOVER_SCALE; } if (typeof defaultHoverScale === "number") { defaultHoverScale = { amount: defaultHoverScale }; } return { ...DEFAULT_HOVER_SCALE, ...defaultHoverScale }; } } ); function getMarkerCtor(config) { const type = getMarkerType(config, false); switch (type) { case "image": case "html": case "element": return MarkerNormal; case "imageLayer": case "videoLayer": return Marker3D; case "elementLayer": return MarkerCSS3D; case "polygon": case "polyline": case "polygonPixels": case "polylinePixels": return MarkerPolygon; case "square": case "rect": case "circle": case "ellipse": case "path": return MarkerSvg; default: throw new import_core16.PSVError("invalid marker type"); } } var MarkersPlugin = class extends import_core16.AbstractConfigurablePlugin { constructor(viewer, config) { super(viewer, config); this.markers = {}; this.state = { allVisible: true, showAllTooltips: false, currentMarker: null, hoveringMarker: null, // require a 2nd render (only the scene) when 3d markers visibility changes needsReRender: false, // use when updating a polygon marker in order to keep the current position lastClientX: null, lastClientY: null }; this.container = document.createElement("div"); this.container.className = "psv-markers"; this.viewer.container.appendChild(this.container); this.svgContainer = document.createElementNS(SVG_NS, "svg"); this.svgContainer.setAttribute("class", "psv-markers-svg-container"); this.container.appendChild(this.svgContainer); this.css3DContainer = new CSS3DContainer(viewer); this.container.appendChild(this.css3DContainer.element); this.container.addEventListener("mouseenter", this, true); this.container.addEventListener("mouseleave", this, true); this.container.addEventListener("mousemove", this, true); this.container.addEventListener("contextmenu", this); } /** * @internal */ init() { super.init(); import_core16.utils.checkStylesheet(this.viewer.container, "markers-plugin"); this.viewer.addEventListener(import_core16.events.ClickEvent.type, this); this.viewer.addEventListener(import_core16.events.DoubleClickEvent.type, this); this.viewer.addEventListener(import_core16.events.RenderEvent.type, this); this.viewer.addEventListener(import_core16.events.ConfigChangedEvent.type, this); this.viewer.addEventListener(import_core16.events.ObjectEnterEvent.type, this); this.viewer.addEventListener(import_core16.events.ObjectHoverEvent.type, this); this.viewer.addEventListener(import_core16.events.ObjectLeaveEvent.type, this); this.viewer.addEventListener(import_core16.events.ReadyEvent.type, this, { once: true }); } /** * @internal */ destroy() { this.clearMarkers(false); this.viewer.unobserveObjects(MARKER_DATA); this.viewer.removeEventListener(import_core16.events.ClickEvent.type, this); this.viewer.removeEventListener(import_core16.events.DoubleClickEvent.type, this); this.viewer.removeEventListener(import_core16.events.RenderEvent.type, this); this.viewer.removeEventListener(import_core16.events.ObjectEnterEvent.type, this); this.viewer.removeEventListener(import_core16.events.ObjectHoverEvent.type, this); this.viewer.removeEventListener(import_core16.events.ObjectLeaveEvent.type, this); this.viewer.removeEventListener(import_core16.events.ReadyEvent.type, this); this.css3DContainer.destroy(); this.viewer.container.removeChild(this.container); super.destroy(); } /** * @internal */ handleEvent(e) { switch (e.type) { case import_core16.events.ReadyEvent.type: if (this.config.markers) { this.setMarkers(this.config.markers); delete this.config.markers; } break; case import_core16.events.RenderEvent.type: this.renderMarkers(); break; case import_core16.events.ClickEvent.type: this.__onClick(e, false); break; case import_core16.events.DoubleClickEvent.type: this.__onClick(e, true); break; case import_core16.events.ObjectEnterEvent.type: case import_core16.events.ObjectLeaveEvent.type: case import_core16.events.ObjectHoverEvent.type: if (e.userDataKey === MARKER_DATA) { const event = e.originalEvent; const marker = e.object.userData[MARKER_DATA]; switch (e.type) { case import_core16.events.ObjectEnterEvent.type: if (marker.config.style?.cursor) { this.viewer.setCursor(marker.config.style.cursor); } else if (marker.config.tooltip || marker.config.content) { this.viewer.setCursor("pointer"); } this.__onEnterMarker(event, marker); break; case import_core16.events.ObjectLeaveEvent.type: this.viewer.setCursor(null); this.__onLeaveMarker(marker); break; case import_core16.events.ObjectHoverEvent.type: this.__onHoverMarker(event, marker); break; } } break; case "mouseenter": { const marker = this.__getTargetMarker(import_core16.utils.getEventTarget(e)); this.__onEnterMarker(e, marker); break; } case "mouseleave": { const marker = this.__getTargetMarker(import_core16.utils.getEventTarget(e)); this.__onLeaveMarker(marker); break; } case "mousemove": { const marker = this.__getTargetMarker(import_core16.utils.getEventTarget(e), true); this.__onHoverMarker(e, marker); break; } case "contextmenu": e.preventDefault(); break; } } /** * Toggles all markers */ toggleAllMarkers() { if (this.state.allVisible) { this.hideAllMarkers(); } else { this.showAllMarkers(); } } /** * Shows all markers */ showAllMarkers() { this.state.allVisible = true; Object.values(this.markers).forEach((marker) => { marker.config.visible = true; }); this.renderMarkers(); this.dispatchEvent(new ShowMarkersEvent()); } /** * Hides all markers */ hideAllMarkers() { this.state.allVisible = false; Object.values(this.markers).forEach((marker) => { marker.config.visible = false; }); this.renderMarkers(); this.dispatchEvent(new HideMarkersEvent()); } /** * Toggles the visibility of all tooltips */ toggleAllTooltips() { if (this.state.showAllTooltips) { this.hideAllTooltips(); } else { this.showAllTooltips(); } } /** * Displays all tooltips */ showAllTooltips() { this.state.showAllTooltips = true; Object.values(this.markers).forEach((marker) => { marker.state.staticTooltip = true; marker.showTooltip(); }); } /** * Hides all tooltips */ hideAllTooltips() { this.state.showAllTooltips = false; Object.values(this.markers).forEach((marker) => { marker.state.staticTooltip = false; marker.hideTooltip(); }); } /** * Returns the total number of markers */ getNbMarkers() { return Object.keys(this.markers).length; } /** * Returns all the markers */ getMarkers() { return Object.values(this.markers); } /** * Adds a new marker to viewer * @throws {@link PSVError} when the marker's id is missing or already exists */ addMarker(config, render = true) { if (this.markers[config.id]) { throw new import_core16.PSVError(`marker "${config.id}" already exists`); } const marker = new (getMarkerCtor(config))(this.viewer, this, config); if (marker.isPoly()) { this.svgContainer.appendChild(marker.domElement); } else if (marker.isCss3d()) { this.css3DContainer.addObject(marker); } else if (marker.is3d()) { this.viewer.renderer.addObject(marker.threeElement); } else { this.container.appendChild(marker.domElement); } this.markers[marker.id] = marker; if (this.state.showAllTooltips) { marker.state.staticTooltip = true; } if (render) { this.__afterChangeMarkers(); } } /** * Returns the internal marker object for a marker id * @throws {@link PSVError} when the marker cannot be found */ getMarker(markerId) { const id = typeof markerId === "object" ? markerId.id : markerId; if (!this.markers[id]) { throw new import_core16.PSVError(`cannot find marker "${id}"`); } return this.markers[id]; } /** * Returns the last marker selected by the user */ getCurrentMarker() { return this.state.currentMarker; } /** * Updates the existing marker with the same id * Every property can be changed but you can't change its type (Eg: `image` to `html`) */ updateMarker(config, render = true) { const marker = this.getMarker(config.id); marker.update(config); if (render) { this.__afterChangeMarkers(); if (marker === this.state.hoveringMarker && marker.config.tooltip?.trigger === "hover" || marker.state.staticTooltip) { marker.showTooltip(this.state.lastClientX, this.state.lastClientY, true); } } } /** * Removes a marker from the viewer */ removeMarker(markerId, render = true) { const marker = this.getMarker(markerId); if (marker.isPoly()) { this.svgContainer.removeChild(marker.domElement); } else if (marker.isCss3d()) { this.css3DContainer.removeObject(marker); } else if (marker.is3d()) { this.viewer.renderer.removeObject(marker.threeElement); } else { this.container.removeChild(marker.domElement); } if (this.state.hoveringMarker === marker) { this.state.hoveringMarker = null; } if (this.state.currentMarker === marker) { this.state.currentMarker = null; } marker.destroy(); delete this.markers[marker.id]; if (render) { this.__afterChangeMarkers(); } } /** * Removes multiple markers */ removeMarkers(markerIds, render = true) { markerIds.forEach((markerId) => this.removeMarker(markerId, false)); if (render) { this.__afterChangeMarkers(); } } /** * Replaces all markers */ setMarkers(markers, render = true) { this.clearMarkers(false); markers?.forEach((marker) => { this.addMarker(marker, false); }); if (render) { this.__afterChangeMarkers(); } } /** * Removes all markers */ clearMarkers(render = true) { Object.keys(this.markers).forEach((markerId) => { this.removeMarker(markerId, false); }); if (render) { this.__afterChangeMarkers(); } } /** * Rotate the view to face the marker */ gotoMarker(markerId, speed = this.config.gotoMarkerSpeed) { const marker = this.getMarker(markerId); if (!speed) { this.viewer.rotate(marker.state.position); if (!import_core16.utils.isNil(marker.config.zoomLvl)) { this.viewer.zoom(marker.config.zoomLvl); } this.dispatchEvent(new GotoMarkerDoneEvent(marker)); return Promise.resolve(); } else { return this.viewer.animate({ ...marker.state.position, zoom: marker.config.zoomLvl, speed }).then(() => { this.dispatchEvent(new GotoMarkerDoneEvent(marker)); }); } } /** * Hides a marker */ hideMarker(markerId) { this.toggleMarker(markerId, false); } /** * Shows a marker */ showMarker(markerId) { this.toggleMarker(markerId, true); } /** * Forces the display of the tooltip of a marker */ showMarkerTooltip(markerId) { const marker = this.getMarker(markerId); marker.state.staticTooltip = true; marker.showTooltip(); } /** * Hides the tooltip of a marker */ hideMarkerTooltip(markerId) { const marker = this.getMarker(markerId); marker.state.staticTooltip = false; marker.hideTooltip(); } /** * Toggles a marker visibility */ toggleMarker(markerId, visible) { const marker = this.getMarker(markerId); marker.config.visible = import_core16.utils.isNil(visible) ? !marker.config.visible : visible; this.renderMarkers(); } /** * Opens the panel with the content of the marker */ showMarkerPanel(markerId) { const marker = this.getMarker(markerId); if (marker.config.content) { this.viewer.panel.show({ id: ID_PANEL_MARKER, content: marker.config.content }); } else { this.hideMarkerPanel(); } } /** * Closes the panel if currently showing the content of a marker */ hideMarkerPanel() { this.viewer.panel.hide(ID_PANEL_MARKER); } /** * Toggles the visibility of the list of markers */ toggleMarkersList() { if (this.viewer.panel.isVisible(ID_PANEL_MARKERS_LIST)) { this.hideMarkersList(); } else { this.showMarkersList(); } } /** * Opens side panel with the list of markers */ showMarkersList() { let markers = []; Object.values(this.markers).forEach((marker) => { if (marker.config.visible && !marker.config.hideList) { markers.push(marker); } }); const e = new RenderMarkersListEvent(markers); this.dispatchEvent(e); markers = e.markers; this.viewer.panel.show({ id: ID_PANEL_MARKERS_LIST, content: MARKERS_LIST_TEMPLATE(markers, this.viewer.config.lang[MarkersButton.id]), noMargin: true, clickHandler: (target) => { const li = import_core16.utils.getClosest(target, ".psv-panel-menu-item"); const markerId = li ? li.dataset[MARKER_DATA] : void 0; if (markerId) { const marker = this.getMarker(markerId); this.dispatchEvent(new SelectMarkerListEvent(marker)); this.gotoMarker(marker.id); this.hideMarkersList(); } } }); } /** * Closes side panel if it contains the list of markers */ hideMarkersList() { this.viewer.panel.hide(ID_PANEL_MARKERS_LIST); } /** * Updates the visibility and the position of all markers */ renderMarkers() { if (this.state.needsReRender) { this.state.needsReRender = false; return; } const zoomLevel = this.viewer.getZoomLevel(); const viewerPosition = this.viewer.getPosition(); const hoveringMarker = this.state.hoveringMarker; Object.values(this.markers).forEach((marker) => { let isVisible = marker.config.visible; let visibilityChanged = false; let position = null; if (isVisible) { position = marker.render({ viewerPosition, zoomLevel, hoveringMarker }); isVisible = !!position; } visibilityChanged = marker.state.visible !== isVisible; marker.state.visible = isVisible; marker.state.position2D = position; if (marker.domElement) { import_core16.utils.toggleClass(marker.domElement, "psv-marker--visible", isVisible); } if (!isVisible) { marker.hideTooltip(); } else if (marker.state.staticTooltip) { marker.showTooltip(); } else if (marker !== this.state.hoveringMarker) { marker.hideTooltip(); } if (visibilityChanged) { this.dispatchEvent(new MarkerVisibilityEvent(marker, isVisible)); if (marker.is3d() || marker.isCss3d()) { this.state.needsReRender = true; } } }); if (this.state.needsReRender) { this.viewer.needsUpdate(); } } __getTargetMarker(target, closest = false) { if (target instanceof Node) { const target2 = closest ? import_core16.utils.getClosest(target, ".psv-marker") : target; return target2 ? target2[MARKER_DATA] : void 0; } else if (Array.isArray(target)) { return target.map((o) => o.userData[MARKER_DATA]).filter((m) => !!m).sort((a, b) => b.config.zIndex - a.config.zIndex)[0]; } else { return null; } } /** * Handles mouse enter events, show the tooltip for non polygon markers */ __onEnterMarker(e, marker) { if (marker) { this.state.hoveringMarker = marker; this.state.lastClientX = e.clientX; this.state.lastClientY = e.clientY; this.dispatchEvent(new EnterMarkerEvent(marker)); if (marker instanceof AbstractStandardMarker) { marker.applyScale({ zoomLevel: this.viewer.getZoomLevel(), viewerPosition: this.viewer.getPosition(), mouseover: true }); } if (!marker.state.staticTooltip && marker.config.tooltip?.trigger === "hover") { marker.showTooltip(e.clientX, e.clientY); } } } /** * Handles mouse leave events, hide the tooltip */ __onLeaveMarker(marker) { if (marker) { this.dispatchEvent(new LeaveMarkerEvent(marker)); if (marker instanceof AbstractStandardMarker) { marker.applyScale({ zoomLevel: this.viewer.getZoomLevel(), viewerPosition: this.viewer.getPosition(), mouseover: false }); } this.state.hoveringMarker = null; if (!marker.state.staticTooltip && marker.config.tooltip?.trigger === "hover") { marker.hideTooltip(); } else if (marker.state.staticTooltip) { marker.showTooltip(); } } } /** * Handles mouse move events, refresh the tooltip for polygon markers */ __onHoverMarker(e, marker) { if (marker) { this.state.lastClientX = e.clientX; this.state.lastClientY = e.clientY; if (marker.isPoly() || marker.is3d() || marker.isCss3d()) { if (marker.config.tooltip?.trigger === "hover") { marker.showTooltip(e.clientX, e.clientY); } } } } /** * Handles mouse click events, select the marker and open the panel if necessary */ __onClick(e, dblclick) { const threeMarker = this.__getTargetMarker(e.data.objects); const stdMarker = this.__getTargetMarker(e.data.target, true); const marker = stdMarker || threeMarker; if (this.state.currentMarker && this.state.currentMarker !== marker) { this.dispatchEvent(new UnselectMarkerEvent(this.state.currentMarker)); this.viewer.panel.hide(ID_PANEL_MARKER); if (!this.state.showAllTooltips && this.state.currentMarker.config.tooltip?.trigger === "click") { this.hideMarkerTooltip(this.state.currentMarker.id); } this.state.currentMarker = null; } if (marker) { this.state.currentMarker = marker; this.dispatchEvent(new SelectMarkerEvent(marker, dblclick, e.data.rightclick)); if (this.config.clickEventOnMarker) { e.data.marker = marker; } else { e.stopImmediatePropagation(); } if (this.markers[marker.id] && !e.data.rightclick) { if (marker.config.tooltip?.trigger === "click") { if (marker.tooltip) { this.hideMarkerTooltip(marker.id); } else { this.showMarkerTooltip(marker.id); } } else { this.showMarkerPanel(marker.id); } } } } __afterChangeMarkers() { this.__refreshUi(); this.__checkObjectsObserver(); this.viewer.needsUpdate(); this.dispatchEvent(new SetMarkersEvent(this.getMarkers())); } /** * Updates the visiblity of the panel and the buttons */ __refreshUi() { const nbMarkers = Object.values(this.markers).filter((m) => !m.config.hideList).length; if (nbMarkers === 0) { if (this.viewer.panel.isVisible(ID_PANEL_MARKERS_LIST) || this.viewer.panel.isVisible(ID_PANEL_MARKER)) { this.viewer.panel.hide(); } } else { if (this.viewer.panel.isVisible(ID_PANEL_MARKERS_LIST)) { this.showMarkersList(); } else if (this.viewer.panel.isVisible(ID_PANEL_MARKER)) { this.state.currentMarker ? this.showMarkerPanel(this.state.currentMarker.id) : this.viewer.panel.hide(); } } this.viewer.navbar.getButton(MarkersButton.id, false)?.toggle(nbMarkers > 0); this.viewer.navbar.getButton(MarkersListButton.id, false)?.toggle(nbMarkers > 0); } /** * Adds or remove the objects observer if there are 3D markers */ __checkObjectsObserver() { const has3d = Object.values(this.markers).some((marker) => marker.is3d()); if (has3d) { this.viewer.observeObjects(MARKER_DATA); } else { this.viewer.unobserveObjects(MARKER_DATA); } } }; MarkersPlugin.id = "markers"; MarkersPlugin.VERSION = "5.11.1"; MarkersPlugin.configParser = getConfig; MarkersPlugin.readonlyOptions = ["markers"]; // src/index.ts import_core17.DEFAULTS.lang[MarkersButton.id] = "Markers"; import_core17.DEFAULTS.lang[MarkersListButton.id] = "Markers list"; (0, import_core17.registerButton)(MarkersButton, "caption:left"); (0, import_core17.registerButton)(MarkersListButton, "caption:left"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { MarkersPlugin, events }); //# sourceMappingURL=index.cjs.map