1 /*=========================================================================
2
3 Program: Visualization Toolkit
4 Module: vtkRibbonFilter.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 "vtkRibbonFilter.h"
16
17 #include "vtkCellArray.h"
18 #include "vtkCellData.h"
19 #include "vtkFloatArray.h"
20 #include "vtkMath.h"
21 #include "vtkInformation.h"
22 #include "vtkInformationVector.h"
23 #include "vtkObjectFactory.h"
24 #include "vtkPointData.h"
25 #include "vtkPoints.h"
26 #include "vtkPolyData.h"
27 #include "vtkPolyLine.h"
28
29 vtkStandardNewMacro(vtkRibbonFilter);
30
31 // Construct ribbon so that width is 0.1, the width does
32 // not vary with scalar values, and the width factor is 2.0.
vtkRibbonFilter()33 vtkRibbonFilter::vtkRibbonFilter()
34 {
35 this->Width = 0.5;
36 this->Angle = 0.0;
37 this->VaryWidth = 0;
38 this->WidthFactor = 2.0;
39
40 this->DefaultNormal[0] = this->DefaultNormal[1] = 0.0;
41 this->DefaultNormal[2] = 1.0;
42
43 this->UseDefaultNormal = 0;
44
45 this->GenerateTCoords = 0;
46 this->TextureLength = 1.0;
47
48 // by default process active point scalars
49 this->SetInputArrayToProcess(0,0,0,vtkDataObject::FIELD_ASSOCIATION_POINTS,
50 vtkDataSetAttributes::SCALARS);
51
52 // by default process active point vectors
53 this->SetInputArrayToProcess(1,0,0,vtkDataObject::FIELD_ASSOCIATION_POINTS,
54 vtkDataSetAttributes::NORMALS);
55 }
56
57 vtkRibbonFilter::~vtkRibbonFilter() = default;
58
59
RequestData(vtkInformation * vtkNotUsed (request),vtkInformationVector ** inputVector,vtkInformationVector * outputVector)60 int vtkRibbonFilter::RequestData(
61 vtkInformation *vtkNotUsed(request),
62 vtkInformationVector **inputVector,
63 vtkInformationVector *outputVector)
64 {
65 // get the info objects
66 vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
67 vtkInformation *outInfo = outputVector->GetInformationObject(0);
68
69 // get the input and output
70 vtkPolyData *input = vtkPolyData::SafeDownCast(
71 inInfo->Get(vtkDataObject::DATA_OBJECT()));
72 vtkPolyData *output = vtkPolyData::SafeDownCast(
73 outInfo->Get(vtkDataObject::DATA_OBJECT()));
74
75 vtkPointData *pd=input->GetPointData();
76 vtkPointData *outPD=output->GetPointData();
77 vtkCellData *cd=input->GetCellData();
78 vtkCellData *outCD=output->GetCellData();
79 vtkCellArray *inLines;
80 vtkDataArray *inNormals;
81 vtkDataArray *inScalars = this->GetInputArrayToProcess(0,inputVector);
82
83 vtkPoints *inPts;
84 vtkIdType numPts;
85 vtkIdType numLines;
86 vtkIdType numNewPts, numNewCells;
87 vtkPoints *newPts;
88 int deleteNormals=0;
89 vtkFloatArray *newNormals;
90 vtkIdType i;
91 double range[2];
92 vtkCellArray *newStrips;
93 vtkIdType npts=0, *pts=nullptr;
94 vtkIdType offset=0;
95 vtkFloatArray *newTCoords=nullptr;
96 int abort=0;
97 vtkIdType inCellId;
98
99 // Check input and initialize
100 //
101 vtkDebugMacro(<<"Creating ribbon");
102
103 if ( !(inPts=input->GetPoints()) ||
104 (numPts = inPts->GetNumberOfPoints()) < 1 ||
105 !(inLines = input->GetLines()) ||
106 (numLines = inLines->GetNumberOfCells()) < 1 )
107 {
108 return 1;
109 }
110
111 // Create the geometry and topology
112 numNewPts = 2 * numPts;
113 newPts = vtkPoints::New();
114 newPts->Allocate(numNewPts);
115 newNormals = vtkFloatArray::New();
116 newNormals->SetNumberOfComponents(3);
117 newNormals->Allocate(3*numNewPts);
118 newStrips = vtkCellArray::New();
119 newStrips->Allocate(newStrips->EstimateSize(1,numNewPts));
120 vtkCellArray *singlePolyline = vtkCellArray::New();
121
122 // Point data: copy scalars, vectors, tcoords. Normals may be computed here.
123 outPD->CopyNormalsOff();
124 if ( (this->GenerateTCoords == VTK_TCOORDS_FROM_SCALARS && inScalars) ||
125 this->GenerateTCoords == VTK_TCOORDS_FROM_LENGTH ||
126 this->GenerateTCoords == VTK_TCOORDS_FROM_NORMALIZED_LENGTH )
127 {
128 newTCoords = vtkFloatArray::New();
129 newTCoords->SetNumberOfComponents(2);
130 newTCoords->Allocate(numNewPts);
131 outPD->CopyTCoordsOff();
132 }
133 outPD->CopyAllocate(pd,numNewPts);
134
135 int generateNormals = 0;
136 inNormals = this->GetInputArrayToProcess(1,inputVector);
137 if ( !inNormals || this->UseDefaultNormal )
138 {
139 deleteNormals = 1;
140 inNormals = vtkFloatArray::New();
141 inNormals->SetNumberOfComponents(3);
142 inNormals->SetNumberOfTuples(numPts);
143
144 if ( this->UseDefaultNormal )
145 {
146 for ( i=0; i < numPts; i++)
147 {
148 inNormals->SetTuple(i,this->DefaultNormal);
149 }
150 }
151 else
152 {
153 // Normal generation has been moved to lower in the function.
154 // This allows each different polylines to share vertices, but have
155 // their normals (and hence their ribbons) calculated independently
156 generateNormals = 1;
157 }
158 }
159
160 // If varying width, get appropriate info.
161 //
162 if ( this->VaryWidth && inScalars )
163 {
164 inScalars->GetRange(range,0);
165 if ((range[1] - range[0]) == 0.0)
166 {
167 vtkWarningMacro(<< "Scalar range is zero!");
168 range[1] = range[0] + 1.0;
169 }
170 }
171
172 // Copy selected parts of cell data; certainly don't want normals
173 //
174 numNewCells = inLines->GetNumberOfCells();
175 outCD->CopyNormalsOff();
176 outCD->CopyAllocate(cd,numNewCells);
177
178 // Create points along each polyline that are connected into NumberOfSides
179 // triangle strips. Texture coordinates are optionally generated.
180 //
181 this->Theta = vtkMath::RadiansFromDegrees( this->Angle );
182 vtkPolyLine *lineNormalGenerator = vtkPolyLine::New();
183 for (inCellId=0, inLines->InitTraversal();
184 inLines->GetNextCell(npts,pts) && !abort; inCellId++)
185 {
186 this->UpdateProgress((double)inCellId/numLines);
187 abort = this->GetAbortExecute();
188
189 if (npts < 2)
190 {
191 vtkWarningMacro(<< "Less than two points in line!");
192 continue; //skip tubing this polyline
193 }
194
195 // If necessary calculate normals, each polyline calculates its
196 // normals independently, avoiding conflicts at shared vertices.
197 if (generateNormals)
198 {
199 singlePolyline->Reset(); //avoid instantiation
200 singlePolyline->InsertNextCell(npts,pts);
201 if ( !lineNormalGenerator->GenerateSlidingNormals(inPts,singlePolyline,
202 inNormals) )
203 {
204 vtkWarningMacro(<< "No normals for line!");
205 continue; //skip tubing this polyline
206 }
207 }
208
209 // Generate the points around the polyline. The strip is not created
210 // if the polyline is bad.
211 //
212 if ( !this->GeneratePoints(offset,npts,pts,inPts,newPts,pd,outPD,
213 newNormals,inScalars,range,inNormals) )
214 {
215 vtkWarningMacro(<< "Could not generate points!");
216 continue; //skip ribboning this polyline
217 }
218
219 // Generate the strip for this polyline
220 //
221 this->GenerateStrip(offset,npts,pts,inCellId,cd,outCD,newStrips);
222
223 // Generate the texture coordinates for this polyline
224 //
225 if ( newTCoords )
226 {
227 this->GenerateTextureCoords(offset,npts,pts,inPts,inScalars,newTCoords);
228 }
229
230 // Compute the new offset for the next polyline
231 offset = this->ComputeOffset(offset,npts);
232
233 }//for all polylines
234
235 singlePolyline->Delete();
236
237 // Update ourselves
238 //
239 if ( deleteNormals )
240 {
241 inNormals->Delete();
242 }
243
244 if ( newTCoords )
245 {
246 outPD->SetTCoords(newTCoords);
247 newTCoords->Delete();
248 }
249
250 output->SetPoints(newPts);
251 newPts->Delete();
252
253 output->SetStrips(newStrips);
254 newStrips->Delete();
255
256 outPD->SetNormals(newNormals);
257 newNormals->Delete();
258 lineNormalGenerator->Delete();
259
260 output->Squeeze();
261
262 return 1;
263 }
264
GeneratePoints(vtkIdType offset,vtkIdType npts,vtkIdType * pts,vtkPoints * inPts,vtkPoints * newPts,vtkPointData * pd,vtkPointData * outPD,vtkFloatArray * newNormals,vtkDataArray * inScalars,double range[2],vtkDataArray * inNormals)265 int vtkRibbonFilter::GeneratePoints(vtkIdType offset,
266 vtkIdType npts, vtkIdType *pts,
267 vtkPoints *inPts, vtkPoints *newPts,
268 vtkPointData *pd, vtkPointData *outPD,
269 vtkFloatArray *newNormals,
270 vtkDataArray *inScalars, double range[2],
271 vtkDataArray *inNormals)
272 {
273 vtkIdType j;
274 int i;
275 double p[3];
276 double pNext[3];
277 double sNext[3] = {0, 0, 0};
278 double sPrev[3];
279 double n[3];
280 double s[3], sp[3], sm[3], v[3];
281 //double bevelAngle;
282 double w[3];
283 double nP[3];
284 double sFactor=1.0;
285 vtkIdType ptId=offset;
286
287 // Use "averaged" segment to create beveled effect.
288 // Watch out for first and last points.
289 //
290 for (j=0; j < npts; j++)
291 {
292 if ( j == 0 ) //first point
293 {
294 inPts->GetPoint(pts[0],p);
295 inPts->GetPoint(pts[1],pNext);
296 for (i=0; i<3; i++)
297 {
298 sNext[i] = pNext[i] - p[i];
299 sPrev[i] = sNext[i];
300 }
301 }
302 else if ( j == (npts-1) ) //last point
303 {
304 for (i=0; i<3; i++)
305 {
306 sPrev[i] = sNext[i];
307 p[i] = pNext[i];
308 }
309 }
310 else
311 {
312 for (i=0; i<3; i++)
313 {
314 p[i] = pNext[i];
315 }
316 inPts->GetPoint(pts[j+1],pNext);
317 for (i=0; i<3; i++)
318 {
319 sPrev[i] = sNext[i];
320 sNext[i] = pNext[i] - p[i];
321 }
322 }
323
324 inNormals->GetTuple(pts[j], n);
325
326 if ( vtkMath::Normalize(sNext) == 0.0 )
327 {
328 vtkWarningMacro(<<"Coincident points!");
329 return 0;
330 }
331
332 for (i=0; i<3; i++)
333 {
334 s[i] = (sPrev[i] + sNext[i]) / 2.0; //average vector
335 }
336 // if s is zero then just use sPrev cross n
337 if (vtkMath::Normalize(s) == 0.0)
338 {
339 vtkWarningMacro(<< "Using alternate bevel vector");
340 vtkMath::Cross(sPrev,n,s);
341 if (vtkMath::Normalize(s) == 0.0)
342 {
343 vtkWarningMacro(<< "Using alternate bevel vector");
344 }
345 }
346 /*
347 if ( (bevelAngle = vtkMath::Dot(sNext,sPrev)) > 1.0 )
348 {
349 bevelAngle = 1.0;
350 }
351 if ( bevelAngle < -1.0 )
352 {
353 bevelAngle = -1.0;
354 }
355 bevelAngle = acos((double)bevelAngle) / 2.0; //(0->90 degrees)
356 if ( (bevelAngle = cos(bevelAngle)) == 0.0 )
357 {
358 bevelAngle = 1.0;
359 }
360
361 bevelAngle = this->Width / bevelAngle; //keep ribbon constant width
362 */
363 vtkMath::Cross(s,n,w);
364 if ( vtkMath::Normalize(w) == 0.0)
365 {
366 vtkWarningMacro(<<"Bad normal s = " <<s[0]<<" "<<s[1]<<" "<< s[2]
367 << " n = " << n[0] << " " << n[1] << " " << n[2]);
368 return 0;
369 }
370
371 vtkMath::Cross(w,s,nP); //create orthogonal coordinate system
372 vtkMath::Normalize(nP);
373
374 // Compute a scale factor based on scalars or vectors
375 if ( inScalars && this->VaryWidth ) // varying by scalar values
376 {
377 sFactor = 1.0 + ((this->WidthFactor - 1.0) *
378 (inScalars->GetComponent(pts[j],0) - range[0])
379 / (range[1]-range[0]));
380 }
381
382 for (i=0; i<3; i++)
383 {
384 v[i] = (w[i]*cos(this->Theta) + nP[i]*sin(this->Theta));
385 sp[i] = p[i] + this->Width * sFactor * v[i];
386 sm[i] = p[i] - this->Width * sFactor * v[i];
387 }
388 newPts->InsertPoint(ptId,sm);
389 newNormals->InsertTuple(ptId,nP);
390 outPD->CopyData(pd,pts[j],ptId);
391 ptId++;
392 newPts->InsertPoint(ptId,sp);
393 newNormals->InsertTuple(ptId,nP);
394 outPD->CopyData(pd,pts[j],ptId);
395 ptId++;
396 }//for all points in polyline
397
398 return 1;
399 }
400
GenerateStrip(vtkIdType offset,vtkIdType npts,vtkIdType * vtkNotUsed (pts),vtkIdType inCellId,vtkCellData * cd,vtkCellData * outCD,vtkCellArray * newStrips)401 void vtkRibbonFilter::GenerateStrip(vtkIdType offset, vtkIdType npts,
402 vtkIdType* vtkNotUsed(pts),
403 vtkIdType inCellId,
404 vtkCellData *cd, vtkCellData *outCD,
405 vtkCellArray *newStrips)
406 {
407 vtkIdType i, idx, outCellId;
408
409 outCellId = newStrips->InsertNextCell(npts*2);
410 outCD->CopyData(cd,inCellId,outCellId);
411 for (i=0; i < npts; i++)
412 {
413 idx = 2*i;
414 newStrips->InsertCellPoint(offset+idx);
415 newStrips->InsertCellPoint(offset+idx+1);
416 }
417 }
418
GenerateTextureCoords(vtkIdType offset,vtkIdType npts,vtkIdType * pts,vtkPoints * inPts,vtkDataArray * inScalars,vtkFloatArray * newTCoords)419 void vtkRibbonFilter::GenerateTextureCoords(vtkIdType offset,
420 vtkIdType npts, vtkIdType *pts,
421 vtkPoints *inPts,
422 vtkDataArray *inScalars,
423 vtkFloatArray *newTCoords)
424 {
425 vtkIdType i;
426 int k;
427 double tc;
428
429 double s0, s;
430 //The first texture coordinate is always 0.
431 for ( k=0; k < 2; k++)
432 {
433 newTCoords->InsertTuple2(offset+k,0.0,0.0);
434 }
435 if ( this->GenerateTCoords == VTK_TCOORDS_FROM_SCALARS && inScalars)
436 {
437 s0 = inScalars->GetTuple1(pts[0]);
438 for (i=1; i < npts; i++)
439 {
440 s = inScalars->GetTuple1(pts[i]);
441 tc = (s - s0) / this->TextureLength;
442 for ( k=0; k < 2; k++)
443 {
444 newTCoords->InsertTuple2(offset+i*2+k,tc,0.0);
445 }
446 }
447 }
448 else if ( this->GenerateTCoords == VTK_TCOORDS_FROM_LENGTH )
449 {
450 double xPrev[3], x[3], len=0.0;
451 inPts->GetPoint(pts[0],xPrev);
452 for (i=1; i < npts; i++)
453 {
454 inPts->GetPoint(pts[i],x);
455 len += sqrt(vtkMath::Distance2BetweenPoints(x,xPrev));
456 tc = len / this->TextureLength;
457 for ( k=0; k < 2; k++)
458 {
459 newTCoords->InsertTuple2(offset+i*2+k,tc,0.0);
460 }
461 xPrev[0]=x[0]; xPrev[1]=x[1]; xPrev[2]=x[2];
462 }
463 }
464 else if ( this->GenerateTCoords == VTK_TCOORDS_FROM_NORMALIZED_LENGTH )
465 {
466 double xPrev[3], x[3], length=0.0, len=0.0;
467 inPts->GetPoint(pts[0],xPrev);
468 for (i=1; i < npts; i++)
469 {
470 inPts->GetPoint(pts[i],x);
471 length += sqrt(vtkMath::Distance2BetweenPoints(x,xPrev));
472 xPrev[0]=x[0]; xPrev[1]=x[1]; xPrev[2]=x[2];
473 }
474
475 inPts->GetPoint(pts[0],xPrev);
476 for (i=1; i < npts; i++)
477 {
478 inPts->GetPoint(pts[i],x);
479 len += sqrt(vtkMath::Distance2BetweenPoints(x,xPrev));
480 tc = len / length;
481 for ( k=0; k < 2; k++)
482 {
483 newTCoords->InsertTuple2(offset+i*2+k,tc,0.0);
484 }
485 xPrev[0]=x[0]; xPrev[1]=x[1]; xPrev[2]=x[2];
486 }
487 }
488 }
489
490 // Compute the number of points in this ribbon
ComputeOffset(vtkIdType offset,vtkIdType npts)491 vtkIdType vtkRibbonFilter::ComputeOffset(vtkIdType offset, vtkIdType npts)
492 {
493 offset += 2 * npts;
494 return offset;
495 }
496
497 // Description:
498 // Return the method of generating the texture coordinates.
GetGenerateTCoordsAsString(void)499 const char *vtkRibbonFilter::GetGenerateTCoordsAsString(void)
500 {
501 if ( this->GenerateTCoords == VTK_TCOORDS_OFF )
502 {
503 return "GenerateTCoordsOff";
504 }
505 else if ( this->GenerateTCoords == VTK_TCOORDS_FROM_SCALARS )
506 {
507 return "GenerateTCoordsFromScalar";
508 }
509 else if ( this->GenerateTCoords == VTK_TCOORDS_FROM_LENGTH )
510 {
511 return "GenerateTCoordsFromLength";
512 }
513 else
514 {
515 return "GenerateTCoordsFromNormalizedLength";
516 }
517 }
518
PrintSelf(ostream & os,vtkIndent indent)519 void vtkRibbonFilter::PrintSelf(ostream& os, vtkIndent indent)
520 {
521 this->Superclass::PrintSelf(os,indent);
522
523 os << indent << "Width: " << this->Width << "\n";
524 os << indent << "Angle: " << this->Angle << "\n";
525 os << indent << "VaryWidth: " << (this->VaryWidth ? "On\n" : "Off\n");
526 os << indent << "Width Factor: " << this->WidthFactor << "\n";
527 os << indent << "Use Default Normal: " << this->UseDefaultNormal << "\n";
528 os << indent << "Default Normal: " << "( "
529 << this->DefaultNormal[0] << ", "
530 << this->DefaultNormal[1] << ", "
531 << this->DefaultNormal[2] << " )\n";
532
533 os << indent << "Generate TCoords: "
534 << this->GetGenerateTCoordsAsString() << endl;
535 os << indent << "Texture Length: " << this->TextureLength << endl;
536 }
537
538