/*========================================================================= 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 "vtkCellArray.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkPoints.h" #include "vtkPolyData.h" #include "vtkPolyDataMapper2D.h" #include "vtkTextMapper.h" #include "vtkTextProperty.h" #include "vtkViewport.h" #include "vtkWindow.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->UseFontSizeFromProperty = 0; 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->AllocateEstimate(1, 2); this->LeaderArrows = vtkCellArray::New(); this->LeaderArrows->AllocateEstimate(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; } } const 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, const 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]); if (!this->UseFontSizeFromProperty) { fontSize = textMapper->SetConstrainedFontSize(viewport, targetWidth, targetHeight); } else { vtkTextProperty* tprop = textMapper->GetTextProperty(); if (!tprop) { vtkGenericWarningMacro(<< "Need text property to apply font size"); return 0; } fontSize = tprop->GetFontSize(); } 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 coordinates 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) { const 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; i < numDivs; i++) { this->LeaderPoints->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; i < numDivs; i++) { this->LeaderLines->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? vtkTypeBool 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"; }