从零开始物理引擎(四)-判断点是否在图形内部

从零开始物理引擎(三)-前期准备完成

功能

实现的功能是判断鼠标在哪个图形上

鼠标位置转换到世界位置

如果将整个渲染过程类比为照相,鼠标位置就是在照片中的位置,本质就是世界矩阵变换的逆变换。

最简单的实现肯定是通过逆矩阵计算,但是由于矩阵求逆的时间复杂度过高,这里采用一个取巧的方法。

因为当前的相机横纵坐标与fucos横纵坐标相同,相机"看"过去的线是垂直于xy平面的。因此,相机所能"看"到的世界的范围可以简单的通过相机z与fov计算出来,如果"看"过去的线不垂直于xy平面则需要更多三角函数处理,因为x,z与θ组成的三角形不是直角三角形。如图所示,进行简单三角函数变换x=tan(\frac{fov}{2})*z,范围再乘2就可以得到。y轴范围同理。又由于view并不是一个正方形,宽高有一定比例,因此x还需要乘窗口的宽高比例,即camera_x=tan(\frac{fov}{2})*z*\frac{view_{width}}{view_{height}}

有了能"看"到的相机范围,只需要得出鼠标在视图中的位置比例,乘过去就可以得到鼠标在世界中的位置,即camera_{mouseX}=\frac{mouseX}{view_{width}}*camera_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分支。 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值