/*========================================================================= Program: Visualization Toolkit Module: vtkLeaderActor2D.cxx Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen All rights reserved. See Copyright.txt or http://www.kitware.com/Copyright.htm for details. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the above copyright notice for more information. =========================================================================*/ #include "vtkLeaderActor2D.h" #include "vtkPoints.h" #include "vtkCellArray.h" #include "vtkPolyData.h" #include "vtkPolyDataMapper2D.h" #include "vtkTextMapper.h" #include "vtkTextProperty.h" #include "vtkViewport.h" #include "vtkWindow.h" #include "vtkObjectFactory.h" #include "vtkMath.h" vtkStandardNewMacro(vtkLeaderActor2D); vtkCxxSetObjectMacro(vtkLeaderActor2D,LabelTextProperty,vtkTextProperty); //---------------------------------------------------------------------------- // Instantiate this object. vtkLeaderActor2D::vtkLeaderActor2D() { this->PositionCoordinate->SetCoordinateSystemToNormalizedViewport(); this->PositionCoordinate->SetValue(0.0, 0.0); this->Position2Coordinate->SetCoordinateSystemToNormalizedViewport(); this->Position2Coordinate->SetValue(0.75, 0.75); this->Position2Coordinate->SetReferenceCoordinate(nullptr); this->Radius = 0.0; this->Length = 0.0; this->Angle = 0.0; this->Label = nullptr; this->LabelFactor = 1.0; this->AutoLabel = 0; this->LabelFormat = new char[8]; snprintf(this->LabelFormat,8,"%s","%-#6.3g"); this->ArrowPlacement = vtkLeaderActor2D::VTK_ARROW_BOTH; this->ArrowStyle = vtkLeaderActor2D::VTK_ARROW_FILLED; this->ArrowLength = 0.04; this->ArrowWidth = 0.02; this->MinimumArrowSize = 2; this->MaximumArrowSize = 25; this->LabelTextProperty = vtkTextProperty::New(); this->LabelTextProperty->SetBold(1); this->LabelTextProperty->SetItalic(1); this->LabelTextProperty->SetShadow(1); this->LabelTextProperty->SetFontFamilyToArial(); this->LabelTextProperty->SetJustificationToCentered(); this->LabelTextProperty->SetVerticalJustificationToCentered(); this->LabelMapper = vtkTextMapper::New(); this->LabelActor = vtkActor2D::New(); this->LabelActor->SetMapper(this->LabelMapper); // Points 0-3 are the side1 of the arrow; Points 4-7 are the side2 of the arrow. this->LeaderPoints = vtkPoints::New(); this->LeaderLines = vtkCellArray::New(); this->LeaderLines->EstimateSize(1,2); this->LeaderArrows = vtkCellArray::New(); this->LeaderArrows->EstimateSize(2,3); this->Leader = vtkPolyData::New(); this->Leader->SetPoints(this->LeaderPoints); this->Leader->SetLines(this->LeaderLines); this->Leader->SetPolys(this->LeaderArrows); this->LeaderMapper = vtkPolyDataMapper2D::New(); this->LeaderMapper->SetInputData(this->Leader); this->LeaderActor = vtkActor2D::New(); this->LeaderActor->SetMapper(this->LeaderMapper); this->LastPosition[0] = this->LastPosition[1] = 0; this->LastPosition2[0] = this->LastPosition2[1] = 0; this->LastSize[0] = this->LastSize[1] = 0; } //---------------------------------------------------------------------------- vtkLeaderActor2D::~vtkLeaderActor2D() { this->LabelMapper->Delete(); this->LabelActor->Delete(); delete [] this->Label; this->Label = nullptr; delete [] this->LabelFormat; this->LabelFormat = nullptr; this->LeaderPoints->Delete(); this->LeaderLines->Delete(); this->LeaderArrows->Delete(); this->Leader->Delete(); this->LeaderMapper->Delete(); this->LeaderActor->Delete(); this->SetLabelTextProperty(nullptr); } //---------------------------------------------------------------------------- void vtkLeaderActor2D::BuildLeader(vtkViewport *viewport) { // Check to see whether we need to rebuild----------------------------- int positionsHaveChanged = 0; if (viewport->GetMTime() > this->BuildTime || (viewport->GetVTKWindow() && viewport->GetVTKWindow()->GetMTime() > this->BuildTime)) { int *lastPosition = this->PositionCoordinate->GetComputedViewportValue(viewport); int *lastPosition2 = this->Position2Coordinate->GetComputedViewportValue(viewport); if (lastPosition[0] != this->LastPosition[0] || lastPosition[1] != this->LastPosition[1] || lastPosition2[0] != this->LastPosition2[0] || lastPosition2[1] != this->LastPosition2[1] ) { positionsHaveChanged = 1; } } int *size = viewport->GetSize(); int viewportSizeHasChanged=0; // See whether fonts have to be rebuilt (font size depends on viewport size) if (this->LastSize[0] != size[0] || this->LastSize[1] != size[1]) { viewportSizeHasChanged = 1; this->LastSize[0] = size[0]; this->LastSize[1] = size[1]; } if (!positionsHaveChanged && !viewportSizeHasChanged && this->GetMTime() < this->BuildTime && this->LabelTextProperty->GetMTime() < this->BuildTime ) { return; } // Okay, we have some work to do. We build the leader in three parts: // 1) the line connecting the two points, 2) the text label, and 3) the // arrow head(s) if any. vtkDebugMacro(<<"Rebuilding leader"); //--------------------------------- // Initialize the data this->LeaderPoints->Initialize(); this->LeaderLines->Initialize(); this->LeaderArrows->Initialize(); this->LeaderActor->SetProperty(this->GetProperty()); this->LabelMapper->SetTextProperty(this->LabelTextProperty); // The easiest part is determining the two end points of the line. double p1[3], p2[3], ray[3]; int *x = this->PositionCoordinate->GetComputedViewportValue(viewport); p1[0] = static_cast(x[0]); p1[1] = static_cast(x[1]); p1[2] = 0.0; this->LastPosition[0] = x[0]; this->LastPosition[1] = x[1]; x = this->Position2Coordinate->GetComputedViewportValue(viewport); p2[0] = static_cast(x[0]); p2[1] = static_cast(x[1]); p2[2] = 0.0; this->LastPosition2[0] = x[0]; this->LastPosition2[1] = x[1]; ray[0] = p2[0] - p1[0]; ray[1] = p2[1] - p1[1]; ray[2] = 0.0; double rayLength = vtkMath::Norm(ray); if ( rayLength <= 0.0 ) { return; } double theta, theta2; if (ray[0] == 0. && ray[1] == 0.) { theta = 0.; } else { theta = atan2(ray[1], ray[0]); } theta2 = theta + vtkMath::Pi(); // If there is a suitable radius then a curved leader must be created. // Remember the radius is expresses as a factor times the distance between (p1,p2). if ( fabs(this->Radius) > 0.5 ) { this->BuildCurvedLeader(p1,p2,ray,rayLength,theta,viewport,viewportSizeHasChanged); return; } // Okay, we can continue building the straight leader-------------------------- this->LeaderPoints->SetNumberOfPoints(8); this->LeaderPoints->SetPoint(0,p1); this->LeaderPoints->SetPoint(4,p2); this->LeaderPoints->Modified(); // Build the labels int i, clippedLeader=0; double xL[3], xR[3], c1[3], c2[3]; double *x1 = this->PositionCoordinate->GetComputedWorldValue(viewport); double *x2 = this->Position2Coordinate->GetComputedWorldValue(viewport); this->Length = sqrt(vtkMath::Distance2BetweenPoints(x1,x2)); if ( this->AutoLabel || (this->Label != nullptr && this->Label[0] != 0) ) { int stringSize[2]; if ( this->AutoLabel ) { char string[512]; snprintf(string, sizeof(string), this->LabelFormat, this->Length); this->LabelMapper->SetInput(string); } else { this->LabelMapper->SetInput(this->Label); } if (this->LabelTextProperty->GetMTime() > this->BuildTime) { this->LabelMapper->GetTextProperty()->ShallowCopy(this->LabelTextProperty); } if (viewportSizeHasChanged || this->LabelTextProperty->GetMTime() > this->BuildTime) { this->SetFontSize(viewport, this->LabelMapper, size, this->LabelFactor, stringSize); } else { this->LabelMapper->GetSize(viewport, stringSize); } for (i=0; i<3; i++) { xL[i] = p1[i] + 0.5*ray[i]; } // Now clip the leader with the label box if ( (clippedLeader=this->ClipLeader(xL,stringSize,p1,ray,c1,c2)) ) { this->LabelActor->SetPosition(xL[0],xL[1]); this->LeaderPoints->SetPoint(3,c1); this->LeaderPoints->SetPoint(7,c2); } else //we cannot fit the text in the leader, it has to be placed next to the leader { double w = static_cast(stringSize[0])/2.0; double h = static_cast(stringSize[1])/2.0; double r = sqrt(h*h+w*w); xL[0] = xL[0] + r*sin(theta); xL[1] = xL[1] - r*cos(theta); this->LabelActor->SetPosition(xL[0],xL[1]); } } // If label visible if ( !clippedLeader ) { //we just draw a single line across 'cause there is no label in the leader this->LeaderLines->InsertNextCell(2); this->LeaderLines->InsertCellPoint(0); this->LeaderLines->InsertCellPoint(4); } else { //draw two lines separated by label this->LeaderLines->InsertNextCell(2); this->LeaderLines->InsertCellPoint(0); this->LeaderLines->InsertCellPoint(3); this->LeaderLines->InsertNextCell(2); this->LeaderLines->InsertCellPoint(4); this->LeaderLines->InsertCellPoint(7); } // Build the arrows--------------------------------------- if ( this->ArrowPlacement == vtkLeaderActor2D::VTK_ARROW_NONE ) { ; //do nothin } else //we are creating arrows { this->Leader->Modified(); // Convert width and length to viewport (pixel) coordinates double dist = sqrt(static_cast(size[0]*size[0] + size[1]*size[1])); double width = this->ArrowWidth * dist / 2.0; double length = this->ArrowLength * dist; if ( length < width && length < this->MinimumArrowSize ) { width = this->MinimumArrowSize*width/length; length = this->MinimumArrowSize; } else if ( width < length && width < this->MinimumArrowSize ) { length = this->MinimumArrowSize*length/width; width = this->MinimumArrowSize; } if ( length > width && length > this->MaximumArrowSize ) { width = this->MaximumArrowSize*width/length; length = this->MaximumArrowSize; } else if ( width > length && width > this->MaximumArrowSize ) { length = this->MaximumArrowSize*length/width; width = this->MaximumArrowSize; } // Find the position along the line for the arrows and create the additional points double a1[3], a2[3]; for (i=0; i<3; i++) { a1[i] = p1[i] + (length/rayLength)*ray[i]; a2[i] = p1[i] + (1.0-(length/rayLength))*ray[i]; } if ( this->ArrowPlacement == vtkLeaderActor2D::VTK_ARROW_POINT1 || this->ArrowPlacement == vtkLeaderActor2D::VTK_ARROW_BOTH ) { xL[0] = a1[0] + width*sin(theta); xL[1] = a1[1] - width*cos(theta); xR[0] = a1[0] + width*sin(theta2); xR[1] = a1[1] - width*cos(theta2); xR[2] = xL[2] = 0.0; this->LeaderPoints->SetPoint(1,xL); this->LeaderPoints->SetPoint(2,xR); if ( this->ArrowStyle == vtkLeaderActor2D::VTK_ARROW_FILLED ) { this->LeaderArrows->InsertNextCell(3); this->LeaderArrows->InsertCellPoint(0); this->LeaderArrows->InsertCellPoint(1); this->LeaderArrows->InsertCellPoint(2); } else if ( this->ArrowStyle == vtkLeaderActor2D::VTK_ARROW_OPEN ) { this->LeaderLines->InsertNextCell(3); this->LeaderLines->InsertCellPoint(1); this->LeaderLines->InsertCellPoint(0); this->LeaderLines->InsertCellPoint(2); } else //if ( this->ArrowStyle == vtkLeaderActor2D::VTK_ARROW_HOLLOW ) { this->LeaderLines->InsertNextCell(4); this->LeaderLines->InsertCellPoint(1); this->LeaderLines->InsertCellPoint(0); this->LeaderLines->InsertCellPoint(2); this->LeaderLines->InsertCellPoint(1); } } if ( this->ArrowPlacement == vtkLeaderActor2D::VTK_ARROW_POINT2 || this->ArrowPlacement == vtkLeaderActor2D::VTK_ARROW_BOTH ) { xL[0] = a2[0] + width*sin(theta); xL[1] = a2[1] - width*cos(theta); xR[0] = a2[0] + width*sin(theta2); xR[1] = a2[1] - width*cos(theta2); xR[2] = xL[2] = 0.0; this->LeaderPoints->SetPoint(5,xL); this->LeaderPoints->SetPoint(6,xR); if ( this->ArrowStyle == vtkLeaderActor2D::VTK_ARROW_FILLED ) { this->LeaderArrows->InsertNextCell(3); this->LeaderArrows->InsertCellPoint(4); this->LeaderArrows->InsertCellPoint(5); this->LeaderArrows->InsertCellPoint(6); } else if ( this->ArrowStyle == vtkLeaderActor2D::VTK_ARROW_OPEN ) { this->LeaderLines->InsertNextCell(3); this->LeaderLines->InsertCellPoint(5); this->LeaderLines->InsertCellPoint(4); this->LeaderLines->InsertCellPoint(6); } else //if ( this->ArrowStyle == vtkLeaderActor2D::VTK_ARROW_HOLLOW ) { this->LeaderLines->InsertNextCell(4); this->LeaderLines->InsertCellPoint(5); this->LeaderLines->InsertCellPoint(4); this->LeaderLines->InsertCellPoint(6); this->LeaderLines->InsertCellPoint(5); } } } //creating arrows this->BuildTime.Modified(); } //---------------------------------------------------------------------------- #define VTK_LA2D_FACTOR 0.015 int vtkLeaderActor2D::SetFontSize(vtkViewport *viewport, vtkTextMapper *textMapper, int *targetSize, double factor, int *stringSize) { int fontSize, targetWidth, targetHeight; targetWidth = targetSize[0] > targetSize[1] ? targetSize[0] : targetSize[1]; targetHeight = static_cast(VTK_LA2D_FACTOR * factor * targetSize[0] + VTK_LA2D_FACTOR * factor * targetSize[1]); fontSize = textMapper->SetConstrainedFontSize(viewport, targetWidth, targetHeight); textMapper->GetSize(viewport, stringSize); return fontSize; } #undef VTK_LA2D_FACTOR //---------------------------------------------------------------------------- int vtkLeaderActor2D::ClipLeader(double center[3], int box[2], double p1[3], double ray[3], double c1[3], double c2[3]) { // Separately compute the parametric coordintes due to x-line and y-line // intersections. Take the coordinate closest to the center of the line. double tx, ty, t; double x = center[0] + box[0]; double y = center[1] + box[1]; // x-line if ( ray[0] != 0.0 ) { tx = (x - p1[0])/ray[0]; } else { tx = VTK_FLOAT_MAX; } // y-line if ( ray[1] != 0.0 ) { ty = (y - p1[1])/ray[1]; } else { ty = VTK_FLOAT_MAX; } // Find the closest intersection point nearest the center of the box t = ( fabs(tx-0.5) < fabs(ty-0.5) ? tx : ty ); if ( fabs(t-0.5) > 0.45 ) { return 0; //won't fit along line } else { t = ( t>0.5 ? t : 1.0-t); //make sure t is to the right of the midpoint for (int i=0; i<3; i++) { c1[i] = p1[i] + (1.0-t)*ray[i]; c2[i] = p1[i] + t*ray[i]; } return 1; } } //---------------------------------------------------------------------------- void vtkLeaderActor2D::BuildCurvedLeader(double p1[3], double p2[3], double ray[3], double rayLength, double theta, vtkViewport *viewport, int viewportChanged) { // Determine where the center is double radius = fabs(this->Radius)*rayLength; double midPoint[3], center[3]; int i; midPoint[0] = p1[0] + 0.5*ray[0]; midPoint[1] = p1[1] + 0.5*ray[1]; midPoint[2] = p1[2] + 0.5*ray[2]; double d = sqrt(radius*radius - rayLength*rayLength/4.0); if ( this->Radius > 0 ) { center[0] = midPoint[0] + d*sin(theta); center[1] = midPoint[1] - d*cos(theta); center[2] = 0.0; } else { center[0] = midPoint[0] - d*sin(theta); center[1] = midPoint[1] + d*cos(theta); center[2] = 0.0; } // Compute some angles; make sure they are <= 180 degrees double phi = atan2(rayLength/2.0,d); double theta1 = atan2(p1[1]-center[1],p1[0]-center[0]); double theta2 = atan2(p2[1]-center[1],p2[0]-center[0]); if ( (theta1 >= 0.0 && theta1 <= vtkMath::Pi() && theta2 >= 0.0 && theta2 <= vtkMath::Pi()) || (theta1 <= 0.0 && theta1 >= -vtkMath::Pi() && theta2 <= 0.0 && theta2 >= -vtkMath::Pi()) ) { ; //do nothin angles are fine } else if ( theta1 >= 0.0 && theta2 <= 0.0 ) { if ( (theta1 - theta2) >= vtkMath::Pi() ) { theta2 = theta2 + 2.0*vtkMath::Pi(); } } else //if ( theta1 <= 0.0 && theta2 >= 0.0 ) { if ( (theta2 - theta1) >= vtkMath::Pi() ) { theta1 = theta1 + 2.0*vtkMath::Pi(); } } // Build the polyline for the leader. Start by generating the points. double x[3]; x[2]=0.0; double length = radius*phi; int numDivs = static_cast((length / 3.0) + 1); //every three pixels for (i=0; i<=numDivs; i++) { theta = theta1 + (static_cast(i)/numDivs)*(theta2-theta1); x[0] = center[0] + radius*cos(theta); x[1] = center[1] + radius*sin(theta); this->LeaderPoints->InsertPoint(i,x); } // Now insert lines. Only those not clipped by the string are added. this->Angle = vtkMath::DegreesFromRadians( theta1 - theta2); if ( this->AutoLabel || (this->Label != nullptr && this->Label[0] != 0) ) { int stringSize[2]; if ( this->AutoLabel ) { char string[512]; snprintf(string, sizeof(string), this->LabelFormat, this->Angle); this->LabelMapper->SetInput(string); } else { this->LabelMapper->SetInput(this->Label); } if (this->LabelTextProperty->GetMTime() > this->BuildTime) { this->LabelMapper->GetTextProperty()->ShallowCopy(this->LabelTextProperty); } if (viewportChanged || this->LabelTextProperty->GetMTime() > this->BuildTime) { int *size = viewport->GetSize(); this->SetFontSize(viewport, this->LabelMapper, size, this->LabelFactor, stringSize); } else { this->LabelMapper->GetSize(viewport, stringSize); } double x1[3], c[3]; theta = (theta1 + theta2)/2.0; c[0] = center[0] + radius*cos(theta); c[1] = center[1] + radius*sin(theta); c[2] = 0.0; this->LabelActor->SetPosition(c[0],c[1]); for (i=0; iLeaderPoints->GetPoint(i,x); this->LeaderPoints->GetPoint(i+1,x1); if ( ! this->InStringBox(c,stringSize,x) && ! this->InStringBox(c,stringSize,x1) ) { this->LeaderLines->InsertNextCell(2); this->LeaderLines->InsertCellPoint(i); this->LeaderLines->InsertCellPoint(i+1); } } } else // no clipping against the string necessary { for (i=0; iLeaderLines->InsertNextCell(2); this->LeaderLines->InsertCellPoint(i); this->LeaderLines->InsertCellPoint(i+1); } } } int vtkLeaderActor2D::InStringBox(double center[3], int stringSize[2], double x[3]) { double minX = center[0] - static_cast(stringSize[0])/2.0; double maxX = center[0] + static_cast(stringSize[0])/2.0; double minY = center[1] - static_cast(stringSize[1])/2.0; double maxY = center[1] + static_cast(stringSize[1])/2.0; if ( minX <= x[0] && x[0] <= maxX && minY <= x[1] && x[1] <= maxY ) { return 1; } else { return 0; } } //---------------------------------------------------------------------------- // Release any graphics resources that are being consumed by this actor. // The parameter window could be used to determine which graphic // resources to release. void vtkLeaderActor2D::ReleaseGraphicsResources(vtkWindow *win) { this->LabelActor->ReleaseGraphicsResources(win); this->LeaderActor->ReleaseGraphicsResources(win); } //---------------------------------------------------------------------------- // Build the axis, ticks, title, and labels and render. int vtkLeaderActor2D::RenderOpaqueGeometry(vtkViewport *viewport) { int renderedSomething=0; this->BuildLeader(viewport); // Everything is built, just have to render if ( (this->Label != nullptr && this->Label[0]) || \ (this->AutoLabel && this->LabelMapper->GetInput() != nullptr ) ) { renderedSomething += this->LabelActor->RenderOpaqueGeometry(viewport); } renderedSomething += this->LeaderActor->RenderOpaqueGeometry(viewport); return renderedSomething; } //---------------------------------------------------------------------------- // Render the axis, ticks, title, and labels. int vtkLeaderActor2D::RenderOverlay(vtkViewport *viewport) { int renderedSomething=0; this->BuildLeader(viewport); // Everything is built, just have to render if ( (this->Label != nullptr && this->Label[0]) || \ (this->AutoLabel && this->LabelMapper->GetInput() != nullptr ) ) { renderedSomething += this->LabelActor->RenderOverlay(viewport); } renderedSomething += this->LeaderActor->RenderOverlay(viewport); return renderedSomething; } //----------------------------------------------------------------------------- // Description: // Does this prop have some translucent polygonal geometry? int vtkLeaderActor2D::HasTranslucentPolygonalGeometry() { return 0; } //---------------------------------------------------------------------------- void vtkLeaderActor2D::ShallowCopy(vtkProp *prop) { vtkLeaderActor2D *a = vtkLeaderActor2D::SafeDownCast(prop); if ( a != nullptr ) { this->SetLabel(a->GetLabel()); this->SetLabelTextProperty(a->GetLabelTextProperty()); this->SetLabelFactor(a->GetLabelFactor()); this->SetArrowPlacement(a->GetArrowPlacement()); this->SetArrowStyle(a->GetArrowStyle()); this->SetArrowLength(a->GetArrowLength()); this->SetArrowWidth(a->GetArrowWidth()); this->SetMinimumArrowSize(a->GetMinimumArrowSize()); this->SetMaximumArrowSize(a->GetMaximumArrowSize()); } // Now do superclass this->vtkActor2D::ShallowCopy(prop); } //---------------------------------------------------------------------------- void vtkLeaderActor2D::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os,indent); os << indent << "Radius: " << this->Radius << "\n"; os << indent << "Label: " << (this->Label ? this->Label : "(none)") << "\n"; if (this->LabelTextProperty) { os << indent << "Label Text Property:\n"; this->LabelTextProperty->PrintSelf(os,indent.GetNextIndent()); } else { os << indent << "Label Text Property: (none)\n"; } os << indent << "Label Factor: " << this->LabelFactor << "\n"; os << indent << "Auto Label: " << (this->AutoLabel ? "On\n" : "Off\n"); os << indent << "Label Format: " << this->LabelFormat << "\n"; os << indent << "Arrow Style: "; if ( this->ArrowStyle == VTK_ARROW_FILLED ) { os << "Filled\n"; } else if ( this->ArrowStyle == VTK_ARROW_OPEN ) { os << "Open\n"; } else // if ( this->ArrowStyle == VTK_ARROW_HOLLOW ) { os << "Hollow\n"; } os << indent << "Arrow Length: " << this->ArrowLength << "\n"; os << indent << "Arrow Width: " << this->ArrowWidth << "\n"; os << indent << "Minimum Arrow Size: " << this->MinimumArrowSize << "\n"; os << indent << "Maximum Arrow Size: " << this->MaximumArrowSize << "\n"; os << indent << "Arrow Placement: "; if ( this->ArrowPlacement == VTK_ARROW_NONE ) { os << "No Arrows\n"; } else if ( this->ArrowPlacement == VTK_ARROW_POINT1 ) { os << "Arrow on first point\n"; } else if ( this->ArrowPlacement == VTK_ARROW_POINT2 ) { os << "Arrow on second point\n"; } else { os << "Arrow on both ends\n"; } os << indent << "Angle: " << this->Angle << "\n"; os << indent << "Length: " << this->Length << "\n"; }