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