#include "nmPebiRegionPressureCalculator.h" #include "nmPebiResultSnapshot.h" #include #include #include #include #include #include #include #include #include #if !defined(_MSC_VER) #include #endif namespace { // 所有区域统计几何均投影到 XY 平面,三角形是剪裁和求交的最小单元。 struct Triangle2D { QPointF m_a; QPointF m_b; QPointF m_c; }; struct PreparedPolygon { QVector m_vecPoints; QVector m_vecTriangles; QRectF m_oBounds; double m_dArea; }; struct CachedCell { CachedCell() : m_nCellId(-1), m_dArea(0.0), m_dPoreVolumeWeight(0.0), m_bWeightValid(false) { } vtkIdType m_nCellId; double m_dArea; double m_dPoreVolumeWeight; bool m_bWeightValid; }; bool isFiniteValue(double dValue) { #if defined(_MSC_VER) return _finite(dValue) != 0; #else return std::isfinite(dValue); #endif } double crossProduct(const QPointF& a, const QPointF& b, const QPointF& c) { return (b.x() - a.x()) * (c.y() - a.y()) - (b.y() - a.y()) * (c.x() - a.x()); } double squaredDistance(const QPointF& a, const QPointF& b) { const double dX = a.x() - b.x(); const double dY = a.y() - b.y(); return dX * dX + dY * dY; } double signedDoubleArea(const QVector& vecPoints) { if(vecPoints.size() < 3) { return 0.0; } // 先平移到首点附近再做鞋带求和,避免大地坐标中的大数相减吞掉小区域面积。 const QPointF oOrigin = vecPoints.first(); double dArea = 0.0; for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) { const QPointF& a = vecPoints[nIndex]; const QPointF& b = vecPoints[(nIndex + 1) % vecPoints.size()]; const double dAx = a.x() - oOrigin.x(); const double dAy = a.y() - oOrigin.y(); const double dBx = b.x() - oOrigin.x(); const double dBy = b.y() - oOrigin.y(); dArea += dAx * dBy - dBx * dAy; } return dArea; } QRectF polygonBounds(const QVector& vecPoints) { double dMinimumX = vecPoints[0].x(); double dMaximumX = dMinimumX; double dMinimumY = vecPoints[0].y(); double dMaximumY = dMinimumY; for(int nIndex = 1; nIndex < vecPoints.size(); ++nIndex) { dMinimumX = qMin(dMinimumX, vecPoints[nIndex].x()); dMaximumX = qMax(dMaximumX, vecPoints[nIndex].x()); dMinimumY = qMin(dMinimumY, vecPoints[nIndex].y()); dMaximumY = qMax(dMaximumY, vecPoints[nIndex].y()); } return QRectF(QPointF(dMinimumX, dMinimumY), QPointF(dMaximumX, dMaximumY)); } bool pointOnSegment(const QPointF& p, const QPointF& a, const QPointF& b, double dLinearTolerance, double dAreaTolerance) { if(qAbs(crossProduct(a, b, p)) > dAreaTolerance) { return false; } return p.x() >= qMin(a.x(), b.x()) - dLinearTolerance && p.x() <= qMax(a.x(), b.x()) + dLinearTolerance && p.y() >= qMin(a.y(), b.y()) - dLinearTolerance && p.y() <= qMax(a.y(), b.y()) + dLinearTolerance; } int orientationSign(const QPointF& a, const QPointF& b, const QPointF& c, double dAreaTolerance) { const double dCross = crossProduct(a, b, c); if(dCross > dAreaTolerance) { return 1; } if(dCross < -dAreaTolerance) { return -1; } return 0; } bool segmentsIntersect(const QPointF& a, const QPointF& b, const QPointF& c, const QPointF& d, double dLinearTolerance, double dAreaTolerance) { const int nAbC = orientationSign(a, b, c, dAreaTolerance); const int nAbD = orientationSign(a, b, d, dAreaTolerance); const int nCdA = orientationSign(c, d, a, dAreaTolerance); const int nCdB = orientationSign(c, d, b, dAreaTolerance); if(nAbC * nAbD < 0 && nCdA * nCdB < 0) { return true; } return (nAbC == 0 && pointOnSegment(c, a, b, dLinearTolerance, dAreaTolerance)) || (nAbD == 0 && pointOnSegment(d, a, b, dLinearTolerance, dAreaTolerance)) || (nCdA == 0 && pointOnSegment(a, c, d, dLinearTolerance, dAreaTolerance)) || (nCdB == 0 && pointOnSegment(b, c, d, dLinearTolerance, dAreaTolerance)); } bool isSimplePolygon(const QVector& vecPoints, double dLinearTolerance, double dAreaTolerance) { // 只检查非相邻边;相邻边共享端点是合法多边形的必要条件。 const int nPointCount = vecPoints.size(); for(int nFirst = 0; nFirst < nPointCount; ++nFirst) { const int nFirstNext = (nFirst + 1) % nPointCount; for(int nSecond = nFirst + 1; nSecond < nPointCount; ++nSecond) { const int nSecondNext = (nSecond + 1) % nPointCount; if(nFirst == nSecond || nFirstNext == nSecond || nSecondNext == nFirst) { continue; } if(segmentsIntersect(vecPoints[nFirst], vecPoints[nFirstNext], vecPoints[nSecond], vecPoints[nSecondNext], dLinearTolerance, dAreaTolerance)) { return false; } } } return true; } bool pointInsideTriangle(const QPointF& p, const QPointF& a, const QPointF& b, const QPointF& c, double dAreaTolerance) { return crossProduct(a, b, p) >= -dAreaTolerance && crossProduct(b, c, p) >= -dAreaTolerance && crossProduct(c, a, p) >= -dAreaTolerance; } bool triangulatePolygon(const QVector& vecPoints, double dAreaTolerance, QVector& vecTriangles) { // 耳切法同时支持凸、凹简单多边形,无需依赖 VTK 的三维三角化管线。 vecTriangles.clear(); QVector vecIndices; for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) { vecIndices.append(nIndex); } while(vecIndices.size() > 3) { bool bEarFound = false; for(int nPosition = 0; nPosition < vecIndices.size(); ++nPosition) { const int nPrevious = vecIndices[ (nPosition + vecIndices.size() - 1) % vecIndices.size()]; const int nCurrent = vecIndices[nPosition]; const int nNext = vecIndices[(nPosition + 1) % vecIndices.size()]; const QPointF& a = vecPoints[nPrevious]; const QPointF& b = vecPoints[nCurrent]; const QPointF& c = vecPoints[nNext]; if(crossProduct(a, b, c) <= dAreaTolerance) { continue; } bool bContainsOtherPoint = false; for(int nOtherPosition = 0; nOtherPosition < vecIndices.size(); ++nOtherPosition) { const int nOther = vecIndices[nOtherPosition]; if(nOther == nPrevious || nOther == nCurrent || nOther == nNext) { continue; } if(pointInsideTriangle(vecPoints[nOther], a, b, c, dAreaTolerance)) { bContainsOtherPoint = true; break; } } if(bContainsOtherPoint) { continue; } Triangle2D oTriangle; oTriangle.m_a = a; oTriangle.m_b = b; oTriangle.m_c = c; vecTriangles.append(oTriangle); vecIndices.remove(nPosition); bEarFound = true; break; } if(!bEarFound) { vecTriangles.clear(); return false; } } if(vecIndices.size() != 3) { vecTriangles.clear(); return false; } Triangle2D oTriangle; oTriangle.m_a = vecPoints[vecIndices[0]]; oTriangle.m_b = vecPoints[vecIndices[1]]; oTriangle.m_c = vecPoints[vecIndices[2]]; if(crossProduct(oTriangle.m_a, oTriangle.m_b, oTriangle.m_c) <= dAreaTolerance) { vecTriangles.clear(); return false; } vecTriangles.append(oTriangle); return true; } bool preparePolygon(const QVector& vecInput, PreparedPolygon& oPolygon) { oPolygon = PreparedPolygon(); if(vecInput.size() < 3) { return false; } for(int nIndex = 0; nIndex < vecInput.size(); ++nIndex) { if(!isFiniteValue(vecInput[nIndex].x()) || !isFiniteValue(vecInput[nIndex].y())) { return false; } } // 容差跟随当前多边形尺度,兼容小尺寸网格和大地坐标。 const QRectF oInputBounds = polygonBounds(vecInput); const double dScale = qMax(1.0, qMax(oInputBounds.width(), oInputBounds.height())); const double dLinearTolerance = dScale * 1.0e-10; const double dAreaTolerance = dScale * dScale * 1.0e-12; const double dSquaredTolerance = dLinearTolerance * dLinearTolerance; // 先去掉相邻重复点及显式闭合点,避免产生零长边。 QVector vecPoints; for(int nIndex = 0; nIndex < vecInput.size(); ++nIndex) { if(vecPoints.isEmpty() || squaredDistance(vecPoints.last(), vecInput[nIndex]) > dSquaredTolerance) { vecPoints.append(vecInput[nIndex]); } } if(vecPoints.size() > 1 && squaredDistance(vecPoints.first(), vecPoints.last()) <= dSquaredTolerance) { vecPoints.remove(vecPoints.size() - 1); } // 反复清理共线中间点,直到点集稳定,降低耳切法退化风险。 bool bRemovedPoint = true; while(bRemovedPoint && vecPoints.size() >= 3) { bRemovedPoint = false; for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) { const QPointF& a = vecPoints[ (nIndex + vecPoints.size() - 1) % vecPoints.size()]; const QPointF& p = vecPoints[nIndex]; const QPointF& b = vecPoints[(nIndex + 1) % vecPoints.size()]; if(pointOnSegment(p, a, b, dLinearTolerance, dAreaTolerance)) { vecPoints.remove(nIndex); bRemovedPoint = true; break; } } } if(vecPoints.size() < 3 || !isSimplePolygon(vecPoints, dLinearTolerance, dAreaTolerance)) { return false; } double dDoubleArea = signedDoubleArea(vecPoints); if(!isFiniteValue(dDoubleArea) || qAbs(dDoubleArea) <= dAreaTolerance) { return false; } // 后续半平面剪裁约定顶点为逆时针顺序。 if(dDoubleArea < 0.0) { std::reverse(vecPoints.begin(), vecPoints.end()); dDoubleArea = -dDoubleArea; } QVector vecTriangles; if(!triangulatePolygon(vecPoints, dAreaTolerance, vecTriangles)) { return false; } oPolygon.m_vecPoints = vecPoints; oPolygon.m_vecTriangles = vecTriangles; oPolygon.m_oBounds = polygonBounds(vecPoints); oPolygon.m_dArea = dDoubleArea * 0.5; return isFiniteValue(oPolygon.m_dArea) && oPolygon.m_dArea > 0.0; } QPointF segmentLineIntersection(const QPointF& s, const QPointF& e, double dStartDistance, double dEndDistance) { const double dDenominator = dStartDistance - dEndDistance; const double dT = dDenominator == 0.0 ? 0.0 : dStartDistance / dDenominator; return QPointF(s.x() + dT * (e.x() - s.x()), s.y() + dT * (e.y() - s.y())); } QVector clipByEdge(const QVector& vecSubject, const QPointF& a, const QPointF& b, double dAreaTolerance) { // Sutherland-Hodgman 半平面剪裁,裁剪边左侧视为三角形内部。 QVector vecOutput; if(vecSubject.isEmpty()) { return vecOutput; } QPointF s = vecSubject.last(); double dStartDistance = crossProduct(a, b, s); bool bStartInside = dStartDistance >= -dAreaTolerance; for(int nIndex = 0; nIndex < vecSubject.size(); ++nIndex) { const QPointF e = vecSubject[nIndex]; const double dEndDistance = crossProduct(a, b, e); const bool bEndInside = dEndDistance >= -dAreaTolerance; if(bEndInside) { if(!bStartInside) { vecOutput.append(segmentLineIntersection( s, e, dStartDistance, dEndDistance)); } vecOutput.append(e); } else if(bStartInside) { vecOutput.append(segmentLineIntersection( s, e, dStartDistance, dEndDistance)); } s = e; dStartDistance = dEndDistance; bStartInside = bEndInside; } return vecOutput; } double triangleIntersectionArea(const Triangle2D& oSubject, const Triangle2D& oClip, double dAreaTolerance) { QVector vecClipped; vecClipped.append(oSubject.m_a); vecClipped.append(oSubject.m_b); vecClipped.append(oSubject.m_c); vecClipped = clipByEdge(vecClipped, oClip.m_a, oClip.m_b, dAreaTolerance); vecClipped = clipByEdge(vecClipped, oClip.m_b, oClip.m_c, dAreaTolerance); vecClipped = clipByEdge(vecClipped, oClip.m_c, oClip.m_a, dAreaTolerance); return vecClipped.size() >= 3 ? qAbs(signedDoubleArea(vecClipped)) * 0.5 : 0.0; } bool rectanglesOverlap(const QRectF& a, const QRectF& b, double dTolerance) { return a.right() >= b.left() - dTolerance && b.right() >= a.left() - dTolerance && a.bottom() >= b.top() - dTolerance && b.bottom() >= a.top() - dTolerance; } bool polygonsHavePositiveAreaIntersection( const PreparedPolygon& a, const PreparedPolygon& b) { // 业务语义是“面积相交”:仅边界或顶点接触不把单元纳入统计。 const double dFirstScale = qMax(a.m_oBounds.width(), a.m_oBounds.height()); const double dSecondScale = qMax(b.m_oBounds.width(), b.m_oBounds.height()); // 交叠容差跟随较小多边形,避免大选区吞掉小单元的真实窄交叠。 const double dScale = qMax(1.0, qMin(dFirstScale, dSecondScale)); const double dLinearTolerance = dScale * 1.0e-10; if(!rectanglesOverlap(a.m_oBounds, b.m_oBounds, dLinearTolerance)) { return false; } const double dAreaTolerance = dScale * dScale * 1.0e-13; const double dPositiveAreaThreshold = qMax( dAreaTolerance, qMin(a.m_dArea, b.m_dArea) * 1.0e-12); for(int nA = 0; nA < a.m_vecTriangles.size(); ++nA) { for(int nB = 0; nB < b.m_vecTriangles.size(); ++nB) { if(triangleIntersectionArea( a.m_vecTriangles[nA], b.m_vecTriangles[nB], dAreaTolerance) > dPositiveAreaThreshold) { return true; } } } return false; } bool readCellPolygon(vtkCell* pCell, QVector& vecPoints) { // 当前 PEBI 结果只接受可直接解释为平面多边形的单元类型。 vecPoints.clear(); if(pCell == NULL || (pCell->GetCellType() != VTK_TRIANGLE && pCell->GetCellType() != VTK_QUAD && pCell->GetCellType() != VTK_POLYGON) || pCell->GetPoints() == NULL || pCell->GetNumberOfPoints() < 3) { return false; } double aPoint[3] = { 0.0, 0.0, 0.0 }; for(vtkIdType nPointIndex = 0; nPointIndex < pCell->GetNumberOfPoints(); ++nPointIndex) { pCell->GetPoints()->GetPoint(nPointIndex, aPoint); if(!isFiniteValue(aPoint[0]) || !isFiniteValue(aPoint[1])) { vecPoints.clear(); return false; } vecPoints.append(QPointF(aPoint[0], aPoint[1])); } return true; } void addCompensated(double dValue, double& dSum, double& dCorrection) { // Kahan 补偿求和用于缓解单元数量较多或权重差异较大时的累计误差。 const double dAdjusted = dValue - dCorrection; const double dCandidate = dSum + dAdjusted; dCorrection = (dCandidate - dSum) - dAdjusted; dSum = dCandidate; } } class nmPebiRegionPressureCalculatorPrivate { public: nmPebiRegionPressureCalculatorPrivate() : m_bPrepared(false), m_dSelectedCellArea(0.0) { } bool m_bPrepared; QSharedPointer m_pSnapshot; QVector m_vecPolygonPoints; QVector m_vecIntersectingCellIds; QVector m_vecCells; double m_dSelectedCellArea; }; nmPebiRegionPressureStatistics::nmPebiRegionPressureStatistics() { clear(); } void nmPebiRegionPressureStatistics::clear() { m_bAvailable = false; m_nPressureFrameIndex = -1; m_dPressureTimeHours = 0.0; m_nIntersectingCellCount = 0; m_nValidCellCount = 0; m_nIgnoredCellCount = 0; m_dSelectedCellAreaSquareMeters = 0.0; m_dEffectivePoreVolumeCubicMeters = 0.0; m_dMinimumPressureMpa = 0.0; m_dMaximumPressureMpa = 0.0; m_dPoreVolumeWeightedPressureMpa = 0.0; } nmPebiRegionPressureCalculator::nmPebiRegionPressureCalculator() : m_pPrivate(new nmPebiRegionPressureCalculatorPrivate) { } nmPebiRegionPressureCalculator::~nmPebiRegionPressureCalculator() { delete m_pPrivate; m_pPrivate = NULL; } bool nmPebiRegionPressureCalculator::prepare( const QSharedPointer& pSnapshot, const QVector& vecPolygonPoints, QString* pError) { if(pError != NULL) { pError->clear(); } const vtkIdType nCellCount = pSnapshot.isNull() ? 0 : pSnapshot->getResultCellCount(); if(m_pPrivate == NULL || pSnapshot.isNull() || !pSnapshot->m_bComplete || pSnapshot->m_pResultGrid == NULL || nCellCount <= 0 || pSnapshot->m_pPorosity == NULL || pSnapshot->m_pThickness == NULL || pSnapshot->m_pPorosity->GetNumberOfComponents() != 1 || pSnapshot->m_pThickness->GetNumberOfComponents() != 1 || pSnapshot->m_pPorosity->GetNumberOfTuples() != nCellCount || pSnapshot->m_pThickness->GetNumberOfTuples() != nCellCount || pSnapshot->m_vecPressureFrames.isEmpty()) { if(pError != NULL) { *pError = QString::fromLatin1( "The PEBI result snapshot is invalid."); } return false; } PreparedPolygon oSelection; if(!preparePolygon(vecPolygonPoints, oSelection)) { if(pError != NULL) { *pError = QString::fromLatin1( "The region polygon is invalid or self-intersecting."); } return false; } // 先在候选状态中完成全部校验和缓存,失败时不破坏上一个可用区域。 nmPebiRegionPressureCalculatorPrivate oCandidate; oCandidate.m_pSnapshot = pSnapshot; oCandidate.m_vecPolygonPoints = oSelection.m_vecPoints; vtkUnstructuredGrid* pGrid = pSnapshot->m_pResultGrid; QVector vecCellPoints; double dAreaCorrection = 0.0; for(vtkIdType nCellId = 0; nCellId < pGrid->GetNumberOfCells(); ++nCellId) { vtkCell* pCell = pGrid->GetCell(nCellId); if(!readCellPolygon(pCell, vecCellPoints)) { continue; } PreparedPolygon oCellPolygon; if(!preparePolygon(vecCellPoints, oCellPolygon) || !polygonsHavePositiveAreaIntersection( oSelection, oCellPolygon)) { continue; } CachedCell oCell; oCell.m_nCellId = nCellId; // 选中规则是“有正面积相交即纳入”,纳入后使用整个单元面积计算孔隙体积权重。 oCell.m_dArea = oCellPolygon.m_dArea; const double dPorosity = pSnapshot->m_pPorosity->GetValue(nCellId); const double dThickness = pSnapshot->m_pThickness->GetValue(nCellId); const double dWeight = oCell.m_dArea * dThickness * dPorosity; // 几何相交单元保留在计数中;非法物性只在压力统计时被忽略。 oCell.m_bWeightValid = isFiniteValue(dPorosity) && isFiniteValue(dThickness) && isFiniteValue(dWeight) && dPorosity > 0.0 && dThickness > 0.0 && dWeight > 0.0; oCell.m_dPoreVolumeWeight = oCell.m_bWeightValid ? dWeight : 0.0; oCandidate.m_vecCells.append(oCell); oCandidate.m_vecIntersectingCellIds.append(nCellId); addCompensated(oCell.m_dArea, oCandidate.m_dSelectedCellArea, dAreaCorrection); } if(!isFiniteValue(oCandidate.m_dSelectedCellArea)) { if(pError != NULL) { *pError = QString::fromLatin1( "The selected cell area is invalid."); } return false; } oCandidate.m_bPrepared = true; *m_pPrivate = oCandidate; return true; } void nmPebiRegionPressureCalculator::clear() { if(m_pPrivate != NULL) { *m_pPrivate = nmPebiRegionPressureCalculatorPrivate(); } } bool nmPebiRegionPressureCalculator::isPrepared() const { return m_pPrivate != NULL && m_pPrivate->m_bPrepared && !m_pPrivate->m_pSnapshot.isNull(); } QString nmPebiRegionPressureCalculator::getSnapshotId() const { return isPrepared() ? m_pPrivate->m_pSnapshot->getSnapshotId() : QString(); } const QVector& nmPebiRegionPressureCalculator::getPolygonPoints() const { return m_pPrivate->m_vecPolygonPoints; } const QVector& nmPebiRegionPressureCalculator::getIntersectingCellIds() const { return m_pPrivate->m_vecIntersectingCellIds; } bool nmPebiRegionPressureCalculator::calculateFrame( int nPressureFrameIndex, nmPebiRegionPressureStatistics& oStatistics, QString* pError) const { oStatistics.clear(); if(pError != NULL) { pError->clear(); } if(!isPrepared() || nPressureFrameIndex < 0 || nPressureFrameIndex >= m_pPrivate->m_pSnapshot->m_vecPressureFrames.size()) { if(pError != NULL) { *pError = QString::fromLatin1( "The pressure frame is invalid."); } return false; } const nmPebiResultSnapshot::PressureFrame& oFrame = m_pPrivate->m_pSnapshot->m_vecPressureFrames[ nPressureFrameIndex]; vtkDoubleArray* pPressure = oFrame.m_pPressure; if(pPressure == NULL || pPressure->GetNumberOfComponents() != 1 || pPressure->GetNumberOfTuples() != m_pPrivate->m_pSnapshot->getResultCellCount() || !isFiniteValue(oFrame.m_dTime)) { if(pError != NULL) { *pError = QString::fromLatin1( "The pressure frame data is invalid."); } return false; } oStatistics.m_nPressureFrameIndex = nPressureFrameIndex; oStatistics.m_dPressureTimeHours = oFrame.m_dTime; oStatistics.m_nIntersectingCellCount = m_pPrivate->m_vecCells.size(); oStatistics.m_dSelectedCellAreaSquareMeters = m_pPrivate->m_dSelectedCellArea; // 交集及静态权重已在 prepare() 缓存,逐帧只读压力并做线性汇总。 double dPoreVolumeSum = 0.0; double dPoreVolumeCorrection = 0.0; double dWeightedPressureSum = 0.0; double dWeightedPressureCorrection = 0.0; double dMinimumPressure = DBL_MAX; double dMaximumPressure = -DBL_MAX; for(int nIndex = 0; nIndex < m_pPrivate->m_vecCells.size(); ++nIndex) { const CachedCell& oCell = m_pPrivate->m_vecCells[nIndex]; const double dPressure = pPressure->GetValue(oCell.m_nCellId); if(!oCell.m_bWeightValid || !isFiniteValue(dPressure)) { continue; } addCompensated(oCell.m_dPoreVolumeWeight, dPoreVolumeSum, dPoreVolumeCorrection); addCompensated(oCell.m_dPoreVolumeWeight * dPressure, dWeightedPressureSum, dWeightedPressureCorrection); dMinimumPressure = qMin(dMinimumPressure, dPressure); dMaximumPressure = qMax(dMaximumPressure, dPressure); ++oStatistics.m_nValidCellCount; } oStatistics.m_nIgnoredCellCount = oStatistics.m_nIntersectingCellCount - oStatistics.m_nValidCellCount; // 空选区或全部权重无效都是可展示结果,不把它们当成计算错误。 if(oStatistics.m_nValidCellCount == 0) { return true; } if(!isFiniteValue(dPoreVolumeSum) || dPoreVolumeSum <= 0.0 || !isFiniteValue(dWeightedPressureSum)) { if(pError != NULL) { *pError = QString::fromLatin1( "The pore-volume weighted pressure is invalid."); } return false; } const double dAveragePressure = dWeightedPressureSum / dPoreVolumeSum; if(!isFiniteValue(dMinimumPressure) || !isFiniteValue(dMaximumPressure) || !isFiniteValue(dAveragePressure)) { if(pError != NULL) { *pError = QString::fromLatin1( "The regional pressure statistics are invalid."); } return false; } oStatistics.m_bAvailable = true; oStatistics.m_dEffectivePoreVolumeCubicMeters = dPoreVolumeSum; oStatistics.m_dMinimumPressureMpa = dMinimumPressure; oStatistics.m_dMaximumPressureMpa = dMaximumPressure; oStatistics.m_dPoreVolumeWeightedPressureMpa = dAveragePressure; return true; }