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