1 /*=========================================================================
2 
3   Program:   Visualization Toolkit
4   Module:    vtkProjectedTerrainPath.cxx
5 
6   Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
7   All rights reserved.
8   See Copyright.txt or http://www.kitware.com/Copyright.htm for details.
9 
10      This software is distributed WITHOUT ANY WARRANTY; without even
11      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
12      PURPOSE.  See the above copyright notice for more information.
13 
14 =========================================================================*/
15 #include "vtkProjectedTerrainPath.h"
16 #include "vtkInformation.h"
17 #include "vtkInformationVector.h"
18 #include "vtkObjectFactory.h"
19 #include "vtkPriorityQueue.h"
20 #include "vtkImageData.h"
21 #include "vtkPolyData.h"
22 #include "vtkPointData.h"
23 #include "vtkPoints.h"
24 #include "vtkCellArray.h"
25 #include "vtkDoubleArray.h"
26 #include "vtkExecutive.h"
27 #include "vtkMath.h"
28 #include "vtkPixel.h"
29 #include <vector>
30 
31 // Define the edge list class--------------------------------------------------
32 struct vtkEdge
33 {
vtkEdgevtkEdge34   vtkEdge(vtkIdType v1, vtkIdType v2) : V1(v1), V2(v2), tPos(-1.0), tNeg(-1.0) {}
35 
36   vtkIdType V1;
37   vtkIdType V2;
38   double    tPos; //parametric coordinates where positive maximum error occurs
39   double    tNeg; //parametric coordinates where negative maximum error occurs
40 };
41 
42 class vtkEdgeList : public std::vector<vtkEdge> {};
43 typedef vtkEdgeList EdgeListType;
44 typedef vtkEdgeList::iterator EdgeListIterator;
45 
46 
47 // Begin vtkProjectedTerrainPath class implementation--------------------------
48 //
49 vtkStandardNewMacro(vtkProjectedTerrainPath);
50 
51 //-----------------------------------------------------------------------------
vtkProjectedTerrainPath()52 vtkProjectedTerrainPath::vtkProjectedTerrainPath()
53 {
54   this->SetNumberOfInputPorts(2);
55 
56   this->ProjectionMode = SIMPLE_PROJECTION;
57   this->HeightOffset = 10.0;
58   this->HeightTolerance = 10.0;
59   this->MaximumNumberOfLines = VTK_ID_MAX;
60   this->PositiveLineError = nullptr;
61   this->NegativeLineError = nullptr;
62 }
63 
64 //-----------------------------------------------------------------------------
65 vtkProjectedTerrainPath::~vtkProjectedTerrainPath() = default;
66 
67 //----------------------------------------------------------------------------
SetSourceConnection(vtkAlgorithmOutput * algOutput)68 void vtkProjectedTerrainPath::SetSourceConnection(vtkAlgorithmOutput* algOutput)
69 {
70   this->SetInputConnection(1, algOutput);
71 }
72 
73 //-----------------------------------------------------------------------------
SetSourceData(vtkImageData * source)74 void vtkProjectedTerrainPath::SetSourceData(vtkImageData *source)
75 {
76   this->SetInputDataInternal(1, source);
77 }
78 
79 //-----------------------------------------------------------------------------
GetSource()80 vtkImageData *vtkProjectedTerrainPath::GetSource()
81 {
82   if (this->GetNumberOfInputConnections(1) < 1)
83   {
84     return nullptr;
85   }
86   return vtkImageData::SafeDownCast(this->GetExecutive()->GetInputData(1, 0));
87 }
88 
89 //-----------------------------------------------------------------------------
FillInputPortInformation(int port,vtkInformation * info)90 int vtkProjectedTerrainPath::FillInputPortInformation(int port,
91                                                       vtkInformation *info)
92 {
93   if (port == 0)
94   {
95     info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPolyData");
96     return 1;
97   }
98   else if (port == 1)
99   {
100     info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkImageData");
101     return 1;
102   }
103   return 0;
104 }
105 
106 //-----------------------------------------------------------------------------
107 // Warning: this method may return negative indices. This is expected behavior
108 //
GetImageIndex(double x[3],double loc[2],int ij[2])109 inline void vtkProjectedTerrainPath::GetImageIndex(double x[3],
110                                                    double loc[2], int ij[2])
111 {
112   loc[0] = (x[0] - this->Origin[0]) / this->Spacing[0];
113   ij[0] = (int) (floor(loc[0]));
114   loc[1] = (x[1] - this->Origin[1]) / this->Spacing[1];
115   ij[1] = (int) (floor(loc[1]));
116 }
117 
118 //-----------------------------------------------------------------------------
RequestData(vtkInformation *,vtkInformationVector ** inputVector,vtkInformationVector * outputVector)119 int vtkProjectedTerrainPath::RequestData(vtkInformation *,
120                                          vtkInformationVector **inputVector,
121                                          vtkInformationVector *outputVector)
122 {
123   // get the input and output
124   vtkInformation *linesInfo = inputVector[0]->GetInformationObject(0);
125   vtkInformation *imageInfo = inputVector[1]->GetInformationObject(0);
126   vtkInformation *outInfo = outputVector->GetInformationObject(0);
127 
128   vtkPolyData *lines = vtkPolyData::SafeDownCast(
129     linesInfo->Get(vtkDataObject::DATA_OBJECT()));
130   vtkImageData *image = vtkImageData::SafeDownCast(
131     imageInfo->Get(vtkDataObject::DATA_OBJECT()));
132   vtkPolyData *output = vtkPolyData::SafeDownCast(
133     outInfo->Get(vtkDataObject::DATA_OBJECT()));
134 
135   vtkPoints *inPoints = lines->GetPoints();
136   vtkIdType numPts = inPoints->GetNumberOfPoints();
137   vtkCellArray *inLines = lines->GetLines();
138   vtkIdType numLines;
139   if ( ! inLines || (numLines=inLines->GetNumberOfCells()) <= 0 )
140   {
141     vtkErrorMacro("This filter requires lines as input");
142     return 1;
143   }
144 
145   if ( ! image )
146   {
147     vtkErrorMacro("This filter requires an image as input");
148     return 1;
149   }
150   image->GetDimensions(this->Dimensions);
151   image->GetOrigin(this->Origin);
152   image->GetSpacing(this->Spacing);
153   image->GetExtent(this->Extent);
154   this->Heights = image->GetPointData()->GetScalars();
155 
156   this->Points = vtkPoints::New();
157   this->Points->SetDataTypeToDouble();
158   this->Points->Allocate(numPts);
159   output->SetPoints(this->Points);
160   this->Points->Delete(); //ok reference counting
161 
162   vtkPointData *inPD = lines->GetPointData();
163   vtkPointData *outPD = output->GetPointData();
164   outPD->CopyAllocate(inPD);
165 
166   // The algorithm runs in three parts. First, the existing points are
167   // projected onto the image (with the height offset). Next, if requested
168   // the edges are checked for occlusion. Finally, if requested, the edges
169   // are adjusted to hug the terrain.
170   int ij[2];
171   vtkIdType i;
172   double x[3], z, loc[2];
173   for (i=0; i<numPts; i++)
174   {
175     inPoints->GetPoint(i,x);
176     this->GetImageIndex(x,loc,ij);
177     z = this->GetHeight(loc,ij);
178     this->Points->InsertPoint(i, x[0], x[1], z);
179     outPD->CopyData(inPD,i,i);
180   }
181 
182   // If mode is simple, then just spit out the original polylines
183   if ( this->ProjectionMode == SIMPLE_PROJECTION )
184   {
185     output->SetLines(inLines);
186     return 1;
187   }
188 
189   // If here, we've got to get fancy and start the subdivision process.
190   // This means creating some fancy data structures: a dynamic list
191   // for the edges. The structure is implicit: the first two entries
192   // in the list (i,i+1) form an edge; the next two (i+1,i+2) form the
193   // next edge, and so on. The list contains point ids referring to
194   // the this->Points array.
195   vtkIdType j, npts=0, *pts=nullptr;
196   this->EdgeList = new EdgeListType;
197   this->PositiveLineError = vtkPriorityQueue::New();
198   this->NegativeLineError = vtkPriorityQueue::New();
199   this->NumLines = 0;
200   for ( inLines->InitTraversal(); inLines->GetNextCell(npts,pts); )
201   {
202     for (j=0; j<(npts-1); j++)
203     {
204       this->EdgeList->push_back(vtkEdge(pts[j],pts[j+1]));
205       this->ComputeError(static_cast<vtkIdType>(this->EdgeList->size()-1)); //puts edges in queues
206       this->NumLines++;
207     }
208   }
209 
210   if ( this->ProjectionMode == NONOCCLUDED_PROJECTION )
211   {
212     this->RemoveOcclusions();
213   }
214   else //if ( this->ProjectionMode == HUG_PROJECTION )
215   {
216     this->HugTerrain();
217   }
218 
219   //Okay now dump out the edges from the edge list into the output polydata
220   vtkCellArray *outLines = vtkCellArray::New();
221   outLines->Allocate(outLines->EstimateSize(static_cast<vtkIdType>(this->EdgeList->size()),2));
222   for (EdgeListIterator iter=this->EdgeList->begin();
223        iter != this->EdgeList->end();
224        ++iter)
225   {
226     outLines->InsertNextCell(2);
227     outLines->InsertCellPoint(iter->V1);
228     outLines->InsertCellPoint(iter->V2);
229   }
230   output->SetLines(outLines);
231   outLines->Delete();
232   vtkDebugMacro("Produced " << outLines->GetNumberOfCells() << " lines from "
233                 << numLines << " input polylines");
234 
235   // Clean up
236   delete this->EdgeList;
237   this->PositiveLineError->Delete();
238   this->NegativeLineError->Delete();
239 
240   return 1;
241 }
242 
243 //-----------------------------------------------------------------------------
244 // Remove all intersections of the line segments with the terrain
RemoveOcclusions()245 void vtkProjectedTerrainPath::RemoveOcclusions()
246 {
247   double error;
248   vtkIdType eId;
249   if ( this->HeightOffset > 0.0 ) //want path above terrain, eliminate negative errors
250   {
251     while ( (eId=this->NegativeLineError->Pop(0,error)) >= 0 &&
252       this->NumLines < this->MaximumNumberOfLines )
253     {
254       this->SplitEdge(eId,(*this->EdgeList)[eId].tNeg);
255     }
256   }
257   else //want path below terrain, eliminate positive errors
258   {
259     while ( (eId=this->PositiveLineError->Pop(0,error)) >= 0 &&
260       this->NumLines < this->MaximumNumberOfLines )
261     {
262       this->SplitEdge(eId,(*this->EdgeList)[eId].tPos);
263     }
264   }
265 }
266 
267 //-----------------------------------------------------------------------------
268 // Adjust the lines so that they hug the terrain within the tolerance specified
HugTerrain()269 void vtkProjectedTerrainPath::HugTerrain()
270 {
271   // Loop until error meets threshold.
272   // Remember that the errors in the priority queues are negative.
273   // Also, splitting an edge can cause the polyline to reintersect the terrain.
274   // This is the reason for the outer while{} loop.
275   double error;
276   vtkIdType eId, stillPopping=1;
277 
278   while ( stillPopping )
279   {
280     stillPopping = 0;
281     while ( (eId=this->PositiveLineError->Pop(0,error)) >= 0 &&
282       this->NumLines < this->MaximumNumberOfLines )
283     {
284       // Have to remove edge (if it exists) from other queue since
285       // it will be reprocessed
286       this->NegativeLineError->DeleteId(eId);
287       if ( (-error) > this->HeightTolerance )
288       {
289         this->SplitEdge(eId,(*this->EdgeList)[eId].tPos);
290         stillPopping = 1;
291       }
292       else
293       {
294         break;
295       }
296     }
297     while ( (eId=this->NegativeLineError->Pop(0,error)) >= 0 &&
298       this->NumLines < this->MaximumNumberOfLines )
299     {
300       // Have to remove edge (if it exists) from other queue since
301       // it will be reprocessed
302       this->PositiveLineError->DeleteId(eId);
303       if ( (-error) > this->HeightTolerance )
304       {
305         this->SplitEdge(eId,(*this->EdgeList)[eId].tNeg);
306         stillPopping = 1;
307       }
308       else
309       {
310         break;
311       }
312     }
313   } //while still popping
314 }
315 
316 
317 //-----------------------------------------------------------------------------
318 // Splits the indicated edge and reinserts the edges back into the EdgeList as
319 // well as the appropriate priority queues.
SplitEdge(vtkIdType eId,double t)320 void vtkProjectedTerrainPath::SplitEdge(vtkIdType eId, double t)
321 {
322   this->NumLines++;
323 
324   // Get the points defining the edge
325   vtkEdge &e =(*this->EdgeList)[eId];
326   double p1[3], p2[3];
327   this->Points->GetPoint(e.V1,p1);
328   this->Points->GetPoint(e.V2,p2);
329 
330   // Now generate the split point and add it to the list of points
331   double x[3], loc[2];
332   int ij[2];
333   x[0] = p1[0] + t*(p2[0]-p1[0]);
334   x[1] = p1[1] + t*(p2[1]-p1[1]);
335   this->GetImageIndex(x,loc,ij);
336   x[2] = this->GetHeight(loc,ij);
337   vtkIdType pId = this->Points->InsertNextPoint(x);
338 
339   // We will create a new edge and update the old one.
340   vtkIdType v2 = e.V2;
341   e.V2 = pId;
342   this->EdgeList->push_back(vtkEdge(pId,v2));
343   vtkIdType eNew = static_cast<vtkIdType>(this->EdgeList->size() - 1);
344 
345   // Recompute the errors along the edges
346   this->ComputeError(eId);
347   this->ComputeError(eNew);
348 }
349 
350 // if the line lies outside of the image.
GetHeight(double loc[2],int ij[2])351 double vtkProjectedTerrainPath::GetHeight(double loc[2], int ij[2])
352 {
353   //  Compute the ij location (assuming 2D image plane)
354   //
355   int i;
356   double pcoords[2];
357   for (i=0; i<2; i++)
358   {
359     if ( ij[i] >= this->Extent[i*2] && ij[i] < this->Extent[i*2 + 1] )
360     {
361       pcoords[i] = loc[i] - (double)ij[i];
362     }
363 
364     else if ( ij[i] < this->Extent[i*2] || ij[i] > this->Extent[i*2+1] )
365     {
366       return this->HeightOffset;
367     }
368 
369     else //if ( ij[i] == this->Extent[i*2+1] )
370     {
371       if (this->Dimensions[i] == 1)
372       {
373         pcoords[i] = 0.0;
374       }
375       else
376       {
377         ij[i] -= 1;
378         pcoords[i] = 1.0;
379       }
380     }
381   }
382 
383   // Interpolate the height
384   double weights[4], s0, s1, s2, s3;
385   vtkPixel::InterpolationFunctions(pcoords,weights);
386   s0 = this->Heights->GetTuple1(ij[0]+   ij[1]*this->Dimensions[0]);
387   s1 = this->Heights->GetTuple1(ij[0]+1+ ij[1]*this->Dimensions[0]);
388   s2 = this->Heights->GetTuple1(ij[0]+  (ij[1]+1)*this->Dimensions[0]);
389   s3 = this->Heights->GetTuple1(ij[0]+1+(ij[1]+1)*this->Dimensions[0]);
390 
391   return (this->Origin[2] + this->HeightOffset + s0*weights[0] + s1*weights[1] +
392     s2*weights[2] + s3*weights[3]);
393 }
394 
395 //-----------------------------------------------------------------------------
396 //This method has the side effect of inserting the edge into the queues
ComputeError(vtkIdType edgeId)397 void vtkProjectedTerrainPath::ComputeError(vtkIdType edgeId)
398 {
399   vtkEdge &e =(*this->EdgeList)[edgeId];
400   double p1[3], p2[3];
401   this->Points->GetPoint(e.V1,p1);
402   this->Points->GetPoint(e.V2,p2);
403 
404   // Now evaluate the edge as it passes over the pixel cell edges. The
405   // interpolation functions are such that the maximum values have to
406   // take place on the boundary of the cell. We process the cell edges in
407   // two passes: first the x-edges, then the y-edges.
408   double negError = VTK_FLOAT_MAX;
409   double posError = -VTK_FLOAT_MAX;
410   double x[3], loc[2], t, zMap, loc1[2], loc2[2], *x1, *x2, error;
411   int ij[2], ij1[2], ij2[2], numInt, i, flip;
412 
413   // Process the x intersections
414   if ( p2[0] >= p1[0] ) //sort along x-axis
415   {
416     x1 = p1;
417     x2 = p2;
418     flip = 0;
419   }
420   else
421   {
422     x1 = p2;
423     x2 = p1;
424     flip = 1;
425   }
426   this->GetImageIndex(x1,loc1,ij1);
427   this->GetImageIndex(x2,loc2,ij2);
428 
429   if ( (numInt=ij2[0]-ij1[0]) > 0 ) //if there are any x-intersections
430   {
431     for (i=1; i<=numInt; i++)
432     {
433       if ( (ij1[0]+i) >= this->Extent[0] )
434       {
435         x[0] = this->Origin[0] + (ij1[0]+i)*this->Spacing[0];
436         t = (x[0] - x1[0]) / (x2[0] - x1[0]);
437         x[1] = x1[1] + t*(x2[1]-x1[1]);
438         x[2] = x1[2] + t*(x2[2]-x1[2]);
439         this->GetImageIndex(x,loc,ij);
440         zMap = this->GetHeight(loc,ij);
441         error = x[2] - zMap;
442         if ( error >= 0.0 )
443         {
444           if (error > posError)
445           {
446             posError = error;
447             e.tPos = (flip ? (1-t) : t);
448           }
449         }
450         else
451         {
452           if (error < negError)
453           {
454             negError = error;
455             e.tNeg = (flip ? (1-t) : t);
456           }
457         }
458       } //if laying on image
459     } //for all x-intersection points
460   } //if x-intersections
461 
462   // Process the y intersections
463   if ( p2[1] >= p1[1] ) //sort along y-axis
464   {
465     x1 = p1;
466     x2 = p2;
467     flip = 0;
468   }
469   else
470   {
471     x1 = p2;
472     x2 = p1;
473     flip = 1;
474   }
475   this->GetImageIndex(x1,loc1,ij1);
476   this->GetImageIndex(x2,loc2,ij2);
477 
478   if ( (numInt=ij2[1]-ij1[1]) > 0 ) //if there are any x-intersections
479   {
480     for (i=1; i<=numInt; i++)
481     {
482       if ( (ij1[1]+i) >= this->Extent[2] )
483       {
484         x[1] = this->Origin[1] + (ij1[1]+i)*this->Spacing[1];
485         t = (x[1] - x1[1]) / (x2[1] - x1[1]);
486         x[0] = x1[0] + t*(x2[0]-x1[0]);
487         x[2] = x1[2] + t*(x2[2]-x1[2]);
488         this->GetImageIndex(x,loc,ij);
489         zMap = this->GetHeight(loc,ij);
490         error = x[2] - zMap;
491         if ( error >= 0.0 )
492         {
493           if (error > posError)
494           {
495             posError = error;
496             e.tPos = (flip ? (1-t) : t);
497           }
498         }
499         else
500         {
501           if (error < negError)
502           {
503             negError = error;
504             e.tNeg = (flip ? (1-t) : t);
505           }
506         }
507       } //if laying on image
508     } //for all x-intersection points
509   } //if x-intersections
510 
511   // Okay, insert the maximum errors for this edge in the queues
512   if ( posError > 0.0 )
513   {
514     this->PositiveLineError->Insert(-posError,edgeId);
515   }
516   if ( negError < 0.0 )
517   {
518     this->NegativeLineError->Insert(negError,edgeId);
519   }
520 }
521 
522 
523 //-----------------------------------------------------------------------------
PrintSelf(ostream & os,vtkIndent indent)524 void vtkProjectedTerrainPath::PrintSelf(ostream& os, vtkIndent indent)
525 {
526   this->Superclass::PrintSelf(os,indent);
527 
528   os << indent << "Projection Mode: ";
529   if ( this->ProjectionMode == SIMPLE_PROJECTION )
530   {
531     os << "Simple Projection\n";
532   }
533   else if ( this->ProjectionMode == NONOCCLUDED_PROJECTION )
534   {
535     os << "Non-occluded Projection\n";
536   }
537   else //if ( this->ProjectionMode == HUG_PROJECTION )
538   {
539     os << "Hug Projection\n";
540   }
541 
542   os << indent << "Height Offset: " << this->HeightOffset << "\n";
543   os << indent << "Height Tolerance: " << this->HeightTolerance << "\n";
544   os << indent << "Maximum Number Of Lines: "
545      << this->MaximumNumberOfLines << "\n";
546 
547 }
548