Files
envi-code/SourceCode/Code2026/CoreCommand/CollisionDetection.cpp
T
2026-09-28 15:28:50 +08:00

1629 lines
80 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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