Stellarium 0.12.3
StelCircleArcRenderer.hpp
1 /*
2  * Stellarium
3  * Copyright (C) 2012 Ferdinand Majerech
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
18  */
19 
20 #ifndef _STELCIRCLEARCRENDERER_HPP_
21 #define _STELCIRCLEARCRENDERER_HPP_
22 
23 #include <QLinkedList>
24 
25 #include "GenericVertexTypes.hpp"
26 #include "StelProjector.hpp"
27 #include "StelSphereGeometry.hpp"
28 #include "StelRenderer.hpp"
29 
30 
52 {
53 public:
59  : renderer(renderer)
60  , projector(&(*projector))
61  {
62  smallCircleVertexBuffer = renderer->createVertexBuffer<VertexP2>(PrimitiveType_LineStrip);
63  }
64 
70  : renderer(renderer)
71  , projector(projector)
72  {
73  smallCircleVertexBuffer = renderer->createVertexBuffer<VertexP2>(PrimitiveType_LineStrip);
74  }
75 
78  {
79  delete smallCircleVertexBuffer;
80  }
81 
100  (const Vec3d& start, const Vec3d& stop, const SphericalCap* clippingCap = NULL,
101  void (*viewportEdgeIntersectCallback)
102  (const Vec3d& screenPos, const Vec3d& direction, void* userData) = NULL,
103  void* userData = NULL)
104  {
105  Vec3d pt1, pt2;
106  rotCenter = Vec3d(0);
107  if (NULL != clippingCap)
108  {
109  pt1 = start;
110  pt2 = stop;
111  if (clippingCap->clipGreatCircle(pt1, pt2))
112  {
113  drawSmallCircleArc(pt1, pt2, viewportEdgeIntersectCallback, userData);
114  }
115  return;
116  }
117  drawSmallCircleArc(start, stop, viewportEdgeIntersectCallback, userData);
118  }
119 
141  void drawGreatCircleArcs(const QVector<Vec3d>& points, const PrimitiveType primitiveType,
142  const SphericalCap* clippingCap)
143  {
144  Q_ASSERT_X(points.size() != 1, Q_FUNC_INFO,
145  "Need zero or at least 2 points to draw circle arcs");
146  switch(primitiveType)
147  {
148  case PrimitiveType_Lines:
149  Q_ASSERT_X(points.size() % 2 == 0, Q_FUNC_INFO,
150  "Need an even number of points to draw great circle arcs from a "
151  "lines primitive type.");
152  for (int p = 0; p < points.size(); p += 2 )
153  {
154  drawGreatCircleArc(points.at(p), points.at(p+1), clippingCap);
155  }
156  break;
157  case PrimitiveType_LineLoop:
158  if(points.size() > 0)
159  {
160  drawGreatCircleArc(points.at(points.size() - 1), points.at(0));
161  }
162  case PrimitiveType_LineStrip:
163  for (int p = 1; p < points.size(); p++)
164  {
165  drawGreatCircleArc(points.at(p - 1), points.at(p));
166  }
167  break;
168  default:
169  Q_ASSERT_X(false, Q_FUNC_INFO, "Unsupported primitive type to draw circle arcs from");
170  }
171  }
172 
174  void setRotCenter(const Vec3d center)
175  {
176  rotCenter = center;
177  }
178 
198  void drawSmallCircleArc
199  (const Vec3d& start, const Vec3d& stop,
200  void (*viewportEdgeIntersectCallback)
201  (const Vec3d& screenPos, const Vec3d& direction, void* userData),
202  void* userData)
203  {
204  Q_ASSERT_X(smallCircleVertexBuffer->length() == 0, Q_FUNC_INFO,
205  "Small circle buffer must be empty before drawing");
206 
207  smallCircleVertexBuffer->unlock();
208 
209  vertexList.clear();
210  Vec3d win1, win2;
211  win1[2] = projector->project(start, win1) ? 1.0 : -1.1;
212  win2[2] = projector->project(stop, win2) ? 1.0 : -1.0;
213  vertexList.append(win1);
214 
215  if (rotCenter.lengthSquared() < 0.00000001)
216  {
217  // Great circle
218  // Tesselate the arc in small segments in a way so that the lines look smooth
219  fIter(start, stop, win1, win2,
220  vertexList.insert(vertexList.end(), win2), 1.0);
221  }
222  else
223  {
224  const Vec3d tmp = (rotCenter ^ start) / rotCenter.length();
225  const double radius = fabs(tmp.length());
226  // Tesselate the arc in small segments in a way so that the lines look smooth
227  fIter(start - rotCenter, stop - rotCenter, win1, win2,
228  vertexList.insert(vertexList.end(), win2), radius);
229  }
230 
231  // And draw.
232  QLinkedList<Vec3d>::ConstIterator i = vertexList.constBegin();
233  while (i + 1 != vertexList.constEnd())
234  {
235  const Vec3d& p1 = *i;
236  const Vec3d& p2 = *(++i);
237  const bool p1InViewport = projector->checkInViewport(p1);
238  const bool p2InViewport = projector->checkInViewport(p2);
239 
240  if ((p1[2] > 0.0 && p1InViewport) || (p2[2] > 0.0 && p2InViewport))
241  {
242  smallCircleVertexBuffer->addVertex(VertexP2(p1[0], p1[1]));
243  if (i + 1 == vertexList.constEnd())
244  {
245  smallCircleVertexBuffer->addVertex(VertexP2(p2[0], p2[1]));
246  drawSmallCircleVertexBuffer();
247  }
248  // We crossed the edge of the viewport
249  if (NULL != viewportEdgeIntersectCallback && p1InViewport != p2InViewport)
250  {
251  // if !p1InViewport, then p2InViewport
252  const Vec3d& a = p1InViewport ? p1 : p2;
253  const Vec3d& b = p1InViewport ? p2 : p1;
254  Vec3d dir = b - a;
255  dir.normalize();
256  viewportEdgeIntersectCallback(projector->viewPortIntersect(a, b),
257  dir, userData);
258  }
259  }
260  else
261  {
262  // Break the line, draw the stored vertex and flush the list
263  if (smallCircleVertexBuffer->length() > 0)
264  {
265  smallCircleVertexBuffer->addVertex(VertexP2(p1[0], p1[1]));
266  }
267  drawSmallCircleVertexBuffer();
268  }
269  }
270 
271  Q_ASSERT_X(smallCircleVertexBuffer->length() == 0, Q_FUNC_INFO,
272  "Small circle buffer must be empty after drawing");
273  }
274 
275 private:
277  StelRenderer* renderer;
278 
280  StelProjector* projector;
281 
283  StelVertexBuffer<VertexP2>* smallCircleVertexBuffer;
284 
286  QLinkedList<Vec3d> vertexList;
287 
289  Vec3d rotCenter;
290 
292  void fIter(const Vec3d& p1, const Vec3d& p2, Vec3d& win1, Vec3d& win2,
293  const QLinkedList<Vec3d>::iterator& iter, double radius,
294  int nbI = 0, bool checkCrossDiscontinuity = true)
295  {
296  const bool crossDiscontinuity =
297  checkCrossDiscontinuity &&
298  projector->intersectViewportDiscontinuity(p1 + rotCenter, p2 + rotCenter);
299 
300  if (crossDiscontinuity && nbI >= 5)
301  {
302  win1[2] = -2.0;
303  win2[2] = -2.0;
304  vertexList.insert(iter, win1);
305  vertexList.insert(iter, win2);
306  return;
307  }
308 
309  // Point splitting the tesselated line in two
310  Vec3d newVertex(p1 + p2);
311  newVertex.normalize();
312  newVertex *= radius;
313  Vec3d win3(newVertex + rotCenter);
314  const bool isValidVertex = projector->projectInPlace(win3);
315 
316  const float v10 = win1[0] - win3[0];
317  const float v11 = win1[1] - win3[1];
318  const float v20 = win2[0] - win3[0];
319  const float v21 = win2[1] - win3[1];
320 
321  const float dist = std::sqrt((v10 * v10 + v11 * v11) * (v20 * v20 + v21 * v21));
322  const float cosAngle = (v10 * v20 + v11 * v21) / dist;
323  if ((cosAngle > -0.999f || dist > 50 * 50 || crossDiscontinuity) && nbI < 5)
324  {
325  // Use the 3rd component of the vector to store whether the vertex is valid
326  win3[2]= isValidVertex ? 1.0 : -1.0;
327  fIter(p1, newVertex, win1, win3, vertexList.insert(iter, win3),
328  radius, nbI + 1, crossDiscontinuity || dist > 50 * 50);
329  fIter(newVertex, p2, win3, win2, iter,
330  radius, nbI + 1, crossDiscontinuity || dist > 50 * 50);
331  }
332  }
333 
335  void drawSmallCircleVertexBuffer()
336  {
337  smallCircleVertexBuffer->lock();
338  renderer->drawVertexBuffer(smallCircleVertexBuffer);
339  smallCircleVertexBuffer->unlock();
340  smallCircleVertexBuffer->clear();
341  }
342 };
343 
344 #endif // _STELCIRCLEARCRENDERER_HPP_