1 /* -*-c++-*- */
2 /* osgEarth - Geospatial SDK for OpenSceneGraph
3 * Copyright 2019 Pelican Mapping
4 * http://osgearth.org
5 *
6 * osgEarth is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
12 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
13 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
14 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
15 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
16 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
17 * IN THE SOFTWARE.
18 *
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program.  If not, see <http://www.gnu.org/licenses/>
21 */
22 #include "GraticuleLabelingEngine"
23 #include <osgEarth/EllipsoidIntersector>
24 
25 #define LC "[GraticuleLabelingEngine] "
26 
27 using namespace osgEarth;
28 using namespace osgEarth::Util;
29 
30 #define MAX_LABELS 60
31 
32 namespace
33 {
34     // Given a view matrix, return the heading of the camera relative to North;
35     // this works for geocentric maps.
36     // TODO: graduate to a utilities class somewhere in the core if generally useful
getCameraHeading(const osg::Matrix & VM)37     double getCameraHeading(const osg::Matrix& VM)
38     {
39         osg::Matrixd VMinverse;
40         VMinverse.invert(VM);
41 
42         osg::Vec3d N(0, 0, 6356752); // north pole, more or less
43         osg::Vec3d b(-VM(0, 2), -VM(1, 2), -VM(2, 2)); // look vector
44         osg::Vec3d E = osg::Vec3d(0, 0, 0)*VMinverse;
45         osg::Vec3d u = E; u.normalize();
46 
47         // account for looking straight downish
48         if (osg::equivalent(b*u, -1.0, 1e-4))
49         {
50             // up vec becomes the look vec.
51             b = osg::Matrixd::transform3x3(VM, osg::Vec3f(0.0, 1.0, 0.0));
52             b.normalize();
53         }
54 
55         osg::Vec3d proj_d = b - u*(b*u);
56         osg::Vec3d n = N - E;
57         osg::Vec3d proj_n = n - u*(n*u);
58         osg::Vec3d proj_e = proj_n^u;
59 
60         double cameraHeading = atan2(proj_e*proj_d, proj_n*proj_d);
61         return cameraHeading;
62     }
63 }
64 
65 //........................................................................
66 
GraticuleLabelingEngine(const SpatialReference * srs)67 GraticuleLabelingEngine::GraticuleLabelingEngine(const SpatialReference* srs)
68 {
69     _srs = srs;
70 
71     // Set up the symbology for x-axis labels
72     TextSymbol* xText = _xLabelStyle.getOrCreate<TextSymbol>();
73     xText->alignment() = TextSymbol::ALIGN_CENTER_BOTTOM;
74     xText->halo()->color().set(0, 0, 0, 1);
75     xText->declutter() = false;
76 
77     // Set up the symbology for y-axis labels
78     TextSymbol* yText = _yLabelStyle.getOrCreate<TextSymbol>();
79     yText->alignment() = TextSymbol::ALIGN_LEFT_BOTTOM;
80     yText->halo()->color().set(0, 0, 0, 1);
81     yText->declutter() = false;
82 }
83 
getVisible(osg::Camera * camera)84 bool GraticuleLabelingEngine::getVisible(osg::Camera* camera)
85 {
86     CameraData& data = _cameraDataMap.get(camera);
87     return data.visible;
88 }
89 
90 void
operator ()(GraticuleLabelingEngine::CameraData & data)91 GraticuleLabelingEngine::UpdateLabelStyles::operator()(GraticuleLabelingEngine::CameraData& data)
92 {
93     for(GraticuleLabelingEngine::LabelNodeVector::iterator i = data.xLabels.begin();
94         i != data.xLabels.end();
95         ++i)
96     {
97         i->get()->setStyle(*_xStyle);
98     }
99 
100     for(GraticuleLabelingEngine::LabelNodeVector::iterator i = data.yLabels.begin();
101         i != data.yLabels.end();
102         ++i)
103     {
104         i->get()->setStyle(*_yStyle);
105     }
106 }
107 
108 void
setStyle(const Style & style)109 GraticuleLabelingEngine::setStyle(const Style& style)
110 {
111     setStyles(style, style);
112 }
113 
114 void
setStyles(const Style & xStyle,const Style & yStyle)115 GraticuleLabelingEngine::setStyles(const Style& xStyle, const Style& yStyle)
116 {
117     _xLabelStyle = xStyle;
118     _yLabelStyle = yStyle;
119 
120     UpdateLabelStyles update(_xLabelStyle, _yLabelStyle);
121     _cameraDataMap.forEach(update);
122 }
123 
124 void
operator ()(GraticuleLabelingEngine::CameraData & data)125 GraticuleLabelingEngine::AcceptCameraData::operator()(GraticuleLabelingEngine::CameraData& data)
126 {
127     for (LabelNodeVector::iterator i = data.xLabels.begin(); i != data.xLabels.end(); ++i)
128         i->get()->accept(_nv);
129 
130     for (LabelNodeVector::iterator i = data.yLabels.begin(); i != data.yLabels.end(); ++i)
131         i->get()->accept(_nv);
132 }
133 
134 void
traverse(osg::NodeVisitor & nv)135 GraticuleLabelingEngine::traverse(osg::NodeVisitor& nv)
136 {
137     if (nv.getVisitorType() == nv.CULL_VISITOR)
138     {
139         osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(&nv);
140         if (cv)
141         {
142             // Find the data corresponding to this camera:
143             CameraData& data = _cameraDataMap.get(cv->getCurrentCamera());
144             bool visible = cullTraverse(*cv, data);
145             data.visible = visible;
146             if (visible)
147             {
148                 // traverse all the labels for this camera:
149                 AcceptCameraData accept(nv);
150                 accept(data);
151             }
152         }
153     }
154     else
155     {
156         AcceptCameraData accept(nv);
157         _cameraDataMap.forEach(accept);
158     }
159 
160     osg::Group::traverse(nv);
161 }
162 
163 bool
cullTraverse(osgUtil::CullVisitor & nv,CameraData & data)164 GraticuleLabelingEngine::cullTraverse(osgUtil::CullVisitor& nv, CameraData& data)
165 {
166     osg::Camera* cam = nv.getCurrentCamera();
167 
168     // Don't draw the labels if we are too far from North-Up:
169     double heading = getCameraHeading(cam->getViewMatrix());
170     if (osg::RadiansToDegrees(fabs(heading)) > 7.0)
171         return false;
172 
173     // Initialize the label pool for this camera if we have not done so:
174     if (data.xLabels.empty())
175     {
176         for (unsigned i = 0; i < MAX_LABELS; ++i)
177         {
178             LabelNode* label = new LabelNode();
179             label->setDynamic(true);
180             label->setStyle(_xLabelStyle);
181             label->setHorizonCulling(false);
182             label->setOcclusionCulling(false);
183             data.xLabels.push_back(label);
184         }
185 
186         for (unsigned i = 0; i < MAX_LABELS; ++i)
187         {
188             LabelNode* label = new LabelNode();
189             label->setDynamic(true);
190             label->setStyle(_yLabelStyle);
191             label->setHorizonCulling(false);
192             label->setOcclusionCulling(false);
193             data.yLabels.push_back(label);
194         }
195     }
196 
197     // Start out with all labels off. We will then turn back on the ones we use:
198     for (unsigned i = 0; i < MAX_LABELS; ++i)
199     {
200         data.xLabels[i]->setNodeMask(0);
201         data.yLabels[i]->setNodeMask(0);
202     }
203 
204     // Intersect the corners of the view frustum with the ellipsoid.
205     // This will yield the approximate geo-extent of the view.
206     // TODO: graduate this to the core if generally useful - could be helpful
207     // for displaying the extent of the current view.
208 
209     // Calculate the "clip to world" matrix = MVPinv.
210     osg::Matrix MVP = (*nv.getModelViewMatrix()) * cam->getProjectionMatrix();
211     osg::Matrix MVPinv;
212     MVPinv.invert(MVP);
213 
214     EllipsoidIntersector ellipsoid(_srs->getEllipsoid());
215 
216     // For each corner, transform the clip coordinates at the near and far
217     // planes into world space and intersect that line with the ellipsoid:
218     osg::Vec3d p0, p1;
219 
220     // find the lower-left corner of the frustum:
221     osg::Vec3d LL_world;
222     p0 = osg::Vec3d(-1, -1, -1) * MVPinv;
223     p1 = osg::Vec3d(-1, -1, +1) * MVPinv;
224     bool LL_ok = ellipsoid.intersectLine(p0, p1, LL_world);
225     if (!LL_ok)
226         return false;
227 
228     // find the upper-left corner of the frustum:
229     osg::Vec3d UL_world;
230     p0 = osg::Vec3d(-1, +1, -1) * MVPinv;
231     p1 = osg::Vec3d(-1, +1, +1) * MVPinv;
232     bool UL_ok = ellipsoid.intersectLine(p0, p1, UL_world);
233     if (!UL_ok)
234         return false;
235 
236     // find the lower-right corner of the frustum:
237     osg::Vec3d LR_world;
238     p0 = osg::Vec3d(+1, -1, -1) * MVPinv;
239     p1 = osg::Vec3d(+1, -1, +1) * MVPinv;
240     bool LR_ok = ellipsoid.intersectLine(p0, p1, LR_world);
241     if (!LR_ok)
242         return false;
243 
244     // Use this for clamping geopoints to the edges of the frustum:
245     ClipSpace window(MVP, MVPinv);
246 
247     return updateLabels(LL_world, UL_world, LR_world, window, data);
248 }
249 
updateLabels(const osg::Vec3d & LL_world,osg::Vec3d & UL_world,osg::Vec3d & LR_world,ClipSpace & clipSpace,CameraData & data)250 bool GraticuleLabelingEngine::updateLabels(const osg::Vec3d& LL_world, osg::Vec3d& UL_world, osg::Vec3d& LR_world, ClipSpace& clipSpace, CameraData& data)
251 {
252     return true;
253 }
254