1629 lines
80 KiB
C++
1629 lines
80 KiB
C++
#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
|