|
|
#include "nmWxDFNGenerateDlg.h"
|
|
|
#include "nmDataAnalyzeManager.h"
|
|
|
#include "nmObjLineCrack.h"
|
|
|
#include "ZxSubAxisX.h"
|
|
|
#include "ZxSubAxisY.h"
|
|
|
#include "ZxPlot.h"
|
|
|
#include "ZxObjBase.h"
|
|
|
#include "nmDataOutline.h"
|
|
|
#include "nmGuiPlot.h"
|
|
|
|
|
|
#include <QMessageBox>
|
|
|
#include <QTime>
|
|
|
#include <cmath>
|
|
|
#include <QComboBox>
|
|
|
|
|
|
nmWxDFNGenerateDlg::nmWxDFNGenerateDlg(QWidget* parent, nmGuiPlot* plot, nmDataOutline* outline)
|
|
|
: iDlgBase(parent), m_pPlot(plot), m_pOutline(outline)
|
|
|
{
|
|
|
setWindowTitle(tr("Generate DFN"));
|
|
|
setMinimumWidth(360);
|
|
|
|
|
|
QVBoxLayout *mainL = new QVBoxLayout(this);
|
|
|
|
|
|
// 裂缝数量
|
|
|
QHBoxLayout *cntL = new QHBoxLayout();
|
|
|
cntL->addWidget(new QLabel(tr("Number of fractures:"), this));
|
|
|
m_pCountSpin = new QSpinBox(this);
|
|
|
m_pCountSpin->setRange(1, 1000);
|
|
|
m_pCountSpin->setValue(5);
|
|
|
cntL->addWidget(m_pCountSpin);
|
|
|
mainL->addLayout(cntL);
|
|
|
|
|
|
// 长度范围
|
|
|
QHBoxLayout *lenRangeL = new QHBoxLayout();
|
|
|
lenRangeL->addWidget(new QLabel(tr("Range of fracture length:"), this));
|
|
|
m_pMinLengthSpin = new QDoubleSpinBox(this);
|
|
|
m_pMinLengthSpin->setRange(0.0, 1e6);
|
|
|
m_pMinLengthSpin->setDecimals(2);
|
|
|
m_pMinLengthSpin->setValue(100.0);
|
|
|
lenRangeL->addWidget(m_pMinLengthSpin);
|
|
|
m_pMaxLengthSpin = new QDoubleSpinBox(this);
|
|
|
m_pMaxLengthSpin->setRange(0.0, 1e6);
|
|
|
m_pMaxLengthSpin->setDecimals(2);
|
|
|
m_pMaxLengthSpin->setValue(250.0);
|
|
|
lenRangeL->addWidget(m_pMaxLengthSpin);
|
|
|
mainL->addLayout(lenRangeL);
|
|
|
|
|
|
// 角度范围
|
|
|
QHBoxLayout *angleRangeL = new QHBoxLayout();
|
|
|
angleRangeL->addWidget(new QLabel(tr("Range of fracture angle:"), this));
|
|
|
m_pMinAngleSpin = new QDoubleSpinBox(this);
|
|
|
m_pMinAngleSpin->setRange(0.0, 360.0);
|
|
|
m_pMinAngleSpin->setDecimals(1);
|
|
|
m_pMinAngleSpin->setValue(0.0);
|
|
|
angleRangeL->addWidget(m_pMinAngleSpin);
|
|
|
m_pMaxAngleSpin = new QDoubleSpinBox(this);
|
|
|
m_pMaxAngleSpin->setRange(0.0, 360.0);
|
|
|
m_pMaxAngleSpin->setDecimals(1);
|
|
|
m_pMaxAngleSpin->setValue(180.0);
|
|
|
angleRangeL->addWidget(m_pMaxAngleSpin);
|
|
|
mainL->addLayout(angleRangeL);
|
|
|
|
|
|
// 分布方式
|
|
|
QHBoxLayout *distL = new QHBoxLayout();
|
|
|
distL->addWidget(new QLabel(tr("Distribution mode:"), this));
|
|
|
m_pDistCombo = new QComboBox(this);
|
|
|
m_pDistCombo->addItem(tr("Uniform distribution"));
|
|
|
m_pDistCombo->addItem(tr("Exponential distribution"));
|
|
|
m_pDistCombo->addItem(tr("Normal distribution"));
|
|
|
distL->addWidget(m_pDistCombo);
|
|
|
mainL->addLayout(distL);
|
|
|
|
|
|
// 按钮区
|
|
|
QHBoxLayout *btnL = new QHBoxLayout();
|
|
|
btnL->addStretch();
|
|
|
m_pBtnGenerate = new QPushButton(tr("Generate"), this);
|
|
|
m_pBtnCancel = new QPushButton(tr("Cancel"), this);
|
|
|
btnL->addWidget(m_pBtnGenerate);
|
|
|
btnL->addWidget(m_pBtnCancel);
|
|
|
mainL->addLayout(btnL);
|
|
|
|
|
|
// 不显示上下箭头
|
|
|
m_pCountSpin -> setButtonSymbols(QAbstractSpinBox::NoButtons);
|
|
|
m_pMinLengthSpin ->setButtonSymbols(QAbstractSpinBox::NoButtons);
|
|
|
m_pMaxLengthSpin ->setButtonSymbols(QAbstractSpinBox::NoButtons);
|
|
|
m_pMinAngleSpin ->setButtonSymbols(QAbstractSpinBox::NoButtons);
|
|
|
m_pMaxAngleSpin ->setButtonSymbols(QAbstractSpinBox::NoButtons);
|
|
|
|
|
|
connect(m_pBtnGenerate, SIGNAL(clicked()), this, SLOT(onBtnGenerateClicked()));
|
|
|
connect(m_pBtnCancel, SIGNAL(clicked()), this, SLOT(onBtnCancelClicked()));
|
|
|
}
|
|
|
|
|
|
void nmWxDFNGenerateDlg::onBtnGenerateClicked()
|
|
|
{
|
|
|
// 验证参数合理性
|
|
|
if(m_pMinLengthSpin->value() <= 0 || m_pMaxLengthSpin->value() <= 0 || count() <= 0) {
|
|
|
QMessageBox::warning(this, tr("Invalid parameters"),
|
|
|
tr("Length and count must be positive."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if(m_pMinAngleSpin->value() > m_pMaxAngleSpin->value()) {
|
|
|
QMessageBox::warning(this, tr("Invalid parameters"),
|
|
|
tr("Minimum angle must be less than or equal to maximum angle."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 清空原来的DFN图元数据
|
|
|
if(!nmDataAnalyzeManager::getCurrentInstance()->getDFNFractureDataList().isEmpty()) {
|
|
|
nmGuiPlot* pCurPlot = nmDataAnalyzeManager::getCurrentInstance()->getPlot();
|
|
|
pCurPlot->deleteAllDFNPlots();
|
|
|
}
|
|
|
|
|
|
generateFractures();
|
|
|
accept();
|
|
|
}
|
|
|
|
|
|
void nmWxDFNGenerateDlg::onBtnCancelClicked()
|
|
|
{
|
|
|
reject();
|
|
|
}
|
|
|
|
|
|
double nmWxDFNGenerateDlg::sampleLength() const
|
|
|
{
|
|
|
double mn = m_pMinLengthSpin->value();
|
|
|
double mx = m_pMaxLengthSpin->value();
|
|
|
int idx = m_pDistCombo->currentIndex();
|
|
|
switch(idx) {
|
|
|
case 0: // 均匀
|
|
|
return mn + (mx - mn) * (double(qrand())/RAND_MAX);
|
|
|
case 1: // 指数
|
|
|
{
|
|
|
// 指数分布采样,期望值 = (mn+mx)/2
|
|
|
double lambda = 2.0 / (mn + mx); // 参数lambda
|
|
|
double u = double(qrand())/RAND_MAX;
|
|
|
while(u == 0) u = double(qrand())/RAND_MAX; // 避免log(0)
|
|
|
double sample = -log(u) / lambda;
|
|
|
|
|
|
// 将采样值限制在[mn, mx]范围内
|
|
|
if(sample < mn) sample = mn;
|
|
|
if(sample > mx) sample = mx;
|
|
|
|
|
|
return sample;
|
|
|
}
|
|
|
case 2: // 正态
|
|
|
{
|
|
|
// 均值设为 (mn+mx)/2,标准差设为 (mx-mn)/6
|
|
|
double mean = (mn + mx) / 2.0;
|
|
|
double sigma = (mx - mn) / 6.0;
|
|
|
|
|
|
// Box-Muller变换生成正态分布随机数
|
|
|
static bool hasSpare = false;
|
|
|
static double spare;
|
|
|
|
|
|
if(hasSpare) {
|
|
|
hasSpare = false;
|
|
|
double sample = spare * sigma + mean;
|
|
|
// 限制在范围内
|
|
|
if(sample < mn) sample = mn;
|
|
|
if(sample > mx) sample = mx;
|
|
|
return sample;
|
|
|
}
|
|
|
|
|
|
hasSpare = true;
|
|
|
double u = double(qrand())/RAND_MAX;
|
|
|
double v = double(qrand())/RAND_MAX;
|
|
|
while(u == 0) u = double(qrand())/RAND_MAX;
|
|
|
while(v == 0) v = double(qrand())/RAND_MAX;
|
|
|
|
|
|
double mag = sigma * sqrt(-2.0 * log(u));
|
|
|
spare = mag * cos(2.0 * M_PI * v);
|
|
|
double sample = mag * sin(2.0 * M_PI * v) + mean;
|
|
|
|
|
|
// 限制在范围内
|
|
|
if(sample < mn) sample = mn;
|
|
|
if(sample > mx) sample = mx;
|
|
|
|
|
|
return sample;
|
|
|
}
|
|
|
}
|
|
|
return mn;
|
|
|
}
|
|
|
|
|
|
double nmWxDFNGenerateDlg::sampleAngle() const
|
|
|
{
|
|
|
double mn = m_pMinAngleSpin->value();
|
|
|
double mx = m_pMaxAngleSpin->value();
|
|
|
int idx = m_pDistCombo->currentIndex();
|
|
|
switch(idx) {
|
|
|
case 0:
|
|
|
return (mn + (mx - mn) * (double(qrand())/RAND_MAX)) * M_PI/180.0; // 转成弧度
|
|
|
case 1:
|
|
|
{
|
|
|
// 指数分布采样,转成弧度
|
|
|
double lambda = 2.0 / (mn + mx);
|
|
|
double u = double(qrand())/RAND_MAX;
|
|
|
while(u == 0) u = double(qrand())/RAND_MAX;
|
|
|
double sample = -log(u) / lambda;
|
|
|
|
|
|
// 限制在范围内
|
|
|
if(sample < mn) sample = mn;
|
|
|
if(sample > mx) sample = mx;
|
|
|
|
|
|
return sample * M_PI/180.0;
|
|
|
}
|
|
|
case 2:
|
|
|
{
|
|
|
// 正态分布采样,转成弧度
|
|
|
double mean = (mn + mx) / 2.0;
|
|
|
double sigma = (mx - mn) / 6.0;
|
|
|
|
|
|
// Box-Muller变换
|
|
|
static bool hasSpareAngle = false;
|
|
|
static double spareAngle;
|
|
|
|
|
|
if(hasSpareAngle) {
|
|
|
hasSpareAngle = false;
|
|
|
double sample = spareAngle * sigma + mean;
|
|
|
if(sample < mn) sample = mn;
|
|
|
if(sample > mx) sample = mx;
|
|
|
return sample * M_PI/180.0;
|
|
|
}
|
|
|
|
|
|
hasSpareAngle = true;
|
|
|
double u = double(qrand())/RAND_MAX;
|
|
|
double v = double(qrand())/RAND_MAX;
|
|
|
while(u == 0) u = double(qrand())/RAND_MAX;
|
|
|
while(v == 0) v = double(qrand())/RAND_MAX;
|
|
|
|
|
|
double mag = sigma * sqrt(-2.0 * log(u));
|
|
|
spareAngle = mag * cos(2.0 * M_PI * v);
|
|
|
double sample = mag * sin(2.0 * M_PI * v) + mean;
|
|
|
|
|
|
if(sample < mn) sample = mn;
|
|
|
if(sample > mx) sample = mx;
|
|
|
|
|
|
return sample * M_PI/180.0;
|
|
|
}
|
|
|
}
|
|
|
return mn * M_PI/180.0;
|
|
|
}
|
|
|
|
|
|
// 计算点到线段的最短距离
|
|
|
double nmWxDFNGenerateDlg::pointToLineDistance(const QPointF& point, const QLineF& line) const
|
|
|
{
|
|
|
QPointF p1 = line.p1();
|
|
|
QPointF p2 = line.p2();
|
|
|
|
|
|
double A = point.x() - p1.x();
|
|
|
double B = point.y() - p1.y();
|
|
|
double C = p2.x() - p1.x();
|
|
|
double D = p2.y() - p1.y();
|
|
|
|
|
|
double dot = A * C + B * D;
|
|
|
double lenSq = C * C + D * D;
|
|
|
|
|
|
if (lenSq == 0) {
|
|
|
// 线段退化为点的情况
|
|
|
return QLineF(point, p1).length();
|
|
|
}
|
|
|
|
|
|
double param = dot / lenSq;
|
|
|
|
|
|
QPointF closestPoint;
|
|
|
if (param < 0) {
|
|
|
// 最近点是线段起点
|
|
|
closestPoint = p1;
|
|
|
} else if (param > 1) {
|
|
|
// 最近点是线段终点
|
|
|
closestPoint = p2;
|
|
|
} else {
|
|
|
// 最近点在线段上
|
|
|
closestPoint = QPointF(p1.x() + param * C, p1.y() + param * D);
|
|
|
}
|
|
|
|
|
|
return QLineF(point, closestPoint).length();
|
|
|
}
|
|
|
|
|
|
// 检查两条线段是否过于接近
|
|
|
bool nmWxDFNGenerateDlg::isLinesTooClose(const QLineF& line1, const QLineF& line2, double tolerance) const
|
|
|
{
|
|
|
QPointF ip;
|
|
|
|
|
|
// 检查线段数学相交
|
|
|
if(line1.intersect(line2, &ip) == QLineF::BoundedIntersection) {
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
// 检查端点到线段距离(四个距离)
|
|
|
double dist1 = pointToLineDistance(line1.p1(), line2); // 新线段端点1到已存在线段
|
|
|
double dist2 = pointToLineDistance(line1.p2(), line2); // 新线段端点2到已存在线段
|
|
|
double dist3 = pointToLineDistance(line2.p1(), line1); // 已存在线段端点1到新线段
|
|
|
double dist4 = pointToLineDistance(line2.p2(), line1); // 已存在线段端点2到新线段
|
|
|
|
|
|
// 找最小距离
|
|
|
double minDist = dist1;
|
|
|
if (dist2 < minDist) minDist = dist2;
|
|
|
if (dist3 < minDist) minDist = dist3;
|
|
|
if (dist4 < minDist) minDist = dist4;
|
|
|
|
|
|
return minDist < tolerance;
|
|
|
}
|
|
|
|
|
|
// 安全距离计算函数
|
|
|
double nmWxDFNGenerateDlg::calculateSafetyMargin(double fractureLength) const
|
|
|
{
|
|
|
if (fractureLength < 50.0) {
|
|
|
return fractureLength * 5;
|
|
|
} else if (fractureLength < 100.0) {
|
|
|
return fractureLength * 1.5;
|
|
|
} else {
|
|
|
return fractureLength * 1;
|
|
|
}
|
|
|
//return fractureLength;
|
|
|
}
|
|
|
|
|
|
void nmWxDFNGenerateDlg::generateFractures()
|
|
|
{
|
|
|
// 获取用户输入的裂缝数量
|
|
|
int cnt = count();
|
|
|
|
|
|
// 获取绘图和轮廓上下文(由主窗口传入)
|
|
|
nmGuiPlot* plot = m_pPlot;
|
|
|
if(!plot || !plot->m_pPlot) {
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
nmDataOutline* outline = m_pOutline;
|
|
|
NM_Data_Outline_Type type = NM_Rect_Outline_Type;
|
|
|
if (outline) {
|
|
|
type = outline->getOutlineType();
|
|
|
}
|
|
|
|
|
|
// 圆心及半径,若为圆形轮廓
|
|
|
QPointF center;
|
|
|
double R = 0;
|
|
|
|
|
|
// 初始化边界框为一个默认的大矩形
|
|
|
double minX = -1000, maxX = 1000;
|
|
|
double minY = -1000, maxY = 1000;
|
|
|
|
|
|
if (outline && type == NM_Round_Outline_Type) {
|
|
|
center = outline->getCenter();
|
|
|
R = outline->getRadius();
|
|
|
} else if (outline && type == NM_Polygon_Outline_Type) {
|
|
|
// 多边形:获取顶点集合
|
|
|
QVector<QPointF> pts = outline->getOutlinePoints();
|
|
|
if (!pts.isEmpty()) {
|
|
|
// 计算包围矩形,仅用于采样范围
|
|
|
QRectF br = QPolygonF(pts).boundingRect();
|
|
|
minX = br.left(); maxX = br.right();
|
|
|
minY = br.top(); maxY = br.bottom();
|
|
|
}
|
|
|
} else if (outline && type == NM_Rect_Outline_Type) {
|
|
|
// 矩形
|
|
|
QVector<QPointF> pts = outline->getOutlinePoints();
|
|
|
if (!pts.isEmpty()) {
|
|
|
minX = maxX = pts[0].x();
|
|
|
minY = maxY = pts[0].y();
|
|
|
for (int i = 1; i < pts.size(); ++i) {
|
|
|
minX = qMin(minX, pts[i].x());
|
|
|
maxX = qMax(maxX, pts[i].x());
|
|
|
minY = qMin(minY, pts[i].y());
|
|
|
maxY = qMax(maxY, pts[i].y());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 用于存放已成功放置的线段,避免后续相交
|
|
|
QVector<QLineF> existing;
|
|
|
const int maxRetries = 100; // 每条裂缝最多尝试的随机放置次数
|
|
|
qsrand(QTime::currentTime().msec()); // 设置随机种子
|
|
|
|
|
|
int placed = 0; // 记录实际放置成功的裂缝数
|
|
|
|
|
|
for(int i = 0; i < cnt; ++i) {
|
|
|
QLineF line;
|
|
|
bool placedThis = false;
|
|
|
|
|
|
for(int t = 0; t < maxRetries; ++t) {
|
|
|
// 采样当前裂缝的长度,角度和安全距离
|
|
|
double len = sampleLength();
|
|
|
double angle = sampleAngle();
|
|
|
double margin = calculateSafetyMargin(len);
|
|
|
|
|
|
QPointF p1, p2;
|
|
|
if(type == NM_Round_Outline_Type) {
|
|
|
// 圆形:在安全半径内采样中心点
|
|
|
double half = len / 2.0;
|
|
|
double safeR = R - margin;
|
|
|
if (safeR <= 0) {
|
|
|
QMessageBox::warning(this, tr("Length too large"),
|
|
|
tr("The specified length is too large to fit inside the circle."));
|
|
|
return;
|
|
|
}
|
|
|
double u = double(qrand())/RAND_MAX;
|
|
|
double r = sqrt(u) * safeR;
|
|
|
double ang = double(qrand())/RAND_MAX * 2 * M_PI;
|
|
|
QPointF c(r*cos(ang)+center.x(), r*sin(ang)+center.y());
|
|
|
p1 = QPointF(c.x() + half*cos(angle), c.y() + half*sin(angle));
|
|
|
p2 = QPointF(c.x() - half*cos(angle), c.y() - half*sin(angle));
|
|
|
} else if(type == NM_Polygon_Outline_Type) {
|
|
|
// 多边形采样:先在安全包围矩形内随机点,确保在多边形内
|
|
|
QPolygonF poly(outline->getOutlinePoints());
|
|
|
QRectF br = poly.boundingRect();
|
|
|
double pMinX = br.left() + margin;
|
|
|
double pMaxX = br.right() - margin;
|
|
|
double pMinY = br.top() + margin;
|
|
|
double pMaxY = br.bottom() - margin;
|
|
|
if (pMinX > pMaxX || pMinY > pMaxY) {
|
|
|
QMessageBox::warning(this, tr("Length too large"),
|
|
|
tr("The specified length is too large to fit inside the polygon."));
|
|
|
return;
|
|
|
}
|
|
|
// 随机采样起点,使用采样的角度
|
|
|
do {
|
|
|
double u = double(qrand())/RAND_MAX;
|
|
|
double v = double(qrand())/RAND_MAX;
|
|
|
p1 = QPointF(pMinX + u*(pMaxX-pMinX),
|
|
|
pMinY + v*(pMaxY-pMinY));
|
|
|
p2 = QPointF(p1.x() + len*cos(angle),
|
|
|
p1.y() + len*sin(angle));
|
|
|
} while (!poly.containsPoint(p1,Qt::OddEvenFill) ||
|
|
|
!poly.containsPoint(p2,Qt::OddEvenFill));
|
|
|
} else {
|
|
|
// 矩形在安全矩形内随机位置
|
|
|
double safeMinX = minX + margin;
|
|
|
double safeMaxX = maxX - margin;
|
|
|
double safeMinY = minY + margin;
|
|
|
double safeMaxY = maxY - margin;
|
|
|
|
|
|
if (safeMinX > safeMaxX || safeMinY > safeMaxY) {
|
|
|
QMessageBox::warning(this, tr("Length too large"),
|
|
|
tr("The specified length is too large to fit inside the region.\n"
|
|
|
"Please choose a smaller length."));
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
double u = double(qrand())/RAND_MAX;
|
|
|
double v = double(qrand())/RAND_MAX;
|
|
|
p1 = QPointF(safeMinX + u*(safeMaxX-safeMinX),
|
|
|
safeMinY + v*(safeMaxY-safeMinY));
|
|
|
p2 = QPointF(p1.x() + len*cos(angle),
|
|
|
p1.y() + len*sin(angle));
|
|
|
}
|
|
|
|
|
|
// 统一相交检测
|
|
|
line = QLineF(p1, p2);
|
|
|
|
|
|
// 检查是否与已放置的任意线段相交
|
|
|
bool inter = false;
|
|
|
// 增加容差检查
|
|
|
double tolerance = 20.0;
|
|
|
for(int j = 0; j < existing.size(); ++j) {
|
|
|
QPointF ip;
|
|
|
if(isLinesTooClose(line, existing[j], tolerance)) {
|
|
|
inter = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
// 如果不相交,则接受此线段
|
|
|
if(!inter) {
|
|
|
placedThis = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (!placedThis) {
|
|
|
qWarning("Failed to place fracture %d", i);
|
|
|
continue;
|
|
|
}
|
|
|
|
|
|
// 成功放置:绘制并记录
|
|
|
existing.append(line);
|
|
|
QVector<QPointF> pts;
|
|
|
pts << line.p1() << line.p2();
|
|
|
QVector<QPointF> scPts = plot->m_pPlot->getPosForValue(pts);
|
|
|
QString name = tr("DFN").arg(placed + 1);
|
|
|
nmObjBase* obj = plot->appendOneObj(NMOT_Line_Fracture, name, scPts);
|
|
|
nmObjLineCrack* crack = dynamic_cast<nmObjLineCrack*>(obj);
|
|
|
if (crack) {
|
|
|
crack->afterCreated();
|
|
|
// 设置属性为 DFN
|
|
|
nmDataAttribute attr = crack->getFractureData()->getFractureType();
|
|
|
attr.setValue(tr("DFN"));
|
|
|
crack->getFractureData()->setFractureType(attr);
|
|
|
crack->setPen(QPen(QBrush(QColor("#00bfff")), 0.4f, Qt::SolidLine));
|
|
|
crack->setLockForDFN();
|
|
|
}
|
|
|
++placed;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
int nmWxDFNGenerateDlg::count() const
|
|
|
{
|
|
|
return m_pCountSpin->value();
|
|
|
}
|