/*========================================================================= Program: Visualization Toolkit Module: vtkParallelopipedRepresentation.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 "vtkParallelopipedRepresentation.h" #include "vtkActor.h" #include "vtkCamera.h" #include "vtkCellArray.h" #include "vtkClosedSurfacePointPlacer.h" #include "vtkDoubleArray.h" #include "vtkEvent.h" #include "vtkInteractorObserver.h" #include "vtkLine.h" #include "vtkMath.h" #include "vtkObjectFactory.h" #include "vtkPlane.h" #include "vtkPlaneCollection.h" #include "vtkPolyData.h" #include "vtkPolyDataMapper.h" #include "vtkProperty.h" #include "vtkRenderWindowInteractor.h" #include "vtkRenderer.h" #include "vtkSmartPointer.h" #include "vtkSphereHandleRepresentation.h" #include #include #include //------------------------------------------------------------------------------ // This class manages topological information for a parallelopiped with a // chair etched out at any node. // README : Uncomment the line that reads "PrintTopology(cout) to // understand what the class does. The goal of the class is succintly // described in that one line. class vtkParallelopipedTopology { public: typedef struct Line { vtkIdType Id[2]; Line(vtkIdType a, vtkIdType b) { Id[0] = a; Id[1] = b; } } LineType; typedef std::vector CellType; typedef std::vector CliqueType; // Diametric opposite of Corner 0 = 6, 1 = 7, 2 = 4, 3 = 5. // Mathematically, if a diametric corner is represented by a 3 bit value: // abc, its diametric opposite = a'b'c. static int GetDiametricOppositeOfCorner(int i) { return ((~i) & 0x6) | (i & 0x1); } // Get the corners connected to corner 'i'. There will be three such corners void GetNeighbors(int c, vtkIdType neighborPtIds[3], int configuration = 0) const { std::set neighbors; const CliqueType& clique = m_Topology[configuration]; for (CliqueType::const_iterator clit = clique.begin(); clit != clique.end(); ++clit) { if (std::find(clit->begin(), clit->end(), c) != clit->end()) { const CellType cell = RotateCell(*clit, c); neighbors.insert(cell[0]); neighbors.insert(cell[cell.size() - 2]); } } int i = 0; for (std::set::const_iterator it = neighbors.begin(); it != neighbors.end(); neighborPtIds[i++] = *it, ++it) { ; } } void GetNeighbors(vtkIdType node, vtkIdType neighborPtIds[3], vtkCellArray* neighborCells, std::vector& lines) { GetNeighbors(8 + GetDiametricOppositeOfCorner(node), neighborPtIds, node + 1); vtkIdType opposingNeighborPtIds[3], opposite = GetDiametricOppositeOfCorner(node); GetNeighbors(opposite, opposingNeighborPtIds); std::vector nodes(2); for (int i = 0; i < 3; i++) { nodes[0] = neighborPtIds[i]; for (int j = 0; j < 3; j++) { nodes[1] = opposingNeighborPtIds[j]; const CliqueType cells = FindCellsContainingNodes(m_Topology[node + 1], nodes); if (!cells.empty()) { PopulateTopology(cells, neighborCells); lines.emplace_back(opposite, nodes[1]); } } } } void FindCellsContainingNodes( int configuration, vtkCellArray* cellArray, const std::vector& nodes) const { vtkParallelopipedTopology::PopulateTopology( FindCellsContainingNodes(m_Topology[configuration], nodes), cellArray); } std::vector FindCellsContainingNodes( int configuration, const std::vector& nodes) { return FindCellsContainingNodes(m_Topology[configuration], nodes); } vtkParallelopipedTopology() { // The topology of a parallelopiped. CliqueType clique; AddCellToClique(clique, 3, 0, 4, 7); AddCellToClique(clique, 1, 2, 6, 5); AddCellToClique(clique, 0, 1, 5, 4); AddCellToClique(clique, 2, 3, 7, 6); AddCellToClique(clique, 0, 3, 2, 1); AddCellToClique(clique, 4, 5, 6, 7); m_Topology.push_back(clique); for (vtkIdType i = 0; i < 8; m_Topology.push_back(GetChairClique(i++, clique))) { ; } // README : The goal of the class is succintly described by the line below // PrintTopology( cout ); } // Populate topoplogy into a vtkCellArray. // If configuration is 0, the topoology populated is that of a parallelopiped. // If configuration > 0, the topology populated is that of a parallelopiped // with a chair at node = (configuration - 1). void PopulateTopology(int configuration, vtkCellArray* cellArray) const { vtkParallelopipedTopology::PopulateTopology(m_Topology[configuration], cellArray); } void PrintTopology(ostream& os) const { os << "Connectivity of Point Ids in a parallelopiped: " << endl; PrintClique(m_Topology[0], os); for (int i = 0; i < 8; i++) { os << "Connectivity of Point Ids in a parallelopiped " << "with chair carved out at node: " << i << endl; PrintClique(m_Topology[i + 1], os); } } private: void AddCellToClique(CliqueType& clique, vtkIdType a, vtkIdType b, vtkIdType c, vtkIdType d) { CellType v(4); v[0] = a; v[1] = b; v[2] = c; v[3] = d; clique.push_back(v); } static CellType RotateCell(const CellType& cell, vtkIdType endval) { CellType outputCell; for (CellType::const_iterator cit = cell.begin(); cit != cell.end(); ++cit) { outputCell.push_back(*cit); if (*cit == endval) break; } for (CellType::const_reverse_iterator cit = cell.rbegin(); cit != cell.rend(); ++cit) { if (*cit == endval) break; outputCell.insert(outputCell.begin(), *cit); } return outputCell; } static CellType ReverseCell(const CellType& cell) { CellType outputCell; for (CellType::const_reverse_iterator cit = cell.rbegin(); cit != cell.rend(); ++cit) outputCell.push_back(*cit); return outputCell; } static CellType ChairCell(const CellType& cell) { CellType outputCell = ReverseCell(cell); for (CellType::iterator cit = outputCell.begin(); cit != outputCell.end(); ++cit) *cit += 8; return outputCell; } static CellType ChairCell(vtkIdType c, const CellType& cell) { CellType tmpCell = RotateCell(cell, c); CellType::iterator it = tmpCell.end(); tmpCell.erase(--it); CellType outputCell = tmpCell; for (CellType::reverse_iterator cit = tmpCell.rbegin(); cit != tmpCell.rend(); ++cit) outputCell.push_back(*cit + 8); return outputCell; } static CliqueType GetChairClique(vtkIdType c, const CliqueType& clique) { CliqueType outputClique; for (CliqueType::const_iterator clit = clique.begin(); clit != clique.end(); ++clit) { if (std::find(clit->begin(), clit->end(), c) == clit->end()) { outputClique.insert(outputClique.begin(), *clit); outputClique.push_back(ChairCell(*clit)); } else { outputClique.insert(outputClique.begin(), ChairCell(c, *clit)); } } return outputClique; } static void PopulateTopology(const CliqueType& clique, vtkCellArray* cellArray) { for (CliqueType::const_iterator clit = clique.begin(); clit != clique.end(); ++clit) { vtkIdType* ids = new vtkIdType[clit->size()]; int i = 0; for (CellType::const_iterator cit = clit->begin(); cit != clit->end(); ids[i++] = *cit, ++cit) { ; } cellArray->InsertNextCell(static_cast(clit->size()), ids); delete[] ids; } } // Find all the cells in a given a configuration (specified by the clique) // that contain the nodes. (specified by nodes). Each cell returned must // contain all the nodes specified. static std::vector FindCellsContainingNodes( const CliqueType& clique, const std::vector& nodes) { std::vector cells; for (CliqueType::const_iterator clit = clique.begin(); clit != clique.end(); ++clit) { bool found = true; for (std::vector::const_iterator nit = nodes.begin(); nit != nodes.end(); ++nit) found &= (std::find(clit->begin(), clit->end(), *nit) != clit->end()); if (found) cells.push_back(*clit); } return cells; } static void PrintCell(const CellType& cell, ostream& os) { for (CellType::const_iterator cit = cell.begin(); cit != cell.end(); os << *cit << " ", ++cit) { ; } } static void PrintClique(const CliqueType& clique, ostream& os) { os << " Clique has " << clique.size() << " cells." << endl; for (CliqueType::const_iterator clit = clique.begin(); clit != clique.end(); ++clit) { os << " Cell PtIds: "; PrintCell(*clit, os); os << endl; } } std::vector m_Topology; }; //------------------------------------------------------------------------------ vtkStandardNewMacro(vtkParallelopipedRepresentation); vtkCxxSetObjectMacro(vtkParallelopipedRepresentation, HandleProperty, vtkProperty); vtkCxxSetObjectMacro(vtkParallelopipedRepresentation, SelectedHandleProperty, vtkProperty); vtkCxxSetObjectMacro(vtkParallelopipedRepresentation, HoveredHandleProperty, vtkProperty); //------------------------------------------------------------------------------ vtkParallelopipedRepresentation::vtkParallelopipedRepresentation() { // This contains all the connectivity information. this->Topology = new vtkParallelopipedTopology; this->LastEventPosition[0] = this->LastEventPosition[1] = 0.0; // Construct the poly data representing the hex this->HexPolyData = vtkPolyData::New(); this->HexMapper = vtkPolyDataMapper::New(); this->HexActor = vtkActor::New(); this->HexMapper->SetInputData(HexPolyData); this->HexActor->SetMapper(this->HexMapper); // 16 points from the parallelopiped and the chair (also modelled as a // parallelopiped). this->Points = vtkPoints::New(VTK_DOUBLE); this->Points->SetNumberOfPoints(16); this->HexPolyData->SetPoints(this->Points); vtkCellArray* cellArray = vtkCellArray::New(); this->Topology->PopulateTopology(0, cellArray); this->HexPolyData->SetPolys(cellArray); this->HexPolyData->BuildCells(); cellArray->Delete(); // The face of the polyhedron vtkIdType pts[4] = { 4, 5, 6, 7 }; vtkCellArray* cells = vtkCellArray::New(); cells->AllocateEstimate(1, 4); cells->InsertNextCell(4, pts); // temporary, replaced later this->HexFacePolyData = vtkPolyData::New(); this->HexFaceMapper = vtkPolyDataMapper::New(); this->HexFaceActor = vtkActor::New(); this->HexFacePolyData->SetPoints(this->Points); this->HexFacePolyData->SetPolys(cells); this->HexFaceMapper->SetInputData(HexFacePolyData); this->HexFaceActor->SetMapper(this->HexFaceMapper); cells->Delete(); // Set some default properties. // Handle properties this->HandleProperty = vtkProperty::New(); this->SelectedHandleProperty = vtkProperty::New(); this->HoveredHandleProperty = vtkProperty::New(); this->HandleProperty->SetColor(1.0, 1.0, 0.7); this->SelectedHandleProperty->SetColor(1.0, 0.2, 0.1); this->HoveredHandleProperty->SetColor(1.0, 0.7, 0.5); // Face properties this->FaceProperty = vtkProperty::New(); this->SelectedFaceProperty = vtkProperty::New(); this->FaceProperty->SetColor(1, 1, 1); this->SelectedFaceProperty->SetColor(0, 0, 1); this->FaceProperty->SetOpacity(0.0); this->SelectedFaceProperty->SetOpacity(0.25); // Outline properties (for the hex and the chair) this->OutlineProperty = vtkProperty::New(); this->OutlineProperty->SetRepresentationToWireframe(); this->OutlineProperty->SetAmbient(1.0); this->OutlineProperty->SetAmbientColor(1.0, 1.0, 1.0); this->OutlineProperty->SetLineWidth(2.0); this->SelectedOutlineProperty = vtkProperty::New(); this->SelectedOutlineProperty->SetRepresentationToWireframe(); this->SelectedOutlineProperty->SetAmbient(1.0); this->SelectedOutlineProperty->SetAmbientColor(0.0, 0.0, 1.0); this->SelectedOutlineProperty->SetLineWidth(2.0); this->HexActor->SetProperty(this->OutlineProperty); this->HexFaceActor->SetProperty(this->FaceProperty); // Handle looks like a sphere. this->HandleRepresentation = nullptr; this->HandleRepresentations = nullptr; vtkSphereHandleRepresentation* hRep = vtkSphereHandleRepresentation::New(); this->SetHandleRepresentation(hRep); hRep->Delete(); this->CurrentHandleIdx = -1; this->LastResizeAxisIdx = -1; this->ChairHandleIdx = -1; // Point placer to dictate placement of the chair point inside the // parallelopiped. this->ChairPointPlacer = vtkClosedSurfacePointPlacer::New(); this->InitialChairDepth = 0.25; this->MinimumThickness = 0.05; this->AbsoluteMinimumThickness = 0.05; this->PlaceFactor = 1.0; // Define the point coordinates and initial placement of the widget double bounds[6] = { -0.5, 0.5, -0.5, 0.5, -0.5, 0.5 }; this->PlaceWidget(bounds); } //------------------------------------------------------------------------------ vtkParallelopipedRepresentation::~vtkParallelopipedRepresentation() { this->HexActor->Delete(); this->HexMapper->Delete(); this->HexPolyData->Delete(); this->Points->Delete(); this->HexFaceActor->Delete(); this->HexFaceMapper->Delete(); this->HexFacePolyData->Delete(); this->SetHandleRepresentation(nullptr); this->FaceProperty->Delete(); this->SelectedFaceProperty->Delete(); this->OutlineProperty->Delete(); this->SelectedOutlineProperty->Delete(); this->SetHandleProperty(nullptr); this->SetSelectedHandleProperty(nullptr); this->SetHoveredHandleProperty(nullptr); this->ChairPointPlacer->Delete(); delete this->Topology; } //------------------------------------------------------------------------------ vtkHandleRepresentation* vtkParallelopipedRepresentation ::GetHandleRepresentation(int handleIndex) { return (handleIndex > 7) ? nullptr : this->HandleRepresentations[handleIndex]; } //------------------------------------------------------------------------------ // You can swap the handle representation to one that you like. void vtkParallelopipedRepresentation ::SetHandleRepresentation(vtkHandleRepresentation* handle) { if (handle == this->HandleRepresentation) { return; } vtkSetObjectBodyMacro(HandleRepresentation, vtkHandleRepresentation, handle); if (this->HandleRepresentation) { // Allocate the 8 handles if they haven't been allocated. if (!this->HandleRepresentations) { this->HandleRepresentations = new vtkHandleRepresentation*[8]; for (int i = 0; i < 8; this->HandleRepresentations[i++] = nullptr) { ; } } } else { // Free the 8 handles if they haven't been freed. if (this->HandleRepresentations) { for (int i = 0; i < 8; this->HandleRepresentations[i++]->Delete()) { ; } delete[] this->HandleRepresentations; this->HandleRepresentations = nullptr; } } for (int i = 0; i < 8; i++) { // We will remove the old handle, in anticipation of the new user- // provided handle type that we are going to set a few lines later. if (this->HandleRepresentations && this->HandleRepresentations[i]) { this->HandleRepresentations[i]->Delete(); this->HandleRepresentations[i] = nullptr; } // Copy the new user-provided handle. if (this->HandleRepresentation) { this->HandleRepresentations[i] = this->HandleRepresentation->NewInstance(); this->HandleRepresentations[i]->ShallowCopy(this->HandleRepresentation); } } } //------------------------------------------------------------------------------ // Remove any existing chairs in the parallelopiped. void vtkParallelopipedRepresentation::RemoveExistingChairs() { // If we have a chair. A chair has 9 faces as opposed to a parallelopiped // which has 6 faces. if (this->HexPolyData->GetPolys()->GetNumberOfCells() == 9) { // Go back to the topology of a parallelopiped. vtkCellArray* parallelopipedcells = vtkCellArray::New(); this->Topology->PopulateTopology(0, parallelopipedcells); this->HexPolyData->SetPolys(parallelopipedcells); this->HexPolyData->BuildCells(); parallelopipedcells->Delete(); // Bring the node that had the chair back to the 4th corner of the // parallelopiped. We will use vector addition by finding the 4th point of // a parallelogram from the other 3 points. vtkIdType neighborPtIds[3]; vtkIdType npts = 0; const vtkIdType* cellPtIds = nullptr; this->Topology->GetNeighbors(this->ChairHandleIdx, neighborPtIds); // First find 4 points that form a parallelogram and contain the chaired // handle. The pointIds shall be stored in "nodes" vtkParallelopipedTopology::CellType nodes(3); nodes[0] = this->ChairHandleIdx; nodes[1] = neighborPtIds[0]; nodes[2] = neighborPtIds[1]; vtkSmartPointer cells = vtkSmartPointer::New(); this->Topology->FindCellsContainingNodes(0, cells, nodes); cells->InitTraversal(); cells->GetNextCell(npts, cellPtIds); // Find the 4th pointId. int j = 0; while (cellPtIds[j] == nodes[0] || cellPtIds[j] == nodes[1] || cellPtIds[j] == nodes[2]) ++j; nodes.push_back(cellPtIds[j]); // Now go about finding the 4th point (Index 0) in the parallelogram.. // 0 ------ 1 // | | // 2 ------ 3 // double p[4][3]; // for 4 points.. 3 we know, 4th to find.. this->Points->GetPoint(nodes[3], p[0]); this->Points->GetPoint(nodes[1], p[1]); this->Points->GetPoint(nodes[2], p[2]); p[3][0] = p[1][0] + p[2][0] - p[0][0]; p[3][1] = p[1][1] + p[2][1] - p[0][1]; p[3][2] = p[1][2] + p[2][2] - p[0][2]; this->Points->SetPoint(nodes[0], p[3]); this->ChairHandleIdx = -1; } } //------------------------------------------------------------------------------ // Node can be an integer within [0,7]. This will create a chair one one of // the handle corners. The '0 < scale < 1' value dicates the starting // depth of the cavity. void vtkParallelopipedRepresentation::UpdateChairAtNode(int node) { vtkIdType npts = 0; const vtkIdType* cellPtIds = nullptr; // If we have a chair somewhere else, remove it. We can have only one // chair at a time. if (this->CurrentHandleIdx != this->ChairHandleIdx && this->HexPolyData->GetPolys()->GetNumberOfCells() == 9) { this->RemoveExistingChairs(); } this->ChairHandleIdx = node; // If we already don't have a chair, create one. (a chair has 6 faces, // unlike a parallelopiped). if (this->HexPolyData->GetPolys()->GetNumberOfCells() != 9) { // chair has 14 points, but we will model this with 2 parallelopipeds. // Hence 16 points. Look at vtkParallelopipedTopology for details. // Scale points with respect to the node. double origin[3], d[3]; this->Points->GetPoint(node, origin); for (int i = 0; i < 8; i++) { this->Points->GetPoint(i, d); d[0] = (d[0] - origin[0]) * this->InitialChairDepth + origin[0]; d[1] = (d[1] - origin[1]) * this->InitialChairDepth + origin[1]; d[2] = (d[2] - origin[2]) * this->InitialChairDepth + origin[2]; this->Points->SetPoint(i + 8, d); } this->Points->SetPoint(node, this->Points->GetPoint(vtkParallelopipedTopology::GetDiametricOppositeOfCorner(node) + 8)); vtkSmartPointer cells = vtkSmartPointer::New(); this->Topology->PopulateTopology(node + 1, cells); this->HexPolyData->SetPolys(cells); this->HexPolyData->BuildCells(); // Synchronize the handle representations with our recently updated // "Points" data-structure. this->PositionHandles(); } else { // We do have a chair. Update the points in the chair by taking the // projection of the chaired node onto the axes of the parallelopiped. // These three PtIds are those that lie on the chair and are connected via // a line to the "Chair node" in question. It is the position of these 3 // points that we seek to find in the next few lines. vtkIdType neighborPtIds[3]; // This will contain the 3 faces that lie on the parallelopiped and have // a chair carved out in them. As you know, we are about to compute the // points at the carved out locations. vtkSmartPointer neighborCells = vtkSmartPointer::New(); // Handle PointID is the diametric opposite of the chair corner on the // higher order parallelopiped (the chair parallelopiped). const vtkIdType chairHandleId = 8 + vtkParallelopipedTopology::GetDiametricOppositeOfCorner(this->CurrentHandleIdx); // Get the world position of the chair handle. double chairPoint[3]; this->Points->GetPoint(chairHandleId, chairPoint); std::vector lines; this->Topology->GetNeighbors(node, neighborPtIds, neighborCells, lines); neighborCells->InitTraversal(); for (int i = 0; i < 3; i++) { double lineEndPt[2][3]; this->Points->GetPoint(lines[i].Id[0], lineEndPt[0]); this->Points->GetPoint(lines[i].Id[1], lineEndPt[1]); double t, neighborPt[3]; // "x" is the point that we are trying to find. neighborCells->GetNextCell(npts, cellPtIds); vtkIdType planePtIds[3]; // For each point in the cell for (int j = 0, idx = 0; j < npts && idx < 3; j++) { // Avoid the points that are on the chair as these are the ones we seek // to find. if (cellPtIds[j] < 8) { planePtIds[idx++] = cellPtIds[j]; } } // Construct a plane from the cell. vtkPlane* plane = vtkPlane::New(); this->DefinePlane(plane, planePtIds[0], planePtIds[1], planePtIds[2]); double endPoint[3] = { chairPoint[0] + lineEndPt[1][0] - lineEndPt[0][0], chairPoint[1] + lineEndPt[1][1] - lineEndPt[0][1], chairPoint[2] + lineEndPt[1][2] - lineEndPt[0][2] }; vtkPlane::IntersectWithLine( chairPoint, endPoint, plane->GetNormal(), plane->GetOrigin(), t, neighborPt); plane->Delete(); vtkDebugMacro(<< "ChairPoint: (" << chairPoint[0] << "," << chairPoint[1] << "," << chairPoint[2] << ") lineEndPts [" << lines[i].Id[0] << "(" << lineEndPt[0][0] << "," << lineEndPt[0][1] << "," << lineEndPt[0][2] << ")-" << lines[i].Id[1] << "(" << lineEndPt[1][0] << "," << lineEndPt[1][1] << "," << lineEndPt[1][2] << ")]" << " Intersection at: (" << neighborPt[0] << "," << neighborPt[1] << "," << neighborPt[2] << ")"); this->Points->SetPoint(neighborPtIds[i], neighborPt); } // Now that we have found the 3 neighbors, we need to compute the other // points in the chair. Note that we have 4 so far (3 neighbors + the // chair node). There are 2 more to be found. Given that they will // have to satisfy a parallelogram relationship, we can easily use // vector addition to evaluate them. for (int i = 0; i < 3; i++) { std::vector nodes(3); vtkSmartPointer cells = vtkSmartPointer::New(); nodes[0] = 8 + vtkParallelopipedTopology::GetDiametricOppositeOfCorner(this->CurrentHandleIdx); nodes[1] = neighborPtIds[i]; nodes[2] = neighborPtIds[(i + 1) % 3]; vtkDebugMacro(<< "Looking for cells containing nodes: " << nodes[0] << "," << nodes[1] << "," << nodes[2] << " in topology " << (this->CurrentHandleIdx + 1)); this->Topology->FindCellsContainingNodes(this->CurrentHandleIdx + 1, cells, nodes); npts = 0; cellPtIds = nullptr; cells->InitTraversal(); cells->GetNextCell(npts, cellPtIds); // Find the 4th pointId. The pointIds shall be stored in "nodes" int j = 0; while (cellPtIds[j] == nodes[0] || cellPtIds[j] == nodes[1] || cellPtIds[j] == nodes[2]) ++j; nodes.push_back(cellPtIds[j]); // Now go about finding the 4th point (Index 3) in the parallelogram.. // 0 ------ 1 // | | // 2 ------ 3 // double p[4][3]; // for 4 points.. 3 we know, 4th to find.. this->Points->GetPoint(nodes[0], p[0]); this->Points->GetPoint(nodes[1], p[1]); this->Points->GetPoint(nodes[2], p[2]); p[3][0] = p[1][0] + p[2][0] - p[0][0]; p[3][1] = p[1][1] + p[2][1] - p[0][1]; p[3][2] = p[1][2] + p[2][2] - p[0][2]; vtkDebugMacro(<< "Parallelogram built from (nodes and points): \n" << "(" << nodes[0] << ") = [" << p[0][0] << "," << p[0][1] << "," << p[0][2] << "]\n" << "(" << nodes[1] << ") = [" << p[1][0] << "," << p[1][1] << "," << p[1][2] << "]\n" << "(" << nodes[2] << ") = [" << p[2][0] << "," << p[2][1] << "," << p[2][2] << "]\n" << "(" << cellPtIds[j] << ") = [" << p[3][0] << "," << p[3][1] << "," << p[3][2] << "]\n"); this->Points->SetPoint(nodes[3], p[3]); } this->Points->SetPoint( 8 + vtkParallelopipedTopology::GetDiametricOppositeOfCorner(this->CurrentHandleIdx), this->Points->GetPoint(this->CurrentHandleIdx)); } } //------------------------------------------------------------------------------ // This is where the bulk of the work is done. int vtkParallelopipedRepresentation ::ComputeInteractionState(int X, int Y, int vtkNotUsed(modify)) { int oldInteractionState = this->InteractionState; // (A) ----------------------------------------------------------- // Handle the request methods. These are mere requests and will not cause // any change in the position of the handles or the shape of the // parallelopiped. The representation will, within this IF block change its // state from a request to a concrete state. if (this->InteractionState == vtkParallelopipedRepresentation::RequestResizeParallelopiped || this->InteractionState == vtkParallelopipedRepresentation::RequestResizeParallelopipedAlongAnAxis || this->InteractionState == vtkParallelopipedRepresentation::RequestChairMode) { this->CurrentHandleIdx = -1; // We are trying to perform user interaction that might potentially // select a handle. Check if we are really near a handle, so it // can be selected. // Loop over all the handles and check if one of them is selected for (int i = 0; i < 8; i++) { this->HandleRepresentations[i]->ComputeInteractionState(X, Y, 0); if (this->HandleRepresentations[i]->GetInteractionState() == vtkHandleRepresentation::Selecting) { // The selected handle. this->CurrentHandleIdx = i; // The shift modifier determines if the handles are going to be // translated along an axes of the parallelopiped. switch (this->InteractionState) { case vtkParallelopipedRepresentation::RequestResizeParallelopiped: this->InteractionState = (this->CurrentHandleIdx == this->ChairHandleIdx) ? ChairMode : ResizingParallelopiped; break; case vtkParallelopipedRepresentation::RequestResizeParallelopipedAlongAnAxis: this->InteractionState = (this->CurrentHandleIdx == this->ChairHandleIdx) ? ChairMode : ResizingParallelopipedAlongAnAxis; break; case vtkParallelopipedRepresentation::RequestChairMode: { // Toggle chair mode if we already have a chair here.. We are // trying to toggle of course.. In this case remove all chairs, if (this->CurrentHandleIdx == this->ChairHandleIdx && this->HexPolyData->GetPolys()->GetNumberOfCells() == 9) { this->RemoveExistingChairs(); this->LastEventPosition[0] = X; this->LastEventPosition[1] = Y; this->InteractionState = vtkParallelopipedRepresentation::Inside; // Synchronize the handle representations with our recently updated // "Points" data-structure. this->PositionHandles(); return this->InteractionState; } // We aren't trying to toggle. Create one // Create a chair with a default cavity depth of 0.1 this->UpdateChairAtNode(this->CurrentHandleIdx); // We are in chair mode. Use the placer to dictate where the // "chaired" handle can move. (It can only move within the // parallelopiped). First set some parameters on the placer. vtkPlaneCollection* pc = vtkPlaneCollection::New(); this->GetParallelopipedBoundingPlanes(pc); this->ChairPointPlacer->SetBoundingPlanes(pc); pc->Delete(); this->InteractionState = ChairMode; break; } } // Highlight the selected handle and unhighlight all others. this->SetHandleHighlight(-1, this->HandleProperty); this->SetHandleHighlight(this->CurrentHandleIdx, this->SelectedHandleProperty); break; } } if (this->CurrentHandleIdx == -1) { // We are near none of the handles. // Now check if we are within the parallelopiped or outside the // parallelopiped. We will use the pointplacer to evaluate this. vtkPlaneCollection* pc = vtkPlaneCollection::New(); this->GetParallelopipedBoundingPlanes(pc); this->ChairPointPlacer->SetBoundingPlanes(pc); pc->Delete(); // Use any random handle as a reference for the point placer. double eventDisplayPos[3] = { static_cast(X), static_cast(Y), 0.0 }; double dummy[4], worldOrient[9], handleWorldPos[4]; this->HandleRepresentations[0]->GetWorldPosition(handleWorldPos); this->InteractionState = (this->ChairPointPlacer->ComputeWorldPosition(this->Renderer, eventDisplayPos, handleWorldPos, dummy, worldOrient) ? vtkParallelopipedRepresentation::Inside : vtkParallelopipedRepresentation::Outside); } if (this->InteractionState == vtkParallelopipedRepresentation::Inside && oldInteractionState == vtkParallelopipedRepresentation::RequestResizeParallelopipedAlongAnAxis) { this->HighlightAllFaces(); } else { // UnHighlight all faces this->UnHighlightAllFaces(); } // Reset any cached "resize along that axis" stuff. this->LastResizeAxisIdx = -1; } // (B) ----------------------------------------------------------- // Handle the resizing operations (along the axis or arbitrarily). else if (this->InteractionState == vtkParallelopipedRepresentation::ResizingParallelopipedAlongAnAxis || this->InteractionState == vtkParallelopipedRepresentation::ResizingParallelopiped) { // Ensure that a handle has been picked. if (this->CurrentHandleIdx != -1) { // Compute world positions corresponding to the current event position // (X,Y) and the last event positions such that they lie at the same // depth that the handle lies on. double axis[3][3], eventWorldPos[4], handleWorldPos[4], handleDisplayPos[4], neighborWorldPos[3][4], neighborDisplayPos[3][4]; this->HandleRepresentations[this->CurrentHandleIdx]->GetWorldPosition(handleWorldPos); vtkInteractorObserver::ComputeWorldToDisplay( this->Renderer, handleWorldPos[0], handleWorldPos[1], handleWorldPos[2], handleDisplayPos); // Now find and get the display positions of the three neighbors of the // current handle. We have to rescale along one of the three edges. vtkIdType neighborIndices[3]; this->Topology->GetNeighbors(this->CurrentHandleIdx, neighborIndices, (this->ChairHandleIdx == -1) ? 0 : this->ChairHandleIdx + 1); // The motion vector in display coords const double motionVector[3] = { X - this->LastEventPosition[0], Y - this->LastEventPosition[1], 0.0 }; double maxConfidence = VTK_DOUBLE_MIN; // The next few lines attempt to find the axis should we scale along. // The axis is the axis of the parallelopiped that is most aligned with // the direction of mouse motion. int axisIdx = this->LastResizeAxisIdx; // To be found out .. // loop over the 3 neighbors of the current handle for (int i = 0; i < 3; i++) { // Compute display position of this neighbor this->Points->GetPoint(neighborIndices[i], neighborWorldPos[i]); vtkInteractorObserver::ComputeWorldToDisplay(this->Renderer, neighborWorldPos[i][0], neighborWorldPos[i][1], neighborWorldPos[i][2], neighborDisplayPos[i]); // Dot product of the motion vector (in display coords) with each // of the three edges (in display coords). The maximum of the three // will determine which axis of the parallelopiped we will rescale along axis[i][0] = neighborDisplayPos[i][0] - handleDisplayPos[0]; axis[i][1] = neighborDisplayPos[i][1] - handleDisplayPos[1]; axis[i][2] = 0.0; vtkMath::Normalize2D(axis[i]); // If we did not compute the resize axis Idx already the last time, // we were in this method, compute it now, by checking which axis // the motion vector is most aligned with. if (this->LastResizeAxisIdx == -1 || this->InteractionState == vtkParallelopipedRepresentation::ResizingParallelopiped) { const double confidence = fabs(vtkMath::Dot2D(axis[i], motionVector)); if (confidence > maxConfidence) { axisIdx = i; maxConfidence = confidence; } } } // Now that we know the axis to translate along, find the amount we should // translate by. The new handle position must lie somewhere along the // line joining the currently selected handle and the neighbor that lies // along the rescale axis. We will evaluate 't E [-inf, 1.0]', the // parametric position along the line. This point will simply be the // point on the aforementioned line that the current event position is // closest to. double directionOfProjection[3], closestPt1[3], closestPt2[3], t1, t; this->Renderer->GetActiveCamera()->GetDirectionOfProjection(directionOfProjection); vtkInteractorObserver::ComputeDisplayToWorld( this->Renderer, X, Y, handleDisplayPos[2], eventWorldPos); double l0[3] = { eventWorldPos[0] - directionOfProjection[0], eventWorldPos[1] - directionOfProjection[1], eventWorldPos[2] - directionOfProjection[2] }; double l1[3] = { eventWorldPos[0] + directionOfProjection[0], eventWorldPos[1] + directionOfProjection[1], eventWorldPos[2] + directionOfProjection[2] }; vtkLine::DistanceBetweenLines( handleWorldPos, neighborWorldPos[axisIdx], l0, l1, closestPt1, closestPt2, t, t1); t = (t > 1.0 ? 1.0 : t); // clamp 't' vtkDebugMacro(<< "Currently selected handle is at : (" << handleWorldPos[0] << "," << handleWorldPos[1] << "," << handleWorldPos[2] << ")\n Pt2 (the selected handle will be moved along the axis represented by" << " itself and Pt2) is at: (" << neighborWorldPos[axisIdx][0] << "," << neighborWorldPos[axisIdx][1] << "," << neighborWorldPos[axisIdx][2] << ")\n The selected handle will be moved to parametric location t = " << t << "with the line being specified by the above 2 points."); // This is the amount by which the face will move towards // (or away from if t < 0.0) the other face. We know that the face has // the following PointIds. // 1) CurrentHandleIdx // 2) Neighbor 1 of currentHandleIdx // 3) Neighbor 2 of CurrentHandleIdx // It will be our job in the next few lines to find the other points in // the face and translate the face. // "nodes" contains the 3 pointIds that we know are present on the face. std::vector nodes(3); nodes[0] = this->CurrentHandleIdx; for (int i = 0, j = 1; i < 3; i++) { if (i != axisIdx) { nodes[j++] = neighborIndices[i]; } } // "cells" below contains the face to be translated. vtkSmartPointer cells = vtkSmartPointer::New(); this->Topology->FindCellsContainingNodes( (this->ChairHandleIdx == -1) ? 0 : this->ChairHandleIdx + 1, cells, nodes); vtkIdType npts = 0; const vtkIdType* cellPtIds = nullptr; cells->InitTraversal(); cells->GetNextCell(npts, cellPtIds); // The translation vector double handleTranslation[3] = { t * neighborWorldPos[axisIdx][0] - t * handleWorldPos[0], t * neighborWorldPos[axisIdx][1] - t * handleWorldPos[1], t * neighborWorldPos[axisIdx][2] - t * handleWorldPos[2] }; double newHandleWorldPos[3] = { handleWorldPos[0] + handleTranslation[0], handleWorldPos[1] + handleTranslation[1], handleWorldPos[2] + handleTranslation[2] }; if (t > 0.0 // We are moving towards the other handle && (vtkMath::Distance2BetweenPoints(neighborWorldPos[axisIdx], newHandleWorldPos)) < (this->AbsoluteMinimumThickness * this->AbsoluteMinimumThickness)) { // Too close. We don't want the parallelopiped collapsing, do we ? vtkDebugMacro(<< "AbsoluteMaximumThickness = " << this->AbsoluteMinimumThickness << " This move will bring us " << sqrt(vtkMath::Distance2BetweenPoints( neighborWorldPos[axisIdx], newHandleWorldPos)) << " far away to (" << newHandleWorldPos[0] << "," << newHandleWorldPos[1] << "," << newHandleWorldPos[2] << "). We can\'t do that."); // Revise 't' so as to maintain minimum thickness. The bottom line is // that although 't E [-inf, 1.0]', 't' will never hit 1.0 unless // AbsoluteMinimumThickness is 0.0. t = 1.0 - this->AbsoluteMinimumThickness / sqrt(vtkMath::Distance2BetweenPoints(neighborWorldPos[axisIdx], handleWorldPos)); // Recompute these 2 positions with our revised 't' value. handleTranslation[0] = t * neighborWorldPos[axisIdx][0] - t * handleWorldPos[0]; handleTranslation[1] = t * neighborWorldPos[axisIdx][1] - t * handleWorldPos[1]; handleTranslation[2] = t * neighborWorldPos[axisIdx][2] - t * handleWorldPos[2]; newHandleWorldPos[0] = handleWorldPos[0] + handleTranslation[0]; newHandleWorldPos[1] = handleWorldPos[1] + handleTranslation[1]; newHandleWorldPos[2] = handleWorldPos[2] + handleTranslation[2]; if (t < 0.0) { // Sanity check. We should never get here in the first place. this->LastEventPosition[0] = X; this->LastEventPosition[1] = Y; return this->InteractionState; } vtkDebugMacro("So we are revising the value of t to " << t << " and newHandleWorldPos to (" << newHandleWorldPos[0] << "," << newHandleWorldPos[1] << "," << newHandleWorldPos[2] << ")"); } // If we have a chair, prevent the handle from being translated beyond // the plane of the chair, otherwise it will cause the chair to turn // inside out. So we will do some dot-product stuff and revise the // "neighborWorldPos", if we have a chair. if (this->ChairHandleIdx != -1) { std::vector nodes2(1); nodes2[0] = vtkParallelopipedTopology::GetDiametricOppositeOfCorner(this->ChairHandleIdx) + 8; const vtkParallelopipedTopology::CliqueType cells2 = this->Topology->FindCellsContainingNodes(this->ChairHandleIdx + 1, nodes2); for (vtkParallelopipedTopology::CliqueType::const_iterator clit = cells2.begin(); clit != cells2.end(); ++clit) { vtkSmartPointer plane = vtkSmartPointer::New(); this->DefinePlane(plane, (*clit)[0], (*clit)[1], (*clit)[2]); double distance = plane->EvaluateFunction(newHandleWorldPos); // Ensure that the handle is on the right side of the chair's plane, // and that it is at least 'MinimumThickness' away from any of the // planes of the chair. if (fabs(distance) < this->MinimumThickness || (distance * (std::find(clit->begin(), clit->end(), this->CurrentHandleIdx + 8) != clit->end() ? -1 : 1) > 0)) { this->LastEventPosition[0] = X; this->LastEventPosition[1] = Y; return this->InteractionState; } } } // Highlight this face... this->SetFaceHighlight(cells, this->SelectedFaceProperty); // Translate this face... for (vtkIdType i = 0; i < npts; this->TranslatePoint(cellPtIds[i++], handleTranslation)) { ; } // Cache the axis along which we resized the previous time, so we don't // have to recompute it. this->LastResizeAxisIdx = axisIdx; // Update the bounding planes. vtkPlaneCollection* pc = vtkPlaneCollection::New(); this->GetParallelopipedBoundingPlanes(pc); this->ChairPointPlacer->SetBoundingPlanes(pc); pc->Delete(); } else { // In theory, we should never get there. this->InteractionState = vtkParallelopipedRepresentation::Outside; } } // (C) ----------------------------------------------------------- // Default method for all other states. else if (this->InteractionState == vtkParallelopipedRepresentation::ChairMode) { // Ensure that a handle has been picked. if (this->CurrentHandleIdx != -1) { double handleWorldPos[4]; this->HandleRepresentations[this->CurrentHandleIdx]->GetWorldPosition(handleWorldPos); // The new handle position, will lie on a plane that passes through the // current world position and is parallel to the focal plane. // To compute this, we will use the help of the focal plane point placer, // and supply it with the offset of the handle's distance from the // focal plane. double eventDisplayPos[3] = { static_cast(X), static_cast(Y), 0.0 }; double newHandlePos[4], worldOrient[9]; if (this->ChairPointPlacer->ComputeWorldPosition( this->Renderer, eventDisplayPos, handleWorldPos, newHandlePos, worldOrient)) { const double handleTranslation[3] = { newHandlePos[0] - handleWorldPos[0], newHandlePos[1] - handleWorldPos[1], newHandlePos[2] - handleWorldPos[2] }; this->TranslatePoint(this->CurrentHandleIdx, handleTranslation); } this->UpdateChairAtNode(this->CurrentHandleIdx); } else { // In theory, we should never get there. this->InteractionState = vtkParallelopipedRepresentation::Outside; } } // (D) ----------------------------------------------------------- // Default method for all other states. else { this->InteractionState = vtkParallelopipedRepresentation::Outside; // Loop over all the handles and check if we are near one of them. for (int i = 0; i < 8; i++) { this->HandleRepresentations[i]->ComputeInteractionState(X, Y, 0); if (this->HandleRepresentations[i]->GetInteractionState() == vtkHandleRepresentation::Selecting) { this->SetHandleHighlight(i, this->HoveredHandleProperty); this->InteractionState = vtkParallelopipedRepresentation::Inside; break; } } if (this->InteractionState == vtkParallelopipedRepresentation::Outside) { // Unhighlight all handles and faces. this->SetHandleHighlight(-1, this->HandleProperty); this->UnHighlightAllFaces(); } } // Cache the last event position. this->LastEventPosition[0] = X; this->LastEventPosition[1] = Y; return this->InteractionState; } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation ::TranslatePoint(int id, const double translation[3]) { double p[3]; this->Points->GetPoint(id, p); p[0] += translation[0]; p[1] += translation[1]; p[2] += translation[2]; this->Points->SetPoint(id, p); if (id < 8) { this->HandleRepresentations[id]->SetWorldPosition(p); } // Update our records. this->PositionHandles(); } //------------------------------------------------------------------------------ // Get the bounding planes of the object. The first 6 planes will // be bounding planes of the parallelopiped. If in chair mode, three // additional planes will be present. The last three planes will be those // of the chair. The Normals of all the planes will point into the object. // void vtkParallelopipedRepresentation::GetBoundingPlanes(vtkPlaneCollection* pc) { vtkSmartPointer cellArray = vtkSmartPointer::New(); this->Topology->PopulateTopology(this->ChairHandleIdx + 1, cellArray); vtkIdType npts = 0; const vtkIdType* ptIds = nullptr; // For each planar cell in our object, we need to find the plane it lies on for (cellArray->InitTraversal(); cellArray->GetNextCell(npts, ptIds);) { vtkIdType planePtIds[3]; // For each cell, get the point ids that comprise the planar cell. for (int i = 0, idx = 0; i < npts && idx < 3; i++) { if (this->CurrentHandleIdx != ptIds[i]) { planePtIds[idx++] = ptIds[i]; } } // Construct a plane from the cell. vtkPlane* plane = vtkPlane::New(); this->DefinePlane(plane, planePtIds[0], planePtIds[1], planePtIds[2]); pc->AddItem(plane); plane->Delete(); } } //------------------------------------------------------------------------------ // Convenience method to get just the planes that define the parallelopiped. // If we aren't in chair mode, this will be the same as GetBoundingPlanes(). // If we are in chair mode, this will be the first 6 planes from amongst // those returned by "GetBoundingPlanes". // All planes have their normals pointing inwards. // void vtkParallelopipedRepresentation ::GetParallelopipedBoundingPlanes(vtkPlaneCollection* pc) { vtkPlaneCollection* pc2 = vtkPlaneCollection::New(); this->GetBoundingPlanes(pc2); vtkPlane* p; int i = 0; for (pc2->InitTraversal(); ((p = pc2->GetNextItem()) && i < 6); ++i) { pc->AddItem(p); } pc2->Delete(); } //------------------------------------------------------------------------------ // Convenience method to populate a plane from 3 pointIds void vtkParallelopipedRepresentation::DefinePlane( vtkPlane* plane, vtkIdType id1, vtkIdType id2, vtkIdType id3) { double p[3][3]; this->Points->GetPoint(id1, p[0]); this->Points->GetPoint(id2, p[1]); this->Points->GetPoint(id3, p[2]); this->DefinePlane(plane, p); } //------------------------------------------------------------------------------ // Convenience method to populate a plane from 3 points. void vtkParallelopipedRepresentation::DefinePlane(vtkPlane* plane, double p[3][3]) { plane->SetOrigin(p[0]); double v1[3] = { p[1][0] - p[0][0], p[1][1] - p[0][1], p[1][2] - p[0][2] }; double v2[3] = { p[2][0] - p[0][0], p[2][1] - p[0][1], p[2][2] - p[0][2] }; double normal[3]; vtkMath::Cross(v1, v2, normal); vtkMath::Normalize(normal); plane->SetNormal(normal); } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::GetActors(vtkPropCollection* pc) { for (int i = 0; i < 8; i++) { this->HandleRepresentations[i]->GetActors(pc); } this->HexActor->GetActors(pc); this->HexFaceActor->GetActors(pc); } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::ReleaseGraphicsResources(vtkWindow* w) { this->HexActor->ReleaseGraphicsResources(w); this->HexFaceActor->ReleaseGraphicsResources(w); for (int i = 0; i < 8; i++) { this->HandleRepresentations[i]->ReleaseGraphicsResources(w); } } //------------------------------------------------------------------------------ int vtkParallelopipedRepresentation::RenderOverlay(vtkViewport* v) { int count = 0; count += this->HexActor->RenderOverlay(v); count += this->HexFaceActor->RenderOverlay(v); for (int i = 0; i < 8; i++) { count += this->HandleRepresentations[i]->RenderOverlay(v); } return count; } //------------------------------------------------------------------------------ int vtkParallelopipedRepresentation::RenderOpaqueGeometry(vtkViewport* viewport) { int count = 0; this->BuildRepresentation(); count += this->HexActor->RenderOpaqueGeometry(viewport); count += this->HexFaceActor->RenderOpaqueGeometry(viewport); for (int i = 0; i < 8; i++) { count += this->HandleRepresentations[i]->RenderOpaqueGeometry(viewport); } return count; } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::PositionHandles() { for (int i = 0; i < 8; ++i) { this->HandleRepresentations[i]->SetWorldPosition(this->Points->GetPoint(i)); } this->Points->GetData()->Modified(); this->HexFacePolyData->Modified(); this->HexPolyData->Modified(); } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::HandlesOn() { for (int i = 0; i < 8; this->HandleRepresentations[i++]->SetVisibility(1)) { ; } } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::HandlesOff() { for (int i = 0; i < 8; this->HandleRepresentations[i++]->SetVisibility(0)) { ; } } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::SetHandleHighlight(int handleIdx, vtkProperty* property) { if (handleIdx == -1) { // Do for all handles for (int i = 0; i < 8; i++) { static_cast(this->HandleRepresentations[i]) ->SetProperty(property); static_cast(this->HandleRepresentations[i]) ->SetSelectedProperty(property); } } else { static_cast(this->HandleRepresentations[handleIdx]) ->SetProperty(property); static_cast(this->HandleRepresentations[handleIdx]) ->SetSelectedProperty(property); } } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation ::SetFaceHighlight(vtkCellArray* face, vtkProperty* p) { if (face) { this->HexFacePolyData->SetPolys(face); } this->HexFaceActor->SetProperty(p); } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::HighlightAllFaces() { vtkSmartPointer cells = vtkSmartPointer::New(); this->Topology->PopulateTopology(this->ChairHandleIdx + 1, cells); this->SetFaceHighlight(cells, this->SelectedFaceProperty); } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::UnHighlightAllFaces() { this->SetFaceHighlight(nullptr, this->FaceProperty); } //------------------------------------------------------------------------------ // Translate by a vector to be computed from the last Pick position and the // supplied event position void vtkParallelopipedRepresentation::Translate(int X, int Y) { double eventPos[2] = { static_cast(X), static_cast(Y) }; double lastEventPos[2] = { this->LastEventPosition[0], this->LastEventPosition[1] }; // First compute the centroid. Its only use is to determine a reference // plane, on which we will assume lastEventPos and eventPos lie. double* pts = static_cast(this->Points->GetData())->GetPointer(0); double center[3] = { 0.0, 0.0, 0.0 }; for (int i = 0; i < 8; i++) { center[0] += *pts++; center[1] += *pts++; center[2] += *pts++; } center[0] /= 8.0; center[1] /= 8.0; center[2] /= 8.0; // Now convert the event positions to world positions as if they lay at the // same plane as the center. double fp[4], lastEventWorldPos[4], eventWorldPos[4]; vtkInteractorObserver::ComputeWorldToDisplay(this->Renderer, center[0], center[1], center[2], fp); vtkInteractorObserver::ComputeDisplayToWorld( this->Renderer, lastEventPos[0], lastEventPos[1], fp[2], lastEventWorldPos); vtkInteractorObserver::ComputeDisplayToWorld( this->Renderer, eventPos[0], eventPos[1], fp[2], eventWorldPos); // Compute the offset from the last event position and translate. double translation[3] = { eventWorldPos[0] - lastEventWorldPos[0], eventWorldPos[1] - lastEventWorldPos[1], eventWorldPos[2] - lastEventWorldPos[2] }; this->Translate(translation); // Update our records this->LastEventPosition[0] = X; this->LastEventPosition[1] = Y; } //------------------------------------------------------------------------------ // Loop through all points and translate them void vtkParallelopipedRepresentation::Translate(double translation[3]) { double* pts = static_cast(this->Points->GetData())->GetPointer(0); for (int i = 0; i < 16; i++) { *pts++ += translation[0]; *pts++ += translation[1]; *pts++ += translation[2]; } // Synchronize the handle representations with our recently updated // "Points" data-structure. this->PositionHandles(); } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::Scale(int vtkNotUsed(X), int Y) { double* pts = static_cast(this->Points->GetData())->GetPointer(0); double* center = static_cast(this->Points->GetData())->GetPointer(3 * 14); double sf = (Y > this->LastEventPosition[1] ? 1.03 : 0.97); for (int i = 0; i < 16; i++, pts += 3) { pts[0] = sf * (pts[0] - center[0]) + center[0]; pts[1] = sf * (pts[1] - center[1]) + center[1]; pts[2] = sf * (pts[2] - center[2]) + center[2]; } // Synchronize the handle representations with our recently updated // "Points" data-structure. this->PositionHandles(); } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::PlaceWidget(double bounds[6]) { double corners[8][3] = { { bounds[0], bounds[2], bounds[4] }, { bounds[1], bounds[2], bounds[4] }, { bounds[1], bounds[3], bounds[4] }, { bounds[0], bounds[3], bounds[4] }, { bounds[0], bounds[2], bounds[5] }, { bounds[1], bounds[2], bounds[5] }, { bounds[1], bounds[3], bounds[5] }, { bounds[0], bounds[3], bounds[5] } }; this->PlaceWidget(corners); } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::PlaceWidget(double corners[8][3]) { // Scale the corners of parallelopiped according to the place factor. // Note that the default place factor is 0.5. So if your corners // appear half way in, don't be surprised. // double center[3] = { 0.0, 0.0, 0.0 }, newCorners[8][3]; for (int j = 0; j < 3; j++) { for (int i = 0; i < 8; center[j] += corners[i][j], i++) { ; } center[j] /= 8.0; for (int i = 0; i < 8; i++) { newCorners[i][j] = center[j] + this->PlaceFactor * (corners[i][j] - center[j]); } } for (int i = 0; i < 8; i++) { this->Points->SetPoint(i, newCorners[i]); } this->AbsoluteMinimumThickness = this->HexPolyData->GetLength() * this->MinimumThickness; this->ChairPointPlacer->SetMinimumDistance(0.5 * this->AbsoluteMinimumThickness); // Initialize the chair points too for (int i = 8; i < 16; i++) { this->Points->SetPoint(i, newCorners[0]); } this->PositionHandles(); } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::GetPolyData(vtkPolyData* pd) { pd->SetPoints(this->HexPolyData->GetPoints()); pd->SetPolys(this->HexPolyData->GetPolys()); } //------------------------------------------------------------------------------ double* vtkParallelopipedRepresentation::GetBounds() { return this->Points->GetBounds(); } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::BuildRepresentation() { this->Points->Modified(); } //------------------------------------------------------------------------------ void vtkParallelopipedRepresentation::PrintSelf(ostream& os, vtkIndent indent) { this->Superclass::PrintSelf(os, indent); os << indent << "Minimum Thickness: " << this->MinimumThickness << "\n"; if (this->HandleProperty) { os << indent << "Handle Property: " << this->HandleProperty << "\n"; } else { os << indent << "Handle Property: (none)\n"; } if (this->HoveredHandleProperty) { os << indent << "Hovered Handle Property: " << this->HoveredHandleProperty << "\n"; } else { os << indent << "Hovered Handle Property: (none)\n"; } if (this->FaceProperty) { os << indent << "Face Property: " << this->FaceProperty << "\n"; } else { os << indent << "Face Property: (none)\n"; } if (this->OutlineProperty) { os << indent << "Outline Property: " << this->OutlineProperty << "\n"; } else { os << indent << "Outline Property: (none)\n"; } if (this->SelectedHandleProperty) { os << indent << "Selected Handle Property: " << this->SelectedHandleProperty << "\n"; } else { os << indent << "Selected Handle Property: (none)\n"; } if (this->SelectedFaceProperty) { os << indent << "Selected Face Property: " << this->SelectedFaceProperty << "\n"; } else { os << indent << "Selected Face Property: (none)\n"; } if (this->SelectedOutlineProperty) { os << indent << "Selected Outline Property: " << this->SelectedOutlineProperty << "\n"; } else { os << indent << "Selected Outline Property: (none)\n"; } // this->InteractionState is printed in superclass // this is commented to avoid PrintSelf errors }