mirror of
https://github.com/OpenFOAM/ThirdParty-6.git
synced 2025-12-08 06:57:43 +00:00
920 lines
30 KiB
C++
920 lines
30 KiB
C++
/*=========================================================================
|
|
|
|
Program: Visualization Toolkit
|
|
Module: vtkLabelPlacementMapper.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.
|
|
|
|
=========================================================================*/
|
|
/*-------------------------------------------------------------------------
|
|
Copyright 2008 Sandia Corporation.
|
|
Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
|
|
the U.S. Government retains certain rights in this software.
|
|
-------------------------------------------------------------------------*/
|
|
|
|
#include "vtkLabelPlacementMapper.h"
|
|
|
|
#include "vtkActor2D.h"
|
|
#include "vtkCamera.h"
|
|
#include "vtkCellArray.h"
|
|
#include "vtkExecutive.h"
|
|
#include "vtkIdTypeArray.h"
|
|
#include "vtkInformation.h"
|
|
#include "vtkLabeledDataMapper.h"
|
|
#include "vtkLabelHierarchy.h"
|
|
#include "vtkLabelHierarchyCompositeIterator.h"
|
|
#include "vtkLabelRenderStrategy.h"
|
|
#include "vtkMath.h"
|
|
#include "vtkFreeTypeLabelRenderStrategy.h"
|
|
#include "vtkObjectFactory.h"
|
|
#include "vtkPoints.h"
|
|
#include "vtkPolyDataMapper.h"
|
|
#include "vtkPolyDataMapper2D.h"
|
|
#include "vtkProperty2D.h"
|
|
#include "vtkRenderer.h"
|
|
#include "vtkRenderWindow.h"
|
|
#include "vtkSelectVisiblePoints.h"
|
|
#include "vtkSmartPointer.h"
|
|
#include "vtkTextProperty.h"
|
|
#include "vtkTimerLog.h"
|
|
#include "vtkTransformCoordinateSystems.h"
|
|
|
|
// From: http://www.flipcode.com/archives/2D_OBB_Intersection.shtml
|
|
class LabelRect
|
|
{
|
|
public:
|
|
|
|
// Rotation origin.
|
|
double RotationOrigin[2];
|
|
|
|
// Rotation amount (radians).
|
|
double Rotation;
|
|
|
|
// Rotated label bounds (xmin, xmax, ymin, ymax).
|
|
double Bounds[4];
|
|
|
|
// Corners of the rotated box, where 0 is the lower left.
|
|
// Corner 0 is lower-left, 1 is lower-right, 2 is upper-right, 3 is upper-left.
|
|
double Corner[4][2];
|
|
|
|
// Two edges of the box extended away from corner[0].
|
|
double Axis[2][2];
|
|
|
|
// origin[a] = corner[0].dot(axis[a]);
|
|
double Origin[2];
|
|
|
|
LabelRect(double center[2], const double w, const double h, double rotation)
|
|
{
|
|
double X[2];
|
|
double Y[2];
|
|
X[0] = cos(rotation)*w/2;
|
|
X[1] = sin(rotation)*w/2;
|
|
Y[0] = -sin(rotation)*h/2;
|
|
Y[1] = cos(rotation)*h/2;
|
|
|
|
Corner[0][0] = center[0] - X[0] - Y[0];
|
|
Corner[0][1] = center[1] - X[1] - Y[1];
|
|
Corner[1][0] = center[0] + X[0] - Y[0];
|
|
Corner[1][1] = center[1] + X[1] - Y[1];
|
|
Corner[2][0] = center[0] + X[0] + Y[0];
|
|
Corner[2][1] = center[1] + X[1] + Y[1];
|
|
Corner[3][0] = center[0] - X[0] + Y[0];
|
|
Corner[3][1] = center[1] - X[1] + Y[1];
|
|
|
|
RotationOrigin[0] = center[0];
|
|
RotationOrigin[1] = center[1];
|
|
|
|
Rotation = rotation;
|
|
|
|
ComputeAxes();
|
|
}
|
|
|
|
LabelRect(double x[4], double rotateOrigin[2], double rotation)
|
|
{
|
|
Rotation = rotation;
|
|
RotationOrigin[0] = rotateOrigin[0];
|
|
RotationOrigin[1] = rotateOrigin[1];
|
|
|
|
Corner[0][0] = x[0];
|
|
Corner[0][1] = x[2];
|
|
Corner[1][0] = x[1];
|
|
Corner[1][1] = x[2];
|
|
Corner[2][0] = x[1];
|
|
Corner[2][1] = x[3];
|
|
Corner[3][0] = x[0];
|
|
Corner[3][1] = x[3];
|
|
|
|
double ca = cos(rotation);
|
|
double sa = sin(rotation);
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
Corner[i][0] -= RotationOrigin[0];
|
|
Corner[i][1] -= RotationOrigin[1];
|
|
double rotx = Corner[i][0]*ca - Corner[i][1]*sa;
|
|
double roty = Corner[i][1]*ca + Corner[i][0]*sa;
|
|
Corner[i][0] = rotx;
|
|
Corner[i][1] = roty;
|
|
Corner[i][0] += RotationOrigin[0];
|
|
Corner[i][1] += RotationOrigin[1];
|
|
}
|
|
|
|
ComputeAxes();
|
|
}
|
|
|
|
// Returns true if the intersection of the boxes is non-empty.
|
|
bool Overlaps(const LabelRect& other) const
|
|
{
|
|
// Take care of easy case first
|
|
if ( Rotation == 0.0 && other.Rotation == 0.0 )
|
|
{
|
|
double d0 = Corner[0][0] - other.Corner[2][0];
|
|
double d1 = other.Corner[0][0] - Corner[2][0];
|
|
double d2 = Corner[0][1] - other.Corner[2][1];
|
|
double d3 = other.Corner[0][1] - Corner[2][1];
|
|
if ( d0 < 0. && d1 < 0. && d2 < 0. && d3 < 0. )
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return Overlaps1Way(other) && other.Overlaps1Way(*this);
|
|
}
|
|
}
|
|
|
|
void Render(vtkRenderer* ren, int shape, int style, double margin, double color[3], double opacity) const
|
|
{
|
|
if (shape == vtkLabelPlacementMapper::NONE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
vtkSmartPointer<vtkCellArray> cells = vtkSmartPointer<vtkCellArray>::New();
|
|
vtkSmartPointer<vtkPoints> pts = vtkSmartPointer<vtkPoints>::New();
|
|
vtkSmartPointer<vtkPolyDataMapper2D> mapper = vtkSmartPointer<vtkPolyDataMapper2D>::New();
|
|
vtkSmartPointer<vtkPolyData> poly = vtkSmartPointer<vtkPolyData>::New();
|
|
vtkSmartPointer<vtkActor2D> actor = vtkSmartPointer<vtkActor2D>::New();
|
|
|
|
double dx[2];
|
|
double dy[2];
|
|
double ax0len = sqrt(Axis[0][0]*Axis[0][0] + Axis[0][1]*Axis[0][1]);
|
|
dx[0] = margin*Axis[0][0]/ax0len;
|
|
dx[1] = margin*Axis[0][1]/ax0len;
|
|
double ax1len = sqrt(Axis[1][0]*Axis[1][0] + Axis[1][1]*Axis[1][1]);
|
|
dy[0] = margin*Axis[1][0]/ax1len;
|
|
dy[1] = margin*Axis[1][1]/ax1len;
|
|
switch (shape)
|
|
{
|
|
case vtkLabelPlacementMapper::ROUNDED_RECT:
|
|
{
|
|
double roundedFactor = vtkMath::Pi()/4;
|
|
double rx[2];
|
|
double ry[2];
|
|
rx[0] = roundedFactor*dx[0];
|
|
rx[1] = roundedFactor*dx[1];
|
|
ry[0] = roundedFactor*dy[0];
|
|
ry[1] = roundedFactor*dy[1];
|
|
pts->InsertNextPoint(Corner[0][0]-dx[0], Corner[0][1]-dx[1], 0);
|
|
pts->InsertNextPoint(Corner[0][0]-rx[0]-ry[0], Corner[0][1]-rx[1]-ry[1], 0);
|
|
pts->InsertNextPoint(Corner[0][0]-dy[0], Corner[0][1]-dy[1], 0);
|
|
pts->InsertNextPoint(Corner[1][0]-dy[0], Corner[1][1]-dy[1], 0);
|
|
pts->InsertNextPoint(Corner[1][0]+rx[0]-ry[0], Corner[1][1]+rx[1]-ry[1], 0);
|
|
pts->InsertNextPoint(Corner[1][0]+dx[0], Corner[1][1]+dx[1], 0);
|
|
pts->InsertNextPoint(Corner[2][0]+dx[0], Corner[2][1]+dx[1], 0);
|
|
pts->InsertNextPoint(Corner[2][0]+rx[0]+ry[0], Corner[2][1]+rx[1]+ry[1], 0);
|
|
pts->InsertNextPoint(Corner[2][0]+dy[0], Corner[2][1]+dy[1], 0);
|
|
pts->InsertNextPoint(Corner[3][0]+dy[0], Corner[3][1]+dy[1], 0);
|
|
pts->InsertNextPoint(Corner[3][0]-rx[0]+ry[0], Corner[3][1]-rx[1]+ry[1], 0);
|
|
pts->InsertNextPoint(Corner[3][0]-dx[0], Corner[3][1]-dx[1], 0);
|
|
cells->InsertNextCell(13);
|
|
for (int i = 0; i < 13; ++i)
|
|
{
|
|
cells->InsertCellPoint(i%12);
|
|
}
|
|
break;
|
|
}
|
|
case vtkLabelPlacementMapper::RECT:
|
|
default:
|
|
{
|
|
pts->InsertNextPoint(Corner[0][0]-dx[0]-dy[0], Corner[0][1]-dx[1]-dy[1], 0);
|
|
pts->InsertNextPoint(Corner[1][0]+dx[0]-dy[0], Corner[1][1]+dx[1]-dy[1], 0);
|
|
pts->InsertNextPoint(Corner[2][0]+dx[0]+dy[0], Corner[2][1]+dx[1]+dy[1], 0);
|
|
pts->InsertNextPoint(Corner[3][0]-dx[0]+dy[0], Corner[3][1]-dx[1]+dy[1], 0);
|
|
cells->InsertNextCell(5);
|
|
for (int c = 0; c < 5; ++c)
|
|
{
|
|
cells->InsertCellPoint(c%4);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
poly->SetPoints(pts);
|
|
if (style == vtkLabelPlacementMapper::OUTLINE)
|
|
{
|
|
poly->SetLines(cells);
|
|
}
|
|
else
|
|
{
|
|
poly->SetPolys(cells);
|
|
}
|
|
mapper->SetInputData(poly);
|
|
actor->SetMapper(mapper);
|
|
actor->GetProperty()->SetColor(color);
|
|
actor->GetProperty()->SetOpacity(opacity);
|
|
actor->RenderOverlay(ren);
|
|
}
|
|
|
|
private:
|
|
// Returns true if other overlaps one dimension of this.
|
|
bool Overlaps1Way(const LabelRect& other) const
|
|
{
|
|
for (int a = 0; a < 2; ++a)
|
|
{
|
|
//double t = other.corner[0].dot(axis[a]);
|
|
double t = other.Corner[0][0]*Axis[a][0] + other.Corner[0][1]*Axis[a][1];
|
|
|
|
// Find the extent of box 2 on axis a
|
|
double tMin = t;
|
|
double tMax = t;
|
|
|
|
for (int c = 1; c < 4; ++c)
|
|
{
|
|
//t = other.corner[c].dot(axis[a]);
|
|
t = other.Corner[c][0]*Axis[a][0] + other.Corner[c][1]*Axis[a][1];
|
|
|
|
if (t < tMin)
|
|
{
|
|
tMin = t;
|
|
}
|
|
else if (t > tMax)
|
|
{
|
|
tMax = t;
|
|
}
|
|
}
|
|
|
|
// We have to subtract off the origin
|
|
|
|
// See if [tMin, tMax] intersects [0, 1]
|
|
if ((tMin > 1 + Origin[a]) || (tMax < Origin[a]))
|
|
{
|
|
// There was no intersection along this dimension;
|
|
// the boxes cannot possibly overlap.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// There was no dimension along which there is no intersection.
|
|
// Therefore the boxes overlap.
|
|
return true;
|
|
}
|
|
|
|
// Updates the axes after the corners move. Assumes the
|
|
// corners actually form a rectangle.
|
|
void ComputeAxes()
|
|
{
|
|
Axis[0][0] = Corner[1][0] - Corner[0][0];
|
|
Axis[0][1] = Corner[1][1] - Corner[0][1];
|
|
Axis[1][0] = Corner[3][0] - Corner[0][0];
|
|
Axis[1][1] = Corner[3][1] - Corner[0][1];
|
|
|
|
// Make the length of each axis 1/edge length so we know any
|
|
// dot product must be less than 1 to fall within the edge.
|
|
|
|
for (int a = 0; a < 2; ++a)
|
|
{
|
|
double len = Axis[a][0]*Axis[a][0] + Axis[a][1]*Axis[a][1];
|
|
Axis[a][0] /= len;
|
|
Axis[a][1] /= len;
|
|
Origin[a] = Corner[0][0]*Axis[a][0] + Corner[0][1]*Axis[a][1];
|
|
}
|
|
|
|
Bounds[0] = Corner[0][0];
|
|
Bounds[1] = Corner[0][0];
|
|
Bounds[2] = Corner[0][1];
|
|
Bounds[3] = Corner[0][1];
|
|
for (int i = 1; i < 4; ++i)
|
|
{
|
|
if (Corner[i][0] < Bounds[0])
|
|
{
|
|
Bounds[0] = Corner[i][0];
|
|
}
|
|
if (Corner[i][0] > Bounds[1])
|
|
{
|
|
Bounds[1] = Corner[i][0];
|
|
}
|
|
if (Corner[i][1] < Bounds[2])
|
|
{
|
|
Bounds[2] = Corner[i][1];
|
|
}
|
|
if (Corner[i][1] > Bounds[3])
|
|
{
|
|
Bounds[3] = Corner[i][1];
|
|
}
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
class vtkLabelPlacementMapper::Internal
|
|
{
|
|
public:
|
|
|
|
/// A rectangular tile on the screen. It contains a set of labels that overlap it.
|
|
struct ScreenTile
|
|
{
|
|
std::vector<LabelRect> Labels;
|
|
ScreenTile() { }
|
|
/// Is there space to place the given rectangle in this tile so that it doesn't overlap any labels in this tile?
|
|
bool IsSpotOpen( const LabelRect& r )
|
|
{
|
|
for ( std::vector<LabelRect>::iterator it = this->Labels.begin(); it != this->Labels.end(); ++ it )
|
|
{
|
|
if (r.Overlaps(*it))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Prepare for the next frame.
|
|
void Reset() { this->Labels.clear(); }
|
|
void Insert( const LabelRect& rect )
|
|
{
|
|
this->Labels.push_back( rect );
|
|
}
|
|
};
|
|
std::vector<std::vector<ScreenTile> > Tiles;
|
|
float ScreenOrigin[2];
|
|
float TileSize[2];
|
|
int NumTiles[2];
|
|
vtkSmartPointer<vtkIdTypeArray> NewLabelsPlaced;
|
|
vtkSmartPointer<vtkIdTypeArray> LastLabelsPlaced;
|
|
|
|
Internal( float viewport[4], float tilesize[2] )
|
|
{
|
|
this->NewLabelsPlaced = vtkSmartPointer<vtkIdTypeArray>::New();
|
|
this->LastLabelsPlaced = vtkSmartPointer<vtkIdTypeArray>::New();
|
|
this->ScreenOrigin[0] = viewport[0];
|
|
this->ScreenOrigin[1] = viewport[2];
|
|
this->TileSize[0] = tilesize[0];
|
|
this->TileSize[1] = tilesize[1];
|
|
this->NumTiles[0] = static_cast<int>( ceil( ( viewport[1] - viewport[0] ) / tilesize[0] ) );
|
|
this->NumTiles[1] = static_cast<int>( ceil( ( viewport[3] - viewport[2] ) / tilesize[1] ) );
|
|
this->Tiles.resize( this->NumTiles[0] );
|
|
for ( int i = 0; i < this->NumTiles[0]; ++ i )
|
|
this->Tiles[i].resize( this->NumTiles[1] );
|
|
}
|
|
|
|
bool PlaceLabel( const LabelRect& r )
|
|
{
|
|
// Determine intersected tiles
|
|
float rx0 = r.Bounds[0] / TileSize[0];
|
|
float rx1 = r.Bounds[1] / TileSize[0];
|
|
float ry0 = r.Bounds[2] / TileSize[1];
|
|
float ry1 = r.Bounds[3] / TileSize[1];
|
|
int tx0 = static_cast<int>( floor( rx0 ) );
|
|
int tx1 = static_cast<int>( ceil( rx1 ) );
|
|
int ty0 = static_cast<int>( floor( ry0 ) );
|
|
int ty1 = static_cast<int>( ceil( ry1 ) );
|
|
if ( tx0 > NumTiles[0] || tx1 < 0 || ty0 > NumTiles[1] || ty1 < 0 )
|
|
return false; // Don't intersect screen.
|
|
if ( tx0 < 0 ) { tx0 = 0; rx0 = 0.; }
|
|
if ( ty0 < 0 ) { ty0 = 0; ry0 = 0.; }
|
|
if ( tx1 >= this->NumTiles[0] ) { tx1 = this->NumTiles[0] - 1; rx1 = tx1; }
|
|
if ( ty1 >= this->NumTiles[1] ) { ty1 = this->NumTiles[1] - 1; ry1 = ty1; }
|
|
// Check all applicable tiles for overlap.
|
|
for ( int tx = tx0; tx <= tx1; ++ tx )
|
|
{
|
|
for ( int ty = ty0; ty <= ty1; ++ ty )
|
|
{
|
|
std::vector<ScreenTile>* trow = &this->Tiles[tx];
|
|
// Do this check here for speed, even though we repeat w/ small mod below.
|
|
if ( ! (*trow)[ty].IsSpotOpen( r ) )
|
|
return false;
|
|
}
|
|
}
|
|
// OK, we made it this far... we can place the label.
|
|
// Add it to each tile it overlaps.
|
|
for ( int tx = tx0; tx <= tx1; ++ tx )
|
|
{
|
|
for ( int ty = ty0; ty <= ty1; ++ ty )
|
|
{
|
|
this->Tiles[tx][ty].Insert( r );
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void Reset( float viewport[4], float tileSize[2] )
|
|
{
|
|
// Clear out any tiles we get to reuse
|
|
for ( int tx = 0; tx < this->NumTiles[0]; ++ tx )
|
|
for ( int ty = 0; ty < this->NumTiles[1]; ++ ty )
|
|
this->Tiles[tx][ty].Reset();
|
|
|
|
// Set new parameter values in case the viewport changed
|
|
this->ScreenOrigin[0] = viewport[0];
|
|
this->ScreenOrigin[1] = viewport[2];
|
|
this->TileSize[0] = tileSize[0];
|
|
this->TileSize[1] = tileSize[1];
|
|
this->NumTiles[0] = static_cast<int>( ceil( ( viewport[1] - viewport[0] ) / tileSize[0] ) );
|
|
this->NumTiles[1] = static_cast<int>( ceil( ( viewport[3] - viewport[2] ) / tileSize[1] ) );
|
|
|
|
// Allocate new tiles (where required...)
|
|
this->Tiles.resize( this->NumTiles[0] );
|
|
for ( int i = 0; i < this->NumTiles[0]; ++ i )
|
|
this->Tiles[i].resize( this->NumTiles[1] );
|
|
|
|
// Save labels from the last frame for use later...
|
|
vtkSmartPointer<vtkIdTypeArray> tmp = this->LastLabelsPlaced;
|
|
this->LastLabelsPlaced = this->NewLabelsPlaced;
|
|
this->NewLabelsPlaced = tmp;
|
|
this->NewLabelsPlaced->Reset();
|
|
}
|
|
};
|
|
|
|
vtkStandardNewMacro(vtkLabelPlacementMapper);
|
|
vtkCxxSetObjectMacro(vtkLabelPlacementMapper, AnchorTransform, vtkCoordinate);
|
|
vtkCxxSetObjectMacro(vtkLabelPlacementMapper, RenderStrategy, vtkLabelRenderStrategy);
|
|
|
|
//----------------------------------------------------------------------------
|
|
vtkLabelPlacementMapper::vtkLabelPlacementMapper()
|
|
{
|
|
this->AnchorTransform = vtkCoordinate::New();
|
|
this->AnchorTransform->SetCoordinateSystemToWorld();
|
|
this->MaximumLabelFraction = 0.05; // Take up no more than 5% of screen real estate with labels.
|
|
this->Buckets = 0;
|
|
this->PositionsAsNormals = false;
|
|
this->IteratorType = vtkLabelHierarchy::QUEUE;
|
|
this->VisiblePoints = vtkSelectVisiblePoints::New();
|
|
this->VisiblePoints->SetTolerance(0.002);
|
|
this->UseUnicodeStrings = false;
|
|
this->PlaceAllLabels = false;
|
|
this->OutputTraversedBounds = false;
|
|
this->GeneratePerturbedLabelSpokes = false;
|
|
this->Style = FILLED;
|
|
this->Shape = NONE;
|
|
this->Margin = 5;
|
|
this->BackgroundColor[0] = 0.5;
|
|
this->BackgroundColor[1] = 0.5;
|
|
this->BackgroundColor[2] = 0.5;
|
|
this->BackgroundOpacity = 1.0;
|
|
|
|
this->LastRendererSize[0] = 0;
|
|
this->LastRendererSize[1] = 0;
|
|
this->LastCameraPosition[0] = 0.0;
|
|
this->LastCameraPosition[1] = 0.0;
|
|
this->LastCameraPosition[2] = 0.0;
|
|
this->LastCameraFocalPoint[0] = 0.0;
|
|
this->LastCameraFocalPoint[1] = 0.0;
|
|
this->LastCameraFocalPoint[2] = 0.0;
|
|
this->LastCameraViewUp[0] = 0.0;
|
|
this->LastCameraViewUp[1] = 0.0;
|
|
this->LastCameraViewUp[2] = 0.0;
|
|
this->LastCameraParallelScale = 0.0;
|
|
|
|
this->UseDepthBuffer = false;
|
|
|
|
this->RenderStrategy = 0;
|
|
vtkSmartPointer<vtkFreeTypeLabelRenderStrategy> s =
|
|
vtkSmartPointer<vtkFreeTypeLabelRenderStrategy>::New();
|
|
this->SetRenderStrategy(s);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
vtkLabelPlacementMapper::~vtkLabelPlacementMapper()
|
|
{
|
|
this->AnchorTransform->Delete();
|
|
delete this->Buckets;
|
|
this->VisiblePoints->Delete();
|
|
if ( this->RenderStrategy )
|
|
{
|
|
this->RenderStrategy->Delete();
|
|
}
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
int vtkLabelPlacementMapper::FillInputPortInformation(
|
|
int vtkNotUsed(port), vtkInformation* info )
|
|
{
|
|
info->Set( vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkLabelHierarchy" );
|
|
info->Set( vtkAlgorithm::INPUT_IS_REPEATABLE(), 1 );
|
|
info->Set( vtkAlgorithm::INPUT_IS_OPTIONAL(), 1 );
|
|
return 1;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void vtkLabelPlacementMapper::RenderOverlay(vtkViewport *viewport,
|
|
vtkActor2D *vtkNotUsed(actor))
|
|
{
|
|
vtkSmartPointer<vtkTimerLog> log = vtkSmartPointer<vtkTimerLog>::New();
|
|
log->StartTimer();
|
|
|
|
vtkRenderer* ren = vtkRenderer::SafeDownCast(viewport);
|
|
if ( ! ren )
|
|
{
|
|
vtkErrorMacro( "No renderer -- can't determine screen space size." );
|
|
return;
|
|
}
|
|
|
|
if ( ! ren->GetRenderWindow() )
|
|
{
|
|
vtkErrorMacro( "No render window -- can't get window size to query z buffer." );
|
|
return;
|
|
}
|
|
|
|
// This will trigger if you do something like ResetCamera before the Renderer or
|
|
// RenderWindow have allocated their appropriate system resources (like creating
|
|
// an OpenGL context). Resource allocation must occur before we can use the Z
|
|
// buffer.
|
|
if ( ren->GetRenderWindow()->GetNeverRendered() )
|
|
{
|
|
vtkDebugMacro( "RenderWindow not initialized -- aborting update." );
|
|
return;
|
|
}
|
|
|
|
vtkCamera* cam = ren->GetActiveCamera();
|
|
if ( ! cam )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the renderer size is zero, silently place no labels.
|
|
int* renSize = ren->GetSize();
|
|
if ( renSize[0] == 0 || renSize[1] == 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Update the pipeline if necessary
|
|
this->Update();
|
|
|
|
int tvpsz[4]; // tiled viewport size (and origin)
|
|
// kd-tree bounds on screenspace (as floats... eventually we
|
|
// should use a median kd-tree -- not naive version)
|
|
float kdbounds[4];
|
|
ren->GetTiledSizeAndOrigin(
|
|
tvpsz, tvpsz + 1, tvpsz + 2, tvpsz + 3 );
|
|
kdbounds[0] = tvpsz[2];
|
|
kdbounds[1] = tvpsz[0] + tvpsz[2];
|
|
kdbounds[2] = tvpsz[3];
|
|
kdbounds[3] = tvpsz[1] + tvpsz[3];
|
|
float tileSize[2] = { 128., 128. }; // fixed for now
|
|
if (
|
|
! this->Buckets ||
|
|
this->Buckets->NumTiles[0] * this->Buckets->TileSize[0] < tvpsz[2] ||
|
|
this->Buckets->NumTiles[1] * this->Buckets->TileSize[1] < tvpsz[3] )
|
|
{
|
|
this->Buckets = new Internal( kdbounds, tileSize );
|
|
}
|
|
else
|
|
{
|
|
this->Buckets->Reset( kdbounds, tileSize );
|
|
}
|
|
|
|
float * zPtr = NULL;
|
|
int placed = 0;
|
|
int occluded = 0;
|
|
|
|
double ll[2];
|
|
double ur[2];
|
|
double x[3];
|
|
double sz[4];
|
|
int origin[2];
|
|
int dispx[2];
|
|
double frustumPlanes[24];
|
|
double aspect = ren->GetTiledAspectRatio();
|
|
cam->GetFrustumPlanes( aspect, frustumPlanes );
|
|
unsigned long allowableLabelArea = static_cast<unsigned long>
|
|
( ( ( kdbounds[1] - kdbounds[0] ) * ( kdbounds[3] - kdbounds[2] ) ) * this->MaximumLabelFraction );
|
|
(void)allowableLabelArea;
|
|
unsigned long renderedLabelArea = 0;
|
|
unsigned long iteratedLabelArea = 0;
|
|
double camVec[3];
|
|
if ( this->PositionsAsNormals )
|
|
{
|
|
cam->GetViewPlaneNormal( camVec );
|
|
}
|
|
|
|
// Make a composite iterator that will iterate over all the input
|
|
// label hierarchies in a round-robin sequence.
|
|
vtkSmartPointer<vtkLabelHierarchyCompositeIterator> inIter =
|
|
vtkSmartPointer<vtkLabelHierarchyCompositeIterator>::New();
|
|
|
|
vtkSmartPointer<vtkPolyData> boundsPoly = vtkSmartPointer<vtkPolyData>::New();
|
|
if ( this->OutputTraversedBounds )
|
|
{
|
|
vtkSmartPointer<vtkPoints> pts = vtkSmartPointer<vtkPoints>::New();
|
|
boundsPoly->SetPoints( pts );
|
|
vtkSmartPointer<vtkCellArray> lines = vtkSmartPointer<vtkCellArray>::New();
|
|
boundsPoly->SetLines( lines );
|
|
inIter->SetTraversedBounds( boundsPoly );
|
|
}
|
|
|
|
int numInputs = this->GetNumberOfInputConnections( 0 );
|
|
for ( int i = 0; i < numInputs; ++i )
|
|
{
|
|
vtkLabelHierarchy* inData = vtkLabelHierarchy::SafeDownCast(
|
|
this->GetInputDataObject( 0, i ) );
|
|
vtkLabelHierarchyIterator* it = inData->NewIterator(
|
|
this->IteratorType, ren, cam, frustumPlanes, this->PositionsAsNormals, tileSize );
|
|
inIter->AddIterator( it );
|
|
it->Delete();
|
|
}
|
|
|
|
vtkSmartPointer<vtkTimerLog> timer = vtkSmartPointer<vtkTimerLog>::New();
|
|
timer->StartTimer();
|
|
|
|
inIter->Begin( this->Buckets->LastLabelsPlaced );
|
|
this->Buckets->NewLabelsPlaced->Initialize();
|
|
|
|
if ( this->UseDepthBuffer )
|
|
{
|
|
this->VisiblePoints->SetRenderer( ren );
|
|
zPtr = this->VisiblePoints->Initialize( true );
|
|
}
|
|
|
|
// Start rendering labels
|
|
this->RenderStrategy->SetRenderer(ren);
|
|
this->RenderStrategy->StartFrame();
|
|
|
|
timer->StopTimer();
|
|
vtkDebugMacro("Iterator initialization time: " << timer->GetElapsedTime());
|
|
timer->StartTimer();
|
|
|
|
vtkSmartPointer<vtkTextProperty> tpropCopy = vtkSmartPointer<vtkTextProperty>::New();
|
|
|
|
for ( ; ! inIter->IsAtEnd(); inIter->Next() )
|
|
{
|
|
// Ignore labels that don't have text or an icon.
|
|
vtkIdType labelType = inIter->GetType();
|
|
if ( labelType < 0 || labelType > 1 )
|
|
{
|
|
vtkDebugMacro("Arf. Bad label type " << labelType);
|
|
continue;
|
|
}
|
|
|
|
inIter->GetPoint( x );
|
|
// Cull points behind the camera. Cannot rely on hither-yon planes because the camera
|
|
// position gets changed during vtkInteractorStyle::Dolly() and RequestData() called from
|
|
// within ResetCameraClippingRange() before the frustum planes are updated.
|
|
// Cull points outside hither-yon planes (other planes get tested below)
|
|
double* eye = cam->GetPosition();
|
|
double* dir = cam->GetViewPlaneNormal();
|
|
if ( ( x[0] - eye[0] ) * dir[0] + ( x[1] - eye[1] ) * dir[1] + ( x[2] - eye[2] ) * dir[2] > 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Ignore labels pointing the wrong direction (HACK)
|
|
if ( this->PositionsAsNormals )
|
|
{
|
|
if ( camVec[0] * x[0] + camVec[1] * x[1] + camVec[2] * x[2] < 0. )
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Test for occlusion using the z-buffer
|
|
if (this->UseDepthBuffer && !this->VisiblePoints->IsPointOccluded(x, zPtr))
|
|
{
|
|
occluded++;
|
|
continue;
|
|
}
|
|
|
|
this->AnchorTransform->SetValue( x );
|
|
int* originPtr = this->AnchorTransform->GetComputedDisplayValue( ren );
|
|
origin[0] = originPtr[0];
|
|
origin[1] = originPtr[1];
|
|
|
|
// Determine the label bounds
|
|
vtkTextProperty* tprop = inIter->GetHierarchy()->GetTextProperty();
|
|
tpropCopy->ShallowCopy( tprop );
|
|
|
|
if ( this->RenderStrategy->SupportsRotation() && inIter->GetHierarchy()->GetOrientations() )
|
|
{
|
|
tpropCopy->SetOrientation( inIter->GetOrientation() );
|
|
}
|
|
|
|
double bds[4];
|
|
if ( this->UseUnicodeStrings )
|
|
{
|
|
this->RenderStrategy->ComputeLabelBounds( tpropCopy, inIter->GetUnicodeLabel(), bds );
|
|
}
|
|
else
|
|
{
|
|
this->RenderStrategy->ComputeLabelBounds( tpropCopy, inIter->GetLabel(), bds );
|
|
}
|
|
|
|
// Offset display position by lower left corner of bounding box
|
|
dispx[0] = static_cast<int>(origin[0] + bds[0]);
|
|
dispx[1] = static_cast<int>(origin[1] + bds[2]);
|
|
|
|
sz[0] = bds[1] - bds[0];
|
|
sz[1] = bds[3] - bds[2];
|
|
|
|
if ( sz[0] < 0 ) sz[0] = -sz[0];
|
|
if ( sz[1] < 0 ) sz[1] = -sz[1];
|
|
|
|
// If it has no size, skip it
|
|
if ( sz[0] == 0.0 || sz[1] == 0.0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ll[0] = dispx[0];
|
|
ll[1] = dispx[1];
|
|
ur[0] = dispx[0] + sz[0];
|
|
ur[1] = dispx[1] + sz[1];
|
|
|
|
if ( ll[1] > kdbounds[3] || ur[1] < kdbounds[2] || ll[0] > kdbounds[1] || ll[1] < kdbounds[0] )
|
|
{
|
|
continue; // cull label not in frame
|
|
}
|
|
|
|
// Special case: if there are bounded sizes, try to render every one we encounter.
|
|
if ( this->RenderStrategy->SupportsBoundedSize() && inIter->GetHierarchy()->GetBoundedSizes() )
|
|
{
|
|
double p[3] = { static_cast<double>(origin[0]), static_cast<double>(origin[1]), 0.0 };
|
|
double boundedSize[2];
|
|
inIter->GetBoundedSize( boundedSize );
|
|
|
|
// Figure out if width is too small to fit
|
|
double xWidth[3] = {x[0] + boundedSize[0], x[1], x[2]};
|
|
this->AnchorTransform->SetValue( xWidth );
|
|
int* origin2 = this->AnchorTransform->GetComputedDisplayValue( ren );
|
|
double pWidth[3] = { static_cast<double>(origin2[0]), static_cast<double>(origin2[1]), 0.0 };
|
|
int width = static_cast<int>(sqrt(vtkMath::Distance2BetweenPoints(p, pWidth)));
|
|
if ( width < 20 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Figure out if height is too small to fit
|
|
double xHeight[3] = {x[0], x[1] + boundedSize[1], x[2]};
|
|
this->AnchorTransform->SetValue( xHeight );
|
|
origin2 = this->AnchorTransform->GetComputedDisplayValue( ren );
|
|
double pHeight[3] = { static_cast<double>(origin2[0]), static_cast<double>(origin2[1]), 0.0 };
|
|
int height = static_cast<int>(sqrt(vtkMath::Distance2BetweenPoints(p, pHeight)));
|
|
if ( height < bds[3] - bds[2] )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Label is not text
|
|
if ( labelType != 0 )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Render it
|
|
if( this->UseUnicodeStrings )
|
|
{
|
|
this->RenderStrategy->RenderLabel( origin, tpropCopy, inIter->GetUnicodeLabel(), width );
|
|
}
|
|
else
|
|
{
|
|
this->RenderStrategy->RenderLabel( origin, tpropCopy, inIter->GetLabel(), width );
|
|
}
|
|
int renderedHeight = static_cast<int>( bds[3] - bds[2] );
|
|
int renderedWidth = static_cast<int>( (bds[1] - bds[0] < width) ? (bds[1] - bds[0]) : width );
|
|
renderedLabelArea += static_cast<unsigned long>( renderedWidth * renderedHeight );
|
|
continue;
|
|
}
|
|
|
|
if ( this->Debug )
|
|
{
|
|
vtkDebugMacro("Try: " << inIter->GetLabelId() << " (" << ll[0] << ", " << ll[1] << " " << ur[0] << "," << ur[1] << ")");
|
|
if ( labelType == 0 )
|
|
{
|
|
if( this->UseUnicodeStrings )
|
|
{
|
|
vtkDebugMacro("Area: " << renderedLabelArea << " / " << allowableLabelArea << " \"" << inIter->GetUnicodeLabel().utf8_str() << "\"");
|
|
}
|
|
else
|
|
{
|
|
vtkDebugMacro("Area: " << renderedLabelArea << " / " << allowableLabelArea << " \"" << inIter->GetLabel().c_str() << "\"");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
vtkDebugMacro("Area: " << renderedLabelArea << " / " << allowableLabelArea);
|
|
}
|
|
}
|
|
|
|
iteratedLabelArea += static_cast<unsigned long>( sz[0] * sz[1] );
|
|
|
|
double orient = tpropCopy->GetOrientation();
|
|
|
|
// Translate to origin to simplify bucketing
|
|
double xTrans[4];
|
|
xTrans[0] = ll[0] - kdbounds[0];
|
|
xTrans[1] = ur[0] - kdbounds[0];
|
|
xTrans[2] = ll[1] - kdbounds[2];
|
|
xTrans[3] = ur[1] - kdbounds[2];
|
|
|
|
double originTrans[2];
|
|
originTrans[0] = origin[0] - kdbounds[0];
|
|
originTrans[1] = origin[1] - kdbounds[2];
|
|
|
|
double orientRad = vtkMath::RadiansFromDegrees(orient);
|
|
LabelRect r( xTrans, originTrans, orientRad );
|
|
|
|
if ( this->PlaceAllLabels || this->Buckets->PlaceLabel( r ) )
|
|
{
|
|
r.Render(ren, this->Shape, this->Style, this->Margin, this->BackgroundColor, this->BackgroundOpacity);
|
|
renderedLabelArea += static_cast<unsigned long>( sz[0] * sz[1] );
|
|
if ( labelType == 0 )
|
|
{
|
|
// label is text
|
|
if( this->UseUnicodeStrings )
|
|
{
|
|
this->RenderStrategy->RenderLabel( origin, tpropCopy, inIter->GetUnicodeLabel() );
|
|
}
|
|
else
|
|
{
|
|
this->RenderStrategy->RenderLabel( origin, tpropCopy, inIter->GetLabel() );
|
|
}
|
|
|
|
// TODO: 1. Perturb coincident points.
|
|
// 2. Use GeneratePerturbedLabelSpokes to possibly render perturbed points.
|
|
}
|
|
else
|
|
{ // label is an icon
|
|
// TODO: Do something ...
|
|
}
|
|
|
|
vtkDebugMacro("Placed: " << inIter->GetLabelId() << " (" << ll[0] << ", " << ll[1] << " " << ur[0] << "," << ur[1] << ") " << labelType);
|
|
placed++;
|
|
}
|
|
}
|
|
|
|
// Done rendering labels
|
|
this->RenderStrategy->EndFrame();
|
|
this->RenderStrategy->SetRenderer(0);
|
|
|
|
if ( this->OutputTraversedBounds )
|
|
{
|
|
// For some reason I cannot use vtkPolyDataMapper, I need to use
|
|
// vtkPolyDataMapper2D. This causes lines behind the camera to be sometimes
|
|
// transformed on-screen. Since this is for debugging, I'm going to punt
|
|
// on this one.
|
|
vtkSmartPointer<vtkTransformCoordinateSystems> trans = vtkSmartPointer<vtkTransformCoordinateSystems>::New();
|
|
vtkSmartPointer<vtkPolyDataMapper2D> boundsMapper = vtkSmartPointer<vtkPolyDataMapper2D>::New();
|
|
vtkSmartPointer<vtkActor2D> boundsActor = vtkSmartPointer<vtkActor2D>::New();
|
|
trans->SetInputCoordinateSystemToWorld();
|
|
trans->SetOutputCoordinateSystemToDisplay();
|
|
trans->SetInputData( boundsPoly );
|
|
trans->SetViewport( ren );
|
|
boundsMapper->SetInputConnection( trans->GetOutputPort() );
|
|
boundsMapper->RenderOverlay( ren, boundsActor );
|
|
}
|
|
|
|
vtkDebugMacro("------");
|
|
vtkDebugMacro("Placed: " << placed);
|
|
vtkDebugMacro("Labels Occluded: " << occluded);
|
|
|
|
delete [] zPtr;
|
|
|
|
timer->StopTimer();
|
|
vtkDebugMacro("Iteration time: " << timer->GetElapsedTime());
|
|
log->StopTimer();
|
|
//cerr << log->GetElapsedTime() << endl;
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void vtkLabelPlacementMapper::ReleaseGraphicsResources(vtkWindow *win)
|
|
{
|
|
this->RenderStrategy->ReleaseGraphicsResources(win);
|
|
}
|
|
|
|
//----------------------------------------------------------------------------
|
|
void vtkLabelPlacementMapper::PrintSelf( ostream& os, vtkIndent indent )
|
|
{
|
|
this->Superclass::PrintSelf( os, indent );
|
|
os << indent << "AnchorTransform: " << this->AnchorTransform << "\n";
|
|
os << indent << "MaximumLabelFraction: " << this->MaximumLabelFraction << "\n";
|
|
os << indent << "PositionsAsNormals: " << ( this->PositionsAsNormals ? "ON" : "OFF" ) << "\n";
|
|
os << indent << "UseUnicodeStrings: " << ( this->UseUnicodeStrings ? "ON" : "OFF" ) << "\n";
|
|
os << indent << "IteratorType: " << this->IteratorType << "\n";
|
|
os << indent << "RenderStrategy: " << this->RenderStrategy << "\n";
|
|
os << indent << "PlaceAllLabels: " << (this->PlaceAllLabels ? "ON" : "OFF" ) << "\n";
|
|
os << indent << "OutputTraversedBounds: " << (this->OutputTraversedBounds ? "ON" : "OFF" ) << "\n";
|
|
os << indent << "GeneratePerturbedLabelSpokes: " << (this->GeneratePerturbedLabelSpokes ? "ON" : "OFF" ) << "\n";
|
|
os << indent << "UseDepthBuffer: "
|
|
<< (this->UseDepthBuffer ? "ON" : "OFF" ) << "\n";
|
|
os << indent << "Style: " << this->Style << "\n";
|
|
os << indent << "Shape: " << this->Shape << "\n";
|
|
os << indent << "Margin: " << this->Margin << "\n";
|
|
os << indent << "BackgroundColor: " << this->BackgroundColor[0] << ", " << this->BackgroundColor[1] << ", " << this->BackgroundColor[2] << endl;
|
|
os << indent << "BackgroundOpacity: " << this->BackgroundOpacity << "\n";
|
|
}
|