【VTK】可拖动的坐标轴MovableAxesWidget

【VTK】可拖动的坐标轴MovableAxesWidget

一直想从头写一个vtkWidget来了解vtk。这两天晚上比较空,正好自己选一个交互实现下。

vtk官网有一个可以拖动轴的例子MovableAxes。可惜AxesActor继承自Prop3D而非Widget无法交互,并且例子只能绕相机方向旋转平移无法绕指定轴。就把这个例子改成vtkWidget好了。

自己实现效果

在这里插入图片描述

官网案例

仿照RoboDK交互方式,很多三维软件都是这种交互。印象中原来在贴吧看到过vtk实现这种交互,也许vtk有现成的,我这次找的时候没找到。


记录下实现vtkWidgetvtkWidgetRep遇到的问题。

1 素材

轴用ConeSource+LineSource,平面用PlaneSource,旋转自己实现一个Source(读stl),文字用VectorText。每个轴、平面、文字公用一个,原点单独设置。

2 Widget常用接口实现

需要在Rep中根据自己定义,实现这几个函数,以便vtk渲染时统一处理不同的Widget

    double *GetBounds() VTK_SIZEHINT(6) override;
    void GetActors(vtkPropCollection *pc) override;
    void ReleaseGraphicsResources(vtkWindow *) override;
    int RenderOpaqueGeometry(vtkViewport *) override;
    int RenderTranslucentPolygonalGeometry(vtkViewport *) override;

3 不同组件的选择

通过实现void RegisterPickers() override;让vtk判断捕捉那些actor

    // 捕捉
    this->picker_ = vtkCellPicker::New();
    this->picker_->SetTolerance(0.004); // 选择范围,window对角线百分比
    for (int i = 0; i < 3; i++) {
        this->picker_->AddPickList(this->axis_actors_[i]);
        this->picker_->AddPickList(this->plane_actors_[i]);
        this->picker_->AddPickList(this->rotate_actors_[i]);
    }
    this->picker_->PickFromListOn();

void MovableAxesRepresentation::SetLockPicker(vtkTypeBool lock)
{
    if (lock == this->lock_camera_) {
        return;
    }
    if (lock) {
        for (int i = 0; i < 3; i++) {
            this->picker_->DeletePickList(this->axis_actors_[i]);
            this->picker_->DeletePickList(this->plane_actors_[i]);
            this->picker_->DeletePickList(this->rotate_actors_[i]);
        }
    } else {
        for (int i = 0; i < 3; i++) {
            this->picker_->AddPickList(this->axis_actors_[i]);
            this->picker_->AddPickList(this->plane_actors_[i]);
            this->picker_->AddPickList(this->rotate_actors_[i]);
        }
    }
    this->lock_camera_ = lock;
    this->Modified();
}

void MovableAxesRepresentation::RegisterPickers()
{
    vtkPickingManager *pm = this->GetPickingManager();
    if (!pm) {
        return;
    }
    pm->AddPicker(this->picker_, this);
}

4 Widget在屏幕上尺寸始终固定

vtkWidgetRepresentation提供了HandleSizeSizeHandlesInPixels用来确保Handle占固定像素,可以直接借助这个接口实现所有尺寸固定。

    this->HandleSize = 5.0;

void MovableAxesRepresentation::SizeHandles()
{
    double radius = this->vtkWidgetRepresentation::SizeHandlesInPixels(2, this->origian_sphere_->GetCenter());
    for (int i = 0; i < 3; i++) {
        this->axis_cones_[i]->SetHeight(3 * radius);
        this->axis_cones_[i]->SetRadius(radius);
        this->rotate_sources_[i]->SetRadius(radius);
        this->label_actors_[i]->SetScale(radius * 3, radius * 3, radius * 3);
    }
    this->origian_sphere_->SetRadius(radius);

    axis_offset_ = radius * 5 * default_axis_offset_;
    plane_offset_ = radius * 5 * default_plane_offset_;
    plane_wide_ = radius * 5 * default_plane_wide_;
    rotate_offset_ = radius * 5 * default_rotate_offset_;
}

5 降低无效刷新

Rep下不调用this->Modified();不重新计算actor、source

void MovableAxesRepresentation::BuildRepresentation()
{
    // 刷新Rep
    if (!this->Renderer || !this->Renderer->GetRenderWindow()) {
        return;
    }

    vtkInformation *info = this->GetPropertyKeys();
    for (int i = 0; i < 3; i++) {
        this->axis_actors_[i]->SetPropertyKeys(info);
        this->plane_actors_[i]->SetPropertyKeys(info);
        this->rotate_actors_[i]->SetPropertyKeys(info);
    }
    this->origian_actor_->SetPropertyKeys(info);

    // actor、source重新计算
    if (this->GetMTime() > this->BuildTime
        || this->Renderer->GetRenderWindow()->GetMTime() > this->BuildTime) {
        ...
     }

    // 重建和renderwindow更改时调整控制柄的大小
    if (this->GetMTime() > this->BuildTime
        || this->Renderer->GetRenderWindow()->GetMTime() > this->BuildTime) {
        this->SizeHandles();
        this->BuildTime.Modified();
    }
}

6 鼠标移入高亮,按下切换光标

Widget订阅事件、修改鼠标样式

    this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonPressEvent, vtkWidgetEvent::Select,
                                            this, MovableAxesWidget::SelectAction);

    this->CallbackMapper->SetCallbackMethod(vtkCommand::LeftButtonReleaseEvent,
                                            vtkWidgetEvent::EndSelect, this, MovableAxesWidget::EndSelectAction);

    this->CallbackMapper->SetCallbackMethod(vtkCommand::MouseMoveEvent,
                                            vtkWidgetEvent::Move, this, MovableAxesWidget::MoveAction);

int MovableAxesWidget::UpdateCursorShape(int state)
{
    // 更新光标
    if (this->ManagesCursor) {
        switch (state) {
        case MovableAxesRepresentation::MoveAxisX:
        case MovableAxesRepresentation::MoveAxisY:
        case MovableAxesRepresentation::MoveAxisZ:
        case MovableAxesRepresentation::MovePlanX:
        case MovableAxesRepresentation::MovePlanY:
        case MovableAxesRepresentation::MovePlanZ:
        case MovableAxesRepresentation::RotateAxisX:
        case MovableAxesRepresentation::RotateAxisY:
        case MovableAxesRepresentation::RotateAxisZ: {
            return this->RequestCursorShape(VTK_CURSOR_SIZEALL);
        }
        default:
            return this->RequestCursorShape(VTK_CURSOR_DEFAULT);
        }
    }

    return 0;
}

Rep实现交互、actor样式切换

    int ComputeInteractionState(int X, int Y, int modify = 0) override;
    void StartWidgetInteraction(double eventPos[2]) override;
    void WidgetInteraction(double newEventPos[2]) override;
    void EndWidgetInteraction(double newEventPos[2]) override;

void MovableAxesRepresentation::HighlightNormal(const int &state)
{
    // 根据交互状态,修改属性
    for (int i = 0; i < 3; i++) {
        this->axis_actors_[i]->SetProperty(this->property_[i]);
        this->plane_actors_[i]->SetProperty(this->property_[i]);
        this->rotate_actors_[i]->SetProperty(this->property_[i]);
    }
    switch (state) {
    case MoveAxisX:
        this->axis_actors_[0]->SetProperty(this->selected_property_[0]);
        break;
    case MoveAxisY:
        this->axis_actors_[1]->SetProperty(this->selected_property_[1]);
        break;
    case MoveAxisZ:
        this->axis_actors_[2]->SetProperty(this->selected_property_[2]);
        break;
    case MovePlanX:
        this->plane_actors_[0]->SetProperty(this->selected_property_[0]);
        break;
    case MovePlanY:
        this->plane_actors_[1]->SetProperty(this->selected_property_[1]);
        break;
    case MovePlanZ:
        this->plane_actors_[2]->SetProperty(this->selected_property_[2]);
        break;
    case RotateAxisX:
        this->rotate_actors_[0]->SetProperty(this->selected_property_[0]);
        break;
    case RotateAxisY:
        this->rotate_actors_[1]->SetProperty(this->selected_property_[1]);
        break;
    case RotateAxisZ:
        this->rotate_actors_[2]->SetProperty(this->selected_property_[2]);
        break;
    default:
        break;
    }
}

7 交互相关坐标计算

  1. 沿着轴平移

获取鼠标运动矢量,跟坐标轴点乘结果就是要平移距离

void MovableAxesRepresentation::MoveAxis(const int &state, double *p1, double *p2)
{

    vtkNew<vtkTransform> posture_transform_;
    posture_transform_->SetMatrix(posture_);

    double v[3] { p2[0] - p1[0], p2[1] - p1[1], p2[2] - p1[2] };
    double normal[3];

    switch (state) {
    case MoveAxisX: {
        normal[0] = posture_->GetElement(0, 0);
        normal[1] = posture_->GetElement(1, 0);
        normal[2] = posture_->GetElement(2, 0);
        auto distance = vtkMath::Dot(v, normal);
        posture_transform_->Translate(distance, 0, 0);
    } break;
    case MoveAxisY: {
        normal[0] = posture_->GetElement(0, 1);
        normal[1] = posture_->GetElement(1, 1);
        normal[2] = posture_->GetElement(2, 1);
        auto distance = vtkMath::Dot(v, normal);
        posture_transform_->Translate(0, distance, 0);
    } break;
    case MoveAxisZ: {
        normal[0] = posture_->GetElement(0, 2);
        normal[1] = posture_->GetElement(1, 2);
        normal[2] = posture_->GetElement(2, 2);
        auto distance = vtkMath::Dot(v, normal);
        posture_transform_->Translate(0, 0, distance);
    } break;
    default:
        return;
    }

    posture_transform_->Update();
    posture_transform_->GetMatrix(posture_);

    this->Modified();
    this->BuildRepresentation();
}
  1. 在平面平移

获取鼠标运动矢量,求其在平面投影

void GetPlaneProjection(const double a[3], const double b[3], double c[3])
{
    double squard = vtkMath::Dot(a, a);
    double scale = vtkMath::Dot(b, a) / squard;
    for (int i = 0; i < 3; i++) {
        c[i] = b[i] - a[i] * scale;
    }
}

void MovableAxesRepresentation::MovePlane(const int &state, double *p1, double *p2)
{
    // 鼠标点移动到自身坐标系中
    double pos_t1[4] { p1[0], p1[1], p1[2], 1 };
    double pos_t2[4] { p2[0], p2[1], p2[2], 1 };
    vtkNew<vtkMatrix4x4> posture_inv;
    vtkMatrix4x4::Invert(posture_, posture_inv);
    auto pos_t = posture_inv->MultiplyDoublePoint(pos_t1);
    double v1[3] = { pos_t[0], pos_t[1], pos_t[2] };
    pos_t = posture_inv->MultiplyDoublePoint(pos_t2);
    double v2[3] = { pos_t[0], pos_t[1], pos_t[2] };

    // 计算鼠标移动向量投影在对应平面
    double v[3] { v2[0] - v1[0], v2[1] - v1[1], v2[2] - v1[2] };
    double normal[3];
    double projection[3];

    switch (state) {
    case MovePlanX: {
        normal[0] = 1;
        normal[1] = 0;
        normal[2] = 0;
    } break;
    case MovePlanY: {
        normal[0] = 0;
        normal[1] = 1;
        normal[2] = 0;
    } break;
    case MovePlanZ: {
        normal[0] = 0;
        normal[1] = 0;
        normal[2] = 1;
    } break;
    default:
        return;
    }

    // 平移
    vtkNew<vtkTransform> posture_transform_;
    posture_transform_->SetMatrix(posture_);
    GetPlaneProjection(normal, v, projection);
    posture_transform_->Translate(projection);
    posture_transform_->Update();
    posture_transform_->GetMatrix(posture_);

    this->Modified();
    this->BuildRepresentation();
}
  1. 沿着轴旋转
void MovableAxesRepresentation::RotateAxis(const int &state, double *p1, double *p2)
{
    // 将鼠标位置移动到自身坐标系下,求两次鼠标位置向量在投影平面的夹角
    vtkNew<vtkTransform> posture_transform;
    posture_transform->SetMatrix(posture_);
    if ((p1[0] - p2[0]) < 1e-4
        && (p1[1] - p2[1]) < 1e-4
        && (p1[2] - p2[2]) < 1e-4) {
        return;
    }

    double pos_t1[4] { p1[0], p1[1], p1[2], 1 };
    double pos_t2[4] { p2[0], p2[1], p2[2], 1 };
    vtkNew<vtkMatrix4x4> posture_inv;
    vtkMatrix4x4::Invert(posture_, posture_inv);
    auto pos_t = posture_inv->MultiplyDoublePoint(pos_t1);
    double v1[3] = { pos_t[0], pos_t[1], pos_t[2] };
    pos_t = posture_inv->MultiplyDoublePoint(pos_t2);
    double v2[3] = { pos_t[0], pos_t[1], pos_t[2] };
    double normal[3];

    switch (state) {
    case RotateAxisX: {
        normal[0] = 1;
        normal[1] = 0;
        normal[2] = 0;
    } break;
    case RotateAxisY: {
        normal[0] = 0;
        normal[1] = 1;
        normal[2] = 0;
    } break;
    case RotateAxisZ: {
        normal[0] = 0;
        normal[1] = 0;
        normal[2] = 1;
    } break;
    default:
        return;
    }

    double projection1[3], projection2[3];
    GetPlaneProjection(normal, v1, projection1);
    GetPlaneProjection(normal, v2, projection2);

    vtkMath::Normalize(projection1);
    vtkMath::Normalize(projection2);
    double axis[3];
    vtkMath::Cross(projection1, projection2, axis);
    double radians = acos(vtkMath::Dot(projection1, projection2));
    double degrees = vtkMath::DegreesFromRadians(radians);

    posture_transform->RotateWXYZ(degrees, axis);

    posture_transform->Update();
    posture_transform->GetMatrix(posture_);

    this->Modified();
    this->BuildRepresentation();
}
  • 14
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Beyond欣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值