功能
实现的功能是判断鼠标在哪个图形上
鼠标位置转换到世界位置
如果将整个渲染过程类比为照相,鼠标位置就是在照片中的位置,本质就是世界矩阵变换的逆变换。
最简单的实现肯定是通过逆矩阵计算,但是由于矩阵求逆的时间复杂度过高,这里采用一个取巧的方法。
因为当前的相机横纵坐标与fucos横纵坐标相同,相机"看"过去的线是垂直于xy平面的。因此,相机所能"看"到的世界的范围可以简单的通过相机z与fov计算出来,如果"看"过去的线不垂直于xy平面则需要更多三角函数处理,因为x,z与θ组成的三角形不是直角三角形。如图所示,进行简单三角函数变换,范围再乘2就可以得到。y轴范围同理。又由于view并不是一个正方形,宽高有一定比例,因此x还需要乘窗口的宽高比例,即
。
有了能"看"到的相机范围,只需要得出鼠标在视图中的位置比例,乘过去就可以得到鼠标在世界中的位置,即。
代码如下,放在camera类中:
// 计算范围
void ZDSJ::Camera::viewPosSize(float _window_rate)
{
float tan_fov_2 = DirectX::XMVectorGetX(DirectX::XMVectorTan(DirectX::XMVectorSet(DirectX::XMConvertToRadians(this->m_fov / 2), 0, 0, 0)));
this->m_view_pos->x = tan_fov_2 * (-this->m_pos.z) * 2 * _window_rate;
this->m_view_pos->y = tan_fov_2 * (-this->m_pos.z) * 2;
}
// 鼠标位置转为世界位置
ZDSJ::Point ZDSJ::Camera::viewPosToWordPos(ZDSJ::Point _pos)
{
ZDSJ::Point point;
ZDSJ::Context* context = ZDSJ::Context::getInstance();
point.x = this->viewPosSize().x * (_pos.x / context->windowWidth()) - this->viewPosSize().x / 2 + this->m_pos.x;
point.y = this->viewPosSize().y * (_pos.y / context->windowHeight()) * -1 + this->viewPosSize().y / 2 + this->m_pos.y;
return point;
}
判断一个点是否在图形内
上一步我们获得了鼠标所指向的点在世界中的位置,接下来如何判断该点是否在图形内。
这里采用三角形测试(野路子,官方名称不知道名字来自于这篇文章,Triangle Tests),将图形进行三角形分割(这一步其实比较好做,因为目前绘制图形的时候就是用三角形拼出来的),随后计算点是否在分割出的三角形内。
判断点是否在三角形内,首先求出三角形的法向量,这里不需要Normalize因为该方法只关心符号,与法向量大小无关。
随后分别将待判断点与三角形顶点连接,构成三个新的三角形,计算出三个新三角形的法向量,如果三个新三角形的法向量与待判断三角形的法向量同向,则说明该点在三角形内,注意这里要考虑顶点顺序。
这里就不自己画图了,把b站视频中的图截出来:
如果点乘结果为0,则说明点在边上。再次重申,这里一定要注意顶点的顺序问题。
代码如下:
bool ZDSJ::DrawAbleAdapter::pointInPolgon2D(float _x, float _y)
{
bool result = false;
size_t triangle_size = this->m_indices.size() / 3;
// 世界矩阵
DirectX::XMMATRIX size = DirectX::XMMatrixScaling(this->m_data->size.x, this->m_data->size.y, this->m_data->size.z);
DirectX::XMMATRIX rotation = DirectX::XMMatrixRotationRollPitchYaw(this->m_data->rotation.x, this->m_data->rotation.y, this->m_data->rotation.z);
DirectX::XMMATRIX pos = DirectX::XMMatrixTranslation(this->m_data->pos.x, this->m_data->pos.y, this->m_data->pos.z);
DirectX::XMMATRIX word = size * rotation * pos;
// 根据定义时的顶点索引分割三角形。
for (int i = 0; i < this->m_indices.size(); i += 3) {
result = this->pointInTriangle2D(_x, _y, i, word);
if (result) {
break;
}
}
return result;
}
// 判断点是否在三角形内,注意顶点顺序
bool ZDSJ::DrawAbleAdapter::pointInTriangle2D(float _x, float _y, short _triangle_index, DirectX::XMMATRIX& _word_matrix)
{
DirectX::XMVECTOR x_0 = DirectX::XMVectorSet(this->m_vertices.at(this->m_indices[_triangle_index]).pos.x, this->m_vertices.at(this->m_indices[_triangle_index]).pos.y, 1, 1);
DirectX::XMVECTOR x_1 = DirectX::XMVectorSet(this->m_vertices.at(this->m_indices[_triangle_index + 1]).pos.x, this->m_vertices.at(this->m_indices[_triangle_index + 1]).pos.y, 1, 1);
DirectX::XMVECTOR x_2 = DirectX::XMVectorSet(this->m_vertices.at(this->m_indices[_triangle_index + 2]).pos.x, this->m_vertices.at(this->m_indices[_triangle_index + 2]).pos.y, 1, 1);
// 这里乘世界矩阵是因为顶点定义时取值是[-1,1],要把顶点变换到世界中
x_0 = DirectX::XMVector4Transform(x_0, _word_matrix);
x_1 = DirectX::XMVector4Transform(x_1, _word_matrix);
x_2 = DirectX::XMVector4Transform(x_2, _word_matrix);
DirectX::XMVECTOR x_10 = DirectX::XMVectorSubtract(x_1, x_0);
DirectX::XMVECTOR x_20 = DirectX::XMVectorSubtract(x_2, x_0);
DirectX::XMVECTOR normal = DirectX::XMVector3Cross(x_10, x_20);
// normal = DirectX::XMVector2Normalize(normal); 这里不需要归一化,因为我们只关注符号
DirectX::XMVECTOR position = DirectX::XMVectorSet(_x, _y, 1, 1);
// (x0-p)x(x2-p)*n;
DirectX::XMVECTOR r_0 = DirectX::XMVector3Cross(DirectX::XMVectorSubtract(position, x_0), x_20);
DirectX::XMVECTOR r_1 = DirectX::XMVector3Cross(x_10, DirectX::XMVectorSubtract(position, x_0));
DirectX::XMVECTOR r_2 = DirectX::XMVector3Cross(DirectX::XMVectorSubtract(position, x_1), DirectX::XMVectorSubtract(position, x_2));
float d_0 = DirectX::XMVectorGetX(DirectX::XMVector3Dot(r_0, normal));
float d_1 = DirectX::XMVectorGetX(DirectX::XMVector3Dot(r_1, normal));
float d_2 = DirectX::XMVectorGetX(DirectX::XMVector3Dot(r_2, normal));
bool isInside = (d_0 >= 0.0f && d_1 >= 0.0f && d_2 >= 0.0f);
return isInside;
}
结果
这里直接返回了该形状对象的指针,后续可以实现诸如右键删除,悬浮高亮等等。
源码放在github,develop分支。