ObjectARX如何判断点和多段线的关系

news2024/12/23 5:22:29

目录

  • 1 基本思路
  • 2 相关知识点
    • 2.1 ECS坐标系概述
    • 2.2 其他点坐标转换接口
    • 2.3 如何获取多段线的顶点ECS坐标
  • 3 实现例程
    • 3.1 接口实现
    • 3.2 测试代码
  • 4 实现效果

 在CAD的二次开发中,点和多段线的关系是一个非常重要且常见的问题,本文实现例程以张帆所著《ObjectARX 开发基础与实例教程》为基础,并完善和修复了部分问题。

1 基本思路

 点和多段线的关系判断算法有两个思路:叉乘判断法(适用于凸多边形)和射线法,本文以射线法进行代码实现。其基本思路为:

  1. 如果点在多段线上,返回该结果。
  2. 从给定点出发,沿某个方向做一条射线,计算射线与多边形交点的数量。如果交点数量为奇数,那么点在图形内部;如果交点数量为偶数,那么点在图形外部。
  3. 在第2条的基础上,排除交点在多段线的顶点上的情况;若出现该情况,需要旋转射线重新判断。

2 相关知识点

2.1 ECS坐标系概述

 在该例程中,有一个关键点是理解ECS(或者OCS)坐标系统。
 ECS是实体(对象)坐标系,其原点是WCS的原点,X和Y轴所在平面的法向量是实体的法向量。ECS的 X 轴和Y轴的方向由任意轴算法确定,也就是X、Y轴的方向是由法向量与 WCS 的关系来确定的。因此,ECS的X 轴和Y轴是唯一且仅由法向量决定的。另外,ECS的Z坐标是指xy平面距离WCS原点的距离(有正负)。
 在ObjectARX有一个暴露的接口可以完成WCS→ECS的转换

bool acdbWcs2Ecs(ads_point p,ads_point q,ads_point norm,bool vec);

 上述接口的实现应该是下列办法

//将WCS转为一个平面实体的ECS坐标
int Wcs2Ecs(const AcGeVector3d vtNorm, const AcGePoint3d ptWcs, BOOL bDisp, AcGePoint3d& ptEcs)
{
	struct resbuf fromrb, torb;
	fromrb.restype = RTSHORT;
	fromrb.resval.rint = 0; // WCS  

	torb.restype = RT3DPOINT;
	ads_point_set(asDblArray(vtNorm), torb.resval.rpoint);

	return acedTrans(asDblArray(ptWcs), &fromrb, &torb, bDisp, asDblArray(ptEcs));
}

 理解了上述知识点,我们之后在判断射线和多段线关系的时候,就可以把射线转为多段线的ECS坐标,然后在多段线所在的ECS平面上,比较射线和多段线的每一条线段的相交关系。从而可以优化算法。

2.2 其他点坐标转换接口

  1. 点或向量坐标变换
函数名作用
acdbUcs2EcsUCS→ECS
acdbEcs2UcsECS→UCS
acdbUcs2WcsUCS→WCS
acdbWcs2UcsWCS→UCS
acdbEcs2WcsECS→WCS
acdbWcs2EcsWCS→ECS
  1. AcGePoint3d和ads_point互转
函数名作用
AcGePoint3d (AcGePoint2d ) → ads_pointasDblArray
ads_point → AcGePoint3d (AcGePoint2d )aspnt3d 或 asPnt2d

2.3 如何获取多段线的顶点ECS坐标

 多段线获取顶点坐标有两种重载形式:

Acad::ErrorStatus getPointAt(
    unsigned int, 
    AcGePoint3d& pt
) const;
Acad::ErrorStatus getPointAt(
    unsigned int index, 
    AcGePoint2d& pt
) const;

 其中第一种获取的是顶点的WCS坐标,第二种获取的是顶点的ECS 2D坐标。这个区别一定要看仔细。

3 实现例程

3.1 接口实现

 该例程在张帆所著方法的基础上,增加了对UCS坐标系的支持。在此,再次向原书作者表达敬意。

#include "dbxutil.h"
#define POINT_POLY_INSIDE 1
#define POINT_POLY_OUTSIDE 0
#define POINT_POLY_ONEDGE 2

// 在数组中查找某个点,返回点在数组中的索引,未找到则返回-1
int FindPoint(const AcGePoint2dArray &points, const AcGePoint2d &point, double tol /*= 1.0E-7*/)
{
	TADSGePoint3d pt1;
	TADSGePoint3d pt2(point);
	for (int i = 0; i < points.length(); i++)
	{
		pt1 = points[i];
		if (IsEqual(pt1, pt2, tol))
		{
			return i;
		}
	}

	return -1;
}

//-----------------------------------------------------------------------------+
//=Description:     几何类射线和多段线的交点
//=Parameter:	   	pPoly[in]	多段线
//=Parameter:	   	geRay[in]	射线(位于多段线的OCS平面上)
//=Parameter:	   	arptIntersect[in] 返回的交点(坐标系为多段线的OCS坐标系)
//=Parameter:	   	tol[in]		容差
//-----------------------------------------------------------------------------+
static void IntersectWithGeRay(const AcDbPolyline *pPoly, const AcGeRay2d &geRay, AcGePoint2dArray& arptIntersect, double tol = 1.0E-7)
{
	arptIntersect.removeAll();

	//设置容差,该容差为两点相同时的容差
	AcGeTol geTol;
	geTol.setEqualPoint(tol);

	// 多段线的每一段分别与射线计算交点
	AcGePoint2d pt2d;
	for (int i = 0; i < pPoly->numVerts(); i++)
	{
		if (i < pPoly->numVerts() - 1 || pPoly->isClosed() == Adesk::kTrue)
		{
			double dBulge = 0;
			pPoly->getBulgeAt(i, dBulge);
			if (fabs(dBulge) < 1.0E-7)
			{
				// 构建几何类的线段来计算交点
				AcGeLineSeg2d geLine;
				Acad::ErrorStatus es = pPoly->getLineSegAt(i, geLine);
				AcGePoint2d ptIntersect;
				if (geLine.intersectWith(geRay, ptIntersect, geTol) == Adesk::kTrue)
				{
					if (FindPoint(arptIntersect, ptIntersect, tol) < 0)
						arptIntersect.append(ptIntersect);
				}
			}
			else
			{
				// 构建几何类的圆弧来计算交点
				AcGeCircArc2d geArc;
				pPoly->getArcSegAt(i, geArc);
				AcGePoint2d pt1, pt2;
				int iCount = 0;
				if (Adesk::kTrue == geArc.intersectWith(geRay, iCount, pt1, pt2, geTol))
				{
					if (FindPoint(arptIntersect, pt1, tol) < 0)
						arptIntersect.append(pt1);
					if (iCount > 1 && FindPoint(arptIntersect, pt2, tol) < 0)
						arptIntersect.append(pt2);
				}
			}
		}
	}
}

// 点是否是多段线的顶点
static bool PointIsPolyVert(AcDbPolyline *pPoly, const AcGePoint2d &pt, double tol)
{
	AcGeTol geTol;
	geTol.setEqualPoint(tol);
	AcGePoint2d ptVert;
	for (int i = 0; i < (int)pPoly->numVerts(); i++)
	{
		pPoly->getPointAt(i, ptVert);
		if (ptVert.isEqualTo(pt, geTol))
		{
			return true;
		}
	}

	return false;
}

// 从数组中过滤掉重复点
static void FilterEqualPoints(AcGePoint2dArray &points, double tol = 1.0E-7)
{
	AcGeTol geTol;
	geTol.setEqualPoint(tol);
	for (int i = points.length() - 1; i > 0; i--)
	{
		for (int j = 0; j < i; j++)
		{
			if (points[i].isEqualTo(points[j],geTol))
			{
				points.removeAt(i);
				break;
			}
		}
	}
}

// 从数组中过滤掉某个点
static void FilterEqualPoints(AcGePoint2dArray &points, const AcGePoint2d &pt, double tol = 1.0E-7)
{
	AcGeTol geTol;
	geTol.setEqualPoint(tol);
	for (int i = points.length() - 1; i >= 0; i--)
		if (points[i].isEqualTo(pt, geTol))
			points.removeAt(i);
}

//-----------------------------------------------------------------------------+
//=Description:     判断点是否在多段线内部
//=Return:          0多段线外,1多段线内,2多段线上
//=Parameter:	   	pPoly[in] 
//=Parameter:	   	ptPickWcs[in] 
//=Parameter:	   	tol[in] 
//-----------------------------------------------------------------------------+
int IsPointInPoly(AcDbPolyline *pPoly, const AcGePoint3d &ptPickWcs, double tol = 1.0E-7)
{
	if(!pPoly || !pPoly->isClosed())
		return POINT_POLY_OUTSIDE;

	AcGeTol geTol;
	geTol.setEqualPoint(tol);

	//转换坐标,将点转为ECS坐标系
	AcGePoint3d ptPick;
	acdbWcs2Ecs(asDblArray(ptPickWcs), asDblArray(ptPick), asDblArray(pPoly->normal()), false);

	//判断点和多段线平面是否共面
	double dElevation = pPoly->elevation();
	if (fabs(dElevation - ptPick.z) > tol)
		return POINT_POLY_OUTSIDE;

	//如果点到多段线的最近点和给定的点重合,表示点在多段线上
	AcGePoint3d ptClosestWcs;
	pPoly->getClosestPointTo(ptPickWcs, ptClosestWcs);
	if (ptPickWcs.isEqualTo(ptClosestWcs, geTol))
		return POINT_POLY_ONEDGE;

	//转换最近点为ECS坐标系下
	AcGePoint3d ptClosest;
	acdbWcs2Ecs(asDblArray(ptClosestWcs), asDblArray(ptClosest), asDblArray(pPoly->normal()), false);

	// 第一个射线的方向是从最近点到当前点,起点是当前点
	// 射线的起点是pt,方向为从最近点到pt,如果反向做判断,则最近点距离pt太近的时候,
	// 最近点也会被作为一个交点(这个交点不太容易被排除掉)
	AcGeVector2d vtRay((ptPick - ptClosest).x, (ptPick - ptClosest).y);
	AcGeRay2d geRay(AcGePoint2d(ptPick.x, ptPick.y), vtRay);

	// 判断点和多段线的位置关系
	while (true)
	{
		bool bContinue = false;
		AcGePoint2dArray arptIntersect;
		IntersectWithGeRay(pPoly, geRay, arptIntersect, 1.0E-4);
		FilterEqualPoints(arptIntersect, 1.0E-4);// IntersectWith函数经常会得到很近的交点,这些点必须进行过滤

		if (arptIntersect.length() == 0)
			return POINT_POLY_OUTSIDE;// 没有交点,表示点在多段线的外部
		else
		{
			//特殊情况1:过滤掉由于射线被反向延长带来的影响,当pt距离最近点比较近的时候,
			//最近点竟然被当作一个交点,所以,首先删除最近点(如果有的话)
			FilterEqualPoints(arptIntersect, AcGePoint2d(ptClosest.x, ptClosest.y));

			//特殊情况2:如果某个交点与最近点在给定点的同一方向,要去掉这个点
			//,这个点明显不是交点,还是由于intersectwith函数的Bug
			for (int i = arptIntersect.length() - 1; i >= 0; i--)
			{
				if ((arptIntersect[i].x - ptPick.x) * (ptClosest.x - ptPick.x) >= 0 &&
					(arptIntersect[i].y - ptPick.y) * (ptClosest.y - ptPick.y) >= 0)
					arptIntersect.removeAt(i);
			}

			for (i = 0; i < arptIntersect.length(); i++)
			{
				if (PointIsPolyVert(pPoly, arptIntersect[i], 1.0E-4))	// 只要有交点是多段线的顶点就重新进行判断
				{
					// 处理给定点很靠近多段线顶点的情况(如果与顶点距离很近,就认为这个点在多段线上,因为这种情况没有什么好的判断方法)
					if (PointIsPolyVert(pPoly, AcGePoint2d(ptPick.x, ptPick.y), 1.0E-4))
						return POINT_POLY_ONEDGE;

					// 将射线旋转一个极小的角度(2度)再次判断(假定这样不会再通过上次判断到的顶点)
					vtRay = vtRay.rotateBy(0.035);
					geRay.set(AcGePoint2d(ptPick.x, ptPick.y), vtRay);
					bContinue = true;
					break;
				}
			}

			if (!bContinue)
			{
				if (0 == arptIntersect.length() % 2)
					return POINT_POLY_OUTSIDE;
				else
					return POINT_POLY_INSIDE;
			}
		}
	}
}

3.2 测试代码

void CmdPtInPoly()
{
	struct resbuf* rb = NULL;
	rb = acutBuildList(RTDXF0, _T("LWPOLYLINE"), RTNONE);
	TCHAR* prompts[2] = { _T("\n请选择了一个实体:"),_T("\n取消了一个实体") };
	ads_name ssPick;
	if (RTNORM == acedSSGet(_T(":S:$-M"), prompts, NULL, rb, ssPick))
	{
		ads_name ent;
		if (RTNORM == acedSSName(ssPick, 0, ent))
		{
			AcDbObjectId id;
			acdbGetObjectId(id, ent);
			AcDbPolyline* pPoly;
			if (Acad::eOk == acdbOpenObject(pPoly, id, AcDb::kForRead))
			{
				ads_point ptRet;
				while (RTNORM == acedGetPoint(NULL, _T("\n请任意点选一点:"), ptRet))
				{
					//判断点是否在多段线以内
					int iRelation = IsPointInPoly(pPoly, Ucs2Wcs(ptRet));
					if (POINT_POLY_INSIDE == iRelation)
						acutPrintf(_T("\n\t点在多段线内"));
					else if (POINT_POLY_ONEDGE == iRelation)
						acutPrintf(_T("\n\t点在多段线上"));
					else if (POINT_POLY_OUTSIDE == iRelation)
						acutPrintf(_T("\n\t点在多段线外"));
				}
				pPoly->close();
			}
		}

		acedSSFree(ssPick);
	}

	acutRelRb(rb);
}

4 实现效果

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/591980.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue事件大小写驼峰命名导致无法执行问题解决

文章目录 问题解决方案问题大致原因 问题 驼峰命名事件名不会正常执行 <!DOCTYPE html> <html lang""> <head><title>Vue Emit Example</title><script src"../js/vue.js"></script> </head> <body…

港科夜闻|香港科技大学与浪潮集团签署战略合作协议,共同推动技术研发和成果转化...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科技大学与浪潮集团签署战略合作协议&#xff0c;共同推动技术研发和成果转化。根据协议&#xff0c;双方将聚焦云计算、大数据、新一代通信等领域&#xff0c;围绕联合研发、人才培养、研发中心建设和超高清显示等方…

基于车站约束的地铁系统协调客流控制模型与算法

1 文章信息 《Model and algorithm of coordinated flow controlling with station-based constraints in a metro system》是2021年发表在Transportation Research Part E上的一篇文章。 2 摘要 随着城市人口的增长和交通需求的快速增长&#xff0c;世界上许多大城市的地铁系统…

Python3数据分析与挖掘建模(7)使用matplotlib和seaborn画图

1. 可视化分析 1.1 概述 可视化分析是数据分析中重要的一环&#xff0c;它可以帮助我们更直观地理解数据的特征、趋势和关系。在Python中&#xff0c;有多个库可以用于数据可视化&#xff0c;包括matplotlib、seaborn和plotly等。 1.2 常用的可视化方法和对应的库&#xff1…

4.3. 缓冲流

缓冲流是Java I/O中的一个重要概念&#xff0c;它可以提高文件读写的性能。在本节中&#xff0c;我们将详细讨论缓冲流的概念、使用方法以及实例。 缓冲流有两种类型&#xff1a;缓冲字节流和缓冲字符流。缓冲字节流包括BufferedInputStream和BufferedOutputStream&#xff0c…

vue中this.$set的用法

this.$set( target, key, value ) target&#xff1a;要更改的数据源(可以是对象或者数组) key&#xff1a;要更改的具体数据 value &#xff1a;重新赋的值 当我们给对象加了一个属性&#xff0c;在控制台能打印出来&#xff0c;但是却没有更新到视图上时&#xff0c;这个时…

【靶场】双重内网渗透测试场景

文章目录 前言一、开始渗透二、横向移动提交flag总结 前言 使用vulfocus搭建一个内网场景靶场拓扑如下&#xff1a; 入口有两个&#xff0c;一个是think PHP2.x命令执行和5x的命令执行漏洞&#xff0c;后续需要搭建二层隧道进行渗透测试。 一、开始渗透 目标&#xff1a; …

Vue为什么组件销毁后定时器会继续

原因 在 Vue 中&#xff0c;组件销毁后定时器可能会继续运行&#xff0c;这是因为这个框架使用了虚拟 DOM 技术。虚拟 DOM 可以提高渲染效率和性能&#xff0c;但也带来了一些问题。 当我们在 Vue 组件中创建定时器时&#xff0c;实际上是在组件的生命周期方法&#xff08;例如…

OJ练习第124题——叶值的最小代价生成树

叶值的最小代价生成树 力扣链接&#xff1a;1130. 叶值的最小代价生成树 题目描述 给你一个正整数数组 arr&#xff0c;考虑所有满足以下条件的二叉树&#xff1a; 每个节点都有 0 个或是 2 个子节点。 数组 arr 中的值与树的中序遍历中每个叶节点的值一一对应。 每个非叶节…

Arcgis for javascript 应用开发相关网站推荐(SDK,github社区等)

一、什么是arcgis for javascript ArcGIS for JavaScript是一种用于构建基于Web的GIS应用程序的开发框架。它允许开发人员使用Esri的地图和地理空间数据来构建具有交互性和可视化效果的应用程序。ArcGIS for JavaScript提供了丰富的API和组件&#xff0c;使开发人员可以将地理…

BLCY-6-5-90、、BLCY-6-25-90比例螺纹插装式溢流阀控制器

BLCY-6-5-90、BLCY-6-8-90、BLCY-6-16-90、BLCY-6-25-90比例螺纹插装式溢流阀是螺纹插装式的先导式溢流阀&#xff0c;可以作中小流量液压系统的压力控制阀&#xff0c;配置比例放大器输出电流&#xff0c;根据输入到线圈电流的大小比例控制系统压力。

【Rust 日报】2023-05-28 一个构建在TCP上的聊天工具

tcp-chat&#xff1a;构建在TCP上的简单快速轻量的聊天工具 tcp-chat通过TCP进行通信&#xff0c;该项目的目的是了解并行性和底层网络通信。 前端工具&#xff1a;Solid、Tauri、Vite 后端工具&#xff1a;Rust、Tokio、Serde GitHub: https://github.com/gatomod/tcp-chat ez…

信号链基础

信号链&#xff08;SIGNAL CHAIN&#xff09;&#xff1a;一个系统中信号从输入到输出的路径。 从信号的采集&#xff0c;放大&#xff0c;传输&#xff0c;处理一直到对相应功率器件产生执行的一整套信号流程叫信号链。具体来说&#xff0c;信号链是对从信号采集&#xff08;传…

WebGPU:下一代 Web 图形和计算 API

WebGPU 是一种新兴的 Web 标准&#xff0c;旨在为现代图形和计算应用提供高性能、低功耗的 API。本文将介绍 WebGPU 的背景、特点、用途以及和 WebGL 的对比。 一、背景 随着 Web 技术的不断发展&#xff0c;越来越多的高性能图形和计算应用开始出现在浏览器中。WebGL 是迄今为…

陈丹琦团队提出低内存高效零阶优化器MeZO,单卡A100可训练300亿参数模型

深度学习自然语言处理 原创作者&#xff1a;辰宜 今天下午突然发现了一篇陈丹琦大佬的巨作~ 大家一起来简单瞅瞅。 本文旨在介绍一种用于fine-tuning语言模型&#xff08;LM&#xff09;的低内存优化器——MeZO&#xff0c;内存减少多达12倍。使用单个A100 800G GPU&#xff0c…

中文完整版FL Studio21永久免费升级

集合最新FL基础操作、编曲技巧、混音技巧、乐理基础、声乐演奏等各类内容&#xff0c;比如更高端版本才有的必备原厂插件Pitcher和Sakura&#xff0c;还有智能编曲插件ORB&#xff0c;编曲软件FL Studio21版本更新现已发布&#xff0c;在这次更新中优化了很多功能&#xff0c;但…

基于 Amazon API Gateway 的跨账号跨网络的私有 API 集成

一、背景介绍 本文主要讨论的问题是在使用 Amazon API Gateway&#xff0c;通过 Private Integration、Private API 来完成私有网络环境下的跨账号或跨网络的 API 集成。API 管理平台会被设计在单独的账号中(亚马逊云科技提供的是多租户的环境)&#xff0c;因为客观上不同业务…

Arm推出新一代高性能CPU内核Cortex-X4以及GPU Immortalis-720 GPU

每年差不多这个时候&#xff0c;智能手机芯片背后的大脑 Arm 都会推出高通、联发科等公司用于下一代SoC的构建模块。在 2023 年 Arm 技术日期间&#xff0c;Arm 推出了一系列涵盖高性能和低功耗用例的新 CPU 内核&#xff0c;以及其第五代 GPU&#xff0c;并提供光线追踪图形支…

chatgpt赋能python:Python中构造方法的介绍与应用

Python中构造方法的介绍与应用 在Python编程语言中&#xff0c;构造方法通常是类中的一个特殊方法&#xff0c;用于在对象创建时初始化其属性。构造方法使用__init__关键字来定义&#xff0c;而且通常会包含self参数&#xff0c;用于引用创建的新对象。在本文中&#xff0c;我…

本地服务器搭建PHP简单Imagewheel云图床

文章目录 1.前言2. Imagewheel网站搭建2.1. Imagewheel下载和安装2.2. Imagewheel网页测试2.3.cpolar的安装和注册 3.本地网页发布3.1.Cpolar临时数据隧道3.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;3.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 4.公网访问测…