Files
envi-code/SourceCode/Code2026/CoreCommand/CollisionDetection.cpp
T

1638 lines
80 KiB
C++
Raw Normal View History

2026-09-28 15:28:50 +08:00
#include "StdAfx.h"
#include "CollisionDetection.h"
#include "aecdbentitybase.h"
#include "aecdbcurvebase.h"
#include "aecdbwall.h"
#include "aecdbbeam.h"
#include "aecdbcolumn.h"
#include "aecdbslab.h"
#include "aecdbopening.h"
#include "aecdbewequipment.h"
#include "aecdbxequipment.h"
#include "AecUIContextMenu.h"
#include "coretools.h"
#include "entityfilter.h"
#include "typeconvert.h"
#undef min
#undef max
#define CD_FLAG _T("CD_FLAG")
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace CDA
{
AABB::AABB()
{
// 使用极端的正负值,确保任何有效的图形 extents 都能覆盖它
m_maxPt.x = m_maxPt.y = m_maxPt.z = -1e20;
m_minPt.x = m_minPt.y = m_minPt.z = 1e20;
}
AABB::AABB(const AcDbExtents &ext)
{
set(ext);
}
// 辅助方法:合并另一个 AABB
void AABB::grow(const AABB &other)
{
m_minPt.x = std::min(m_minPt.x, other.m_minPt.x);
m_minPt.y = std::min(m_minPt.y, other.m_minPt.y);
m_minPt.z = std::min(m_minPt.z, other.m_minPt.z);
m_maxPt.x = std::max(m_maxPt.x, other.m_maxPt.x);
m_maxPt.y = std::max(m_maxPt.y, other.m_maxPt.y);
m_maxPt.z = std::max(m_maxPt.z, other.m_maxPt.z);
}
void AABB::set(const AcDbExtents &ext)
{
m_maxPt = ext.maxPoint();
m_minPt = ext.minPoint();
}
// 辅助方法:计算包围盒的中心点 (用于拆分节点)
AcGePoint3d AABB::centroid() const
{
return AcGePoint3d((m_minPt.x + m_maxPt.x) * 0.5,
(m_minPt.y + m_maxPt.y) * 0.5,
(m_minPt.z + m_maxPt.z) * 0.5);
}
// 辅助方法:获取最长轴 (0=X, 1=Y, 2=Z)
int AABB::getLongestAxis() const
{
double dx = m_maxPt.x - m_minPt.x;
double dy = m_maxPt.y - m_minPt.y;
double dz = m_maxPt.z - m_minPt.z;
if (dx >= dy && dx >= dz) return 0;
if (dy >= dx && dy >= dz) return 1;
return 2;
}
bool AABB::intersects(const AABB &other) const
{
if (m_maxPt.x < other.m_minPt.x || m_minPt.x > other.m_maxPt.x) return false;
if (m_maxPt.y < other.m_minPt.y || m_minPt.y > other.m_maxPt.y) return false;
if (m_maxPt.z < other.m_minPt.z || m_minPt.z > other.m_maxPt.z) return false;
return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 参数改用 const 引用传递
bool EntityProxy::set(const AcDbObjectId &id)
{
AcDbEntityPointer ent(id, AcDb::kForRead);
if (ent.openStatus() != Acad::eOk) return false;
AcDbExtents ext;
if (ent->getGeomExtents(ext) != Acad::eOk) return false;
m_entityId = id;
m_box.set(ext);
m_centroid = m_box.centroid();
return true;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 外部世界只需要调用这一个接口
void BVHTree::Build(const AcDbObjectIdArray &ar)
{
if (ar.isEmpty()) return;
EntityProxy proxy;
for (int i(0); i < ar.length(); ++i)
{
if (proxy.set(ar[i]))
m_allEntities.push_back(proxy);
}
// 预分配内存,这在 C++ 中极其重要!
// 数学上,N 个叶子节点的满二叉树,总节点数最多是 2N - 1
// 提前 reserve 可以彻底避免递归中 push_back 导致的动态扩容和内存搬运性能损耗
m_bvhNodes.clear();
m_bvhNodes.reserve(m_allEntities.size() * 2);
// 压入根节点 (索引为 0)
m_bvhNodes.push_back(BVHNode());
// 启动递归建树
BuildNode(0, 0, (int)m_allEntities.size());
}
void BVHTree::GetIntersections(const AABB &queryBox, std::vector<AcDbObjectId> &hitIds) const
{
// 如果树还没建好,直接返回
if (m_bvhNodes.empty())
{
return;
}
// 手搓一个极其轻量的栈。64 层深度足以容纳上百万实体的树!
const int MAX_STACK_SIZE = 64;
int stack[MAX_STACK_SIZE];
int stackPtr = 0;
// 把根节点(索引为 0)压入栈
stack[stackPtr++] = 0;
while (stackPtr > 0)
{
// 弹出栈顶节点
int nodeIdx = stack[--stackPtr];
const BVHNode &node = m_bvhNodes[nodeIdx];
// 1. 核心粗筛:如果连这个大管辖区的包围盒都不碰,直接跳过这整整半片厂房!
if (!node.box.intersects(queryBox))
{
continue;
}
// 2. 如果碰到了,判断是不是叶子节点
if (node.isLeaf())
{
// 已经是叶子节点了:遍历里面那几个具体的实体,进行精确 AABB 检测
for (int i = 0; i < node.primitiveCount; ++i)
{
const EntityProxy &proxy = m_allEntities[node.leftFirst + i];
if (proxy.m_box.intersects(queryBox))
{
hitIds.push_back(proxy.m_entityId);
}
}
}
else
{
// 绝对的防线:因为我们每次要压入 2 个元素,所以必须保证至少还有 2 个空位
if (stackPtr <= MAX_STACK_SIZE - 2)
{
stack[stackPtr++] = node.leftFirst; // 左子节点
stack[stackPtr++] = node.leftFirst + 1; // 右子节点
}
else
{
// 触发了这一步,说明图纸数据极度病态,击穿了 256 层深度。
// 在工业软件中,通常会在这里输出一行 Debug 日志,
// 丢弃这部分极深层级的遍历以保全主程序不崩溃。
//acutPrintf(_T("\n警告: BVH 树深度超过 %d,放弃深层碰撞检测。"), MAX_STACK_SIZE);
continue;
}
}
}
}
void BVHTree::GetIntersections(AcDbObjectId &id, std::vector<AcDbObjectId> &hitIds) const
{
AcDbEntityPointer ent(id, AcDb::kForRead);
if (ent.openStatus() != Acad::eOk) return;
AcDbExtents ext;
if (ent->getGeomExtents(ext) != Acad::eOk) return;
AABB ab(ext);
GetIntersections(ab, hitIds);
}
void BVHTree::GetAllIntersections(std::vector<std::pair<AcDbObjectId, AcDbObjectId>> &hitPairs) const
{
if (m_bvhNodes.empty())
{
return;
}
// 从根节点开始,让整棵树自己和自己比较
CollideNodes(0, 0, hitPairs);
}
// 内部递归建树函数,直接操作类成员变量
void BVHTree::BuildNode(int nodeIdx, int entityOffset, int entityCount)
{
// 1. 计算这批实体的总包围盒
AABB totalBox;
for (int i = 0; i < entityCount; ++i)
{
totalBox.grow(m_allEntities[entityOffset + i].m_box);
}
// 注意:不要在这里使用引用 (BVHNode& node = m_bvhNodes[nodeIdx]) 然后贯穿整个函数。
// 因为后面有 push_back 操作,理论上如果 capacity 不够会导致底层数组重新分配,
// 引用就会瞬间变成悬空引用(野指针)。这是极其经典的 C++ 崩溃暗坑!
m_bvhNodes[nodeIdx].box = totalBox;
// 2. 终止条件:如果实体数量足够少,直接变成叶子节点
if (entityCount <= 4)
{
m_bvhNodes[nodeIdx].leftFirst = entityOffset;
m_bvhNodes[nodeIdx].primitiveCount = entityCount;
return;
}
// 3. 找出当前 Box 的最长轴以及空间的中心点
int longestAxis = totalBox.getLongestAxis();
double splitPos = 0.0;
if (longestAxis == 0)
{
splitPos = totalBox.centroid().x;
}
else if (longestAxis == 1)
{
splitPos = totalBox.centroid().y;
}
else
{
splitPos = totalBox.centroid().z;
}
// 4. 使用 std::partition 原地划分数组
std::vector<EntityProxy>::iterator beginIt = m_allEntities.begin() + entityOffset;
std::vector<EntityProxy>::iterator endIt = beginIt + entityCount;
std::vector<EntityProxy>::iterator midIt = std::partition(
beginIt,
endIt,
PartitionPredicate(longestAxis, splitPos)
);
int leftCount = (int)std::distance(beginIt, midIt);
// 5. 极端边界处理:如果所有实体的质心都完全重合,强行平分
if (leftCount == 0 || leftCount == entityCount)
{
leftCount = entityCount / 2;
}
// 6. 分配左右子节点的索引
int leftChildIdx = (int)m_bvhNodes.size();
m_bvhNodes.push_back(BVHNode()); // 压入左节点
m_bvhNodes.push_back(BVHNode()); // 压入右节点
// 安全地更新当前节点信息
m_bvhNodes[nodeIdx].leftFirst = leftChildIdx;
m_bvhNodes[nodeIdx].primitiveCount = 0;
// 7. 递归构建左子树和右子树
BuildNode(leftChildIdx, entityOffset, leftCount);
BuildNode(leftChildIdx + 1, entityOffset + leftCount, entityCount - leftCount);
}
// 核心的双树递归遍历函数
void BVHTree::CollideNodes(int nodeIdxA, int nodeIdxB, std::vector<std::pair<AcDbObjectId, AcDbObjectId>> &hitPairs) const
{
const BVHNode &nA = m_bvhNodes[nodeIdxA];
const BVHNode &nB = m_bvhNodes[nodeIdxB];
// 极速粗筛:如果这两个节点的包围盒根本不相交,直接砍掉整条递归分支!
if (!nA.box.intersects(nB.box))
{
return;
}
// 情况 1:正在检查同一个节点 (nodeIdxA == nodeIdxB)
if (nodeIdxA == nodeIdxB)
{
if (nA.isLeaf())
{
// 同一个叶子节点内部的实体两两比较
// 剪刀 1:j 从 i + 1 开始,完美避开 A-B 和 B-A,且不比较自己
for (int i = 0; i < nA.primitiveCount; ++i)
{
for (int j = i + 1; j < nA.primitiveCount; ++j)
{
const EntityProxy &p1 = m_allEntities[nA.leftFirst + i];
const EntityProxy &p2 = m_allEntities[nA.leftFirst + j];
if (p1.m_box.intersects(p2.m_box))
{
hitPairs.push_back(std::make_pair(p1.m_entityId, p2.m_entityId));
}
}
}
}
else
{
// 同一个内部节点,拆分成三个子任务:
// 1. 左子树内部自己和自己查
CollideNodes(nA.leftFirst, nA.leftFirst, hitPairs);
// 2. 右子树内部自己和自己查
CollideNodes(nA.leftFirst + 1, nA.leftFirst + 1, hitPairs);
// 3. 左子树 vs 右子树
// 剪刀 2:只调一次!绝不调用 CollideNodes(右, 左),彻底打破跨分支的对称性!
CollideNodes(nA.leftFirst, nA.leftFirst + 1, hitPairs);
}
}
// 情况 2:正在检查两个完全不同的节点 (nodeIdxA != nodeIdxB)
else
{
if (nA.isLeaf() && nB.isLeaf())
{
// 两个都是叶子节点,进行二分图式的完全交叉比较
for (int i = 0; i < nA.primitiveCount; ++i)
{
for (int j = 0; j < nB.primitiveCount; ++j)
{
const EntityProxy &p1 = m_allEntities[nA.leftFirst + i];
const EntityProxy &p2 = m_allEntities[nB.leftFirst + j];
if (p1.m_box.intersects(p2.m_box))
{
hitPairs.push_back(std::make_pair(p1.m_entityId, p2.m_entityId));
}
}
}
}
else if (nA.isLeaf())
{
// A 是叶子,B 是内部节点,所以我们把 B 拆开,继续用 A 去和 B 的子节点比
CollideNodes(nodeIdxA, nB.leftFirst, hitPairs);
CollideNodes(nodeIdxA, nB.leftFirst + 1, hitPairs);
}
else if (nB.isLeaf())
{
// B 是叶子,A 是内部节点,拆分 A
CollideNodes(nA.leftFirst, nodeIdxB, hitPairs);
CollideNodes(nA.leftFirst + 1, nodeIdxB, hitPairs);
}
else
{
// A 和 B 都是内部节点,为了最快缩小包围盒范围,理论上应该拆分包围盒表面积更大的那个。
// 这里为了代码简洁,我们统一固定拆分 A 节点。
CollideNodes(nA.leftFirst, nodeIdxB, hitPairs);
CollideNodes(nA.leftFirst + 1, nodeIdxB, hitPairs);
}
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
extern void DeepFaceToSolid(AcDbBlockReference *pRef, AcDbVoidPtrArray &arGetSolidPtrs);
namespace
{
bool collisionDetection(AcDb3dSolid *pSolidA, AcDb3dSolid *pSolidB, AcGePoint3d *pPtIns = NULL)
{
if (!pSolidA || !pSolidB) return false;
// 1. 过滤自身的比较
if (pSolidA == pSolidB) return false;
AcDb3dSolid *pInterferenceSolid = NULL;
Adesk::Boolean bIntersects = Adesk::kFalse;
// 4. 调用底层的干涉检查 API
// 这里的关键是将第二个参数设为 Adesk::kFalse,禁止它生成相交实体!
Acad::ErrorStatus es = pSolidA->checkInterference( pSolidB
, Adesk::kFalse
, bIntersects
, pInterferenceSolid );
// 5. 防御性编程:清理内存
// 即使我们传了 kFalse,如果由于某些不可预知的内核原因返回了一个实体指针,
// 因为它是内存常驻(非数据库常驻)实体,如果不 delete 会造成严重的内存泄漏。
if (pInterferenceSolid != NULL)
{
delete pInterferenceSolid;
pInterferenceSolid = NULL;
}
// 6. 返回真实的碰撞结果
if (es == Acad::eOk && bIntersects == Adesk::kTrue)
{
return true;
}
return false;
}
AcDb3dSolid *getOpeningSolid(AcDbEntity *pEnt)
{
AecDbXOpening *pOpen = AecDbXOpening::cast(pEnt);
if (!pOpen) return NULL;
OpeningData data;
pOpen->GetOpeningData(data);
pOpen->GetAt(KEY_BOMEL, data.ptPos.z);
// 得到完整的长宽高,和插入位置,构建出一个solid
AcDbObjectId idWall;
idWall.setFromOldId(data.idWall);
AcDbObjectPointer<AecDbXWall> wall(idWall, AcDb::kForRead);
if (wall.openStatus() != Acad::eOk) return NULL;
AcDbCurve *pCrv(wall->GetBaseCurve());
if (!pCrv) return NULL;
AcDb3dSolid *pSolid(NULL);
do
{
AcGePoint3d ptOnCrv;
if (pCrv->getClosestPointTo(data.ptPos, ptOnCrv) != Acad::eOk)
break;
// 墙体在插入点处的切线方向
AcGeVector3d vtDir;
if (pCrv->getFirstDeriv(ptOnCrv, vtDir) != Acad::eOk)
break;
vtDir.normalize();
AcGeVector3d vtUp(vtDir);
vtUp.rotateBy(PI * .5, AcGeVector3d::kZAxis);
vtUp.normalize();
/*
vtDir ->->->
pt0---------pt1
| ptPos |
pt3---------pt2
*/
AcGePoint3d pt0, pt1, pt2, pt3;
pt0 = data.ptPos - vtDir * data.dWidth * .5;
pt0 += vtUp * data.dThick * .5;
pt1 = pt0 + vtDir * data.dWidth;
pt2 = pt1 - vtUp * data.dThick;
pt3 = pt2 - vtDir * data.dWidth;
AcDbVoidPtrArray ar;
ar.append(new AcDbLine(pt0,pt1));
ar.append(new AcDbLine(pt1,pt2));
ar.append(new AcDbLine(pt2,pt3));
ar.append(new AcDbLine(pt3,pt0));
AcDbRegion *pReg = CoreTools::createRegion(ar);
deleteArrayVoidPtrs<AcDbLine>(ar);
if (pReg)
{
AcDbSweepOptions op;
pSolid = new AcDb3dSolid();
if (pSolid->createExtrudedSolid(pReg, AcGeVector3d::kZAxis * data.dHeight, op) != Acad::eOk)
{
DELETE_PTR(pSolid);
}
DELETE_PTR(pReg);
}
} while (false);
DELETE_PTR(pCrv);
return pSolid;
}
void get3dSolidCacheFromEntity(AcDbEntity *pEnt, AcDbVoidPtrArray &ar_all, AcDbVoidPtrArray &ar_must_del)
{
if (!pEnt) return;
const AcDbVoidPtrArray *pAr(NULL);
AcDbVoidPtrArray _ar;
AecDbEntityBase *pEntBase = AecDbEntityBase::cast(pEnt);
if (pEntBase)
{
pAr = &pEntBase->Get3DBuffer();
// AecDbXOpening 实体比较特殊,是由AcDbBlockRefrence组成的,block里面由shell构成需要特殊处理
AecDbXOpening *pOpen = AecDbXOpening::cast(pEnt);
if (pOpen)
{
AcDb3dSolid *pSolid = getOpeningSolid(pEnt);
if (pSolid)
{
_ar.append(pSolid);
ar_must_del.append(pSolid);
}
}
else
_ar = *pAr;
}
AecDbCurveBase *pCrvBase = AecDbCurveBase::cast(pEnt);
if (pCrvBase)
{
pAr = &pCrvBase->Get3DBuffer();
_ar = *pAr;
}
for (int i(0); i < _ar.length(); ++i)
{
AcDb3dSolid *pSolid = AcDb3dSolid::cast((AcDbEntity*)_ar[i]);
if (pSolid)
ar_all.append(pSolid);
}
return;
}
bool isAecMInterfaces(AcDbObjectId &id)
{
return UIMenuItem::isAecMInterfaces(id);
}
bool isOpening(AcDbObjectId &id)
{
return CDA::isSameOf<AecDbXOpening>(id);
}
bool isWall(AcDbObjectId &id)
{
return CDA::isSameOf<AecDbXWall>(id);
}
bool isEqu(AcDbObjectId &id)
{
if (CDA::isSameOf<AecDbEwEquipment>(id) || CDA::isSameOf<AecDbXEquipment>(id))
return true;
return false;
}
bool isPipeValve(AcDbObjectId &id)
{
if (id.objectClass() != AecDbEwEquipment::desc())
return false;
AcDbObjectPointer<AecDbEwEquipment> ent(id, AcDb::kForRead);
if (ent.openStatus() != Acad::eOk)
return false;
int subRuntime(0), valveType(0);
ent->GetAt(KEY_SUBRUNTIME, subRuntime);
if (subRuntime != eValve)
return false;
if (!ent->GetAt(PIPE_VALVE, valveType))
return false;
return true;
}
bool isEquButNoValve(AcDbObjectId &id)
{
return isEqu(id) && !isPipeValve(id);
}
bool isEnvelope(AcDbObjectId &id)
{
CEntityProperty::EEntityType et(CEntityProperty::getTypeOfEntity(id));
return et == CEntityProperty::eArchitecture
|| et == CEntityProperty::eStructure
|| et == CEntityProperty::eGuardBar
|| et == CEntityProperty::eHanger
|| et == CEntityProperty::eStair;
}
bool isHanger(AcDbObjectId &id)
{
CEntityProperty::EEntityType et(CEntityProperty::getTypeOfEntity(id));
return et == CEntityProperty::eHanger;
}
bool isHole(AcDbObjectId &id)
{
return CEntityProperty::isHole(id);
}
bool isJSJ(AcDbObjectId &id)
{
return CEntityProperty::isJSJ(id);
}
bool isPipe(AcDbObjectId &id)
{
CEntityProperty::EEntityType et(CEntityProperty::getTypeOfEntity(id));
return et == CEntityProperty::eCabtry
|| et == CEntityProperty::eCabtryFitting
|| et == CEntityProperty::eCable
|| et == CEntityProperty::eHVACFitting
|| et == CEntityProperty::eHVAC
|| et == CEntityProperty::ePipeFitting
|| et == CEntityProperty::ePipe ;
}
// 门窗和墙体需要碰撞检测吗?
bool needCD_WallAndOpening(AcDbObjectId &id1, AcDbObjectId &id2)
{
AcDbObjectPointer<AecDbXOpening> opening(id1, AcDb::kForRead);
AcDbObjectId idWall;
if (opening.open(id1, AcDb::kForRead) != Acad::eOk)
{
opening.open(id2, AcDb::kForRead);
idWall = id1;
}
else
{
idWall = id2;
}
if (opening.openStatus() != Acad::eOk) return true;
OpeningData data;
::memset(&data, 0, sizeof(OpeningData));
if (!opening->GetOpeningData(data)) return true;
AcDbObjectId idOpenWall;
idOpenWall.setFromOldId(data.idWall);
// 是墙上的门窗,不需要比较,不碰撞
if (idWall == idOpenWall)
return false;
return true;
}
// 两个有管嘴的实体需要碰撞吗?
bool needCD_2Interface(AcDbObjectId &id1, AcDbObjectId &id2)
{
AcDbEntityPointer ent1(id1, AcDb::kForRead);
AcDbEntityPointer ent2(id2, AcDb::kForRead);
if (ent1.openStatus() != Acad::eOk || ent2.openStatus() != Acad::eOk) return true;
AecMInterfaces *pInf1 = dynamic_cast<AecMInterfaces *>(ent1.object());
AecMInterfaces *pInf2 = dynamic_cast<AecMInterfaces *>(ent2.object());
if (!pInf1 || !pInf2) return true;
AcArray<AecInterface> arInfs1, arInfs2;
pInf1->GetInterface(arInfs1);
pInf2->GetInterface(arInfs2);
AecInterface inf1, inf2;
for (int i(0); i < arInfs1.length(); ++i)
{
inf1 = arInfs1[i];
for (int j(0); j < arInfs2.length(); ++j)
{
inf2 = arInfs2[j];
// 不能严格判断距离,小于10就算连上
if (inf1.ptPos.distanceTo(inf2.ptPos) < 10)
return false;
}
}
return true;
}
// 如果两个个实体不需要检测,返回false,需要则返回true
bool needCD(AcDbObjectId &id1, AcDbObjectId &id2)
{
// 如果有一个实体是洞口,不需要碰撞检测
if (isHole(id1) || isHole(id2)) return false;
// 管线和支架不需要碰撞
if ((isPipe(id1) && isHanger(id2))
|| (isPipe(id2) && isHanger(id1))) return false;
// 设备和支架不需要碰撞
if ((isEqu(id1) && isHanger(id2))
|| (isEqu(id2) && isHanger(id1))) return false;
// 构筑物之间,不需要碰撞检测
if (isEnvelope(id1) && isEnvelope(id2) ) return false;
// 墙和窗体,判断两个实体的的连接关系,如果存在连接关系,不需要碰撞检测
if ((isWall(id1) && isOpening(id2))
|| (isWall(id2) && isOpening(id1)))
return needCD_WallAndOpening(id1, id2);
// 由于板和设备都有接口,所以,放到接口的判断之前判断
// 板实体上经常会有非阀门的设备,如果是这样不应该碰撞检测
if ((CDA::isSameOf<AecDbXSlab>(id1) && isEquButNoValve(id2))
|| (CDA::isSameOf<AecDbXSlab>(id2) && isEquButNoValve(id1)))
return false;
// 有接口的两个实体,判断是否有接口相连,如果有,不需要碰撞检测
if (isAecMInterfaces(id1) && isAecMInterfaces(id2))
return needCD_2Interface(id1, id2);
// 板实体上的集水井不需要碰撞
if ( (isJSJ(id1) && CDA::isSameOf<AecDbXSlab>(id2))
|| (isJSJ(id2) && CDA::isSameOf<AecDbXSlab>(id1)))
return false;
return true;
}
bool collisionDetection(AcDbObjectId &id1, AcDbObjectId &id2)
{
if (!needCD(id1, id2)) return false;
using namespace CDA;
// 1. 在整个检测期间,将两个实体保持打开状态,防止其内部数据变为野指针!
AcDbEntityPointer pEnt1(id1, AcDb::kForRead);
if (pEnt1.openStatus() != Acad::eOk) return false;
AcDbEntityPointer pEnt2(id2, AcDb::kForRead);
if (pEnt2.openStatus() != Acad::eOk) return false;
// 2. 获取缓存数组
AcDbVoidPtrArray ar1, ar2, ar_must_del;
get3dSolidCacheFromEntity(pEnt1.object(), ar1, ar_must_del);
get3dSolidCacheFromEntity(pEnt2.object(), ar2, ar_must_del);
if (ar1.isEmpty() || ar2.isEmpty()) return false;
// 比较两个实体的原则,是只要它们的任意一个子组件发生碰撞,我们就认为它们发生了碰撞。
// 只比较子组件中是AcDb3dSolid的部分,其他类型的子组件(如曲面、线框等)不参与碰撞检测。
// 遍历 A 里面的每一个小组件
AcDbExtents ext1, ext2;
AABB ab1, ab2;
AcDb3dSolid *pSolid1(NULL), *pSolid2(NULL);
for (int i = 0; i < ar1.length(); ++i)
{
// 只比较3dSolid
pSolid1 = AcDb3dSolid::cast((AcDbEntity *)ar1[i]);
if (!pSolid1) continue;
// 获取 A 子组件的 AABB
if (pSolid1->getGeomExtents(ext1) != Acad::eOk) continue;
ab1.set(ext1);
// 遍历 B 里面的每一个小组件
for (int j = 0; j < ar2.length(); ++j)
{
pSolid2 = AcDb3dSolid::cast((AcDbEntity *)ar2[j]);
if (!pSolid2) continue;
// 获取 B 子组件的 AABB
if (pSolid2->getGeomExtents(ext2) != Acad::eOk) continue;
ab2.set(ext2);
// 微观粗筛:用 C++98 风格判断两个子 Extents 是否重叠
if (!ab1.intersects(ab2))
continue; // 子包围盒不碰,直接跳过底层几何计算!
if (collisionDetection(pSolid1, pSolid2))
{
deleteArrayVoidPtrs<AcDbEntity>(ar_must_del);
return true;
}
}
}
deleteArrayVoidPtrs<AcDbEntity>(ar_must_del);
return false;
}
}
namespace
{
void clearOldCDFlag()
{
ads_name ss, ent;
struct resbuf *pRb = acutBuildList(RTDXF0, _T("3DSOLID"), 0);
int nRes;
nRes = acedSSGet(_T("A"), NULL, NULL, pRb, ss);
acutRelRb(pRb);
if (nRes != RTNORM) return;
Adesk::Int32 lLen = 0L;
acedSSLength(ss, &lLen);
if (lLen == 0) return;
AecDbObjXDataMap xData;
AcDbObjectId id;
for (long i = 0; i < lLen; i++)
{
acedSSName(ss, i, ent);
acdbGetObjectId(id, ent);
if (!id.isValid()) continue;
if (xData.Read(id, CD_FLAG))
{
AcDbEntityPointer ent(id, AcDb::kForWrite);
if (ent.openStatus() == Acad::eOk)
ent->erase();
}
}
acedSSFree(ss);
}
}
CCollisionDetectionDlg *g_pDlg = NULL;
void pzjc(const bool &bSelect)
{
clearOldCDFlag();
using namespace CDA;
ads_name ss, ent;
struct resbuf* pRb = acutBuildList(RTDXF0, _T("AEC_*,INSERT"), 0);
int nRes;
if (!bSelect)
nRes = acedSSGet(_T("A"), NULL, NULL, pRb, ss);
else
nRes = acedSSGet(NULL, NULL, NULL, pRb, ss);
acutRelRb(pRb);
if (nRes != RTNORM) return;
Adesk::Int32 lLen = 0L;
acedSSLength(ss, &lLen);
if (lLen == 0) return;
AcDbObjectIdArray arids;
AcDbObjectId id;
for (long i = 0; i < lLen; i++)
{
acedSSName(ss, i, ent);
acdbGetObjectId(id, ent);
if (!id.isValid()) continue;
arids.append(id);
}
acedSSFree(ss);
if (arids.isEmpty()) return;
BVHTree bvh;
bvh.Build(arids);
// 1. 获取所有粗筛相交的实体对 (绝无重复!)
std::vector< std::pair<AcDbObjectId, AcDbObjectId> > hitPairs, hitReals;
bvh.GetAllIntersections(hitPairs);
// 2. 逐对进行底层内核精细检查
acedSetStatusBarProgressMeter(_T("正在处理实体,请稍候..."), 0, (int)hitPairs.size());
for (size_t i = 0; i < hitPairs.size(); ++i)
{
if (acedUsrBrk() == 1)
{
if (AfxMessageBox(_T("用户按下了 ESC 键, 确定取消检查操作吗?"), MB_YESNO) == IDYES)
break;
}
if (collisionDetection(hitPairs[i].first, hitPairs[i].second))
{
hitReals.push_back(hitPairs[i]);
}
acedSetStatusBarProgressMeterPos((int)i + 1);
}
acedRestoreStatusBar();
acutPrintf(_T("\nnumber of cd:%d"), hitReals.size());
if (hitReals.empty())
{
CString sMsg;
sMsg.Format(_T("检查了%d个实体,未找到碰撞点。"), lLen);
AfxMessageBox(sMsg);
return;
}
{
if (g_pDlg)
{
if (::IsWindow(g_pDlg->m_hWnd))
g_pDlg->DestroyWindow();
DELETE_PTR(g_pDlg);
}
else
{
g_pDlg = new CCollisionDetectionDlg(hitReals, acedGetAcadFrame());
g_pDlg->Create(CCollisionDetectionDlg::IDD, acedGetAcadFrame());
g_pDlg->CenterWindow();
g_pDlg->ShowWindow(SW_SHOW);
}
}
}
void pzjca()
{
pzjc(false);
}
void pzjcs()
{
pzjc(true);
}
#pragma region CCollisionDetectionDlg
namespace
{
static int CALLBACK CompareFunc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
typedef CCollisionDetectionDlg::DataContext DataContext;
typedef CCollisionDetectionDlg::SortContext SortContext;
XLISTCTRLDATA *pXData1 = reinterpret_cast<XLISTCTRLDATA*>(lParam1);
XLISTCTRLDATA *pXData2 = reinterpret_cast<XLISTCTRLDATA*>(lParam2);
if (!pXData1 || !pXData2) return 0;
DataContext *pData1 = reinterpret_cast<DataContext*>(pXData1->dwItemData);
DataContext *pData2 = reinterpret_cast<DataContext*>(pXData2->dwItemData);
SortContext *pContext = reinterpret_cast<SortContext *>(lParamSort);
if (!pData1 || !pData2 || !pContext) return 0;
int nResult = 0;
// 根据点击的列号,执行不同的比较逻辑
switch (pContext->m_nColumn )
{
case 0:
if (pData1->m_nIdx < pData2->m_nIdx)
nResult = -1;
else if (pData1->m_nIdx > pData2->m_nIdx)
nResult = 1;
else
nResult = 0;
break;
case 1:
nResult = pData1->m_sName1.CompareNoCase(pData2->m_sName1);
break;
case 2:
nResult = pData1->m_sSys1.CompareNoCase(pData2->m_sSys1);
break;
case 3:
nResult = pData1->m_sName2.CompareNoCase(pData2->m_sName2);
break;
case 4:
nResult = pData1->m_sSys2.CompareNoCase(pData2->m_sSys2);
break;
default:
break;
}
// 根据升降序状态返回最终结果
return pContext->m_bAscending ? nResult : -nResult;
}
void GetCurrentDynamicView(AcDbViewTableRecord &viewRec)
{
struct resbuf rb;
struct resbuf rbWcs, rbUcs;
// 0 代表 WCS,1 代表 UCS
rbWcs.restype = RTSHORT;
rbWcs.resval.rint = 0;
rbUcs.restype = RTSHORT;
rbUcs.resval.rint = 1;
// 1. 获取 VIEWMODE (包含透视、剪裁等布尔状态)
if (acedGetVar(_T("VIEWMODE"), &rb) == RTNORM)
{
viewRec.setPerspectiveEnabled((rb.resval.rint & 1) != 0);
viewRec.setFrontClipEnabled((rb.resval.rint & 2) != 0);
viewRec.setBackClipEnabled((rb.resval.rint & 4) != 0);
viewRec.setFrontClipAtEye((rb.resval.rint & 16) == 0);
}
ads_point ptTrans;
// 2. 获取 VIEWDIR (视线方向,UCS矢量 -> WCS矢量)
if (acedGetVar(_T("VIEWDIR"), &rb) == RTNORM)
{
// acedTrans 的第 4 个参数为 1,表示正在转换一个方向矢量(Vector)而不是坐标点
acedTrans(rb.resval.rpoint, &rbUcs, &rbWcs, 1, ptTrans);
viewRec.setViewDirection(AcGeVector3d(ptTrans[0], ptTrans[1], ptTrans[2]));
}
// 3. 获取 TARGET (目标中心点,UCS点 -> WCS点)
if (acedGetVar(_T("TARGET"), &rb) == RTNORM)
{
// acedTrans 的第 4 个参数为 0,表示正在转换一个坐标点(Point)
acedTrans(rb.resval.rpoint, &rbUcs, &rbWcs, 0, ptTrans);
viewRec.setTarget(AcGePoint3d(ptTrans[0], ptTrans[1], ptTrans[2]));
}
// 4. 获取 VIEWTWIST (相机扭曲角度)
if (acedGetVar(_T("VIEWTWIST"), &rb) == RTNORM)
{
viewRec.setViewTwist(rb.resval.rreal);
}
// 5. 获取 LENSLENGTH (镜头焦距)
if (acedGetVar(_T("LENSLENGTH"), &rb) == RTNORM)
{
viewRec.setLensLength(rb.resval.rreal);
}
}
// 主函数:将视图缩放至指定实体
void ZoomToEntity(AcDbObjectId entId)
{
Acad::ErrorStatus es;
AcDbEntity *pEnt = NULL;
es = acdbOpenAcDbEntity(pEnt, entId, AcDb::kForRead);
if (es != Acad::eOk)
{
acutPrintf(_T("\n无法打开实体."));
return;
}
AcDbExtents extents;
es = pEnt->getGeomExtents(extents);
pEnt->close();
if (es != Acad::eOk)
{
acutPrintf(_T("\n无法获取实体几何边界."));
return;
}
AcGePoint3d ptMax(extents.maxPoint()), ptMin(extents.minPoint());
// 构造 WCS 包围盒的 8 个顶点
AcGePoint3d ptWcs[8];
ptWcs[0] = ptMin;
ptWcs[1] = AcGePoint3d(ptMin.x, ptMin.y, ptMax.z);
ptWcs[2] = AcGePoint3d(ptMin.x, ptMax.y, ptMin.z);
ptWcs[3] = AcGePoint3d(ptMin.x, ptMax.y, ptMax.z);
ptWcs[4] = AcGePoint3d(ptMax.x, ptMin.y, ptMin.z);
ptWcs[5] = AcGePoint3d(ptMax.x, ptMin.y, ptMax.z);
ptWcs[6] = AcGePoint3d(ptMax.x, ptMax.y, ptMin.z);
ptWcs[7] = ptMax;
// WCS(0) 到 DCS(2) 转换,寻找真正的屏幕极值
resbuf rbWcs, rbDcs;
rbWcs.restype = RTSHORT;
rbWcs.resval.rint = 0;
rbDcs.restype = RTSHORT;
rbDcs.resval.rint = 2;
double minX = 1e20, minY = 1e20;
double maxX = -1e20, maxY = -1e20;
for (int i = 0; i < 8; ++i)
{
ads_point ptInWcs, ptInDcs;
ptInWcs[0] = ptWcs[i].x;
ptInWcs[1] = ptWcs[i].y;
ptInWcs[2] = ptWcs[i].z;
// 这里的 acedTrans 依旧会自动引用当前屏幕正在呈现的 DCS 矩阵
acedTrans(ptInWcs, &rbWcs, &rbDcs, 0, ptInDcs);
if (ptInDcs[0] < minX) minX = ptInDcs[0];
if (ptInDcs[0] > maxX) maxX = ptInDcs[0];
if (ptInDcs[1] < minY) minY = ptInDcs[1];
if (ptInDcs[1] > maxY) maxY = ptInDcs[1];
}
double dWidth = fabs(maxX - minX) * 2.;
double dHeight = fabs(maxY - minY) * 2.;
AcGePoint2d centerPt(minX + (maxX - minX) * .5, minY + (maxY - minY) * .5);
struct resbuf screenRb;
acedGetVar(_T("SCREENSIZE"), &screenRb);
if (screenRb.resval.rpoint[1] > 0.0)
{
double screenRatio = screenRb.resval.rpoint[0] / screenRb.resval.rpoint[1];
if (dWidth > dHeight * screenRatio)
{
dHeight = dWidth / screenRatio;
dWidth = dHeight * screenRatio;
}
}
// 核心修复:通过封装的系统变量函数,获取完整的动态三维环境
AcDbViewTableRecord viewRec;
GetCurrentDynamicView(viewRec);
// 将计算出的 DCS 中心点与高度覆盖进去
viewRec.setCenterPoint(centerPt);
viewRec.setHeight(dHeight);
viewRec.setWidth(dWidth);
// 更新视图,彻底解决视角被拉回俯视图的问题
acedSetCurrentView(&viewRec, NULL);
}
}
extern UINT WM_XLISTCTRL_CHECKBOX_CLICKED;
extern UINT WM_XLISTCTRL_HEADER_MOUSE_CLICKED;
IMPLEMENT_DYNAMIC(CCollisionDetectionDlg, CDialog)
BEGIN_MESSAGE_MAP(CCollisionDetectionDlg, CDialog)
ON_WM_SIZE()
ON_WM_GETMINMAXINFO()
ON_WM_CLOSE()
ON_BN_CLICKED(IDOK, &CCollisionDetectionDlg::OnOK)
ON_BN_CLICKED(IDC_BTN_REFRESH, &CCollisionDetectionDlg::OnRefreshBtnClicked)
ON_REGISTERED_MESSAGE(WM_XLISTCTRL_CHECKBOX_CLICKED, &CCollisionDetectionDlg::OnCheckBoxClicked)
ON_REGISTERED_MESSAGE(WM_XLISTCTRL_HEADER_MOUSE_CLICKED, &CCollisionDetectionDlg::OnHeaderMouseClicked)
ON_NOTIFY(NM_DBLCLK, IDC_LIST, &CCollisionDetectionDlg::OnNMDblclkList)
END_MESSAGE_MAP()
CCollisionDetectionDlg::CCollisionDetectionDlg(IDPairs &pairs, CWnd *pParent)
: m_szMinDlgSize(800, 600)
, m_idPairs(pairs)
, m_nSortCol(-1)
, m_bAscending(true)
{
m_CdFlags.resize(pairs.size());
for (size_t i(0); i < m_CdFlags.size(); ++i)
m_CdFlags[i] = AcDbObjectId::kNull;
}
CCollisionDetectionDlg::~CCollisionDetectionDlg()
{
clearDatas();
}
void CCollisionDetectionDlg::DoDataExchange(CDataExchange *pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_LIST, m_wndList);
}
BOOL CCollisionDetectionDlg::OnInitDialog()
{
CDialog::OnInitDialog();
initColumns();
layoutControls(0, 0);
refreshList();
return 0;
}
void CCollisionDetectionDlg::refreshList()
{
m_wndList.DeleteAllItems();
const AecDbObjXDataMap *pXData1(NULL), *pXData2(NULL);
CString sName1, sName2, sSys1, sSys2;
AcDbObjectId id1, id2;
IDPairs::const_iterator it(m_idPairs.begin());
for (int i(0); it != m_idPairs.end(); ++it, ++i)
{
id1 = it->first;
id2 = it->second;
sName1 = CEntityProperty::getNameOfEntity(id1);
sName2 = CEntityProperty::getNameOfEntity(id2);
sSys1 = _T("无");
pXData1 = CEntityProperty::getXDataConst(id1);
if (pXData1)
pXData1->GetAt(PIPE_SYS, sSys1);
sSys2 = _T("无");
pXData2 = CEntityProperty::getXDataConst(id2);
if (pXData2)
pXData2->GetAt(PIPE_SYS, sSys2);
m_wndList.InsertItem(i, TypeConvert::i2s(i + 1));
m_wndList.SetItemText(i, 1, sName1);
m_wndList.SetItemText(i, 2, sSys1);
m_wndList.SetItemText(i, 3, sName2);
m_wndList.SetItemText(i, 4, sSys2);
m_wndList.SetCheckbox(i, 0, FALSE);
m_wndList.SetItemData(i, (DWORD_PTR)(new DataContext(i, sName1, sSys1, sName2, sSys2)));
}
}
void CCollisionDetectionDlg::initColumns()
{
double dDpiScale = CoreTools::GetDPIScale();
m_wndList.SetFont(GetFont());
m_wndList.SetExtendedStyle(
m_wndList.GetExtendedStyle()
| LVS_EX_FULLROWSELECT
| LVS_EX_GRIDLINES);
// 第一列,选择列
LV_COLUMN lvcolumn;
::memset(&lvcolumn, 0, sizeof(lvcolumn));
lvcolumn.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
lvcolumn.fmt = LVCFMT_CENTER;
int width(int(30 * dDpiScale));
lvcolumn.pszText = const_cast<LPTSTR>(_T("序号"));
lvcolumn.iSubItem = 0;
lvcolumn.cx = width;
m_wndList.InsertColumn(0, &lvcolumn);
lvcolumn.pszText = const_cast<LPTSTR>(_T("实体类型1"));
lvcolumn.iSubItem = 1;
lvcolumn.cx = width;
m_wndList.InsertColumn(1, &lvcolumn);
lvcolumn.pszText = const_cast<LPTSTR>(_T("系统类型1"));
lvcolumn.iSubItem = 2;
lvcolumn.cx = width;
m_wndList.InsertColumn(2, &lvcolumn);
lvcolumn.pszText = const_cast<LPTSTR>(_T("实体类型2"));
lvcolumn.iSubItem = 3;
lvcolumn.cx = width;
m_wndList.InsertColumn(3, &lvcolumn);
lvcolumn.pszText = const_cast<LPTSTR>(_T("系统类型2"));
lvcolumn.iSubItem = 4;
lvcolumn.cx = width;
m_wndList.InsertColumn(4, &lvcolumn);
// 设置第一列为选择项
if (m_wndList.m_HeaderCtrl.GetItemCount() > 0)
{
HDITEM hditem;
hditem.mask = HDI_FORMAT | HDI_IMAGE;
m_wndList.m_HeaderCtrl.GetItem(0, &hditem);
hditem.fmt |= HDF_IMAGE;
hditem.iImage = XHEADERCTRL_UNCHECKED_IMAGE;
m_wndList.m_HeaderCtrl.SetItem(0, &hditem);
}
}
void CCollisionDetectionDlg::layoutControls(const int &cx, const int &cy)
{
if (!m_wndList.GetSafeHwnd()) return;
double dDpiScale = CoreTools::GetDPIScale();
CRect rcBtn(CPoint(0, 0), CSize(30, 90));
CWnd *pOKBtn = GetDlgItem(IDOK);
CWnd *pRefreshBtn = GetDlgItem(IDC_BTN_REFRESH);
if (pOKBtn)
pOKBtn->GetWindowRect(rcBtn);
CSize szBtn = rcBtn.Size();
CRect rcDlg;
GetClientRect(rcDlg);
int nSpace(int(5 * dDpiScale));
// "确定"按钮 下边与dlg距离 nSpace
rcBtn.bottom = rcDlg.bottom - nSpace;
rcBtn.top = rcBtn.bottom - szBtn.cy;
rcBtn.left = LONG((rcDlg.Width() - szBtn.cx) * .5);
rcBtn.right = rcBtn.left + szBtn.cx;
// m_wndList 上边、左边、右边与dlg距离 nSpace
CRect rcList;
rcList.left = rcDlg.left + nSpace;
rcList.top = rcDlg.top + nSpace;
rcList.right = rcDlg.right - nSpace;
rcList.bottom = rcBtn.top - nSpace;
if (pOKBtn)
pOKBtn->MoveWindow(rcBtn);
// pRefreshBtn bottom与pOKBtn相同, right与wndlist的right相同
rcBtn.right = rcList.right;
rcBtn.left = rcBtn.right - szBtn.cx;
if (pRefreshBtn)
pRefreshBtn->MoveWindow(rcBtn);
m_wndList.MoveWindow(rcList);
int col1 = int(80 * dDpiScale);
m_wndList.GetClientRect(rcList);
CSize sz = rcList.Size();
sz.cx -= col1; // 第一列为序号
sz.cx = LONG(sz.cx * .25);
m_wndList.SetColumnWidth(0, col1);
m_wndList.SetColumnWidth(1, (int)sz.cx);
m_wndList.SetColumnWidth(2, (int)sz.cx);
m_wndList.SetColumnWidth(3, (int)sz.cx);
m_wndList.SetColumnWidth(4, LVSCW_AUTOSIZE_USEHEADER);
}
void CCollisionDetectionDlg::eraseFlag(AcDbObjectId &id)
{
AcDbEntityPointer ent(id, AcDb::kForWrite);
if (ent.openStatus() == Acad::eOk)
ent->erase();
}
AcDbObjectId CCollisionDetectionDlg::createFlag(const AcDbObjectId & id1, const AcDbObjectId & id2)
{
AcDbObjectId id(AcDbObjectId::kNull);
if (!id1.isValid() || !id2.isValid()) return id;
AcDbEntityPointer ent1(id1, AcDb::kForRead);
AcDbEntityPointer ent2(id2, AcDb::kForRead);
if (ent1.openStatus() != Acad::eOk || ent2.openStatus() != Acad::eOk) return id;
AcDbVoidPtrArray ar1, ar2, ar_must_del;
get3dSolidCacheFromEntity(ent1, ar1, ar_must_del);
get3dSolidCacheFromEntity(ent2, ar2, ar_must_del);
AcDb3dSolid *pInterferenceSolid = NULL;
Adesk::Boolean bIntersects = Adesk::kFalse;
double volume(.0);
AcGePoint3d centroid(AcGePoint3d::kOrigin);
double momInertia[3], prodInertia[3], prinMoments[3], radiiGyration[3];
AcGeVector3d prinAxes[3];
AcDbExtents extents;
AecDbObjXDataMap xData;
xData.SetAt(CD_FLAG, 1);
bool bExit = false;
for (int i1(0); i1 < ar1.length(); ++i1)
{
if (bExit) break;
AcDb3dSolid *pSolid1 = AcDb3dSolid::cast((AcDbEntity *)(ar1)[i1]);
if (!pSolid1) continue;
for (int i2(0); i2 < ar2.length(); ++i2)
{
if (bExit) break;
AcDb3dSolid *pSolid2 = AcDb3dSolid::cast((AcDbEntity *)(ar2)[i2]);
if (!pSolid2) continue;
bIntersects = Adesk::kFalse;
Acad::ErrorStatus es = pSolid1->checkInterference(pSolid2
, Adesk::kTrue
, bIntersects
, pInterferenceSolid);
// 得到碰撞的中心点
if (es == Acad::eOk && bIntersects == Adesk::kTrue && pInterferenceSolid)
{
centroid = (AcGePoint3d::kOrigin);
es = pInterferenceSolid->getMassProp(volume
, centroid
, momInertia
, prodInertia
, prinMoments
, prinAxes
, radiiGyration
, extents);
DELETE_PTR(pInterferenceSolid);
if (es == Acad::eOk)
{
AcDb3dSolid *pSolid = new AcDb3dSolid;
if (pSolid->createSphere(extents.maxPoint().distanceTo(extents.minPoint()) * .5 * 1.5) == Acad::eOk)
{
AcGeMatrix3d mat;
mat.setToTranslation(centroid.asVector());
pSolid->transformBy(mat);
pSolid->setColorIndex(1);
// 做一下标记
xData.Write(pSolid, _T("CD_FLAG"));
CoreTools::AddToModelSpace(pSolid, NULL, true, &id);
bExit = true;
}
else
DELETE_PTR(pSolid);
}
}
}
}
deleteArrayVoidPtrs<AcDbEntity>(ar_must_del);
return id;
}
void CCollisionDetectionDlg::clearDatas()
{
AcAxDocLock lock;
for (size_t i(0); i < m_CdFlags.size(); ++i)
eraseFlag(m_CdFlags[i]);
for (int i(0); i < m_wndList.GetItemCount(); ++i)
{
DataContext *pData = (DataContext *)m_wndList.GetItemData(i);
DELETE_PTR(pData);
}
}
void CCollisionDetectionDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
layoutControls(cx, cy);
}
void CCollisionDetectionDlg::OnGetMinMaxInfo(MINMAXINFO *lpMMI)
{
CDialog::OnGetMinMaxInfo(lpMMI);
// 只有当 m_minSize 有效(即 OnInitDialog 运行过)时才限制
// 防止窗口创建初期 m_minSize 为 0 导致窗口无法显示
if (m_szMinDlgSize.cx > 0 && m_szMinDlgSize.cy > 0)
{
// ptMinTrackSize 代表窗口允许的最小“追踪”尺寸 (拖拽时的尺寸)
lpMMI->ptMinTrackSize.x = m_szMinDlgSize.cx; // 限制最小宽度
lpMMI->ptMinTrackSize.y = m_szMinDlgSize.cy; // 限制最小高度
}
}
LRESULT CCollisionDetectionDlg::OnCheckBoxClicked(WPARAM nItem, LPARAM nSubItem)
{
if (nItem >= m_idPairs.size() || nItem < 0) return 0;
if (nItem >= m_CdFlags.size() || nItem < 0) return 0;
{
AcAxDocLock lock;
DataContext &ct = *(DataContext*)m_wndList.GetItemData((int)nItem);
int idxFlag(ct.m_nIdx);
// 得到两个碰撞的实体id
AcDbObjectId id1(m_idPairs[idxFlag].first), id2(m_idPairs[idxFlag].second);
// 碰撞标记id
AcDbObjectId &idFlag = m_CdFlags[idxFlag];
bool bCheck = m_wndList.GetItemCheckedState((int)nItem, (int)nSubItem) != 0 ? true : false;
if (bCheck) // 需要显示碰撞标记
{
if (idFlag.isValid())
{
AcDbEntityPointer ent(idFlag, AcDb::kForRead);
if (ent.openStatus() == Acad::eOk && ent->visibility() != AcDb::kVisible)
{
ent->upgradeOpen();
ent->setVisibility(AcDb::kVisible);
ent->downgradeOpen();
}
}
else
{ // 创建碰撞标记
idFlag = createFlag(id1, id2);
}
}
else // 需要隐藏碰撞标记
{
// 不需要考虑idFlag无效的情况,因为如果无效说明之前没有创建过标记,自然也就不需要隐藏了
AcDbEntityPointer ent(idFlag, AcDb::kForRead);
if (ent.openStatus() == Acad::eOk && ent->visibility() == AcDb::kVisible)
{
ent->upgradeOpen();
ent->setVisibility(AcDb::kInvisible);
ent->downgradeOpen();
}
}
}
::SetFocus(adsw_acadDocWnd());
CoreTools::refreshGraphics();
return 0;
}
LRESULT CCollisionDetectionDlg::OnHeaderMouseClicked(WPARAM, LPARAM nSubItem)
{
if (m_nSortCol == nSubItem)
{
m_bAscending = !m_bAscending;
}
else
{
m_nSortCol = (int)nSubItem;
m_bAscending = true;
}
// 构建 排序信息
SortContext sc;
// 如果点了同一列,就切换排序顺序;如果点了不同列,默认升序
sc.m_bAscending = m_bAscending;
sc.m_nColumn = m_nSortCol;
m_wndList.SortItems(CompareFunc, reinterpret_cast<DWORD_PTR>(&sc));
CHeaderCtrl *pHeader = m_wndList.GetHeaderCtrl();
if (!pHeader) return 0;
for (int i = 0; i < pHeader->GetItemCount(); ++i)
{
HDITEM hditem;
// 只需要获取和设置格式(Format)掩码
hditem.mask = HDI_FORMAT;
pHeader->GetItem(i, &hditem);
// 核心位运算:先清除掉当前列可能存在的任何排序标志
hditem.fmt &= ~(HDF_SORTUP | HDF_SORTDOWN);
// 如果是当前正在排序的列,则追加对应的方向标志
if (i == m_nSortCol)
{
if (m_bAscending)
{
hditem.fmt |= HDF_SORTUP;
}
else
{
hditem.fmt |= HDF_SORTDOWN;
}
}
// 将修改后的格式写回表头
pHeader->SetItem(i, &hditem);
}
return 0;
}
void CCollisionDetectionDlg::OnNMDblclkList(NMHDR *pNMHDR, LRESULT *pResult)
{
LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
// 获取被双击的行号 (-1 表示没有选中项)
int nIndex = pNMItemActivate->iItem;
// 获取被双击的列号
int nSubItem = pNMItemActivate->iSubItem;
DataContext &ct = *(DataContext*)m_wndList.GetItemData(nIndex);
int idxFlag(ct.m_nIdx);
// 得到两个碰撞的实体id
AcDbObjectId id1(m_idPairs[idxFlag].first), id2(m_idPairs[idxFlag].second);
// 碰撞标记id
AcDbObjectId &idFlag = m_CdFlags[idxFlag];
AcAxDocLock lock;
if (!idFlag.isValid())
{
idFlag = createFlag(id1, id2);
AcDbEntityPointer ent(idFlag, AcDb::kForWrite);
if (ent.openStatus() == Acad::eOk)
{
ent->setVisibility(AcDb::kInvisible);
ent->close();
}
}
ZoomToEntity(idFlag);
}
void CCollisionDetectionDlg::PostNcDestroy()
{
CDialog::PostNcDestroy();
DELETE_PTR(g_pDlg);
// 清理图形
}
void CCollisionDetectionDlg::OnCancel()
{
CDialog::OnCancel();
DestroyWindow();
}
void CCollisionDetectionDlg::OnClose()
{
CDialog::OnClose();
DestroyWindow();
}
void CCollisionDetectionDlg::OnOK()
{
CDialog::OnOK();
DestroyWindow();
}
void CCollisionDetectionDlg::OnRefreshBtnClicked()
{
if (m_idPairs.empty()) return;
// 删除无效的flag球
clearOldCDFlag();
// 清空数据
clearDatas();
IDPairs prs;
// 对列表内的结果重新碰撞验证
for (size_t i(0); i < m_idPairs.size(); ++i)
{
if (collisionDetection(m_idPairs[i].first, m_idPairs[i].second))
{
prs.push_back(m_idPairs[i]);
}
}
m_idPairs = prs;
m_CdFlags.resize(m_idPairs.size());
for (size_t i(0); i < m_CdFlags.size(); ++i)
m_CdFlags[i] = AcDbObjectId::kNull;
refreshList();
}
#pragma endregion
�