UNPKG

@photo-sphere-viewer/markers-plugin

Version:

Photo Sphere Viewer plugin to display various markers/hotspots on the viewer.

1 lines 165 kB
{"version":3,"sources":["src/index.ts","src/events.ts","src/MarkersButton.ts","src/icons/pin.svg","src/MarkersListButton.ts","src/constants.ts","src/icons/pin-list.svg","src/MarkersPlugin.ts","src/CSS3DContainer.ts","../node_modules/three/examples/jsm/renderers/CSS3DRenderer.js","src/MarkerType.ts","src/markers/AbstractStandardMarker.ts","src/markers/AbstractDomMarker.ts","src/markers/Marker.ts","src/markers/Marker3D.ts","../shared/ChromaKeyMaterial.ts","../shared/shaders/chromaKey.fragment.glsl","../shared/shaders/chromaKey.vertex.glsl","../shared/video-utils.ts","src/utils.ts","src/markers/MarkerCSS3D.ts","src/markers/MarkerNormal.ts","src/markers/MarkerPolygon.ts","src/markers/MarkerSvg.ts"],"sourcesContent":["import { DEFAULTS, registerButton } from '@photo-sphere-viewer/core';\nimport * as events from './events';\nimport { MarkersButton } from './MarkersButton';\nimport { MarkersListButton } from './MarkersListButton';\n\nDEFAULTS.lang[MarkersButton.id] = 'Markers';\nDEFAULTS.lang[MarkersListButton.id] = 'Markers list';\nregisterButton(MarkersButton, 'caption:left');\nregisterButton(MarkersListButton, 'caption:left');\n\nexport type { Marker } from './markers/Marker';\nexport type { MarkerType } from './MarkerType';\nexport { MarkersPlugin } from './MarkersPlugin';\nexport * from './model';\nexport { events };\n\n/** @internal */\nimport './styles/index.scss';\n","import { TypedEvent } from '@photo-sphere-viewer/core';\nimport type { Marker } from './markers/Marker';\nimport type { MarkersPlugin } from './MarkersPlugin';\n\n/**\n * Base class for events dispatched by {@link MarkersPlugin}\n */\nexport abstract class MarkersPluginEvent extends TypedEvent<MarkersPlugin> {}\n\n/**\n * @event Triggered when the visibility of a marker changes\n */\nexport class MarkerVisibilityEvent extends MarkersPluginEvent {\n static override readonly type = 'marker-visibility';\n override type: 'marker-visibility';\n\n /** @internal */\n constructor(\n public readonly marker: Marker,\n public readonly visible: boolean,\n ) {\n super(MarkerVisibilityEvent.type);\n }\n}\n\n/**\n * @event Triggered when the animation to a marker is done\n */\nexport class GotoMarkerDoneEvent extends MarkersPluginEvent {\n static override readonly type = 'goto-marker-done';\n override type: 'goto-marker-done';\n\n /** @internal */\n constructor(public readonly marker: Marker) {\n super(GotoMarkerDoneEvent.type);\n }\n}\n\n/**\n * @event Triggered when the user puts the cursor away from a marker\n */\nexport class LeaveMarkerEvent extends MarkersPluginEvent {\n static override readonly type = 'leave-marker';\n override type: 'leave-marker';\n\n /** @internal */\n constructor(public readonly marker: Marker) {\n super(LeaveMarkerEvent.type);\n }\n}\n\n/**\n * @event Triggered when the user puts the cursor hover a marker\n */\nexport class EnterMarkerEvent extends MarkersPluginEvent {\n static override readonly type = 'enter-marker';\n override type: 'enter-marker';\n\n /** @internal */\n constructor(public readonly marker: Marker) {\n super(EnterMarkerEvent.type);\n }\n}\n\n/**\n * @event Triggered when the user clicks on a marker\n */\nexport class SelectMarkerEvent extends MarkersPluginEvent {\n static override readonly type = 'select-marker';\n override type: 'select-marker';\n\n /** @internal */\n constructor(\n public readonly marker: Marker,\n public readonly doubleClick: boolean,\n public readonly rightClick: boolean,\n ) {\n super(SelectMarkerEvent.type);\n }\n}\n\n/**\n * @event Triggered when a marker is selected from the side panel\n */\nexport class SelectMarkerListEvent extends MarkersPluginEvent {\n static override readonly type = 'select-marker-list';\n override type: 'select-marker-list';\n\n /** @internal */\n constructor(public readonly marker: Marker) {\n super(SelectMarkerListEvent.type);\n }\n}\n\n/**\n * @event Triggered when a marker was selected and the user clicks elsewhere\n */\nexport class UnselectMarkerEvent extends MarkersPluginEvent {\n static override readonly type = 'unselect-marker';\n override type: 'unselect-marker';\n\n /** @internal */\n constructor(public readonly marker: Marker) {\n super(UnselectMarkerEvent.type);\n }\n}\n\n/**\n * @event Triggered when the markers are hidden\n */\nexport class HideMarkersEvent extends MarkersPluginEvent {\n static override readonly type = 'hide-markers';\n override type: 'hide-markers';\n\n /** @internal */\n constructor() {\n super(HideMarkersEvent.type);\n }\n}\n\n/**\n * @event Triggered when the markers change\n */\nexport class SetMarkersEvent extends MarkersPluginEvent {\n static override readonly type = 'set-markers';\n override type: 'set-markers';\n\n /** @internal */\n constructor(public readonly markers: Marker[]) {\n super(SetMarkersEvent.type);\n }\n}\n\n/**\n * @event Triggered when the markers are shown\n */\nexport class ShowMarkersEvent extends MarkersPluginEvent {\n static override readonly type = 'show-markers';\n override type: 'show-markers';\n\n /** @internal */\n constructor() {\n super(ShowMarkersEvent.type);\n }\n}\n\n/**\n * @event Used to alter the list of markers displayed in the side-panel\n */\nexport class RenderMarkersListEvent extends MarkersPluginEvent {\n static override readonly type = 'render-markers-list';\n override type: 'render-markers-list';\n\n /** @internal */\n constructor(\n /** the list of markers to display, can be modified */\n public markers: Marker[],\n ) {\n super(RenderMarkersListEvent.type);\n }\n}\n\nexport type MarkersPluginEvents =\n | MarkerVisibilityEvent\n | GotoMarkerDoneEvent\n | LeaveMarkerEvent\n | EnterMarkerEvent\n | SelectMarkerEvent\n | SelectMarkerListEvent\n | UnselectMarkerEvent\n | HideMarkersEvent\n | SetMarkersEvent\n | ShowMarkersEvent\n | RenderMarkersListEvent;\n","import type { Navbar } from '@photo-sphere-viewer/core';\nimport { AbstractButton } from '@photo-sphere-viewer/core';\nimport { HideMarkersEvent, ShowMarkersEvent } from './events';\nimport type { MarkersPlugin } from './MarkersPlugin';\nimport pin from './icons/pin.svg';\n\nexport class MarkersButton extends AbstractButton {\n static override readonly id = 'markers';\n\n private readonly plugin: MarkersPlugin;\n\n constructor(navbar: Navbar) {\n super(navbar, {\n className: 'psv-markers-button',\n icon: pin,\n hoverScale: true,\n collapsable: true,\n tabbable: true,\n });\n\n this.plugin = this.viewer.getPlugin('markers');\n\n if (this.plugin) {\n this.plugin.addEventListener(ShowMarkersEvent.type, this);\n this.plugin.addEventListener(HideMarkersEvent.type, this);\n\n this.toggleActive(true);\n }\n }\n\n override destroy() {\n if (this.plugin) {\n this.plugin.removeEventListener(ShowMarkersEvent.type, this);\n this.plugin.removeEventListener(HideMarkersEvent.type, this);\n }\n\n super.destroy();\n }\n\n override isSupported() {\n return !!this.plugin;\n }\n\n handleEvent(e: Event) {\n if (e instanceof ShowMarkersEvent) {\n this.toggleActive(true);\n } else if (e instanceof HideMarkersEvent) {\n this.toggleActive(false);\n }\n }\n\n onClick() {\n this.plugin.toggleAllMarkers();\n }\n}\n","<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"10 9 81 81\"><path fill=\"currentColor\" d=\"M50.5 90S22.9 51.9 22.9 36.6 35.2 9 50.5 9s27.6 12.4 27.6 27.6S50.5 90 50.5 90zm0-66.3c-6.1 0-11 4.9-11 11s4.9 11 11 11 11-4.9 11-11-4.9-11-11-11z\"/><!--Created by Rohith M S from the Noun Project--></svg>\n","import type { Navbar } from '@photo-sphere-viewer/core';\nimport { AbstractButton, events } from '@photo-sphere-viewer/core';\nimport { ID_PANEL_MARKERS_LIST } from './constants';\nimport type { MarkersPlugin } from './MarkersPlugin';\nimport pinList from './icons/pin-list.svg';\n\nexport class MarkersListButton extends AbstractButton {\n static override readonly id = 'markersList';\n\n private readonly plugin: MarkersPlugin;\n\n constructor(navbar: Navbar) {\n super(navbar, {\n className: ' psv-markers-list-button',\n icon: pinList,\n hoverScale: true,\n collapsable: true,\n tabbable: true,\n });\n\n this.plugin = this.viewer.getPlugin('markers');\n\n if (this.plugin) {\n this.viewer.addEventListener(events.ShowPanelEvent.type, this);\n this.viewer.addEventListener(events.HidePanelEvent.type, this);\n }\n }\n\n override destroy() {\n this.viewer.removeEventListener(events.ShowPanelEvent.type, this);\n this.viewer.removeEventListener(events.HidePanelEvent.type, this);\n\n super.destroy();\n }\n\n override isSupported() {\n return !!this.plugin;\n }\n\n handleEvent(e: Event) {\n if (e instanceof events.ShowPanelEvent) {\n this.toggleActive(e.panelId === ID_PANEL_MARKERS_LIST);\n } else if (e instanceof events.HidePanelEvent) {\n this.toggleActive(false);\n }\n }\n\n onClick() {\n this.plugin.toggleMarkersList();\n }\n}\n","import { utils } from '@photo-sphere-viewer/core';\nimport type { Marker } from './markers/Marker';\nimport icon from './icons/pin-list.svg';\n\n/**\n * Namespace for SVG creation\n * @internal\n */\nexport const SVG_NS = 'http://www.w3.org/2000/svg';\n\n/**\n * Property name added to marker elements\n * @internal\n */\nexport const MARKER_DATA = 'psvMarker';\n\n/**\n * Property name added to marker elements (dash-case)\n * @internal\n */\nexport const MARKER_DATA_KEY = utils.dasherize(MARKER_DATA);\n\n/**\n * Panel identifier for marker content\n * @internal\n */\nexport const ID_PANEL_MARKER = 'marker';\n\n/**\n * Panel identifier for markers list\n * @internal\n */\nexport const ID_PANEL_MARKERS_LIST = 'markersList';\n\n/**\n * Default configuration for the \"hoverScale\" parameters\n * @internal\n */\nexport const DEFAULT_HOVER_SCALE = {\n amount: 2,\n duration: 100,\n easing: 'linear',\n};\n\n/**\n * Markers list template\n * @internal\n */\nexport const MARKERS_LIST_TEMPLATE = (markers: Marker[], title: string) => `\n<div class=\"psv-panel-menu psv-panel-menu--stripped\">\n <h1 class=\"psv-panel-menu-title\">${icon} ${title}</h1>\n <ul class=\"psv-panel-menu-list\">\n ${markers.map(marker => `\n <li data-${MARKER_DATA_KEY}=\"${marker.id}\" class=\"psv-panel-menu-item\" tabindex=\"0\">\n ${marker.type === 'image' ? `<span class=\"psv-panel-menu-item-icon\"><img src=\"${marker.definition}\"/></span>` : ''}\n <span class=\"psv-panel-menu-item-label\">${marker.getListContent()}</span>\n </li>\n `).join('')}\n </ul>\n</div>\n`;\n","<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"9 9 81 81\"><path fill=\"currentColor\" d=\"M37.5 90S9.9 51.9 9.9 36.6 22.2 9 37.5 9s27.6 12.4 27.6 27.6S37.5 90 37.5 90zm0-66.3c-6.1 0-11 4.9-11 11s4.9 11 11 11 11-4.9 11-11-4.9-11-11-11zM86.7 55H70c-1.8 0-3.3-1.5-3.3-3.3s1.5-3.3 3.3-3.3h16.7c1.8 0 3.3 1.5 3.3 3.3S88.5 55 86.7 55zm0-25h-15a3.3 3.3 0 0 1-3.3-3.3c0-1.8 1.5-3.3 3.3-3.3h15c1.8 0 3.3 1.5 3.3 3.3 0 1.8-1.5 3.3-3.3 3.3zM56.5 73h30c1.8 0 3.3 1.5 3.3 3.3 0 1.8-1.5 3.3-3.3 3.3h-30a3.3 3.3 0 0 1-3.3-3.3 3.2 3.2 0 0 1 3.3-3.3z\"/><!--Created by Rohith M S from the Noun Project--></svg>\n","import type { PluginConstructor, Point, Viewer } from '@photo-sphere-viewer/core';\nimport { AbstractConfigurablePlugin, PSVError, events, utils } from '@photo-sphere-viewer/core';\nimport { Object3D } from 'three';\nimport { CSS3DContainer } from './CSS3DContainer';\nimport { getMarkerType } from './MarkerType';\nimport { MarkersButton } from './MarkersButton';\nimport { MarkersListButton } from './MarkersListButton';\nimport {\n DEFAULT_HOVER_SCALE,\n ID_PANEL_MARKER,\n ID_PANEL_MARKERS_LIST,\n MARKERS_LIST_TEMPLATE,\n MARKER_DATA,\n SVG_NS,\n} from './constants';\nimport {\n EnterMarkerEvent,\n GotoMarkerDoneEvent,\n HideMarkersEvent,\n LeaveMarkerEvent,\n MarkerVisibilityEvent,\n MarkersPluginEvents,\n RenderMarkersListEvent,\n SelectMarkerEvent,\n SelectMarkerListEvent,\n SetMarkersEvent,\n ShowMarkersEvent,\n UnselectMarkerEvent,\n} from './events';\nimport { AbstractStandardMarker } from './markers/AbstractStandardMarker';\nimport { Marker } from './markers/Marker';\nimport { Marker3D } from './markers/Marker3D';\nimport { MarkerCSS3D } from './markers/MarkerCSS3D';\nimport { MarkerNormal } from './markers/MarkerNormal';\nimport { MarkerPolygon } from './markers/MarkerPolygon';\nimport { MarkerSvg } from './markers/MarkerSvg';\nimport { MarkerConfig, MarkersPluginConfig, ParsedMarkersPluginConfig, UpdatableMarkersPluginConfig } from './model';\n\nconst getConfig = utils.getConfigParser<MarkersPluginConfig, ParsedMarkersPluginConfig>(\n {\n clickEventOnMarker: false,\n gotoMarkerSpeed: '8rpm',\n markers: null,\n defaultHoverScale: null,\n },\n {\n defaultHoverScale(defaultHoverScale) {\n if (!defaultHoverScale) {\n return null;\n }\n if (defaultHoverScale === true) {\n defaultHoverScale = DEFAULT_HOVER_SCALE;\n }\n if (typeof defaultHoverScale === 'number') {\n defaultHoverScale = { amount: defaultHoverScale };\n }\n return {\n ...DEFAULT_HOVER_SCALE,\n ...defaultHoverScale,\n };\n },\n },\n);\n\nfunction getMarkerCtor(config: MarkerConfig): typeof Marker {\n const type = getMarkerType(config, false);\n\n switch (type) {\n case 'image':\n case 'html':\n case 'element':\n return MarkerNormal;\n case 'imageLayer':\n case 'videoLayer':\n return Marker3D;\n case 'elementLayer':\n return MarkerCSS3D;\n case 'polygon':\n case 'polyline':\n case 'polygonPixels':\n case 'polylinePixels':\n return MarkerPolygon;\n case 'square':\n case 'rect':\n case 'circle':\n case 'ellipse':\n case 'path':\n return MarkerSvg;\n default:\n throw new PSVError('invalid marker type');\n }\n}\n\n/**\n * Displays various markers on the viewer\n */\nexport class MarkersPlugin extends AbstractConfigurablePlugin<\n MarkersPluginConfig,\n ParsedMarkersPluginConfig,\n UpdatableMarkersPluginConfig,\n MarkersPluginEvents\n> {\n static override readonly id = 'markers';\n static override readonly VERSION = PKG_VERSION;\n static override readonly configParser = getConfig;\n static override readonly readonlyOptions: Array<keyof MarkersPluginConfig> = ['markers'];\n\n private readonly markers: Record<string, Marker> = {};\n\n private readonly state = {\n allVisible: true,\n showAllTooltips: false,\n currentMarker: null as Marker,\n hoveringMarker: null as Marker,\n // require a 2nd render (only the scene) when 3d markers visibility changes\n needsReRender: false,\n // use when updating a polygon marker in order to keep the current position\n lastClientX: null as number,\n lastClientY: null as number,\n };\n\n private readonly container: HTMLElement;\n private readonly svgContainer: SVGElement;\n private readonly css3DContainer: CSS3DContainer;\n\n static withConfig(config: MarkersPluginConfig): [PluginConstructor, any] {\n return [MarkersPlugin, config];\n }\n\n constructor(viewer: Viewer, config: MarkersPluginConfig) {\n super(viewer, config);\n\n this.container = document.createElement('div');\n this.container.className = 'psv-markers';\n this.viewer.container.appendChild(this.container);\n\n this.container.addEventListener('contextmenu', e => e.preventDefault());\n\n this.svgContainer = document.createElementNS(SVG_NS, 'svg');\n this.svgContainer.setAttribute('class', 'psv-markers-svg-container');\n this.container.appendChild(this.svgContainer);\n\n this.css3DContainer = new CSS3DContainer(viewer);\n this.container.appendChild(this.css3DContainer.element);\n\n // Markers events via delegation\n this.container.addEventListener('mouseenter', this, true);\n this.container.addEventListener('mouseleave', this, true);\n this.container.addEventListener('mousemove', this, true);\n }\n\n /**\n * @internal\n */\n override init() {\n super.init();\n\n utils.checkStylesheet(this.viewer.container, 'markers-plugin');\n\n // Viewer events\n this.viewer.addEventListener(events.ClickEvent.type, this);\n this.viewer.addEventListener(events.DoubleClickEvent.type, this);\n this.viewer.addEventListener(events.RenderEvent.type, this);\n this.viewer.addEventListener(events.ConfigChangedEvent.type, this);\n this.viewer.addEventListener(events.ObjectEnterEvent.type, this);\n this.viewer.addEventListener(events.ObjectHoverEvent.type, this);\n this.viewer.addEventListener(events.ObjectLeaveEvent.type, this);\n this.viewer.addEventListener(events.ReadyEvent.type, this, { once: true });\n }\n\n /**\n * @internal\n */\n override destroy() {\n this.clearMarkers(false);\n\n this.viewer.unobserveObjects(MARKER_DATA);\n\n this.viewer.removeEventListener(events.ClickEvent.type, this);\n this.viewer.removeEventListener(events.DoubleClickEvent.type, this);\n this.viewer.removeEventListener(events.RenderEvent.type, this);\n this.viewer.removeEventListener(events.ObjectEnterEvent.type, this);\n this.viewer.removeEventListener(events.ObjectHoverEvent.type, this);\n this.viewer.removeEventListener(events.ObjectLeaveEvent.type, this);\n this.viewer.removeEventListener(events.ReadyEvent.type, this);\n\n this.css3DContainer.destroy();\n this.viewer.container.removeChild(this.container);\n\n super.destroy();\n }\n\n /**\n * @internal\n */\n handleEvent(e: Event) {\n switch (e.type) {\n case events.ReadyEvent.type:\n if (this.config.markers) {\n this.setMarkers(this.config.markers);\n delete this.config.markers;\n }\n break;\n\n case events.RenderEvent.type:\n this.renderMarkers();\n break;\n\n case events.ClickEvent.type:\n this.__onClick(e as events.ClickEvent, false);\n break;\n\n case events.DoubleClickEvent.type:\n this.__onClick(e as events.DoubleClickEvent, true);\n break;\n\n case events.ObjectEnterEvent.type:\n case events.ObjectLeaveEvent.type:\n case events.ObjectHoverEvent.type:\n if ((e as events.ObjectEvent).userDataKey === MARKER_DATA) {\n const event = (e as events.ObjectEvent).originalEvent;\n const marker: Marker = (e as events.ObjectEvent).object.userData[MARKER_DATA];\n switch (e.type) {\n case events.ObjectEnterEvent.type:\n if (marker.config.style?.cursor) {\n this.viewer.setCursor(marker.config.style.cursor);\n } else if (marker.config.tooltip || marker.config.content) {\n this.viewer.setCursor('pointer');\n }\n this.__onEnterMarker(event, marker);\n break;\n case events.ObjectLeaveEvent.type:\n this.viewer.setCursor(null);\n this.__onLeaveMarker(marker);\n break;\n case events.ObjectHoverEvent.type:\n this.__onHoverMarker(event, marker);\n break;\n }\n }\n break;\n\n case 'mouseenter': {\n const marker = this.__getTargetMarker(utils.getEventTarget(e));\n this.__onEnterMarker(e as MouseEvent, marker);\n break;\n }\n\n case 'mouseleave': {\n const marker = this.__getTargetMarker(utils.getEventTarget(e));\n this.__onLeaveMarker(marker);\n break;\n }\n\n case 'mousemove': {\n const marker = this.__getTargetMarker(utils.getEventTarget(e), true);\n this.__onHoverMarker(e as MouseEvent, marker);\n break;\n }\n }\n }\n\n /**\n * Toggles all markers\n */\n toggleAllMarkers() {\n if (this.state.allVisible) {\n this.hideAllMarkers();\n } else {\n this.showAllMarkers();\n }\n }\n\n /**\n * Shows all markers\n */\n showAllMarkers() {\n this.state.allVisible = true;\n Object.values(this.markers).forEach((marker) => {\n marker.config.visible = true;\n });\n this.renderMarkers();\n this.dispatchEvent(new ShowMarkersEvent());\n }\n\n /**\n * Hides all markers\n */\n hideAllMarkers() {\n this.state.allVisible = false;\n Object.values(this.markers).forEach((marker) => {\n marker.config.visible = false;\n });\n this.renderMarkers();\n this.dispatchEvent(new HideMarkersEvent());\n }\n\n /**\n * Toggles the visibility of all tooltips\n */\n toggleAllTooltips() {\n if (this.state.showAllTooltips) {\n this.hideAllTooltips();\n } else {\n this.showAllTooltips();\n }\n }\n\n /**\n * Displays all tooltips\n */\n showAllTooltips() {\n this.state.showAllTooltips = true;\n Object.values(this.markers).forEach((marker) => {\n marker.state.staticTooltip = true;\n marker.showTooltip();\n });\n }\n\n /**\n * Hides all tooltips\n */\n hideAllTooltips() {\n this.state.showAllTooltips = false;\n Object.values(this.markers).forEach((marker) => {\n marker.state.staticTooltip = false;\n marker.hideTooltip();\n });\n }\n\n /**\n * Returns the total number of markers\n */\n getNbMarkers(): number {\n return Object.keys(this.markers).length;\n }\n\n /**\n * Returns all the markers\n */\n getMarkers(): Marker[] {\n return Object.values(this.markers);\n }\n\n /**\n * Adds a new marker to viewer\n * @throws {@link PSVError} when the marker's id is missing or already exists\n */\n addMarker(config: MarkerConfig, render = true) {\n if (this.markers[config.id]) {\n throw new PSVError(`marker \"${config.id}\" already exists`);\n }\n\n // @ts-ignore\n const marker: Marker = new (getMarkerCtor(config))(this.viewer, this, config);\n\n if (marker.isPoly()) {\n this.svgContainer.appendChild(marker.domElement);\n } else if (marker.isCss3d()) {\n this.css3DContainer.addObject(marker as MarkerCSS3D);\n } else if (marker.is3d()) {\n this.viewer.renderer.addObject(marker.threeElement);\n } else {\n this.container.appendChild(marker.domElement);\n }\n\n this.markers[marker.id] = marker;\n\n if (this.state.showAllTooltips) {\n marker.state.staticTooltip = true;\n }\n\n if (render) {\n this.__afterChangeMarkers();\n }\n }\n\n /**\n * Returns the internal marker object for a marker id\n * @throws {@link PSVError} when the marker cannot be found\n */\n getMarker(markerId: string | MarkerConfig): Marker {\n const id = typeof markerId === 'object' ? markerId.id : markerId;\n\n if (!this.markers[id]) {\n throw new PSVError(`cannot find marker \"${id}\"`);\n }\n\n return this.markers[id];\n }\n\n /**\n * Returns the last marker selected by the user\n */\n getCurrentMarker(): Marker {\n return this.state.currentMarker;\n }\n\n /**\n * Updates the existing marker with the same id\n * Every property can be changed but you can't change its type (Eg: `image` to `html`)\n */\n updateMarker(config: MarkerConfig, render = true) {\n const marker = this.getMarker(config.id);\n\n marker.update(config);\n\n if (render) {\n this.__afterChangeMarkers();\n\n if (\n (marker === this.state.hoveringMarker && marker.config.tooltip?.trigger === 'hover')\n || marker.state.staticTooltip\n ) {\n marker.showTooltip(this.state.lastClientX, this.state.lastClientY, true);\n }\n }\n }\n\n /**\n * Removes a marker from the viewer\n */\n removeMarker(markerId: string | MarkerConfig, render = true) {\n const marker = this.getMarker(markerId);\n\n if (marker.isPoly()) {\n this.svgContainer.removeChild(marker.domElement);\n } else if (marker.isCss3d()) {\n this.css3DContainer.removeObject(marker as MarkerCSS3D);\n } else if (marker.is3d()) {\n this.viewer.renderer.removeObject(marker.threeElement);\n } else {\n this.container.removeChild(marker.domElement);\n }\n\n if (this.state.hoveringMarker === marker) {\n this.state.hoveringMarker = null;\n }\n\n if (this.state.currentMarker === marker) {\n this.state.currentMarker = null;\n }\n\n marker.destroy();\n delete this.markers[marker.id];\n\n if (render) {\n this.__afterChangeMarkers();\n }\n }\n\n /**\n * Removes multiple markers\n */\n removeMarkers(markerIds: string[], render = true) {\n markerIds.forEach(markerId => this.removeMarker(markerId, false));\n\n if (render) {\n this.__afterChangeMarkers();\n }\n }\n\n /**\n * Replaces all markers\n */\n setMarkers(markers: MarkerConfig[] | null, render = true) {\n this.clearMarkers(false);\n\n markers?.forEach((marker) => {\n this.addMarker(marker, false);\n });\n\n if (render) {\n this.__afterChangeMarkers();\n }\n }\n\n /**\n * Removes all markers\n */\n clearMarkers(render = true) {\n Object.keys(this.markers).forEach((markerId) => {\n this.removeMarker(markerId, false);\n });\n\n if (render) {\n this.__afterChangeMarkers();\n }\n }\n\n /**\n * Rotate the view to face the marker\n */\n gotoMarker(markerId: string | MarkerConfig, speed: string | number = this.config.gotoMarkerSpeed): Promise<void> {\n const marker = this.getMarker(markerId);\n\n if (!speed) {\n this.viewer.rotate(marker.state.position);\n if (!utils.isNil(marker.config.zoomLvl)) {\n this.viewer.zoom(marker.config.zoomLvl);\n }\n this.dispatchEvent(new GotoMarkerDoneEvent(marker));\n return Promise.resolve();\n } else {\n return this.viewer\n .animate({\n ...marker.state.position,\n zoom: marker.config.zoomLvl,\n speed: speed,\n })\n .then(() => {\n this.dispatchEvent(new GotoMarkerDoneEvent(marker));\n });\n }\n }\n\n /**\n * Hides a marker\n */\n hideMarker(markerId: string | MarkerConfig) {\n this.toggleMarker(markerId, false);\n }\n\n /**\n * Shows a marker\n */\n showMarker(markerId: string | MarkerConfig) {\n this.toggleMarker(markerId, true);\n }\n\n /**\n * Forces the display of the tooltip of a marker\n */\n showMarkerTooltip(markerId: string | MarkerConfig) {\n const marker = this.getMarker(markerId);\n marker.state.staticTooltip = true;\n marker.showTooltip();\n }\n\n /**\n * Hides the tooltip of a marker\n */\n hideMarkerTooltip(markerId: string | MarkerConfig) {\n const marker = this.getMarker(markerId);\n marker.state.staticTooltip = false;\n marker.hideTooltip();\n }\n\n /**\n * Toggles a marker visibility\n */\n toggleMarker(markerId: string | MarkerConfig, visible?: boolean) {\n const marker = this.getMarker(markerId);\n marker.config.visible = utils.isNil(visible) ? !marker.config.visible : visible;\n this.renderMarkers();\n }\n\n /**\n * Opens the panel with the content of the marker\n */\n showMarkerPanel(markerId: string | MarkerConfig) {\n const marker = this.getMarker(markerId);\n\n if (marker.config.content) {\n this.viewer.panel.show({\n id: ID_PANEL_MARKER,\n content: marker.config.content,\n });\n } else {\n this.hideMarkerPanel();\n }\n }\n\n /**\n * Closes the panel if currently showing the content of a marker\n */\n hideMarkerPanel() {\n this.viewer.panel.hide(ID_PANEL_MARKER);\n }\n\n /**\n * Toggles the visibility of the list of markers\n */\n toggleMarkersList() {\n if (this.viewer.panel.isVisible(ID_PANEL_MARKERS_LIST)) {\n this.hideMarkersList();\n } else {\n this.showMarkersList();\n }\n }\n\n /**\n * Opens side panel with the list of markers\n */\n showMarkersList() {\n let markers: Marker[] = [];\n Object.values(this.markers).forEach((marker) => {\n if (marker.config.visible && !marker.config.hideList) {\n markers.push(marker);\n }\n });\n\n const e = new RenderMarkersListEvent(markers);\n this.dispatchEvent(e);\n markers = e.markers;\n\n this.viewer.panel.show({\n id: ID_PANEL_MARKERS_LIST,\n content: MARKERS_LIST_TEMPLATE(markers, this.viewer.config.lang[MarkersButton.id]),\n noMargin: true,\n clickHandler: (target) => {\n const li = utils.getClosest(target, '.psv-panel-menu-item');\n const markerId = li ? li.dataset[MARKER_DATA] : undefined;\n\n if (markerId) {\n const marker = this.getMarker(markerId);\n\n this.dispatchEvent(new SelectMarkerListEvent(marker));\n\n this.gotoMarker(marker.id);\n this.hideMarkersList();\n }\n },\n });\n }\n\n /**\n * Closes side panel if it contains the list of markers\n */\n hideMarkersList() {\n this.viewer.panel.hide(ID_PANEL_MARKERS_LIST);\n }\n\n /**\n * Updates the visibility and the position of all markers\n */\n renderMarkers() {\n if (this.state.needsReRender) {\n this.state.needsReRender = false;\n return;\n }\n\n const zoomLevel = this.viewer.getZoomLevel();\n const viewerPosition = this.viewer.getPosition();\n const hoveringMarker = this.state.hoveringMarker;\n\n Object.values(this.markers).forEach((marker) => {\n let isVisible = marker.config.visible;\n let visibilityChanged = false;\n let position: Point = null;\n\n if (isVisible) {\n position = marker.render({ viewerPosition, zoomLevel, hoveringMarker });\n isVisible = !!position;\n }\n\n visibilityChanged = marker.state.visible !== isVisible;\n marker.state.visible = isVisible;\n marker.state.position2D = position;\n\n if (marker.domElement) {\n utils.toggleClass(marker.domElement, 'psv-marker--visible', isVisible);\n }\n\n if (!isVisible) {\n marker.hideTooltip();\n } else if (marker.state.staticTooltip) {\n marker.showTooltip();\n } else if (marker !== this.state.hoveringMarker) {\n marker.hideTooltip();\n }\n\n if (visibilityChanged) {\n this.dispatchEvent(new MarkerVisibilityEvent(marker, isVisible));\n\n if (marker.is3d() || marker.isCss3d()) {\n this.state.needsReRender = true;\n }\n }\n });\n\n if (this.state.needsReRender) {\n this.viewer.needsUpdate();\n }\n }\n\n /**\n * Returns the marker associated to an event target\n */\n private __getTargetMarker(target: HTMLElement, closest?: boolean): Marker;\n private __getTargetMarker(target: Object3D[]): Marker;\n private __getTargetMarker(target: HTMLElement | Object3D[], closest = false): Marker {\n if (target instanceof Node) {\n const target2 = closest ? utils.getClosest(target, '.psv-marker') : target;\n return target2 ? (target2 as any)[MARKER_DATA] : undefined;\n } else if (Array.isArray(target)) {\n return target\n .map(o => o.userData[MARKER_DATA] as Marker)\n .filter(m => !!m)\n .sort((a, b) => b.config.zIndex - a.config.zIndex)[0];\n } else {\n return null;\n }\n }\n\n /**\n * Handles mouse enter events, show the tooltip for non polygon markers\n */\n private __onEnterMarker(e: MouseEvent, marker?: Marker) {\n if (marker) {\n this.state.hoveringMarker = marker;\n this.state.lastClientX = e.clientX;\n this.state.lastClientY = e.clientY;\n\n this.dispatchEvent(new EnterMarkerEvent(marker));\n\n if (marker instanceof AbstractStandardMarker) {\n marker.applyScale({\n zoomLevel: this.viewer.getZoomLevel(),\n viewerPosition: this.viewer.getPosition(),\n mouseover: true,\n });\n }\n\n if (!marker.state.staticTooltip && marker.config.tooltip?.trigger === 'hover') {\n marker.showTooltip(e.clientX, e.clientY);\n }\n }\n }\n\n /**\n * Handles mouse leave events, hide the tooltip\n */\n private __onLeaveMarker(marker?: Marker) {\n if (marker) {\n this.dispatchEvent(new LeaveMarkerEvent(marker));\n\n if (marker instanceof AbstractStandardMarker) {\n marker.applyScale({\n zoomLevel: this.viewer.getZoomLevel(),\n viewerPosition: this.viewer.getPosition(),\n mouseover: false,\n });\n }\n\n this.state.hoveringMarker = null;\n\n if (!marker.state.staticTooltip && marker.config.tooltip?.trigger === 'hover') {\n marker.hideTooltip();\n } else if (marker.state.staticTooltip) {\n marker.showTooltip();\n }\n }\n }\n\n /**\n * Handles mouse move events, refresh the tooltip for polygon markers\n */\n private __onHoverMarker(e: MouseEvent, marker?: Marker) {\n if (marker) {\n this.state.lastClientX = e.clientX;\n this.state.lastClientY = e.clientY;\n\n if (marker.isPoly() || marker.is3d() || marker.isCss3d()) {\n if (marker.config.tooltip?.trigger === 'hover') {\n marker.showTooltip(e.clientX, e.clientY);\n }\n }\n }\n }\n\n /**\n * Handles mouse click events, select the marker and open the panel if necessary\n */\n private __onClick(e: events.ClickEvent | events.DoubleClickEvent, dblclick: boolean) {\n const threeMarker = this.__getTargetMarker(e.data.objects);\n const stdMarker = this.__getTargetMarker(e.data.target, true);\n\n // give priority to standard markers which are always on top of Three markers\n const marker = stdMarker || threeMarker;\n\n if (this.state.currentMarker && this.state.currentMarker !== marker) {\n this.dispatchEvent(new UnselectMarkerEvent(this.state.currentMarker));\n\n this.viewer.panel.hide(ID_PANEL_MARKER);\n\n if (!this.state.showAllTooltips && this.state.currentMarker.config.tooltip?.trigger === 'click') {\n this.hideMarkerTooltip(this.state.currentMarker.id);\n }\n\n this.state.currentMarker = null;\n }\n\n if (marker) {\n this.state.currentMarker = marker;\n\n this.dispatchEvent(new SelectMarkerEvent(marker, dblclick, e.data.rightclick));\n\n if (this.config.clickEventOnMarker) {\n // add the marker to event data\n e.data.marker = marker;\n } else {\n e.stopImmediatePropagation();\n }\n\n // the marker could have been deleted in an event handler\n if (this.markers[marker.id] && !e.data.rightclick) {\n if (marker.config.tooltip?.trigger === 'click') {\n if (marker.tooltip) {\n this.hideMarkerTooltip(marker.id);\n } else {\n this.showMarkerTooltip(marker.id);\n }\n } else {\n this.showMarkerPanel(marker.id);\n }\n }\n }\n }\n\n private __afterChangeMarkers() {\n this.__refreshUi();\n this.__checkObjectsObserver();\n this.viewer.needsUpdate();\n this.dispatchEvent(new SetMarkersEvent(this.getMarkers()));\n }\n\n /**\n * Updates the visiblity of the panel and the buttons\n */\n private __refreshUi() {\n const nbMarkers = Object.values(this.markers).filter(m => !m.config.hideList).length;\n\n if (nbMarkers === 0) {\n this.viewer.panel.hide(ID_PANEL_MARKER);\n this.viewer.panel.hide(ID_PANEL_MARKERS_LIST);\n } else {\n if (this.viewer.panel.isVisible(ID_PANEL_MARKERS_LIST)) {\n this.showMarkersList();\n } else if (this.viewer.panel.isVisible(ID_PANEL_MARKER)) {\n this.state.currentMarker ? this.showMarkerPanel(this.state.currentMarker.id) : this.viewer.panel.hide();\n }\n }\n\n this.viewer.navbar.getButton(MarkersButton.id, false)?.toggle(nbMarkers > 0);\n this.viewer.navbar.getButton(MarkersListButton.id, false)?.toggle(nbMarkers > 0);\n }\n\n /**\n * Adds or remove the objects observer if there are 3D markers\n */\n private __checkObjectsObserver() {\n const has3d = Object.values(this.markers).some(marker => marker.is3d());\n\n if (has3d) {\n this.viewer.observeObjects(MARKER_DATA);\n } else {\n this.viewer.unobserveObjects(MARKER_DATA);\n }\n }\n}\n","import { events, type Viewer } from '@photo-sphere-viewer/core';\nimport { Scene } from 'three';\nimport { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer.js';\nimport { MARKER_DATA } from './constants';\nimport { MarkerCSS3D } from './markers/MarkerCSS3D';\n\n/**\n * @internal\n */\nexport class CSS3DContainer {\n element: HTMLElement;\n\n private readonly renderer: CSS3DRenderer;\n private readonly scene: Scene;\n private readonly intersectionObserver: IntersectionObserver;\n\n constructor(\n private viewer: Viewer,\n ) {\n this.element = document.createElement('div');\n this.element.className = 'psv-markers-css3d-container';\n\n this.renderer = new CSS3DRenderer({ element: this.element });\n this.scene = new Scene();\n\n this.intersectionObserver = new IntersectionObserver((entries) => {\n entries.forEach((entry) => {\n const marker = (entry.target as any)[MARKER_DATA] as MarkerCSS3D;\n if (marker.config.visible) {\n marker.viewportIntersection = entry.isIntersecting;\n }\n });\n }, {\n root: this.element,\n });\n\n viewer.addEventListener(events.ReadyEvent.type, this, { once: true });\n viewer.addEventListener(events.SizeUpdatedEvent.type, this);\n viewer.addEventListener(events.RenderEvent.type, this);\n }\n\n handleEvent(e: Event) {\n switch (e.type) {\n case events.ReadyEvent.type:\n case events.SizeUpdatedEvent.type:\n this.updateSize();\n break;\n case events.RenderEvent.type:\n this.render();\n break;\n }\n }\n\n destroy(): void {\n this.viewer.removeEventListener(events.ReadyEvent.type, this);\n this.viewer.removeEventListener(events.SizeUpdatedEvent.type, this);\n this.viewer.removeEventListener(events.RenderEvent.type, this);\n\n this.intersectionObserver.disconnect();\n }\n\n private updateSize() {\n const size = this.viewer.getSize();\n this.renderer.setSize(size.width, size.height);\n }\n\n private render() {\n this.renderer.render(this.scene, this.viewer.renderer.camera);\n }\n\n addObject(marker: MarkerCSS3D) {\n this.scene.add(marker.threeElement);\n this.intersectionObserver.observe(marker.domElement);\n }\n\n removeObject(marker: MarkerCSS3D) {\n this.scene.remove(marker.threeElement);\n this.intersectionObserver.unobserve(marker.domElement);\n }\n}\n","import {\n\tMatrix4,\n\tObject3D,\n\tQuaternion,\n\tVector3\n} from 'three';\n\n// Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs\n\nconst _position = new Vector3();\nconst _quaternion = new Quaternion();\nconst _scale = new Vector3();\n\n/**\n * The base 3D object that is supported by {@link CSS3DRenderer}.\n *\n * @augments Object3D\n * @three_import import { CSS3DObject } from 'three/addons/renderers/CSS3DRenderer.js';\n */\nclass CSS3DObject extends Object3D {\n\n\t/**\n\t * Constructs a new CSS3D object.\n\t *\n\t * @param {DOMElement} [element] - The DOM element.\n\t */\n\tconstructor( element = document.createElement( 'div' ) ) {\n\n\t\tsuper();\n\n\t\t/**\n\t\t * This flag can be used for type testing.\n\t\t *\n\t\t * @type {boolean}\n\t\t * @readonly\n\t\t * @default true\n\t\t */\n\t\tthis.isCSS3DObject = true;\n\n\t\t/**\n\t\t * The DOM element which defines the appearance of this 3D object.\n\t\t *\n\t\t * @type {DOMElement}\n\t\t * @readonly\n\t\t * @default true\n\t\t */\n\t\tthis.element = element;\n\t\tthis.element.style.position = 'absolute';\n\t\tthis.element.style.pointerEvents = 'auto';\n\t\tthis.element.style.userSelect = 'none';\n\n\t\tthis.element.setAttribute( 'draggable', false );\n\n\t\tthis.addEventListener( 'removed', function () {\n\n\t\t\tthis.traverse( function ( object ) {\n\n\t\t\t\tif (\n\t\t\t\t\tobject.element instanceof object.element.ownerDocument.defaultView.Element &&\n\t\t\t\t\tobject.element.parentNode !== null\n\t\t\t\t) {\n\n\t\t\t\t\tobject.element.remove();\n\n\t\t\t\t}\n\n\t\t\t} );\n\n\t\t} );\n\n\t}\n\n\tcopy( source, recursive ) {\n\n\t\tsuper.copy( source, recursive );\n\n\t\tthis.element = source.element.cloneNode( true );\n\n\t\treturn this;\n\n\t}\n\n}\n\n/**\n * A specialized version of {@link CSS3DObject} that represents\n * DOM elements as sprites.\n *\n * @augments CSS3DObject\n * @three_import import { CSS3DSprite } from 'three/addons/renderers/CSS3DRenderer.js';\n */\nclass CSS3DSprite extends CSS3DObject {\n\n\t/**\n\t * Constructs a new CSS3D sprite object.\n\t *\n\t * @param {DOMElement} [element] - The DOM element.\n\t */\n\tconstructor( element ) {\n\n\t\tsuper( element );\n\n\t\t/**\n\t\t * This flag can be used for type testing.\n\t\t *\n\t\t * @type {boolean}\n\t\t * @readonly\n\t\t * @default true\n\t\t */\n\t\tthis.isCSS3DSprite = true;\n\n\t\t/**\n\t\t * The sprite's rotation in radians.\n\t\t *\n\t\t * @type {number}\n\t\t * @default 0\n\t\t */\n\t\tthis.rotation2D = 0;\n\n\t}\n\n\tcopy( source, recursive ) {\n\n\t\tsuper.copy( source, recursive );\n\n\t\tthis.rotation2D = source.rotation2D;\n\n\t\treturn this;\n\n\t}\n\n}\n\n//\n\nconst _matrix = new Matrix4();\nconst _matrix2 = new Matrix4();\n\n/**\n * This renderer can be used to apply hierarchical 3D transformations to DOM elements\n * via the CSS3 [transform]{@link https://www.w3schools.com/cssref/css3_pr_transform.asp} property.\n * `CSS3DRenderer` is particularly interesting if you want to apply 3D effects to a website without\n * canvas based rendering. It can also be used in order to combine DOM elements with WebGLcontent.\n *\n * There are, however, some important limitations:\n *\n * - It's not possible to use the material system of *three.js*.\n * - It's also not possible to use geometries.\n * - The renderer only supports 100% browser and display zoom.\n *\n * So `CSS3DRenderer` is just focused on ordinary DOM elements. These elements are wrapped into special\n * 3D objects ({@link CSS3DObject} or {@link CSS3DSprite}) and then added to the scene graph.\n *\n * @three_import import { CSS3DRenderer } from 'three/addons/renderers/CSS3DRenderer.js';\n */\nclass CSS3DRenderer {\n\n\t/**\n\t * Constructs a new CSS3D renderer.\n\t *\n\t * @param {CSS3DRenderer~Parameters} [parameters] - The parameters.\n\t */\n\tconstructor( parameters = {} ) {\n\n\t\tconst _this = this;\n\n\t\tlet _width, _height;\n\t\tlet _widthHalf, _heightHalf;\n\n\t\tconst cache = {\n\t\t\tcamera: { style: '' },\n\t\t\tobjects: new WeakMap()\n\t\t};\n\n\t\tconst domElement = parameters.element !== undefined ? parameters.element : document.createElement( 'div' );\n\n\t\tdomElement.style.overflow = 'hidden';\n\n\t\t/**\n\t\t * The DOM where the renderer appends its child-elements.\n\t\t *\n\t\t * @type {DOMElement}\n\t\t */\n\t\tthis.domElement = domElement;\n\n\t\tconst viewElement = document.createElement( 'div' );\n\t\tviewElement.style.transformOrigin = '0 0';\n\t\tviewElement.style.pointerEvents = 'none';\n\t\tdomElement.appendChild( viewElement );\n\n\t\tconst cameraElement = document.createElement( 'div' );\n\n\t\tcameraElement.style.transformStyle = 'preserve-3d';\n\n\t\tviewElement.appendChild( cameraElement );\n\n\t\t/**\n\t\t * Returns an object containing the width and height of the renderer.\n\t\t *\n\t\t * @return {{width:number,height:number}} The size of the renderer.\n\t\t */\n\t\tthis.getSize = function () {\n\n\t\t\treturn {\n\t\t\t\twidth: _width,\n\t\t\t\theight: _height\n\t\t\t};\n\n\t\t};\n\n\t\t/**\n\t\t * Renders the given scene using the given camera.\n\t\t *\n\t\t * @param {Object3D} scene - A scene or any other type of 3D object.\n\t\t * @param {Camera} camera - The camera.\n\t\t */\n\t\tthis.render = function ( scene, camera ) {\n\n\t\t\tconst fov = camera.projectionMatrix.elements[ 5 ] * _heightHalf;\n\n\t\t\tif ( camera.view && camera.view.enabled ) {\n\n\t\t\t\t// view offset\n\t\t\t\tviewElement.style.transform = `translate( ${ - camera.view.offsetX * ( _width / camera.view.width ) }px, ${ - camera.view.offsetY * ( _height / camera.view.height ) }px )`;\n\n\t\t\t\t// view fullWidth and fullHeight, view width and height\n\t\t\t\tviewElement.style.transform += `scale( ${ camera.view.fullWidth / camera.view.width }, ${ camera.view.fullHeight / camera.view.height } )`;\n\n\t\t\t} else {\n\n\t\t\t\tviewElement.style.transform = '';\n\n\t\t\t}\n\n\t\t\tif ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();\n\t\t\tif ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();\n\n\t\t\tlet tx, ty;\n\n\t\t\tif ( camera.isOrthographicCamera ) {\n\n\t\t\t\ttx = - ( camera.right + camera.left ) / 2;\n\t\t\t\tty = ( camera.top + camera.bottom ) / 2;\n\n\t\t\t}\n\n\t\t\tconst scaleByViewOffset = camera.view && camera.view.enabled ? camera.view.height / camera.view.fullHeight : 1;\n\t\t\tconst cameraCSSMatrix = camera.isOrthographicCamera ?\n\t\t\t\t`scale( ${ scaleByViewOffset } )` + 'scale(' + fov + ')' + 'translate(' + epsilon( tx ) + 'px,' + epsilon( ty ) + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse ) :\n\t\t\t\t`scale( ${ scaleByViewOffset } )` + 'translateZ(' + fov + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse );\n\t\t\tconst perspective = camera.isPerspectiveCamera ? 'perspective(' + fov + 'px) ' : '';\n\n\t\t\tconst style = perspective + cameraCSSMatrix +\n\t\t\t\t'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)';\n\n\t\t\tif ( cache.camera.style !== style ) {\n\n\t\t\t\tcameraElement.style.transform = style;\n\n\t\t\t\tcache.camera.style = style;\n\n\t\t\t}\n\n\t\t\trenderObject( scene, scene, camera, cameraCSSMatrix );\n\n\t\t};\n\n\t\t/**\n\t\t * Resizes the renderer to the given width and height.\n\t\t *\n\t\t * @param {number} width - The width of the renderer.\n\t\t * @param {number} height -