图形学和计算机辅助设计(CAD)课程大作业-线对象选择与Snap

该代码实现了一个基于QPainter的计算机图形学项目,用户可以进行线和圆弧的选择(pick)以及吸附(snap)操作。当鼠标靠近曲线时,曲线会高亮显示,鼠标位置会吸附到最近的曲线点上。单击线或圆弧可以选中它们,再次单击空白区域则取消选择。整个操作逻辑参照了AutoCAD的snap功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

下面是计算机图形学和计算机辅助设计大作业记录,实现的是线和圆弧的pick及snap功能,绘制使用的是QPainter,参考的AutoCAD的snap逻辑,原理是计算projectPoint, 即鼠标位置和curve的距离,当小于snapbox的尺寸时,修改snapbox的位置,操作时感觉靠近曲线磁吸过去了。

QXViewWgt .h

#pragma once

#include <QWidget>
#include "ui_QXViewWgt.h"

class QXViewWgt : public QWidget
{
	Q_OBJECT

public:
	QXViewWgt(QWidget *parent = Q_NULLPTR);
	~QXViewWgt();

	void paintEvent(QPaintEvent* event);
	void mouseMoveEvent(QMouseEvent* event);
	void mousePressEvent(QMouseEvent* event);

private:
	Ui::QXViewWgt ui;
};

QXViewWgt .cpp

#include "QXViewWgt.h"
#include <QtGui/QPainter>
#include <QMouseEvent>
#include <Eigen/Eigen>
#include <vector>
#include <memory>

#define _USE_MATH_DEFINES
#include <math.h>

const double MIN_DIST = 1e-4;

namespace vege
{ 
static QPainter* s_painter = nullptr;

enum CurveState
{
	CST_DEFAULT,
	CST_SNAPPED,
	CST_PICKED
};

class Curve2d
{
protected:
	CurveState _state;
public:

	
	Curve2d():_state(CST_DEFAULT)
	{}
	virtual ~Curve2d() {}
	virtual bool projectPoint(Eigen::Vector2d p, Eigen::Vector2d& q) = 0;
	virtual void paint() = 0;
	virtual void setState(CurveState s) 
	{ 
		if (_state == CST_PICKED && _state != s)
		{
			int k = 0;
		}
		_state = s; 
	};

	virtual bool stateIs(CurveState s)
	{
		return _state == s;
	};
};

class Line2d : public Curve2d
{
public:
	Eigen::Vector2d _s;
	Eigen::Vector2d _e;

	Line2d(Eigen::Vector2d s, Eigen::Vector2d e):_s(s), _e(e)
	{}

	virtual bool projectPoint(Eigen::Vector2d p, Eigen::Vector2d& q)
	{
		Eigen::Vector2d d = _e - _s;
		double v_se_length = d.norm();
		d.normalize();
		auto v_sp = p - _s;
		auto v_sq_length = (p - _s).dot(d);
		if (fabs(v_sq_length) < MIN_DIST)
		{
			q = _s;
			return true;
		}
		if (fabs(v_sq_length -v_se_length) < MIN_DIST)
		{
			q = _e;
			return true;
		}

		if (v_sq_length > 0.0 && v_sq_length < v_se_length)
		{
			q = _s + d * v_sq_length;
			return true;
		}

		q = { 0, 0 };
		return false;
	}

	virtual void paint()
	{
		switch (_state)
		{
		case CST_DEFAULT:
		{
			QPen pen0;
			pen0.setColor(QColor(255, 255, 255));
			pen0.setWidth(1);
			s_painter->setPen(pen0);
			s_painter->drawLine(_s(0), _s(1), _e(0), _e(1));
		}
		break;
		case CST_SNAPPED:
		{
			QPen pen0;
			pen0.setColor(QColor(255, 255, 255));
			pen0.setWidth(4);

			s_painter->setPen(pen0);
			s_painter->drawLine(_s(0), _s(1), _e(0), _e(1));

			pen0.setColor(QColor(0, 0, 0));
			pen0.setWidth(2);
			pen0.setStyle(Qt::DotLine);
			s_painter->setPen(pen0);
			s_painter->drawLine(_s(0), _s(1), _e(0), _e(1));
		}
		break;
		case CST_PICKED:
		{
			QPen pen0;
			pen0.setColor(QColor(255, 0, 255));
			pen0.setWidth(2);

			s_painter->setPen(pen0);
			s_painter->drawLine(_s(0), _s(1), _e(0), _e(1));
		}
		break;
		default:
			break;
		}
	}

};

class Arc2d : public Curve2d
{
public:
	Eigen::Vector2d _c;
	double _r;
	double _sang;
	double _eang;

	Arc2d(Eigen::Vector2d c, double r, double sang, double eang) :_c(c),
		_r(r),
		_sang(sang),
		_eang(eang)
	{}

	double degToRad(double degv)
	{
		return (degv*M_PI) / 180.0;
	}

	double radToDeg(double radv)
	{
		return (radv*180.0) / M_PI;
	}

	Eigen::Vector2d startPoint()
	{
		return _c + Eigen::Vector2d(cos(degToRad(_sang)), sin(degToRad(_sang))) * _r;
	}

	Eigen::Vector2d endPoint()
	{
		return _c + Eigen::Vector2d(cos(degToRad(_eang)), sin(degToRad(_eang))) * _r;
	}

	double angle()
	{
		if (_eang > _sang)
		{
			return _eang - _sang;
		}
		else
		{
			return 360.0 - _sang + _eang;
		}
	}

	virtual bool projectPoint(Eigen::Vector2d p, Eigen::Vector2d& q)
	{
		Eigen::Vector2d d = p - _c;

		d.normalize();

		Eigen::Vector2d s = startPoint();
		if ((s - p).norm() < 1e-4)
		{
			q = s;
			return true;
		}

		Eigen::Vector2d e = endPoint();
		if ((e - p).norm() < 1e-4)
		{
			q = e;
			return true;
		}

		bool on_flag = false;
		double t = radToDeg(acos(d[0]));
		if (d[1] < 0.0)
		{
			t = 360.0 - t;
			if (t > 360.0)
			{
				t -= 360.0;
			}
		}

		if (_eang > _sang)
		{
			if (t > _sang  && t < _eang)
			{
				on_flag = true;
			}
		}
		else
		{
			if ((t > _sang && t < 360.0) ||
				(t >= 0.0 && t < _eang)
				)
			{
				on_flag = true;
			}
		}

		if (on_flag)
		{
			q = _c + d * _r;
			return true;
		}

		q = { 0, 0 };
		return false;
		
	}

	virtual void paint()
	{
		switch (_state)
		{
		case CST_DEFAULT:
		{
			QPen pen0;
			pen0.setColor(QColor(255, 255, 255));
			pen0.setWidth(1);
			s_painter->setPen(pen0);
			s_painter->drawArc(_c[0] - _r, _c[1] - _r, 2.0*_r, 2.0*_r, -_sang*16, -angle()*16);
		}
		break;
		case CST_SNAPPED:
		{
			QPen pen0;
			pen0.setColor(QColor(255, 255, 255));
			pen0.setWidth(4);

			s_painter->setPen(pen0);
			s_painter->drawArc(_c[0] - _r, _c[1] - _r, 2.0*_r, 2.0*_r, -_sang * 16, -angle() * 16);

			pen0.setColor(QColor(0, 0, 0));
			pen0.setWidth(2);
			pen0.setStyle(Qt::DotLine);
			s_painter->setPen(pen0);
			s_painter->drawArc(_c[0] - _r, _c[1] - _r, 2.0*_r, 2.0*_r, -_sang * 16, -angle() * 16);
		}
		break;
		case CST_PICKED:
		{
			QPen pen0;
			pen0.setColor(QColor(255, 0, 255));
			pen0.setWidth(2);

			s_painter->setPen(pen0);
			s_painter->drawArc(_c[0] - _r, _c[1] - _r, 2.0*_r, 2.0*_r, -_sang * 16, -angle() * 16);
		}
		break;
		default:
			break;
		}
	}
};

}// end of namespace vege

Eigen::Vector2d _mosPos(0.0,0.0);
double _mosBoxHalfSize = 12;
double _snapSize = 1.414*_mosBoxHalfSize;
std::vector<vege::Curve2d*> _curves;
vege::Curve2d* _snappedCurve = nullptr;
vege::Curve2d* _pickedCurve = nullptr;

using namespace vege;

QXViewWgt::QXViewWgt(QWidget *parent)
	: QWidget(parent)
{
	ui.setupUi(this);

	_curves.push_back(new Line2d({ 20,100 }, { 1200,100 }));
	_curves.push_back(new Line2d({ 30,180 }, { 955,800 }));

	_curves.push_back(new Arc2d({ 450,400 }, 200, 180, 45 ));
}

QXViewWgt::~QXViewWgt()
{
	for (auto& curv : _curves)
	{
		delete curv;
	}
}

void QXViewWgt::mousePressEvent(QMouseEvent* event)
{
	if (_snappedCurve)
	{
		if (_pickedCurve == nullptr && _pickedCurve != _snappedCurve)
		{
			_pickedCurve = _snappedCurve;
			_pickedCurve->setState(CST_PICKED);
		}

	}
	else
	{
		if (_pickedCurve)
		{
			_pickedCurve->setState(CST_DEFAULT);
			_pickedCurve = nullptr;
		}

	}

	update();
}

void QXViewWgt::mouseMoveEvent(QMouseEvent* event)
{
	_mosPos(0) = event->pos().x();
	_mosPos(1) = event->pos().y();

	Curve2d* curSnapped = nullptr;
	for (auto& curve : _curves)
	{

		QPen pen0;
		Eigen::Vector2d pt_q;
		bool issucess = curve->projectPoint(_mosPos, pt_q);
		if (issucess)
		{
			double dist = (pt_q - _mosPos).norm();
			if (dist < _snapSize)
			{
				_mosPos = pt_q;
				curSnapped = curve;
				break;
			}
		}
	}

	if (curSnapped)
	{
		if (_pickedCurve && _pickedCurve == curSnapped)
		{
			_snappedCurve = curSnapped;
		}
		else
		{
			if (_snappedCurve)
			{
				if (_snappedCurve->stateIs(CST_SNAPPED))
				{
					_snappedCurve->setState(CST_DEFAULT);
				}
			}


			_snappedCurve = curSnapped;

			if (_snappedCurve->stateIs(CST_DEFAULT))
			{
				_snappedCurve->setState(CST_SNAPPED);
			}
		}
	}
	else
	{
		if (_snappedCurve)
		{
			if (_snappedCurve->stateIs(CST_SNAPPED))
			{
				_snappedCurve->setState(CST_DEFAULT);
			}

			_snappedCurve = nullptr;
		}

	}

	this->update();
}

void QXViewWgt::paintEvent(QPaintEvent* event)
{
	QPainter painter(this);
	vege::s_painter = &painter;

	painter.fillRect(0, 0, this->width(), this->height(), Qt::black);

	// draw curves
	for (auto& curve : _curves)
	{
		curve->paint();
	}

	// draw snap box
	QPen pen;
	pen.setColor(QColor(255, 255, 255));
	pen.setWidth(2);
	painter.setPen(pen);
	painter.drawRect(_mosPos(0) - _mosBoxHalfSize, _mosPos(1) - _mosBoxHalfSize, _mosBoxHalfSize * 2, _mosBoxHalfSize * 2);

	painter.end();
}

截图
1.绘制一些线和圆弧
在这里插入图片描述
2.当鼠标靠近线,线高亮,同时鼠标吸到线上,鼠标在snapbox范围轻微移动,将保持吸靠到线不变
在这里插入图片描述
3.此时单击线将被选中
在这里插入图片描述
4.单击空白处,将取消选中的线,整个操作逻辑符合AutoCAD的pick-snap风格

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值