diff --git a/README.md b/README.md index 19cad96..49f2947 100755 --- a/README.md +++ b/README.md @@ -24,10 +24,25 @@ Check out the examples: * [Ripple Rings](https://vasturiano.github.io/three-globe/example/ripples/) ([source](https://github.com/vasturiano/three-globe/blob/master/example/ripples/index.html)) * [Solar Terminator](https://vasturiano.github.io/three-globe/example/solar-terminator/) ([source](https://github.com/vasturiano/three-globe/blob/master/example/solar-terminator/index.html)) * [Labels](https://vasturiano.github.io/three-globe/example/labels/) ([source](https://github.com/vasturiano/three-globe/blob/master/example/labels/index.html)) +* [HTML Markers](https://vasturiano.github.io/three-globe/example/htmlMarkers/) ([source](https://github.com/vasturiano/three-globe/blob/master/example/htmlMarkers/index.html)) * [Satellites](https://vasturiano.github.io/three-globe/example/satellites/) ([source](https://github.com/vasturiano/three-globe/blob/master/example/satellites/index.html)) * [Custom Globe Material](https://vasturiano.github.io/three-globe/example/custom-material/) ([source](https://github.com/vasturiano/three-globe/blob/master/example/custom-material/index.html)) * [Custom Layer](https://vasturiano.github.io/three-globe/example/custom/) ([source](https://github.com/vasturiano/three-globe/blob/master/example/custom/index.html)) +Available Map Layers: +* [Globe Layer](#globe-layer) +* [Points Layer](#points-layer) +* [Arcs Layer](#arcs-layer) +* [Polygons Layer](#polygons-layer) +* [Paths Layer](#paths-layer) +* [Hex Bin Layer](#hex-bin-layer) +* [Hexed Polygons Layer](#hexed-polygons-layer) +* [Tiles Layer](#tiles-layer) +* [Rings Layer](#rings-layer) +* [HTML Elements Layer](#html-elements-layer) +* [3D Objects Layer](#3d-objects-layer) +* [Custom Layer](#custom-layer) + ## Quick start ```js @@ -223,7 +238,18 @@ new ThreeGlobe({ configOptions }) | labelDotOrientation([str or fn]) | Label object accessor function or attribute for the orientation of the label if the dot marker is present. Possible values are `right`, `top` and `bottom`. | `() => 'bottom'` | | labelsTransitionDuration([num]) | Getter/setter for duration (ms) of the transition to animate label changes involving position modifications (`lat`, `lng`, `altitude`, `rotation`). A value of `0` will move the labels immediately to their final position. New labels are animated by scaling their size. | 1000 | -### Objects Layer +### HTML Elements Layer + +| Method | Description | Default | +| --- | --- | :--: | +| htmlElementsData([array]) | Getter/setter for the list of objects to represent in the HTML elements map layer. Each HTML element is rendered using [ThreeJS CSS2DRenderer](https://threejs.org/docs/#examples/en/renderers/CSS2DRenderer). | `[]` | +| htmlLat([num, str or fn]) | HTML element accessor function, attribute or a numeric constant for the latitude coordinate of the element's central position. | `lat` | +| htmlLng([num, str or fn]) | HTML element accessor function, attribute or a numeric constant for the longitude coordinate of the element's central position. | `lng` | +| htmlAltitude([num, str or fn]) | HTML element accessor function, attribute or a numeric constant for the altitude coordinate of the element's position, in terms of globe radius units. | 0 | +| htmlElement([str or fn]) | Accessor function or attribute to retrieve the DOM element to use. Should return an instance of [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement). | `null` | +| htmlTransitionDuration([num]) | Getter/setter for duration (ms) of the transition to animate HTML elements position changes. A value of `0` will move the elements immediately to their final position. | 1000 | + +### 3D Objects Layer | Method | Description | Default | | --- | --- | :--: | diff --git a/example/htmlMarkers/index.html b/example/htmlMarkers/index.html new file mode 100755 index 0000000..a56f457 --- /dev/null +++ b/example/htmlMarkers/index.html @@ -0,0 +1,85 @@ +
+ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/globe-kapsule.js b/src/globe-kapsule.js index 0b4f1a8..9af189f 100755 --- a/src/globe-kapsule.js +++ b/src/globe-kapsule.js @@ -29,6 +29,7 @@ import PathsLayerKapsule from './layers/paths'; import TilesLayerKapsule from './layers/tiles'; import LabelsLayerKapsule from './layers/labels'; import RingsLayerKapsule from './layers/rings'; +import HtmlElementsLayerKapsule from './layers/htmlElements'; import ObjectsLayerKapsule from './layers/objects'; import CustomLayerKapsule from './layers/custom'; @@ -45,6 +46,7 @@ const layers = [ 'tilesLayer', 'labelsLayer', 'ringsLayer', + 'htmlElementsLayer', 'objectsLayer', 'customLayer' ]; @@ -202,6 +204,16 @@ const linkedRingsLayerProps = Object.assign(...[ 'ringRepeatPeriod' ].map(p => ({ [p]: bindRingsLayer.linkProp(p)}))); +const bindHtmlElementsLayer = linkKapsule('htmlElementsLayer', HtmlElementsLayerKapsule); +const linkedHtmlElementsLayerProps = Object.assign(...[ + 'htmlElementsData', + 'htmlLat', + 'htmlLng', + 'htmlAltitude', + 'htmlElement', + 'htmlTransitionDuration' +].map(p => ({ [p]: bindHtmlElementsLayer.linkProp(p)}))); + const bindObjectsLayer = linkKapsule('objectsLayer', ObjectsLayerKapsule); const linkedObjectsLayerProps = Object.assign(...[ 'objectsData', @@ -240,6 +252,7 @@ export default Kapsule({ ...linkedTilesLayerProps, ...linkedLabelsLayerProps, ...linkedRingsLayerProps, + ...linkedHtmlElementsLayerProps, ...linkedObjectsLayerProps, ...linkedCustomLayerProps }, @@ -248,11 +261,40 @@ export default Kapsule({ getGlobeRadius, getCoords: (state, ...args) => polar2Cartesian(...args), toGeoCoords: (state, ...args) => cartesian2Polar(...args), + setPointOfView: (state, globalPov, globePos) => { + let isBehindGlobe = undefined; + if (globalPov) { + const globeRadius = getGlobeRadius(); + const pov = globePos ? globalPov.clone().sub(globePos) : globalPov; // convert to local vector + + let povDist, povEdgeDist, povEdgeAngle, maxSurfacePosAngle; + isBehindGlobe = pos => { + povDist === undefined && (povDist = pov.length()); + + // check if it's behind plane of globe's visible area + // maxSurfacePosAngle === undefined && (maxSurfacePosAngle = Math.acos(globeRadius / povDist)); + // return pov.angleTo(pos) > maxSurfacePosAngle; + + // more sophisticated method that checks also pos altitude + povEdgeDist === undefined && (povEdgeDist = Math.sqrt(povDist**2 - globeRadius**2)); + povEdgeAngle === undefined && (povEdgeAngle = Math.acos(povEdgeDist / povDist)); + const povPosDist = pov.distanceTo(pos); + if (povPosDist < povEdgeDist) return false; // pos is closer than visible edge of globe + + const posDist = pos.length(); + const povPosAngle = Math.acos((povDist**2 + povPosDist**2 - posDist**2) / (2 * povDist * povPosDist)); // triangle solver + return povPosAngle < povEdgeAngle; // pos is within globe's visible area cone + }; + } + + // pass behind globe checker for layers that need it + state.layersThatNeedBehindGlobeChecker.forEach(l => l.isBehindGlobe(isBehindGlobe)); + }, ...linkedGlobeLayerMethods }, stateInit: () => { - return { + const layers = { globeLayer: GlobeLayerKapsule(), pointsLayer: PointsLayerKapsule(), arcsLayer: ArcsLayerKapsule(), @@ -263,9 +305,15 @@ export default Kapsule({ tilesLayer: TilesLayerKapsule(), labelsLayer: LabelsLayerKapsule(), ringsLayer: RingsLayerKapsule(), + htmlElementsLayer: HtmlElementsLayerKapsule(), objectsLayer: ObjectsLayerKapsule(), customLayer: CustomLayerKapsule() - } + }; + + return { + ...layers, + layersThatNeedBehindGlobeChecker: Object.values(layers).filter(l => l.hasOwnProperty('isBehindGlobe')) + }; }, init(threeObj, state, { animateIn = true, waitForGlobeReady = true }) { diff --git a/src/index.d.ts b/src/index.d.ts index 21dcd07..dc99d9d 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,4 +1,4 @@ -import { Object3D, Vector2, Material } from 'three'; +import { Object3D, Vector2, Vector3, Material } from 'three'; type Accessor