647 lines
22 KiB
C++
647 lines
22 KiB
C++
#include "vtkViewport.h"
|
|
#include <vtkPropAssembly.h>
|
|
#include <vtkAssembly.h>
|
|
#include <vtkPropCollection.h>
|
|
#include <vtkProp3D.h>
|
|
#include <vtkProp3DCollection.h>
|
|
#include <vtkCamera.h>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
#include <vtkInteractorStyleTrackballCamera.h>
|
|
#include <vtkObjectFactory.h>
|
|
#include <vtkAxesActor.h>
|
|
#include <vtkRenderer.h>
|
|
#include <vtkRenderWindow.h>
|
|
#include <vtkRenderWindowInteractor.h>
|
|
#include <vtkCornerAnnotation.h>
|
|
#include <vtkOrientationMarkerWidget.h>
|
|
#include <vtkCameraOrientationWidget.h>
|
|
#include <vtkPlaneSource.h>
|
|
#include <vtkActor.h>
|
|
#include <vtkAxes.h>
|
|
#include <vtkNamedColors.h>
|
|
#include <vtkCellPicker.h>
|
|
#include <vtkTextProperty.h>
|
|
#include <vtkPolyDataMapper.h>
|
|
#include <vtkProperty.h>
|
|
#include <vtkCallbackCommand.h>
|
|
#include <vtkMath.h>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <cstdlib>
|
|
#include "vtkHandlerWidget.h"
|
|
#include "vtkObjectsContext.h"
|
|
#include "Math/Assembly.h"
|
|
#include "Math/ContainerBox.h"
|
|
#include "Math/Cylinder.h"
|
|
#include "Math/Transform.h"
|
|
#include "Vtk/Math/vtkAssembly.h"
|
|
|
|
namespace uLib {
|
|
namespace Vtk {
|
|
|
|
struct ViewportData {
|
|
vtkSmartPointer<vtkRenderer> m_Renderer;
|
|
vtkSmartPointer<vtkCornerAnnotation> m_Annotation;
|
|
vtkSmartPointer<vtkOrientationMarkerWidget> m_Marker;
|
|
vtkSmartPointer<vtkCameraOrientationWidget> m_CameraWidget;
|
|
|
|
vtkSmartPointer<vtkPlaneSource> m_GridSource;
|
|
vtkSmartPointer<vtkActor> m_GridActor;
|
|
vtkSmartPointer<vtkAxes> m_OriginAxes;
|
|
vtkSmartPointer<vtkActor> m_OriginAxesActor;
|
|
|
|
vtkSmartPointer<vtkNamedColors> m_Colors;
|
|
|
|
vtkSmartPointer<vtkHandlerWidget> m_HandlerWidget;
|
|
vtkSmartPointer<vtkCellPicker> m_Picker;
|
|
vtkSmartPointer<vtkCallbackCommand> m_KeyCallback;
|
|
|
|
ViewportData()
|
|
: m_Renderer(vtkSmartPointer<vtkRenderer>::New())
|
|
, m_Annotation(vtkSmartPointer<vtkCornerAnnotation>::New())
|
|
, m_Marker(vtkSmartPointer<vtkOrientationMarkerWidget>::New())
|
|
, m_CameraWidget(nullptr)
|
|
, m_Colors(vtkSmartPointer<vtkNamedColors>::New())
|
|
{}
|
|
};
|
|
|
|
Viewport::Viewport()
|
|
: pv(new ViewportData())
|
|
, m_GridAxis(Y)
|
|
{
|
|
}
|
|
|
|
void Viewport::DisableHandler() {
|
|
if (pv->m_HandlerWidget) {
|
|
pv->m_HandlerWidget->SetEnabled(0);
|
|
}
|
|
}
|
|
|
|
Viewport::~Viewport()
|
|
{
|
|
if (pv->m_HandlerWidget) {
|
|
pv->m_HandlerWidget->SetEnabled(0);
|
|
pv->m_HandlerWidget->SetInteractor(nullptr);
|
|
pv->m_HandlerWidget = nullptr;
|
|
}
|
|
if (pv->m_Renderer) {
|
|
if (pv->m_Renderer->GetActiveCamera()) {
|
|
pv->m_Renderer->GetActiveCamera()->RemoveAllObservers();
|
|
}
|
|
pv->m_Renderer->RemoveAllObservers();
|
|
pv->m_Renderer->RemoveAllViewProps();
|
|
}
|
|
if (pv->m_Marker && !std::getenv("CTEST_PROJECT_NAME")) {
|
|
pv->m_Marker->SetEnabled(false);
|
|
pv->m_Marker->SetInteractor(nullptr);
|
|
}
|
|
if (pv->m_CameraWidget && !std::getenv("CTEST_PROJECT_NAME")) {
|
|
pv->m_CameraWidget->Off();
|
|
pv->m_CameraWidget->SetInteractor(nullptr);
|
|
}
|
|
delete pv;
|
|
}
|
|
|
|
vtkRenderer* Viewport::GetRenderer() { return pv->m_Renderer; }
|
|
vtkCornerAnnotation* Viewport::GetAnnotation() { return pv->m_Annotation; }
|
|
vtkCameraOrientationWidget* Viewport::GetCameraWidget() { return pv->m_CameraWidget; }
|
|
|
|
void Viewport::SetupPipeline(vtkRenderWindowInteractor* iren)
|
|
{
|
|
if (!iren) return;
|
|
|
|
// Trackball-camera interaction style
|
|
vtkNew<vtkInteractorStyleTrackballCamera> style;
|
|
iren->SetInteractorStyle(style);
|
|
|
|
// Corner annotation
|
|
pv->m_Annotation->GetTextProperty()->SetColor(1, 1, 1);
|
|
pv->m_Annotation->GetTextProperty()->SetFontFamilyToArial();
|
|
pv->m_Annotation->GetTextProperty()->SetOpacity(0.5);
|
|
pv->m_Annotation->SetMaximumFontSize(10);
|
|
pv->m_Annotation->SetText(0, "uLib VTK viewer.");
|
|
pv->m_Renderer->AddViewProp(pv->m_Annotation);
|
|
|
|
// right corner annotation
|
|
pv->m_Annotation->SetText(1, "Grid: -");
|
|
|
|
// Orientation axes marker (bottom-left corner)
|
|
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
|
vtkNew<vtkAxesActor> axes;
|
|
pv->m_Marker->SetInteractor(iren);
|
|
pv->m_Marker->SetOrientationMarker(axes);
|
|
pv->m_Marker->SetViewport(0.0, 0.0, 0.2, 0.2);
|
|
pv->m_Marker->SetEnabled(true);
|
|
pv->m_Marker->InteractiveOff();
|
|
}
|
|
|
|
// Grid Plane centered at (0,0,0)
|
|
pv->m_GridSource = vtkSmartPointer<vtkPlaneSource>::New();
|
|
pv->m_GridActor = vtkSmartPointer<vtkActor>::New();
|
|
vtkNew<vtkPolyDataMapper> gridMapper;
|
|
gridMapper->SetInputConnection(pv->m_GridSource->GetOutputPort());
|
|
|
|
pv->m_GridActor->SetMapper(gridMapper);
|
|
pv->m_GridActor->GetProperty()->SetRepresentationToWireframe();
|
|
pv->m_GridActor->GetProperty()->SetColor(0.4, 0.4, 0.4);
|
|
pv->m_GridActor->GetProperty()->SetLighting(0);
|
|
pv->m_GridActor->GetProperty()->SetOpacity(0.5);
|
|
pv->m_GridActor->PickableOff();
|
|
pv->m_Renderer->AddActor(pv->m_GridActor);
|
|
|
|
// Global Origin Axes
|
|
pv->m_OriginAxes = vtkSmartPointer<vtkAxes>::New();
|
|
pv->m_OriginAxes->SetScaleFactor(1.0); // will be updated
|
|
|
|
vtkNew<vtkPolyDataMapper> axesMapper;
|
|
axesMapper->SetInputConnection(pv->m_OriginAxes->GetOutputPort());
|
|
|
|
pv->m_OriginAxesActor = vtkSmartPointer<vtkActor>::New();
|
|
pv->m_OriginAxesActor->SetMapper(axesMapper);
|
|
pv->m_OriginAxesActor->PickableOff();
|
|
pv->m_Renderer->AddActor(pv->m_OriginAxesActor);
|
|
|
|
UpdateGrid();
|
|
|
|
// Observe interactor to update grid during interaction
|
|
vtkNew<vtkCallbackCommand> interactionCallback;
|
|
interactionCallback->SetClientData(this);
|
|
interactionCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){
|
|
static_cast<Viewport*>(clientdata)->UpdateGrid();
|
|
});
|
|
iren->AddObserver(vtkCommand::InteractionEvent, interactionCallback);
|
|
pv->m_Renderer->GetActiveCamera()->AddObserver(vtkCommand::ModifiedEvent, interactionCallback);
|
|
|
|
|
|
// Camera-orientation widget (VTK >= 9)
|
|
#if VTK_MAJOR_VERSION >= 9
|
|
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
|
pv->m_CameraWidget = vtkSmartPointer<vtkCameraOrientationWidget>::New();
|
|
pv->m_CameraWidget->SetParentRenderer(pv->m_Renderer);
|
|
pv->m_CameraWidget->SetInteractor(iren);
|
|
pv->m_CameraWidget->On();
|
|
}
|
|
#endif
|
|
pv->m_Renderer->SetBackground(0.15, 0.15, 0.15);
|
|
pv->m_Renderer->ResetCamera();
|
|
|
|
// Setup layering for overimposed rendering
|
|
if (iren->GetRenderWindow()) {
|
|
iren->GetRenderWindow()->SetNumberOfLayers(2);
|
|
pv->m_Renderer->SetLayer(0);
|
|
}
|
|
|
|
// Setup Handler Widget
|
|
if (!std::getenv("CTEST_PROJECT_NAME")) {
|
|
pv->m_HandlerWidget = vtkSmartPointer<vtkHandlerWidget>::New();
|
|
pv->m_HandlerWidget->SetInteractor(iren);
|
|
pv->m_HandlerWidget->SetCurrentRenderer(pv->m_Renderer);
|
|
if (pv->m_HandlerWidget->GetOverlayRenderer()) {
|
|
pv->m_HandlerWidget->GetOverlayRenderer()->SetLayer(1);
|
|
}
|
|
|
|
// Observe InteractionEvent to update the selected puppet when the widget moves it
|
|
vtkNew<vtkCallbackCommand> widgetInteractionCallback;
|
|
widgetInteractionCallback->SetClientData(this);
|
|
widgetInteractionCallback->SetCallback([](vtkObject*, unsigned long, void* clientdata, void*){
|
|
auto* self = static_cast<Viewport*>(clientdata);
|
|
for (auto* p : self->m_Puppets) {
|
|
if (p->IsSelected()) {
|
|
p->SyncFromVtk();
|
|
}
|
|
}
|
|
});
|
|
pv->m_HandlerWidget->AddObserver(vtkCommand::InteractionEvent, widgetInteractionCallback);
|
|
}
|
|
|
|
// Picking for selection
|
|
pv->m_Picker = vtkSmartPointer<vtkCellPicker>::New();
|
|
vtkNew<vtkCallbackCommand> clickCallback;
|
|
clickCallback->SetClientData(this);
|
|
clickCallback->SetCallback([](vtkObject* caller, unsigned long, void* clientdata, void*){
|
|
auto* iren = static_cast<vtkRenderWindowInteractor*>(caller);
|
|
auto* self = static_cast<Viewport*>(clientdata);
|
|
|
|
int* pos = iren->GetEventPosition();
|
|
self->pv->m_Picker->Pick(pos[0], pos[1], 0, self->pv->m_Renderer);
|
|
vtkProp* picked = self->pv->m_Picker->GetViewProp();
|
|
|
|
// 1. Recursive helper to check if a container prop contains a target prop
|
|
std::function<bool(vtkProp*, vtkProp*)> containsProp;
|
|
containsProp = [&containsProp](vtkProp* container, vtkProp* target) -> bool {
|
|
if (container == target) return true;
|
|
vtkPropCollection* parts = nullptr;
|
|
if (auto* pa = vtkPropAssembly::SafeDownCast(container))
|
|
parts = pa->GetParts();
|
|
else if (auto* aa = vtkAssembly::SafeDownCast(container))
|
|
parts = aa->GetParts();
|
|
if (parts) {
|
|
parts->InitTraversal();
|
|
for (int i = 0; i < parts->GetNumberOfItems(); ++i) {
|
|
if (containsProp(parts->GetNextProp(), target))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
Puppet* target = nullptr;
|
|
if (picked) {
|
|
// 2. Find the leaf puppet: the one that contains 'picked' and is not a parent of another that also contains it.
|
|
// Actually, we can just find all matches and pick the one with most 'nested' prop?
|
|
// A simpler way: we know 'picked' is the LEAF prop from VTK.
|
|
// Find a puppet that contains it.
|
|
Puppet* leafPuppet = nullptr;
|
|
for (auto* p : self->m_Puppets) {
|
|
if (containsProp(p->GetProp(), picked)) {
|
|
// If we already have a candidate, check if this one is smaller (nested)
|
|
if (!leafPuppet || containsProp(leafPuppet->GetProp(), p->GetProp())) {
|
|
leafPuppet = p;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (leafPuppet) {
|
|
target = leafPuppet;
|
|
|
|
// 3. Model-driven hierarchy climb:
|
|
// If the leaf puppet has a uLib object, climb its parents.
|
|
// If any parent is an Assembly with GroupSelection=true, select the assembly puppet instead.
|
|
uLib::Object* currentObj = leafPuppet->GetContent();
|
|
|
|
while (currentObj) {
|
|
// Object doesn't have parent, but AffineTransform does
|
|
uLib::Object* parentObj = nullptr;
|
|
if (auto* at = dynamic_cast<uLib::AffineTransform*>(currentObj)) {
|
|
parentObj = dynamic_cast<uLib::Object*>(at->GetParent());
|
|
}
|
|
|
|
if (auto* parentAsm = dynamic_cast<::uLib::Assembly*>(parentObj)) {
|
|
if (parentAsm->GetGroupSelection()) {
|
|
// Find the puppet for this parent assembly
|
|
auto it = self->m_ObjectToPuppet.find(parentAsm);
|
|
if (it != self->m_ObjectToPuppet.end()) {
|
|
target = it->second;
|
|
// Keep climbing to find even larger groups
|
|
}
|
|
}
|
|
}
|
|
currentObj = parentObj;
|
|
}
|
|
}
|
|
}
|
|
self->SelectPuppet(target);
|
|
});
|
|
iren->AddObserver(vtkCommand::LeftButtonPressEvent, clickCallback);
|
|
|
|
// Keyboard events for widget coordinate frame
|
|
pv->m_KeyCallback = vtkSmartPointer<vtkCallbackCommand>::New();
|
|
pv->m_KeyCallback->SetClientData(this);
|
|
pv->m_KeyCallback->SetCallback([](vtkObject* caller, unsigned long event, void* clientdata, void*){
|
|
auto* iren = static_cast<vtkRenderWindowInteractor*>(caller);
|
|
auto* self = static_cast<Viewport*>(clientdata);
|
|
|
|
std::string key = iren->GetKeySym();
|
|
bool handled = false;
|
|
|
|
if (self->pv->m_HandlerWidget && self->pv->m_HandlerWidget->GetEnabled()) {
|
|
if (key == "l") {
|
|
if (event == vtkCommand::KeyPressEvent) {
|
|
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::LOCAL);
|
|
std::cout << "Widget Frame: LOCAL" << std::endl;
|
|
}
|
|
handled = true;
|
|
}
|
|
else if (key == "g") {
|
|
if (event == vtkCommand::KeyPressEvent) {
|
|
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::GLOBAL);
|
|
std::cout << "Widget Frame: GLOBAL" << std::endl;
|
|
}
|
|
handled = true;
|
|
}
|
|
else if (key == "c") {
|
|
if (event == vtkCommand::KeyPressEvent) {
|
|
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER);
|
|
std::cout << "Widget Frame: CENTER" << std::endl;
|
|
}
|
|
handled = true;
|
|
}
|
|
else if (key == "k") {
|
|
if (event == vtkCommand::KeyPressEvent) {
|
|
self->pv->m_HandlerWidget->SetReferenceFrame(vtkHandlerWidget::CENTER_LOCAL);
|
|
std::cout << "Widget Frame: CENTER_LOCAL" << std::endl;
|
|
}
|
|
handled = true;
|
|
}
|
|
else if (key == "1") {
|
|
if (event == vtkCommand::KeyPressEvent) {
|
|
self->pv->m_HandlerWidget->SetTranslationEnabled(!self->pv->m_HandlerWidget->GetTranslationEnabled());
|
|
}
|
|
handled = true;
|
|
}
|
|
else if (key == "2") {
|
|
if (event == vtkCommand::KeyPressEvent) {
|
|
self->pv->m_HandlerWidget->SetRotationEnabled(!self->pv->m_HandlerWidget->GetRotationEnabled());
|
|
}
|
|
handled = true;
|
|
}
|
|
else if (key == "3") {
|
|
if (event == vtkCommand::KeyPressEvent) {
|
|
self->pv->m_HandlerWidget->SetScalingEnabled(!self->pv->m_HandlerWidget->GetScalingEnabled());
|
|
}
|
|
handled = true;
|
|
}
|
|
}
|
|
|
|
if (key == "f") {
|
|
if (event == vtkCommand::KeyPressEvent) {
|
|
self->ZoomSelected();
|
|
}
|
|
handled = true;
|
|
}
|
|
|
|
if (handled) {
|
|
self->pv->m_KeyCallback->SetAbortFlag(1);
|
|
iren->Render();
|
|
}
|
|
});
|
|
iren->AddObserver(vtkCommand::KeyPressEvent, pv->m_KeyCallback, 1.0);
|
|
iren->AddObserver(vtkCommand::CharEvent, pv->m_KeyCallback, 1.0);
|
|
}
|
|
|
|
void Viewport::Reset()
|
|
{
|
|
ZoomAuto();
|
|
Render();
|
|
}
|
|
|
|
void Viewport::ZoomAuto()
|
|
{
|
|
if (pv->m_Renderer) {
|
|
pv->m_Renderer->ResetCameraClippingRange();
|
|
pv->m_Renderer->ResetCamera();
|
|
}
|
|
}
|
|
|
|
void Viewport::ZoomSelected()
|
|
{
|
|
if (!pv->m_Renderer) return;
|
|
|
|
Puppet* selected = nullptr;
|
|
for (auto* p : m_Puppets) {
|
|
if (p->IsSelected()) {
|
|
selected = p;
|
|
break;
|
|
}
|
|
}
|
|
if (!selected) return;
|
|
|
|
vtkProp* prop = selected->GetProp();
|
|
if (!prop) return;
|
|
|
|
double* b = prop->GetBounds();
|
|
if (!b) return;
|
|
|
|
double bounds[6];
|
|
std::copy(b, b + 6, bounds);
|
|
|
|
if (bounds[0] > bounds[1]) return; // Invalid bounds
|
|
|
|
// Expand bounds by a factor from center (e.g. 2.0 to have some margin)
|
|
double center[3] = {(bounds[0] + bounds[1]) / 2.0, (bounds[2] + bounds[3]) / 2.0, (bounds[4] + bounds[5]) / 2.0};
|
|
double h_ext[3] = {(bounds[1] - bounds[0]) / 2.0, (bounds[3] - bounds[2]) / 2.0, (bounds[5] - bounds[4]) / 2.0};
|
|
|
|
// Ensure a minimum size to avoid camera issues with flat/point objects
|
|
double max_h = std::max({h_ext[0], h_ext[1], h_ext[2], 0.1});
|
|
|
|
double newBounds[6];
|
|
for (int i=0; i<3; ++i) {
|
|
double current_h = std::max(h_ext[i], max_h * 0.1);
|
|
newBounds[2*i] = center[i] - 2.5 * current_h;
|
|
newBounds[2*i+1] = center[i] + 2.5 * current_h;
|
|
}
|
|
|
|
pv->m_Renderer->ResetCamera(newBounds);
|
|
pv->m_Renderer->ResetCameraClippingRange();
|
|
this->Render();
|
|
}
|
|
|
|
void Viewport::AddPuppet(Puppet& prop)
|
|
{
|
|
this->RegisterPuppet(&prop, false);
|
|
Render();
|
|
}
|
|
|
|
void Viewport::RemovePuppet(Puppet& prop)
|
|
{
|
|
this->UnregisterPuppet(&prop);
|
|
Render();
|
|
}
|
|
|
|
void Viewport::RegisterPuppet(Puppet* p, bool isPart) {
|
|
if (!p) return;
|
|
if (std::find(m_Puppets.begin(), m_Puppets.end(), p) != m_Puppets.end()) return;
|
|
|
|
m_Puppets.push_back(p);
|
|
p->ConnectRenderer(pv->m_Renderer);
|
|
|
|
// If it's a part of an assembly, we don't want to draw it twice.
|
|
// Assembly itself already draws its parts.
|
|
// But we need ConnectRenderer above to allow highliting and property updates.
|
|
if (isPart) {
|
|
pv->m_Renderer->RemoveViewProp(p->GetProp());
|
|
}
|
|
|
|
// Get the object and register in map
|
|
uLib::Object* obj = p->GetContent();
|
|
|
|
// If it's an assembly, we need to observe its children
|
|
if (auto* as = dynamic_cast<::uLib::Vtk::Assembly*>(p)) {
|
|
this->ObserveContext(as->GetChildrenContext());
|
|
}
|
|
|
|
if (obj) m_ObjectToPuppet[obj] = p;
|
|
}
|
|
|
|
void Viewport::UnregisterPuppet(Puppet* p) {
|
|
if (!p) return;
|
|
if (p->IsSelected()) SelectPuppet(nullptr);
|
|
|
|
auto it = std::find(m_Puppets.begin(), m_Puppets.end(), p);
|
|
if (it != m_Puppets.end()) m_Puppets.erase(it);
|
|
|
|
// Remove from map
|
|
for (auto mapIt = m_ObjectToPuppet.begin(); mapIt != m_ObjectToPuppet.end(); ) {
|
|
if (mapIt->second == p) mapIt = m_ObjectToPuppet.erase(mapIt);
|
|
else ++mapIt;
|
|
}
|
|
|
|
p->DisconnectRenderer(pv->m_Renderer);
|
|
}
|
|
|
|
void Viewport::ObserveContext(vtkObjectsContext* ctx) {
|
|
if (!ctx) return;
|
|
|
|
// Process existing puppets
|
|
for (auto const& [obj, puppet] : ctx->GetPuppets()) {
|
|
this->RegisterPuppet(puppet, true);
|
|
}
|
|
|
|
// Listen for future puppets
|
|
uLib::Object::connect(ctx, &vtkObjectsContext::PuppetAdded, [this](Puppet* p){
|
|
this->RegisterPuppet(p, true);
|
|
});
|
|
uLib::Object::connect(ctx, &vtkObjectsContext::PuppetRemoved, [this](Puppet* p){
|
|
this->UnregisterPuppet(p);
|
|
});
|
|
}
|
|
|
|
void Viewport::SelectPuppet(Puppet* prop)
|
|
{
|
|
for (auto* p : m_Puppets) {
|
|
p->SetSelected(p == prop);
|
|
}
|
|
|
|
if (pv->m_HandlerWidget) {
|
|
if (prop) {
|
|
vtkProp3D* prop3d = vtkProp3D::SafeDownCast(prop->GetProp());
|
|
if (prop3d) {
|
|
pv->m_HandlerWidget->SetProp3D(prop3d);
|
|
pv->m_HandlerWidget->SetEnabled(1);
|
|
pv->m_HandlerWidget->PlaceWidget(prop3d->GetBounds());
|
|
}
|
|
} else {
|
|
pv->m_HandlerWidget->SetEnabled(0);
|
|
pv->m_HandlerWidget->SetProp3D(nullptr);
|
|
}
|
|
}
|
|
|
|
Render();
|
|
OnSelectionChanged(prop);
|
|
}
|
|
|
|
void Viewport::SetGridVisible(bool visible)
|
|
{
|
|
if (pv->m_GridActor) {
|
|
pv->m_GridActor->SetVisibility(visible);
|
|
Render();
|
|
}
|
|
}
|
|
|
|
bool Viewport::GetGridVisible() const
|
|
{
|
|
if (pv->m_GridActor) {
|
|
return pv->m_GridActor->GetVisibility() != 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void Viewport::SetGridAxis(Axis axis)
|
|
{
|
|
m_GridAxis = axis;
|
|
UpdateGrid();
|
|
Render();
|
|
}
|
|
|
|
void Viewport::addProp(vtkProp* prop)
|
|
{
|
|
if (pv->m_Renderer) {
|
|
pv->m_Renderer->AddActor(prop);
|
|
Render();
|
|
}
|
|
}
|
|
|
|
void Viewport::RemoveProp(vtkProp* prop)
|
|
{
|
|
if (pv->m_Renderer) {
|
|
pv->m_Renderer->RemoveViewProp(prop);
|
|
Render();
|
|
}
|
|
}
|
|
|
|
void Viewport::UpdateGrid()
|
|
{
|
|
if (!pv->m_Renderer || !pv->m_GridSource) return;
|
|
if (pv->m_GridActor && !pv->m_GridActor->GetVisibility()) return;
|
|
|
|
vtkCamera* camera = pv->m_Renderer->GetActiveCamera();
|
|
if (!camera) return;
|
|
|
|
// Determine the "scale" of the view (how many units are visible vertically)
|
|
double viewHeight;
|
|
if (camera->GetParallelProjection()) {
|
|
viewHeight = 2.0 * camera->GetParallelScale();
|
|
} else {
|
|
double distance = camera->GetDistance();
|
|
// ViewAngle is height angle in degrees
|
|
double angleRad = camera->GetViewAngle() * vtkMath::Pi() / 180.0;
|
|
viewHeight = 2.0 * distance * std::tan(angleRad / 2.0);
|
|
}
|
|
|
|
if (viewHeight <= 0) viewHeight = 1.0;
|
|
|
|
// We want roughly 5-15 grid divisions visible.
|
|
// Spacing should be a power of 10 (1mm, 1cm, 10cm, 1m, 10m...)
|
|
double log10Spacing = std::floor(std::log10(viewHeight / 5.0));
|
|
double spacing = std::pow(10.0, log10Spacing);
|
|
|
|
// Spacing should be at least 1mm
|
|
if (spacing < 1.0) spacing = 1.0;
|
|
|
|
// Get current focal point to center the grid near what we're looking at
|
|
double focalPoint[3];
|
|
camera->GetFocalPoint(focalPoint);
|
|
|
|
// Indices for the two dimensions of the grid plane
|
|
int idxH, idxV, idxN;
|
|
if (m_GridAxis == X) { idxH = 1; idxV = 2; idxN = 0; }
|
|
else if (m_GridAxis == Y) { idxH = 0; idxV = 2; idxN = 1; }
|
|
else { idxH = 0; idxV = 1; idxN = 2; }
|
|
|
|
// Align center to spacing
|
|
double centerH = std::round(focalPoint[idxH] / spacing) * spacing;
|
|
double centerV = std::round(focalPoint[idxV] / spacing) * spacing;
|
|
double centerN = 0.0; // Grid plane typically passes through the origin
|
|
|
|
// Number of lines
|
|
int numLines = 20;
|
|
double halfSize = (numLines / 2.0) * spacing;
|
|
|
|
double minH = centerH - halfSize;
|
|
double maxH = centerH + halfSize;
|
|
double minV = centerV - halfSize;
|
|
double maxV = centerV + halfSize;
|
|
|
|
// Update Plane Source mapping axes to origin/point1/point2
|
|
double origin[3] = {0,0,0}, p1[3] = {0,0,0}, p2[3] = {0,0,0};
|
|
origin[idxH] = minH; origin[idxV] = minV; origin[idxN] = centerN;
|
|
p1[idxH] = maxH; p1[idxV] = minV; p1[idxN] = centerN;
|
|
p2[idxH] = minH; p2[idxV] = maxV; p2[idxN] = centerN;
|
|
|
|
pv->m_GridSource->SetOrigin(origin);
|
|
pv->m_GridSource->SetPoint1(p1);
|
|
pv->m_GridSource->SetPoint2(p2);
|
|
pv->m_GridSource->SetXResolution(numLines);
|
|
pv->m_GridSource->SetYResolution(numLines);
|
|
pv->m_GridSource->Update();
|
|
|
|
if (pv->m_OriginAxes) {
|
|
pv->m_OriginAxes->SetScaleFactor(spacing);
|
|
}
|
|
|
|
// Update annotation for grid size
|
|
char gridLabel[32];
|
|
if (spacing >= 1000.0) {
|
|
sprintf(gridLabel, "Grid: %.1f m", spacing / 1000.0);
|
|
} else if (spacing >= 10.0) {
|
|
sprintf(gridLabel, "Grid: %.1f cm", spacing / 10.0);
|
|
} else {
|
|
sprintf(gridLabel, "Grid: %.0f mm", spacing);
|
|
}
|
|
pv->m_Annotation->SetText(1, gridLabel);
|
|
}
|
|
|
|
} // namespace Vtk
|
|
} // namespace uLib
|