【vtk】鼠标拖动移动Actor

这篇博客展示了如何使用VTK(Visualization Toolkit)创建一个交互式的3D场景,通过自定义的鼠标事件处理类MouseInteractorStyle2实现在3D空间中选择和移动对象。程序创建了一个圆柱和平面,并在点击圆柱时生成一个小球,用户可以拖动小球来移动圆柱。主要涉及的技术包括VTK的Actor、Mapper、PolyData、RenderWindowInteractor等组件以及自定义交互风格的实现。

https://blog.youkuaiyun.com/weixin_42834523/article/details/104844660

#include <vtkVersion.h>
#include <vtkSmartPointer.h>
#include <vtkActor.h>
#include <vtkSphereSource.h>
#include <vtkRendererCollection.h>
#include <vtkCellArray.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <vtkObjectFactory.h>
#include <vtkPlaneSource.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkPropPicker.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkRenderer.h>
#include <vtkTransform.h>

#include <vtkCylinderSource.h>

#include "vtkAutoInit.h"
VTK_MODULE_INIT(vtkRenderingOpenGL2);
VTK_MODULE_INIT(vtkInteractionStyle);
VTK_MODULE_INIT(vtkRenderingVolumeOpenGL2);
VTK_MODULE_INIT(vtkRenderingFreeType);

// Handle mouse events
bool move = true;
vtkActor* a = nullptr;

class MouseInteractorStyle2 : public vtkInteractorStyleTrackballCamera
{
   
   
public:
    static MouseInteractorStyle2* New();
    vtkTypeMacro(MouseInteractorStyle2, vtkInteractorStyleTrackballCamera);

    virtual void OnLeftButtonDown()
    {
   
   
        int* clickPos = this->GetInteractor()->GetEventPosition();

        // Pick from this location.
        vtkSmartPointer<vtkPropPicker>  picker =
                vtkSmartPointer<vtkPropPicker>::New();
        picker->Pick(clickPos[0], clickPos[1], 0, this->GetDefaultRenderer());

        double* pos = picker->GetPickPosition(
<think>我们参考用户提供的引用和需求,目标是实现通过鼠标上下拖动对曲面上选取附近区域进行凸起或凹进拉伸。这需要结合鼠标交互、选取和曲面变形。 主要思路: 1. 使用vtkInteractorStyleTrackballCamera或自定义交互样式来捕获鼠标事件。 2. 当鼠标按下时,通过射线拾取(vtkCellPicker)确定曲面上的。 3. 在鼠标拖动时,根据鼠标移动的垂直距离(dy)计算位移量,将该及其附近沿着法线方向移动。 4. 使用高斯函数或类似衰减函数来平滑影响周围,形成自然过渡。 步骤: (1)创建vtkPolyData对象表示曲面(例如引用[2]中的圆柱面)。 (2)设置交互器样式,重写鼠标按下和拖动事件。 (3)在鼠标按下事件中,使用拾取器获取坐标和id(或单元id)。 (4)在鼠标拖动事件中,计算鼠标垂直位移(dy),将其转换为位移量(例如乘以一个缩放因子)。 (5)根据拾取的位置,计算曲面在该的法线(可以使用vtkPolyDataNormals预先计算)。 (6)对位移量应用衰减函数(例如高斯函数),影响拾取周围一定半径内的。位移方向为法线方向或反向(根据上下拖动决定)。 (7)更新vtkPolyData的坐标,并重新渲染。 注意:为了保证性能,我们只更新受影响区域(即改变的),而不是整个网格。 引用[1]提到鼠标表面绘制曲线,这里我们借鉴其交互方式;引用[2]提供了构建曲面的代码;引用[3]展示了选择区域。 具体实现: 1. 构建曲面:可以使用引用[2]的方法构建一个圆柱面(或其他曲面)。 2. 创建拾取器(vtkCellPicker)并设置精度。 3. 自定义交互样式(继承自vtkInteractorStyleTrackballCamera): - 重写OnLeftButtonDown():记录按下的位置,并拾取,记录拾取的坐标、id、法线等。 - 重写OnMouseMove():当左键按下并拖动时,计算鼠标垂直位移,计算位移量,然后更新曲面。 4. 位移计算:假设鼠标移动距离dy(屏幕坐标),将其转换为实际位移。可以定义一个缩放因子,例如:displacement = dy * scaleFactor。 5. 影响范围:以拾取为中心,设置一个半径,在这个半径内的受到位移的影响,且距离拾取越远影响越小。使用高斯衰减函数: weight = exp(- (distance^2) / (2 * sigma^2)) 其中,sigma控制衰减程度。 6. 更新坐标:对于每个受影响的,新位置 = 原位置 + weight * displacement * 法线方向(或反向)。 7. 由于坐标改变,需要重新计算法线(如果需要光照效果)并更新网格。 代码结构: class MouseInteractorStyle : public vtkInteractorStyleTrackballCamera { public: static MouseInteractorStyle* New(); vtkTypeMacro(MouseInteractorStyle, vtkInteractorStyleTrackballCamera); virtual void OnLeftButtonDown() override; virtual void OnMouseMove() override; // 设置要操作的polydata void SetPolyData(vtkSmartPointer<vtkPolyData> polydata); // 设置法线数组(在polydata中应有预先计算的法线) void SetNormals(vtkSmartPointer<vtkFloatArray> normals); private: vtkSmartPointer<vtkPolyData> PolyData; vtkSmartPointer<vtkFloatArray> Normals; vtkSmartPointer<vtkCellPicker> Picker; // 拾取器 bool LeftButtonPressed; int StartY; // 记录鼠标按下的位置 vtkIdType PickedPointId; // 拾取的的id double PickedPoint[3]; // 拾取的坐标 }; 实现OnLeftButtonDown: LeftButtonPressed = true; this->Interactor->GetEventPosition(&x, &y); StartY = y; // 使用拾取器 Picker->Pick(x, y, 0, renderer); vtkIdType cellId = Picker->GetCellId(); if (cellId != -1) { // 获取拾取的id(我们这里假设拾取的是,但拾取器返回的是单元,所以需要转换) // 由于我们可能想移动一个及其周围,所以可以直接使用邻近 // 简单做法:取单元中最近的?或者使用拾取器(vtkPointPicker)? // 但是拾取器可能不容易拾取,所以这里我们用单元拾取器,然后获取单元上最近的 double closestPoint[3]; double dist; vtkCell *cell = PolyData->GetCell(cellId); cell->EvaluatePosition(Picker->GetPickPosition(), closestPoint, subId, pcoords, dist, weights); // 找到单元中距离拾取位置最近的 // 但是,我们这里简化为:取单元的一个(比如第一个)作为拾取?或者计算拾取位置投影到单元上的最近,然后找到网格上最近的? // 另一种方法:使用vtkPointPicker // 这里为了简单,改用vtkPointPicker } 实际上,我们可以使用vtkPointPicker来直接拾取。因此,在初始化时,我们使用拾取器: Picker = vtkSmartPointer<vtkPointPicker>::New(); 这样,在OnLeftButtonDown中: Picker->Pick(x, y, 0, renderer); PickedPointId = Picker->GetPointId(); if (PickedPointId != -1) { PolyData->GetPoint(PickedPointId, PickedPoint); // 同时,我们可以从法线数组中获取该的法线 } 在OnMouseMove中: if (LeftButtonPressed && PickedPointId != -1) { int currentY = this->Interactor->GetEventPosition()[1]; double dy = currentY - StartY; // 注意:这个dy是屏幕坐标的差值 StartY = currentY; // 更新StartY,下一次使用 // 转换dy为位移量:根据需求设定比例,比如0.1 double displacement = dy * 0.1; // 获取当前的位移方向(法线方向),注意:法线可能是归一化的 double normal[3]; Normals->GetTuple(PickedPointId, normal); // 位移方向:向上拖动(dy为负,则向上,即正位移;向下拖动(dy为正)则负位移。注意屏幕坐标系是上小下大) // 因此,位移量取符号:这里位移方向与法线方向相同则为凸起,相反为凹进。我们约定:向上拖动(dy为负)则凸起(加上法线方向位移),向下拖动(凹进,减去法线方向位移) // 所以:位移向量 = displacement * normal (注意:displacement的正负已经包含方向) // 但是,我们不仅移动拾取,还要移动周围 // 设置影响半径和衰减函数 double radius = 20.0; // 影响半径,单位与世界坐标一致 double sigma = radius / 3.0; // 高斯函数的sigma // 遍历所有,计算与拾取的距离 vtkPoints *points = PolyData->GetPoints(); for (vtkIdType id = 0; id < points->GetNumberOfPoints(); ++id) { double p[3]; points->GetPoint(id, p); double dist = sqrt(vtkMath::Distance2BetweenPoints(p, PickedPoint)); if (dist > radius) { continue; } // 计算权重 double weight = exp(- (dist*dist) / (2 * sigma*sigma)); // 当前的法线(注意:每个都有自己的法线) double n[3]; Normals->GetTuple(id, n); // 计算位移向量:displacement * weight * n double displacementVector[3]; for (int i = 0; i < 3; i++) { displacementVector[i] = displacement * weight * n[i]; } // 更新坐标 double newPoint[3]; for (int i = 0; i < 3; i++) { newPoint[i] = p[i] + displacementVector[i]; } points->SetPoint(id, newPoint); } points->Modified(); // 更新法线?因为坐标变了,所以需要重新计算法线 // 但是,这里为了简单,我们可以在交互结束后再重新计算法线(或者每次移动后重新计算)。如果性能有问题,可以优化为交互结束后再计算。 // 重新计算整个网格的法线 vtkNew<vtkPolyDataNormals> normalGenerator; normalGenerator->SetInputData(PolyData); normalGenerator->ComputePointNormalsOn(); normalGenerator->Update(); PolyData = normalGenerator->GetOutput(); Normals = vtkFloatArray::SafeDownCast(PolyData->GetPointData()->GetNormals()); // 更新mapper // ... 这里需要将更新后的PolyData设置到mapper // 刷新渲染 this->Interactor->Render(); } 注意:上述代码在每次鼠标移动时都会遍历所有,对于大数据量可能性能不好。我们可以优化为只存储受影响区域的(例如,在拾取时就确定一个邻域,然后在移动时只更新这个邻域内的)。 另外,重新计算法线也可能比较耗时,可以考虑在交互结束时(比如鼠标按钮释放时)再重新计算。 因此,我们可以优化: - 在OnLeftButtonDown中,除了记录拾取,还要记录该周围一定半径内的id及其原始位置(用于连续位移的累加?)。这样在拖动时,只需要更新这些。 - 在OnLeftButtonUp事件中,重新计算整个网格的法线(如果不考虑实时光照,可以不用实时更新法线,但这样渲染效果会不正确)。 另一种优化:使用vtkDeformPointFilter?或者vtkWarpVector?但这里我们直接操作。 考虑到实时性,我们可以预先计算每个的邻接关系(使用邻域),但这里我们使用空间划分(比如vtkKdTree)来快速查找半径内的。 改进后的步骤(OnLeftButtonDown): - 拾取。 - 使用vtkKdTree找到该周围一定半径内的,存储这些的id(这样在拖动时就不用每次都遍历所有)。 - 记录这些的原始位置(用于每次位移的叠加?但每次拖动我们都是基于上一次变形后的结果,所以不需要原始位置,只需要当前的位置) 但是,注意:每次拖动都是基于当前网格状态,所以不需要存储初始状态(除非我们想实现撤销功能)。 因此,在OnLeftButtonDown中,我们只需要找到邻域id集合。 代码调整: 在自定义交互样式类中: std::vector<vtkIdType> NeighborPointIds; // 存储邻域id OnLeftButtonDown中: 拾取PickedPointId 构建kd树,查找半径内的 vtkNew<vtkKdTreePointLocator> kdTree; kdTree->SetDataSet(PolyData); kdTree->BuildLocator(); vtkNew<vtkIdList> idList; kdTree->FindPointsWithinRadius(radius, PickedPoint, idList); // 将idList中的id存储到NeighborPointIds(注意:包括拾取自己) OnMouseMove中: 遍历NeighborPointIds中的id,进行位移计算(同样使用高斯权重,但这次距离是相对于拾取的距离) 这样,每次鼠标移动时,遍历的数大大减少。 另外,关于法线更新:在移动过程中,我们可以不更新法线(实时计算法线开销大),在鼠标释放时再更新。但是,光照效果在移动过程中会不正确。为了平衡,我们可以考虑在移动过程中只更新的位置(不更新法线),在鼠标释放时再重新计算法线。这样,移动过程中只有位置变化,法线不变,虽然光照不准确但可以接受,释放后更新法线并重新渲染。 因此,我们还需要重写OnLeftButtonUp事件: virtual void OnLeftButtonUp() override { // 重新计算法线 vtkNew<vtkPolyDataNormals> normalGenerator; normalGenerator->SetInputData(PolyData); normalGenerator->ComputePointNormalsOn(); normalGenerator->Update(); PolyData->DeepCopy(normalGenerator->GetOutput()); Normals = vtkFloatArray::SafeDownCast(PolyData->GetPointData()->GetNormals()); // 更新mapper // ... this->Interactor->Render(); // 调用父类方法 vtkInteractorStyleTrackballCamera::OnLeftButtonUp(); } 注意:在移动过程中,我们不更新法线,所以移动时看起来光照可能不会实时变化,但位置变了,形状变了,光照效果会有偏差,但为了性能,可以接受。 完整代码示例(伪代码)由于篇幅,这里只给出关键部分的伪代码。 另外,初始化时,需要为曲面计算法线(一开始就要有法线): vtkNew<vtkPolyDataNormals> normalGenerator; normalGenerator->SetInputData(originalPolyData); normalGenerator->ComputePointNormalsOn(); normalGenerator->Update(); PolyData = normalGenerator->GetOutput(); Normals = vtkFloatArray::SafeDownCast(PolyData->GetPointData()->GetNormals()); 最后,将自定义的交互样式设置给交互器。 总结:该方法通过鼠标拖动实现曲面的局部凸起或凹陷,主要利用了拾取、邻域查找、高斯衰减位移和法线方向移动。 注意:上述实现中,每次鼠标移动都会更新坐标并重新渲染,对于大规模网格可能会有性能问题,可以考虑优化(如使用VTK的增量显示或减少刷新频率)。 相关问题: 1. 如何优化大规模网格的交互变形性能? 2. 除了高斯衰减,还有哪些变形衰减函数? 3. 如何实现更精确的曲面变形(如使用拉普拉斯平滑或微分坐标)? 4. 如何撤销变形操作? 根据用户需求,我们给出了C++和VTK的实现思路。 </think>### C++ VTK实现鼠标拖动曲面变形的方法 在VTK中实现通过鼠标上下拖动对曲面上选取进行凸起或凹进拉伸,需要结合选取、位移计算、网格变形和交互事件处理。以下是完整的实现方法: --- ### 一、核心实现步骤 #### 1. **基础场景搭建** ```cpp #include <vtkSmartPointer.h> #include <vtkPolyData.h> #include <vtkPoints.h> #include <vtkPolyDataMapper.h> #include <vtkActor.h> #include <vtkRenderer.h> #include <vtkRenderWindow.h> #include <vtkRenderWindowInteractor.h> #include <vtkInteractorStyleTrackballCamera.h> #include <vtkCellPicker.h> #include <vtkPolyDataNormals.h> #include <vtkKdTreePointLocator.h> #include <vtkTransform.h> #include <vtkMath.h> // 创建曲面数据(示例:圆柱面) vtkSmartPointer<vtkPolyData> CreateCylinderSurface() { auto points = vtkSmartPointer<vtkPoints>::New(); const double r = 76.0; // 半径 const int heightSteps = 80; const int thetaSteps = 61; for (int z = 0; z < heightSteps; z++) { for (int i = 0; i < thetaSteps; i++) { double theta = vtkMath::Pi() / 2 * i / (thetaSteps - 1); double x = r * cos(theta); double y = r * sin(theta); points->InsertNextPoint(x, y, z); } } // ...(构建网格拓扑,此处省略) return polyData; } ``` #### 2. **自定义交互样式类** ```cpp class MouseInteractorStyle : public vtkInteractorStyleTrackballCamera { public: static MouseInteractorStyle* New(); vtkTypeMacro(MouseInteractorStyle, vtkInteractorStyleTrackballCamera); // 设置可变形曲面 void SetDeformableMesh(vtkPolyData* mesh) { PolyData = mesh; Locator = vtkSmartPointer<vtkKdTreePointLocator>::New(); Locator->SetDataSet(PolyData); Locator->BuildLocator(); // 预计算法线 auto normalGenerator = vtkSmartPointer<vtkPolyDataNormals>::New(); normalGenerator->SetInputData(PolyData); normalGenerator->ComputePointNormalsOn(); normalGenerator->Update(); Normals = vtkFloatArray::SafeDownCast( normalGenerator->GetOutput()->GetPointData()->GetNormals() ); } // 鼠标按下事件 virtual void OnLeftButtonDown() override { int* clickPos = this->Interactor->GetEventPosition(); vtkNew<vtkCellPicker> picker; picker->SetTolerance(0.001); picker->Pick(clickPos[0], clickPos[1], 0, this->GetDefaultRenderer()); if (picker->GetCellId() != -1) { // 获取最近ID double pickPos[3]; picker->GetPickPosition(pickPos); PickedPointId = Locator->FindClosestPoint(pickPos); // 存储初始状态 PolyData->GetPoint(PickedPointId, LastPoint); StartY = clickPos[1]; IsDeforming = true; } vtkInteractorStyleTrackballCamera::OnLeftButtonDown(); } // 鼠标移动事件 virtual void OnMouseMove() override { if (IsDeforming && PickedPointId != -1) { int currentY = this->Interactor->GetEventPosition()[1]; double dy = (currentY - StartY) * 0.1; // 位移缩放因子 // 获取法线方向 double normal[3]; Normals->GetTuple(PickedPointId, normal); // 变形核心算法 DeformMesh(PickedPointId, dy, normal); StartY = currentY; this->Interactor->Render(); } vtkInteractorStyleTrackballCamera::OnMouseMove(); } // 鼠标释放事件 virtual void OnLeftButtonUp() override { IsDeforming = false; vtkInteractorStyleTrackballCamera::OnLeftButtonUp(); } private: // 网格变形算法 void DeformMesh(vtkIdType centerId, double displacement, double* direction) { vtkPoints* points = PolyData->GetPoints(); const double radius = 20.0; // 影响半径 const double sigma = radius / 3.0; // 高斯衰减因子 // 获取中心坐标 double centerPoint[3]; points->GetPoint(centerId, centerPoint); // 查找邻域 vtkNew<vtkIdList> pointIds; Locator->FindPointsWithinRadius(radius, centerPoint, pointIds); // 更新位置 for (vtkIdType i = 0; i < pointIds->GetNumberOfIds(); i++) { vtkIdType pid = pointIds->GetId(i); double p[3]; points->GetPoint(pid, p); // 计算距离衰减权重 double dist = vtkMath::Distance2BetweenPoints(p, centerPoint); double weight = exp(-dist / (2 * sigma * sigma)); // 沿法线方向位移 double newPos[3]; for (int j = 0; j < 3; j++) { newPos[j] = p[j] + displacement * weight * direction[j]; } points->SetPoint(pid, newPos); } points->Modified(); // 更新法线(可选) // vtkNew<vtkPolyDataNormals> normalGenerator; // normalGenerator->SetInputData(PolyData); // normalGenerator->Update(); // Normals->DeepCopy(normalGenerator->GetOutput()->GetPointData()->GetNormals()); } vtkIdType PickedPointId = -1; bool IsDeforming = false; int StartY = 0; double LastPoint[3]; vtkSmartPointer<vtkPolyData> PolyData; vtkSmartPointer<vtkKdTreePointLocator> Locator; vtkSmartPointer<vtkFloatArray> Normals; }; vtkStandardNewMacro(MouseInteractorStyle); ``` #### 3. **主程序集成** ```cpp int main() { // 创建曲面 auto surface = CreateCylinderSurface(); // 创建渲染管线 auto mapper = vtkSmartPointer<vtkPolyDataMapper>::New(); mapper->SetInputData(surface); auto actor = vtkSmartPointer<vtkActor>::New(); actor->SetMapper(mapper); auto renderer = vtkSmartPointer<vtkRenderer>::New(); renderer->AddActor(actor); renderer->SetBackground(0.2, 0.3, 0.4); auto renderWindow = vtkSmartPointer<vtkRenderWindow>::New(); renderWindow->AddRenderer(renderer); auto interactor = vtkSmartPointer<vtkRenderWindowInteractor>::New(); interactor->SetRenderWindow(renderWindow); // 设置自定义交互样式 auto style = vtkSmartPointer<MouseInteractorStyle>::New(); style->SetDeformableMesh(surface); style->SetDefaultRenderer(renderer); interactor->SetInteractorStyle(style); // 启动交互 renderWindow->Render(); interactor->Start(); return 0; } ``` --- ### 二、关键技术说明 1. **选取机制** - 使用`vtkCellPicker`进行精确的曲面拾取 - 通过`vtkKdTreePointLocator`查找最近(空间加速查询) 2. **变形算法** - **位移计算**:鼠标移动距离映射为法线方向位移 - **高斯衰减**:$w = e^{-\frac{d^2}{2\sigma^2}}$ 控制变形强度随距离衰减 - **法线方向**:使用预计算的顶法线确定变形方向 3. **性能优化** - 邻域空间索引(`vtkKdTreePointLocator`) - 局部更新仅影响半径内的顶 - 法线更新可选(变形期间可暂不更新) 4. **交互流程** ```mermaid sequenceDiagram participant Mouse participant Interactor participant Picker participant Deformer Mouse->>Interactor: 左键按下 Interactor->>Picker: 执行拾取 Picker-->>Interactor: 返回网格位置 Interactor->>Deformer: 记录初始状态 Mouse->>Interactor: 拖动鼠标 Interactor->>Deformer: 计算位移量 Deformer->>Renderer: 更新网格并渲染 Mouse->>Interactor: 左键释放 Interactor->>Deformer: 结束变形 ``` --- ### 三、相关优化建议 1. **变形效果优化** - 使用**均值曲率流**保持曲面光滑度 - 添加位移约束防止自交迭 - 结合`vtkWarpVector`实现高效位移映射 2. **性能提升** ```cpp // 使用多线程加速邻域计算 vtkNew<vtkSMPTools> smp; smp->For(0, pointIds->GetNumberOfIds(), [&](vtkIdType begin, vtkIdType end) { for (vtkIdType i = begin; i < end; i++) { // 并行处理位移 } }); ``` 3. **高级交互扩展** - 添加**撤销栈**(`vtkTransform`存储历史状态) - 支持**硬度笔刷**(可调整衰减半径和强度) - 结合**物理引擎**实现弹性变形 --- ### 四、相关参考资源 1. VTK拾取机制与交互原理[^1] 2. 网格变形中的微分坐标应用[^2] 3. 实时曲面编辑的最佳实践[^3] ---
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值