- 一、环境
VS2013 + QT5.7.0 - 二、效果
1.可以创建任意多条线段;
2.鼠标在靠近到线段时产生吸附效果;
3.可以拖动任意一条线段的任意部位(线段的两个端点或者整条线段)。
效果图:
- 三、说明
1.创建线段的定义:
线段具有起始点和终止点。
//点
struct PointEx {
double x;
double y;
PointEx(double a = 0, double b = 0) {
x = a;
y = b;
}
};
//线段
struct LineSegment {
PointEx startPoint;
PointEx endPoint;
LineSegment(PointEx a, PointEx b) {
startPoint = a;
endPoint = b;
}
LineSegment() {
}
};
2.定义线段显示的定义:
struct LINESEG {
bool bDraw;//是否绘制
bool bSelLine;//是否选中线
bool bSelStartPt;//是否选中线段起点
bool bSelEndPt;//是否选线段终点
LineSegment* seg;
LINESEG() {
bDraw = false;
bSelLine = false;
bSelStartPt = false;
bSelEndPt = false;
seg = new LineSegment;
}
};
3.把创建的线段使用,vector保存起来
std::vector<LINESEG*> lineSegs;//线段列表
4.剩余的就是在鼠标点击、移动、松开时的逻辑控制了
//按下鼠标
void MyGraphCal::mousePressEvent(QMouseEvent *event) {
switch(event->button()) {
case Qt::LeftButton:
bLBtnDown = true;
selectLineSeg = nullptr;
selectLineSeg = getSeleled();
if(selectLineSeg != nullptr){//选中线段
selectLineSeg->bDraw = false;
if(selectLineSeg->bSelStartPt) {//选中起点
startPoint.setX(selectLineSeg->seg->endPoint.x);
startPoint.setY(selectLineSeg->seg->endPoint.y);
endPoint.setX(selectLineSeg->seg->startPoint.x);
endPoint.setY(selectLineSeg->seg->startPoint.y);
} else if(selectLineSeg->bSelEndPt) {//选中终点
startPoint.setX(selectLineSeg->seg->startPoint.x);
startPoint.setY(selectLineSeg->seg->startPoint.y);
endPoint.setX(selectLineSeg->seg->endPoint.x);
endPoint.setY(selectLineSeg->seg->endPoint.y);
} else if(selectLineSeg->bSelLine){//选中线段
movePoint = event->pos();
startPoint.setX(selectLineSeg->seg->startPoint.x);
startPoint.setY(selectLineSeg->seg->startPoint.y);
endPoint.setX(selectLineSeg->seg->endPoint.x);
endPoint.setY(selectLineSeg->seg->endPoint.y);
}
update();
} else {//未选中
startPoint = event->pos();
endPoint = startPoint;
tempLine = new LINESEG;
tempLine->seg->startPoint.x = startPoint.x();
tempLine->seg->startPoint.y = startPoint.y();
}
break;
default:
break;
}
}
//移动鼠标
void MyGraphCal::mouseMoveEvent(QMouseEvent *event) {
QPointF movePt = event->pos();
if (selectLineSeg != nullptr){//选中线段
if(bLBtnDown) {//鼠标按下
if(selectLineSeg->bSelStartPt || selectLineSeg->bSelEndPt) {//选中起点或者终点
endPoint = movePt;
} else if(selectLineSeg->bSelLine) {//选中线段
double disX = movePt.x() - movePoint.x();
double disY = movePt.y() - movePoint.y();
startPoint.setX(startPoint.x() + disX);
startPoint.setY(startPoint.y() + disY);
endPoint.setX(endPoint.x() + disX);
endPoint.setY(endPoint.y() + disY);
movePoint = movePt;
}
}
} else {//未选中线段
if(bLBtnDown) {
endPoint = movePt;
} else {
selSeg(movePt);
}
}
update();
}
//松开鼠标
void MyGraphCal::mouseReleaseEvent(QMouseEvent *event) {
switch(event->button()) {
case Qt::LeftButton:
bLBtnDown = false;
if(selectLineSeg != nullptr){
if(selectLineSeg->bSelStartPt) {//选中起点
selectLineSeg->seg->startPoint.x = event->pos().x();
selectLineSeg->seg->startPoint.y = event->pos().y();
} else if(selectLineSeg->bSelEndPt) {//选中终点
selectLineSeg->seg->endPoint.x = event->pos().x();
selectLineSeg->seg->endPoint.y = event->pos().y();
} else if(selectLineSeg->bSelLine) {//选中线段
selectLineSeg->seg->startPoint.x = startPoint.x();
selectLineSeg->seg->startPoint.y = startPoint.y();
selectLineSeg->seg->endPoint.x = endPoint.x();
selectLineSeg->seg->endPoint.y = endPoint.y();
}
selectLineSeg->bDraw = true;
selectLineSeg->bSelStartPt = false;
selectLineSeg->bSelEndPt = false;
selectLineSeg->bSelLine = false;
selectLineSeg = nullptr;
} else {
tempLine->seg->endPoint.x = event->pos().x();
tempLine->seg->endPoint.y = event->pos().y();
tempLine->bDraw = true;
lineSegs.push_back(tempLine);
}
break;
default:
break;
}
}
5.注意要添加上线段鼠标移动的激活操作,否则鼠标只有在按下的时候才会激活mouseMoveEvent
ui.centralWidget->setMouseTracking(true);
setMouseTracking(true);
6.如何判断当前鼠标靠近某一条线段呢?
- 先确定出当前点到这条线段的所在直线的垂足;
PointEx perpendicular(PointEx p, LineSegment l) {
double r = relation(p, l);
PointEx tp;
tp.x = l.startPoint.x + r*(l.endPoint.x - l.startPoint.x);
tp.y = l.startPoint.y + r*(l.endPoint.y - l.startPoint.y);
return tp;
}
- 判断垂足是不是在这条线段上;
- 如果不在这条线段上,则判断垂足距离哪个端点比较近,选中选中的端点;
- 如果在这条线段上,则选中这条线段
void MyGraphCal::selSeg(QPointF&pt) {
int num = lineSegs.size();
for(int i = 0; i < num; i++) {
LINESEG* oneLine = lineSegs.at(i);
LineSegment* oneLineDeg = oneLine->seg;
PointEx ptEx(pt.x(), pt.y());
PointEx np;//线段上的点
double dis = pToLinesegDist(ptEx, *oneLineDeg, np);
if(dis < 5 && dis >= 0.0) {
double l = relation(np, *oneLineDeg);
if(abs(l)< EP) {//起点
oneLine->bSelStartPt = true;
oneLine->bSelLine = false;
oneLine->bSelEndPt = false;
} else if(abs(l - 1.0) < EP) {//终点
oneLine->bSelEndPt = true;
oneLine->bSelLine = false;
oneLine->bSelStartPt = false;
} else if(l < 1 && l > 0) {//整条线
oneLine->bSelLine = true;
oneLine->bSelEndPt = false;
oneLine->bSelStartPt = false;
}
} else {
oneLine->bSelLine = false;
oneLine->bSelEndPt = false;
oneLine->bSelStartPt = false;
}
}
}
- 四、向量
以上计算过程中用到了向量和向量的点积
向量的几何意义:一条有方向的线段
这就是上面定义的线段的来源,定义一点线段要定一它的起始点和终止点,从起始点到终止点的方向就是向量的方向。
点积的结合意义:向量a、b,r = a*b=|a|*|b|cosα。也就是:向量a的模乘以向量b在向量a上的投影的长度。
因为α是一个角度,所以可以通过结果r的正负获取两条线段之间的简单关系:
r>0:两个向量之间的夹角在0-90度之间
r=0:两个向量互相垂直
r<0:两个向量之间的夹角在90-180度之间。
以上!