Code_TYMPAN  4.4.0
Industrial site acoustic simulation
TYAcousticModel9613Solver2024.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (C) <2012-2024> <EDF-DTG> <FRANCE>
3  * This file is part of Code_TYMPAN (R).
4  * Code_TYMPAN (R) is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  * Code_TYMPAN (R) is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
11  * See the GNU General Public License for more details.
12  * You should have received a copy of the GNU General Public License along
13  * with Code_TYMPAN (R). If not, see <https://www.gnu.org/licenses/>.
14  */
15 
17 #include <fstream>
18 #include <string>
19 
20 namespace
21 {
22 size_t readReflectionOrderFromConfig(const std::string& filePath, size_t defaultValue)
23 {
24  std::ifstream file(filePath);
25  if (!file.is_open())
26  {
27  return defaultValue;
28  }
29 
30  const std::string key = "REFLECTION_ORDER=";
31  std::string line;
32 
33  while (std::getline(file, line))
34  {
35  if (line.compare(0, key.size(), key) == 0)
36  {
37  try
38  {
39  return static_cast<size_t>(std::stoul(line.substr(key.size())));
40  }
41  catch (...)
42  {
43  return defaultValue;
44  }
45  }
46  }
47 
48  return defaultValue;
49 }
50 
52  double seuilConfondus)
53 {
54  const OPoint3D& A = segA._ptA;
55  const OPoint3D& B = segA._ptB;
56  const OPoint3D& C = segB._ptA;
57  const OPoint3D& D = segB._ptB;
58 
59  const double ux = B._x - A._x;
60  const double uy = B._y - A._y;
61  const double uz = B._z - A._z;
62 
63  const double vx = D._x - C._x;
64  const double vy = D._y - C._y;
65  const double vz = D._z - C._z;
66 
67  // Normal of the plane spanned by the 2 segment directions.
68  const double nx = uy * vz - uz * vy;
69  const double ny = uz * vx - ux * vz;
70  const double nz = ux * vy - uy * vx;
71 
72  const double anx = std::abs(nx);
73  const double any = std::abs(ny);
74  const double anz = std::abs(nz);
75 
76  // Degenerate or quasi-parallel case: keep legacy robust behavior.
77  if ((anx <= TYSEUILCONFONDUS) && (any <= TYSEUILCONFONDUS) && (anz <= TYSEUILCONFONDUS))
78  {
79  return segA.intersects(segB, pt, seuilConfondus) != INTERS_NULLE;
80  }
81 
82  // Project to the coordinate plane where the 2D determinant is the most stable.
83  double a1, a2, u1, u2, c1, c2, v1, v2;
84 
85  if ((anx >= any) && (anx >= anz))
86  {
87  // Drop X => work in YZ
88  a1 = A._y;
89  a2 = A._z;
90  u1 = uy;
91  u2 = uz;
92  c1 = C._y;
93  c2 = C._z;
94  v1 = vy;
95  v2 = vz;
96  }
97  else if ((any >= anx) && (any >= anz))
98  {
99  // Drop Y => work in XZ
100  a1 = A._x;
101  a2 = A._z;
102  u1 = ux;
103  u2 = uz;
104  c1 = C._x;
105  c2 = C._z;
106  v1 = vx;
107  v2 = vz;
108  }
109  else
110  {
111  // Drop Z => work in XY
112  a1 = A._x;
113  a2 = A._y;
114  u1 = ux;
115  u2 = uy;
116  c1 = C._x;
117  c2 = C._y;
118  v1 = vx;
119  v2 = vy;
120  }
121 
122  const double w1 = c1 - a1;
123  const double w2 = c2 - a2;
124 
125  const double det = u1 * v2 - u2 * v1;
126 
127  // Near-parallel in projected plane: keep legacy behavior.
128  if (std::abs(det) <= TYSEUILCONFONDUS)
129  {
130  return segA.intersects(segB, pt, seuilConfondus) != INTERS_NULLE;
131  }
132 
133  const double t = (w1 * v2 - w2 * v1) / det;
134  const double s = (w1 * u2 - w2 * u1) / det;
135 
136  const double lenU = std::sqrt(ux * ux + uy * uy + uz * uz);
137  const double lenV = std::sqrt(vx * vx + vy * vy + vz * vz);
138  const double paramTol = seuilConfondus / std::max(1.0, std::max(lenU, lenV));
139 
140  if ((t < -paramTol) || (t > 1.0 + paramTol) || (s < -paramTol) || (s > 1.0 + paramTol))
141  {
142  return false;
143  }
144 
145  pt._x = A._x + t * ux;
146  pt._y = A._y + t * uy;
147  pt._z = A._z + t * uz;
148 
149  return true;
150 }
151 } // namespace
152 
154  : TYAcousticModel9613Solver(solver)
155 {
156 }
157 
159 {
160  // zmin = -2 * lambda / (C2 * C3)
161  OSpectreOctave zmin = _lambda * (-2.0);
162  zmin = zmin.div(C3 * C2);
163  return zmin;
164 }
165 
166 OSpectreOctave TYAcousticModel9613Solver2024::calculKmeteo(const bool vertical, const double d_SS,
167  const double d_SR, const double d, const double z,
168  const double e, const OSpectreOctave& z_min) const
169 {
170  // K_meteo = exp{-(1 / 2000) * sqrt(([max(d_SS, d_SR) + e] * min(d_SS, d_SR) * d) / [2 * (z - z_min)])}
171  // for vertical diffraction K_meteo = 1 for lateral diffraction
172  //
173  // This is not specified explicitly in the standard, but we also set:
174  // K_meteo = 1 when z < z_min
175  // which avoids a division by zero and has no effect on the computation of Dz
176 
177  OSpectreOctave K_meteo{1.0};
178 
179  if (!vertical)
180  {
181  return K_meteo;
182  }
183 
184  double* kMeteo = K_meteo.getTabValReel();
185  const double* zMin = z_min.getTabValReel();
186 
187  const double dMax = std::max(d_SS, d_SR);
188  const double dMin = std::min(d_SS, d_SR);
189  const double numerator = (dMax + e) * dMin * d;
190 
191  for (std::size_t i = 0; i < TY_SPECTRE_OCT_NB_ELMT; ++i)
192  {
193  if (z > zMin[i])
194  {
195  kMeteo[i] = std::exp(-(1.0 / 2000.0) * std::sqrt(numerator / (2.0 * (z - zMin[i]))));
196  }
197  }
198 
199  return K_meteo;
200 }
201 
203  const OSpectreOctave& C3, const OSpectreOctave& Kmeteo,
204  const OSpectreOctave& zmin) const
205 {
206  // Dz = 10 * log10[1 + (2 + (C2 / lambda) * C3 * z) * Kmeteo] dB for z > zmin
207  // Dz = 0 dB for z <= zmin
208 
209  OSpectreOctave Dz{0.0};
210 
211  double* dz = Dz.getTabValReel();
212  const double* lambda = _lambda.getTabValReel();
213  const double* c3 = C3.getTabValReel();
214  const double* kmeteo = Kmeteo.getTabValReel();
215  const double* zMin = zmin.getTabValReel();
216 
217  for (std::size_t i = 0; i < TY_SPECTRE_OCT_NB_ELMT; ++i)
218  {
219  if (z > zMin[i])
220  {
221  const double term = 2.0 + (C2 / lambda[i]) * c3[i] * z;
222  dz[i] = 10.0 * std::log10(1.0 + term * kmeteo[i]);
223  }
224  }
225 
226  return Dz;
227 }
228 
229 void TYAcousticModel9613Solver2024::computeCheminReflexion(const std::deque<TYSIntersection>& tabIntersect,
230  const OSegment3D& ray,
231  const tympan::AcousticSource& source,
232  TYTabChemin9613Solver& TabChemins,
233  double distance) const
234 {
235  // Multiple reflection order
236  // TODO : Externalize in computation parameters
237  const std::string solverConfigPath = "C:\\projects\\tympan_tools\\17534-3\\solver_conf.txt";
238  const size_t defaultReflectionOrder = 1;
239  const size_t multipleReflectionOrder =
240  readReflectionOrderFromConfig(solverConfigPath, defaultReflectionOrder);
241 
242  if (multipleReflectionOrder == 0)
243  {
244  return;
245  }
246 
247  std::vector<ReflectingSegmentCache> reflectingSegments;
248  reflectingSegments.reserve(tabIntersect.size());
249 
250  // Keep only barriers that are both infrastructure and intersecting the EL plane.
251  // Their support-line geometry is cached once here, then reused during the DFS.
252  for (const auto& inter : tabIntersect)
253  {
254  if (inter.isInfra && inter.bIntersect[1])
255  {
256  reflectingSegments.push_back(makeReflectingSegmentCache(inter));
257  }
258  }
259 
260  if (reflectingSegments.empty())
261  {
262  return;
263  }
264 
265  std::vector<const TYSIntersection*> currentCombination;
266  currentCombination.reserve(multipleReflectionOrder);
267 
268  std::deque<OPoint3D> currentImageSources;
269  currentImageSources.push_back(ray._ptA);
270 
271  // Streaming exploration of ordered reflection combinations.
272  // Each valid prefix is turned directly into an acoustic path, avoiding the
273  // construction of an intermediate global set of combinations/candidates.
274  buildReflectionPathsStreaming(multipleReflectionOrder, reflectingSegments, currentCombination,
275  currentImageSources, tabIntersect, ray, source, TabChemins, distance);
276 }
277 
279  const std::deque<std::deque<TYSIntersection>>& tabIntersectSegments,
280  const std::deque<OPoint3D>& pathPoints2D, double hs, double hr, double& Gs, double& Gm, double& Gr) const
281 {
282  Gs = 0.5;
283  Gm = 0.5;
284  Gr = 0.5;
285 
286  if (!computeGroundFactorSourceZone(tabIntersectSegments, pathPoints2D, hs, Gs))
287  {
288  return false;
289  }
290 
291  if (!computeGroundFactorMiddleZone(tabIntersectSegments, pathPoints2D, hs, hr, Gm))
292  {
293  return false;
294  }
295 
296  if (!computeGroundFactorReceiverZone(tabIntersectSegments, pathPoints2D, hr, Gr))
297  {
298  return false;
299  }
300 
301  return true;
302 }
303 
305  const std::deque<std::deque<TYSIntersection>>& tabIntersectSegments,
306  const std::deque<OPoint3D>& pathPoints2D, double hs, double& Gs) const
307 {
308  double heightRatio = 30.0;
309  Gs = 0.5;
310 
311  if (pathPoints2D.size() < 2)
312  {
313  return false;
314  }
315 
316  if (tabIntersectSegments.size() != pathPoints2D.size() - 1)
317  {
318  return false;
319  }
320 
321  const double srcZoneLength = heightRatio * hs;
322 
323  // Same fallback behavior as legacy code.
324  if (srcZoneLength <= TYSEUILCONFONDUS)
325  {
326  Gs = 0.5;
327  return true;
328  }
329 
330  double traversedLength = 0.0; // curvilinear length along the broken path
331  double coveredLength = 0.0; // sum of dp returned by computeGZone
332  double weightedGroundSum = 0.0;
333 
334  for (size_t i = 0; i + 1 < pathPoints2D.size(); ++i)
335  {
336  const OPoint3D& ptA = pathPoints2D[i];
337  const OPoint3D& ptB = pathPoints2D[i + 1];
338 
339  const OSegment3D currentSegment(ptA, ptB);
340  const double currentSegmentLength = currentSegment.longueur();
341 
342  if (currentSegmentLength <= TYSEUILCONFONDUS)
343  {
344  continue;
345  }
346 
347  const double remainingLength = srcZoneLength - traversedLength;
348 
349  if (remainingLength <= TYSEUILCONFONDUS)
350  {
351  break;
352  }
353 
354  OPoint3D ptEndZone = ptB;
355 
356  // The source-zone limit lies inside the current segment.
357  if (remainingLength < currentSegmentLength)
358  {
359  const double alpha = remainingLength / currentSegmentLength;
360 
361  ptEndZone._x = ptA._x + alpha * (ptB._x - ptA._x);
362  ptEndZone._y = ptA._y + alpha * (ptB._y - ptA._y);
363  ptEndZone._z = ptA._z + alpha * (ptB._z - ptA._z);
364 
365  double GCurSeg = 0.0;
366  double dpCurSeg = 0.0;
367  computeGZone(ptA, ptEndZone, GCurSeg, dpCurSeg, tabIntersectSegments[i]);
368 
369  weightedGroundSum += GCurSeg;
370  coveredLength += dpCurSeg;
371  traversedLength = srcZoneLength;
372 
373  break;
374  }
375 
376  // The whole current segment belongs to the source zone.
377  double GCurSeg = 0.0;
378  double dpCurSeg = 0.0;
379  computeGZone(ptA, ptB, GCurSeg, dpCurSeg, tabIntersectSegments[i]);
380 
381  weightedGroundSum += GCurSeg;
382  coveredLength += dpCurSeg;
383  traversedLength += currentSegmentLength;
384  }
385 
386  if (coveredLength > TYSEUILCONFONDUS)
387  {
388  Gs = weightedGroundSum / coveredLength;
389  }
390  else
391  {
392  Gs = 0.5;
393  }
394 
395  return true;
396 }
397 
399  const std::deque<std::deque<TYSIntersection>>& tabIntersectSegments,
400  const std::deque<OPoint3D>& pathPoints2D, double hr, double& Gr) const
401 {
402  double heightRatio = 30.0;
403  Gr = 0.5;
404 
405  if (pathPoints2D.size() < 2)
406  {
407  return false;
408  }
409 
410  if (tabIntersectSegments.size() != pathPoints2D.size() - 1)
411  {
412  return false;
413  }
414 
415  const double rcpZoneLength = heightRatio * hr;
416 
417  // Same fallback behavior as legacy code.
418  if (rcpZoneLength <= TYSEUILCONFONDUS)
419  {
420  Gr = 0.5;
421  return true;
422  }
423 
424  double traversedLength = 0.0; // curvilinear length traversed backward from receiver
425  double coveredLength = 0.0; // sum of dp returned by computeGZone
426  double weightedGroundSum = 0.0;
427 
428  for (size_t i = pathPoints2D.size() - 1; i > 0; --i)
429  {
430  const OPoint3D& ptA = pathPoints2D[i - 1];
431  const OPoint3D& ptB = pathPoints2D[i];
432 
433  const OSegment3D currentSegment(ptA, ptB);
434  const double currentSegmentLength = currentSegment.longueur();
435 
436  if (currentSegmentLength <= TYSEUILCONFONDUS)
437  {
438  continue;
439  }
440 
441  const double remainingLength = rcpZoneLength - traversedLength;
442 
443  if (remainingLength <= TYSEUILCONFONDUS)
444  {
445  break;
446  }
447 
448  // The receiver-zone limit lies inside the current segment.
449  if (remainingLength < currentSegmentLength)
450  {
451  const double alpha = remainingLength / currentSegmentLength;
452 
453  // Start point of the receiver zone on the current segment,
454  // located at distance "remainingLength" from ptB toward ptA.
455  OPoint3D ptStartZone;
456  ptStartZone._x = ptB._x + alpha * (ptA._x - ptB._x);
457  ptStartZone._y = ptB._y + alpha * (ptA._y - ptB._y);
458  ptStartZone._z = ptB._z + alpha * (ptA._z - ptB._z);
459 
460  double GCurSeg = 0.0;
461  double dpCurSeg = 0.0;
462  computeGZone(ptStartZone, ptB, GCurSeg, dpCurSeg, tabIntersectSegments[i - 1]);
463 
464  weightedGroundSum += GCurSeg;
465  coveredLength += dpCurSeg;
466  traversedLength = rcpZoneLength;
467 
468  break;
469  }
470 
471  // The whole current segment belongs to the receiver zone.
472  double GCurSeg = 0.0;
473  double dpCurSeg = 0.0;
474  computeGZone(ptA, ptB, GCurSeg, dpCurSeg, tabIntersectSegments[i - 1]);
475 
476  weightedGroundSum += GCurSeg;
477  coveredLength += dpCurSeg;
478  traversedLength += currentSegmentLength;
479  }
480 
481  if (coveredLength > TYSEUILCONFONDUS)
482  {
483  Gr = weightedGroundSum / coveredLength;
484  }
485  else
486  {
487  Gr = 0.5;
488  }
489 
490  return true;
491 }
492 
494  const std::deque<std::deque<TYSIntersection>>& tabIntersectSegments,
495  const std::deque<OPoint3D>& pathPoints2D, double hs, double hr, double& Gm) const
496 {
497  double heightRatio = 30.0;
498  Gm = 0.5;
499 
500  if (pathPoints2D.size() < 2)
501  {
502  return false;
503  }
504 
505  if (tabIntersectSegments.size() != pathPoints2D.size() - 1)
506  {
507  return false;
508  }
509 
510  const double srcZoneLength = heightRatio * hs;
511  const double rcpZoneLength = heightRatio * hr;
512 
513  // Compute total curvilinear length of the broken path.
514  double dp = 0.0;
515  for (size_t i = 0; i + 1 < pathPoints2D.size(); ++i)
516  {
517  dp += OSegment3D(pathPoints2D[i], pathPoints2D[i + 1]).longueur();
518  }
519 
520  // No middle zone when source and receiver zones touch or overlap.
521  if ((srcZoneLength + rcpZoneLength) >= (dp - TYSEUILCONFONDUS))
522  {
523  Gm = 0.5;
524  return true;
525  }
526 
527  const double middleZoneBegin = srcZoneLength;
528  const double middleZoneEnd = dp - rcpZoneLength;
529 
530  double traversedLength = 0.0; // curvilinear abscissa at segment start
531  double coveredLength = 0.0; // sum of dp returned by computeGZone
532  double weightedGroundSum = 0.0;
533 
534  for (size_t i = 0; i + 1 < pathPoints2D.size(); ++i)
535  {
536  const OPoint3D& ptA = pathPoints2D[i];
537  const OPoint3D& ptB = pathPoints2D[i + 1];
538 
539  const OSegment3D currentSegment(ptA, ptB);
540  const double currentSegmentLength = currentSegment.longueur();
541 
542  if (currentSegmentLength <= TYSEUILCONFONDUS)
543  {
544  continue;
545  }
546 
547  const double segmentBegin = traversedLength;
548  const double segmentEnd = traversedLength + currentSegmentLength;
549 
550  // Overlap between current segment and middle zone in curvilinear abscissa.
551  const double overlapBegin = std::max(segmentBegin, middleZoneBegin);
552  const double overlapEnd = std::min(segmentEnd, middleZoneEnd);
553 
554  if ((overlapEnd - overlapBegin) > TYSEUILCONFONDUS)
555  {
556  const double alphaBegin = (overlapBegin - segmentBegin) / currentSegmentLength;
557  const double alphaEnd = (overlapEnd - segmentBegin) / currentSegmentLength;
558 
559  OPoint3D ptZoneBegin;
560  ptZoneBegin._x = ptA._x + alphaBegin * (ptB._x - ptA._x);
561  ptZoneBegin._y = ptA._y + alphaBegin * (ptB._y - ptA._y);
562  ptZoneBegin._z = ptA._z + alphaBegin * (ptB._z - ptA._z);
563 
564  OPoint3D ptZoneEnd;
565  ptZoneEnd._x = ptA._x + alphaEnd * (ptB._x - ptA._x);
566  ptZoneEnd._y = ptA._y + alphaEnd * (ptB._y - ptA._y);
567  ptZoneEnd._z = ptA._z + alphaEnd * (ptB._z - ptA._z);
568 
569  double GCurSeg = 0.0;
570  double dpCurSeg = 0.0;
571  computeGZone(ptZoneBegin, ptZoneEnd, GCurSeg, dpCurSeg, tabIntersectSegments[i]);
572 
573  weightedGroundSum += GCurSeg;
574  coveredLength += dpCurSeg;
575  }
576 
577  traversedLength = segmentEnd;
578  }
579 
580  if (coveredLength > TYSEUILCONFONDUS)
581  {
582  Gm = weightedGroundSum / coveredLength;
583  }
584  else
585  {
586  Gm = 0.5;
587  }
588 
589  return true;
590 }
591 
593  const TYSIntersection& rhs) const
594 {
595  const OSegment3D& seg1 = lhs.segInter[1];
596  const OSegment3D& seg2 = rhs.segInter[1];
597 
598  const bool sameOrder = (seg1._ptA == seg2._ptA) && (seg1._ptB == seg2._ptB);
599 
600  const bool reverseOrder = (seg1._ptA == seg2._ptB) && (seg1._ptB == seg2._ptA);
601 
602  return sameOrder || reverseOrder;
603 }
604 
606  const std::vector<const TYSIntersection*>& currentCombination,
607  const std::deque<OPoint3D>& imageSourcesList, const OPoint3D& receptorPoint,
608  std::deque<OPoint3D>& reflectionPointsList) const
609 {
610  reflectionPointsList.clear();
611 
612  if (currentCombination.empty())
613  {
614  return true;
615  }
616 
617  // By convention:
618  // imageSourcesList[0] = S0 = source point
619  // imageSourcesList[i] = image source after i reflections
620  if (imageSourcesList.size() != currentCombination.size() + 1)
621  {
622  return false;
623  }
624 
625  OPoint3D nextPoint = receptorPoint; // P(n+1) = R
626 
627  // Reflection points are reconstructed backward:
628  // starting from the receptor, intersect the current image ray with the
629  // corresponding reflecting segment, then continue with the previous order.
630  for (int i = static_cast<int>(currentCombination.size()) - 1; i >= 0; --i)
631  {
632  const OPoint3D& currentImageSource = imageSourcesList[i + 1]; // Si
633  const OSegment3D& currentBarrier = currentCombination[i]->segInter[1]; // Bi
634 
635  OPoint3D currentReflectionPoint;
636 
637  // Intersection must be computed on segments, not on infinite lines.
638  if (!intersectSegmentsFastInTheirPlane(currentBarrier, OSegment3D(currentImageSource, nextPoint),
639  currentReflectionPoint, TYSEUILCONFONDUS))
640  {
641  reflectionPointsList.clear();
642  return false;
643  }
644 
645  reflectionPointsList.push_front(currentReflectionPoint);
646  nextPoint = currentReflectionPoint;
647  }
648 
649  return true;
650 }
651 
653  const std::vector<const TYSIntersection*>& barrierCombination,
654  const std::deque<OPoint3D>& reflectionPointsList, const std::deque<TYSIntersection>& tabIntersect,
655  const OPoint3D& sourcePoint, const OPoint3D& receptorPoint) const
656 {
657  const size_t reflectionOrder = barrierCombination.size();
658 
659  if (reflectionPointsList.size() != reflectionOrder)
660  {
661  return false;
662  }
663 
664  std::deque<OPoint3D> pathPoints;
665  pathPoints.push_back(sourcePoint);
666  for (const auto& point : reflectionPointsList)
667  {
668  pathPoints.push_back(point);
669  }
670  pathPoints.push_back(receptorPoint);
671 
672  // For each leg [Pi, P(i+1)], only the reflecting barriers on which belong Pi or P(i+1) are allowed to
673  // touch the path. Any other barrier intersection invalidates the candidate.
674  for (size_t i = 0; i <= reflectionOrder; ++i)
675  {
676  const OSegment3D pathSegment(pathPoints[i], pathPoints[i + 1]);
677 
678  for (const auto& sceneBarrier : tabIntersect)
679  {
680  if ((i >= 1) && sameReflectingSegment(sceneBarrier, *barrierCombination[i - 1]))
681  {
682  continue;
683  }
684 
685  if ((i < reflectionOrder) && sameReflectingSegment(sceneBarrier, *barrierCombination[i]))
686  {
687  continue;
688  }
689 
690  OPoint3D intersectionPoint;
691  if (sceneBarrier.segInter[1].intersects(pathSegment, intersectionPoint, TYSEUILCONFONDUS))
692  {
693  return false;
694  }
695  }
696  }
697 
698  return true;
699 }
700 
702  const std::vector<const TYSIntersection*>& barrierCombination,
703  const std::deque<OPoint3D>& imageSourcesList, const std::deque<OPoint3D>& reflectionPointsList,
704  const OSegment3D& directRay, const tympan::AcousticSource& source, TYTabChemin9613Solver& TabChemins,
705  double distance) const
706 {
707  const size_t reflectionOrder = barrierCombination.size();
708 
709  if (reflectionOrder == 0)
710  {
711  return false;
712  }
713 
714  if (imageSourcesList.size() != reflectionOrder + 1)
715  {
716  return false;
717  }
718 
719  if (reflectionPointsList.size() != reflectionOrder)
720  {
721  return false;
722  }
723 
724  // Build path points:
725  // P0 = S
726  // P1 ... Pn = reflection points
727  // P(n+1) = R
728  std::deque<OPoint3D> pathPoints;
729  std::deque<OPoint3D> pathPoints2D;
730  pathPoints.push_back(directRay._ptA);
731 
732  for (const auto& point : reflectionPointsList)
733  {
734  pathPoints.push_back(point);
735  }
736 
737  pathPoints.push_back(directRay._ptB);
738 
739  TYTabEtape9613Solver tabEtapes;
740  double pathLength = 0.0;
741  double dp = 0.0;
742 
743  OPoint3D A2D;
744  OPoint3D B2D;
745  for (size_t i = 0; i + 1 < pathPoints.size(); ++i)
746  {
747  OSegment3D segment(pathPoints[i], pathPoints[i + 1]);
748  pathLength += segment.longueur();
749 
750  A2D = OPoint3D{segment._ptA._x, segment._ptA._y, 0.0};
751  B2D = OPoint3D{segment._ptB._x, segment._ptB._y, 0.0};
752 
753  // dp is the broken-path length projected in the horizontal plane.
754  dp += OSegment3D{A2D, B2D}.longueur();
755  pathPoints2D.push_back(A2D);
756  }
757  pathPoints2D.push_back(B2D);
758 
759  {
760  TYEtape9613Solver etape;
761  etape._pt = pathPoints[0];
762  etape._type = TYSOURCE;
763 
764  const OSegment3D firstLeg(pathPoints[0], pathPoints[1]);
765  etape._Absorption =
766  source.directivity->lwAdjustment(OVector3D(firstLeg._ptA, firstLeg._ptB), firstLeg.longueur());
767 
768  tabEtapes.push_back(etape);
769  }
770 
771  for (size_t i = 0; i < reflectionOrder; ++i)
772  {
774  dynamic_cast<tympan::AcousticBuildingMaterial*>(barrierCombination[i]->material);
775 
776  if (material == nullptr)
777  {
778  return false;
779  }
780 
781  if (!barrierCombination[i]->pFaceGeomData)
782  {
783  return false;
784  }
785 
786  TYEtape9613Solver etape;
787  etape._pt = reflectionPointsList[i];
788  etape._type = TYREFLEXION;
789  etape._Absorption = material->spectrum;
790 
791  tabEtapes.push_back(etape);
792  }
793 
794  OSegment3D segMeanSlope;
795  meanSlope(directRay, segMeanSlope);
796 
797  double hs{0.0}, hr{0.0};
798  computeSegmentEdgesHeights(hs, hr, segMeanSlope, directRay);
799 
800  double Gs{0.0}, Gm{0.0}, Gr{0.0};
801 
802  // Recompute the scene intersections for each broken-path leg [Pi, P(i+1)].
803  // Ground factors must be evaluated on the reflected path, not on the direct ray.
804  std::deque<std::deque<TYSIntersection>> tabIntersectSegments;
805  OSegment3D seg;
806  tabIntersectSegments.resize(reflectionOrder + 1);
807  for (size_t i = 0; i < reflectionOrder + 1; ++i)
808  {
809  seg = OSegment3D{pathPoints[i], pathPoints[i + 1]};
810  _solver.selectFaces(tabIntersectSegments[i], seg, source.volume_id);
811  }
812 
813  getGroundfactors(tabIntersectSegments, pathPoints2D, hs, hr, Gs, Gm, Gr);
814 
815  std::unique_ptr<TYChemin9613Solver> chemin = createChemin();
816  chemin->setType(TYTypeChemin::CHEMIN_REFLEX);
817  chemin->setLongueur(pathLength);
818  chemin->setDistance(distance);
819  chemin->calcAttenuation(tabEtapes, *_pSolverAtmos, dp, hs, hr, Gs, Gm, Gr);
820 
821  // The minimal extension condition is evaluated reflection by reflection on
822  // the broken reflected path.
823  for (size_t i = 0; i < reflectionOrder; ++i)
824  {
825  const OPoint3D& S = pathPoints[i];
826  const OPoint3D& O = pathPoints[i + 1];
827  const OPoint3D& R = pathPoints[i + 2];
828 
829  const double a = barrierCombination[i]->pFaceGeomData->a;
830  const double h = barrierCombination[i]->pFaceGeomData->h;
831  const OVector3D& normal = barrierCombination[i]->pFaceGeomData->n;
832 
833  chemin->calcMinimalExtensionCondition(S, O, R, a, h, normal);
834  }
835 
836  TabChemins.push_back(*chemin);
837  return true;
838 }
839 
842 {
844  cache.barrier = &inter;
845 
846  const OSegment3D& seg = inter.segInter[1];
847 
848  cache.ax = seg._ptA._x;
849  cache.ay = seg._ptA._y;
850  cache.az = seg._ptA._z;
851 
852  cache.ux = seg._ptB._x - seg._ptA._x;
853  cache.uy = seg._ptB._y - seg._ptA._y;
854  cache.uz = seg._ptB._z - seg._ptA._z;
855 
856  // The cache stores the support line of the reflecting segment.
857  // invNorm2 is used to project points on this line without recomputing the
858  // segment norm at each recursive step.
859  const double norm2 = cache.ux * cache.ux + cache.uy * cache.uy + cache.uz * cache.uz;
860  if (norm2 > TYSEUILCONFONDUS * TYSEUILCONFONDUS)
861  {
862  cache.invNorm2 = 1.0 / norm2;
863  }
864  else
865  {
866  cache.invNorm2 = 0.0;
867  }
868 
869  return cache;
870 }
871 
873  const OPoint3D& inputPoint,
874  OPoint3D& reflectedPoint) const
875 {
876  // Keep legacy behavior for degenerate cases.
877  if ((cache.barrier == nullptr) || (cache.invNorm2 <= 0.0))
878  {
879  if (cache.barrier != nullptr)
880  {
881  cache.barrier->segInter[1].symetrieOf(inputPoint, reflectedPoint);
882  }
883  return;
884  }
885 
886  const double apx = inputPoint._x - cache.ax;
887  const double apy = inputPoint._y - cache.ay;
888  const double apz = inputPoint._z - cache.az;
889 
890  const double t = (apx * cache.ux + apy * cache.uy + apz * cache.uz) * cache.invNorm2;
891 
892  const double hx = cache.ax + t * cache.ux;
893  const double hy = cache.ay + t * cache.uy;
894  const double hz = cache.az + t * cache.uz;
895 
896  // Reflection is performed about the support line of the segment in plane EL.
897  reflectedPoint._x = 2.0 * hx - inputPoint._x;
898  reflectedPoint._y = 2.0 * hy - inputPoint._y;
899  reflectedPoint._z = 2.0 * hz - inputPoint._z;
900 }
901 
903  size_t reflectionOrder, const std::vector<ReflectingSegmentCache>& reflectingSegments,
904  std::vector<const TYSIntersection*>& currentCombination, std::deque<OPoint3D>& currentImageSources,
905  const std::deque<TYSIntersection>& tabIntersect, const OSegment3D& ray,
906  const tympan::AcousticSource& source, TYTabChemin9613Solver& TabChemins, double distance) const
907 {
908  if (currentCombination.size() == reflectionOrder)
909  {
910  return;
911  }
912 
913  for (const auto& segment : reflectingSegments)
914  {
915  // Only consecutive reuse of the same reflecting segment is forbidden.
916  if (!currentCombination.empty() && currentCombination.back() == segment.barrier)
917  {
918  continue;
919  }
920 
921  const OPoint3D& previousImageSource = currentImageSources.back();
922 
923  OPoint3D nextImageSource;
924  reflectPointAboutCachedLine(segment, previousImageSource, nextImageSource);
925 
926  currentCombination.push_back(segment.barrier);
927  currentImageSources.push_back(nextImageSource);
928 
929  // currentImageSources always stores:
930  // S0 = source point, then one image source per current reflection.
931  buildReflectionPathForCombination(currentCombination, currentImageSources, tabIntersect, ray, source,
932  TabChemins, distance);
933 
934  buildReflectionPathsStreaming(reflectionOrder, reflectingSegments, currentCombination,
935  currentImageSources, tabIntersect, ray, source, TabChemins, distance);
936 
937  currentImageSources.pop_back();
938  currentCombination.pop_back();
939  }
940 }
941 
943  const std::vector<const TYSIntersection*>& currentCombination,
944  const std::deque<OPoint3D>& imageSourcesList, const std::deque<TYSIntersection>& tabIntersect,
945  const OSegment3D& ray, const tympan::AcousticSource& source, TYTabChemin9613Solver& TabChemins,
946  double distance) const
947 {
948  std::deque<OPoint3D> reflectionPointsList;
949 
950  // Geometry is built in 3 stages:
951  // 1. reconstruct reflection points from current image sources,
952  // 2. validate the broken path against the scene,
953  // 3. build the acoustic path only for valid combinations.
954  if (!buildReflectionPoints(currentCombination, imageSourcesList, ray._ptB, reflectionPointsList))
955  {
956  return false;
957  }
958 
959  if (!validateReflectionCandidate(currentCombination, reflectionPointsList, tabIntersect, ray._ptA,
960  ray._ptB))
961  {
962  return false;
963  }
964 
965  return buildReflectionPath(currentCombination, imageSourcesList, reflectionPointsList, ray, source,
966  TabChemins, distance);
967 }
#define TYSEUILCONFONDUS
Definition: 3d.h:47
#define INTERS_NULLE
No intersection.
Definition: 3d.h:35
NxReal s
Definition: NxVec3.cpp:317
std::pair< unsigned int, unsigned int > segment
#define min(a, b)
std::deque< TYChemin9613Solver > TYTabChemin9613Solver
TYChemin collection.
std::deque< TYEtape9613Solver > TYTabEtape9613Solver
TYEtape collection.
@ TYREFLEXION
Definition: acoustic_path.h:25
@ TYSOURCE
Definition: acoustic_path.h:28
double _y
y coordinate of OCoord3D
Definition: 3d.h:283
double _z
z coordinate of OCoord3D
Definition: 3d.h:284
double _x
x coordinate of OCoord3D
Definition: 3d.h:282
The 3D point class.
Definition: 3d.h:487
Class to define a segment.
Definition: 3d.h:1141
virtual double longueur() const
Return the segment length.
Definition: 3d.cpp:1238
virtual int symetrieOf(const OPoint3D &pt, OPoint3D &ptSym) const
Return the symmetrical of a point.
Definition: 3d.cpp:1243
OPoint3D _ptA
Point A of the segment.
Definition: 3d.h:1253
virtual int intersects(const OSegment3D &seg, OPoint3D &pt, double seuilConfondus) const
Return the intersection point with another segment.
Definition: 3d.cpp:1267
OPoint3D _ptB
Point B of the segment.
Definition: 3d.h:1255
OSpectreAbstract & div(const OSpectreAbstract &spectre) const
Division of two spectrums.
Definition: spectre.cpp:278
double * getTabValReel() override
Get an array of the real values of the spectrum.
Definition: spectre.h:614
The 3D vector class.
Definition: 3d.h:298
TYAcousticModel9613Solver2024(TYSolver9613Solver2024 &solver)
bool validateReflectionCandidate(const std::vector< const TYSIntersection * > &barrierCombination, const std::deque< OPoint3D > &reflectionPointsList, const std::deque< TYSIntersection > &tabIntersect, const OPoint3D &sourcePoint, const OPoint3D &receptorPoint) const
Validate a reflection candidate against the reflecting segments of the scene. A reflection candidate ...
bool computeGroundFactorMiddleZone(const std::deque< std::deque< TYSIntersection >> &tabIntersectSegments, const std::deque< OPoint3D > &pathPoints2D, double hs, double hr, double &Gm) const
Compute the ground factor of the middle zone for a reflected path.
std::unique_ptr< TYChemin9613Solver > createChemin() const override
bool sameReflectingSegment(const TYSIntersection &lhs, const TYSIntersection &rhs) const
Check whether two reflecting segments are identical in plane EL.
ReflectingSegmentCache makeReflectingSegmentCache(const TYSIntersection &inter) const
Build the geometric cache associated with one reflecting segment.
void buildReflectionPathsStreaming(size_t reflectionOrder, const std::vector< ReflectingSegmentCache > &reflectingSegments, std::vector< const TYSIntersection * > &currentCombination, std::deque< OPoint3D > &currentImageSources, const std::deque< TYSIntersection > &tabIntersect, const OSegment3D &ray, const tympan::AcousticSource &source, TYTabChemin9613Solver &TabChemins, double distance) const
Recursively build and validate reflection paths in streaming mode.
OSpectreOctave calculZMin(const double C2, const OSpectreOctave &C3) const override
Compute zmin, the min value of z for which the barrier attenuation Dz is null. This minimum distance ...
void computeCheminReflexion(const std::deque< TYSIntersection > &tabIntersect, const OSegment3D &ray, const tympan::AcousticSource &source, TYTabChemin9613Solver &TabChemins, double distance) const override
Compute the list of path generated by reflection on the vertical walls.
void reflectPointAboutCachedLine(const ReflectingSegmentCache &cache, const OPoint3D &inputPoint, OPoint3D &reflectedPoint) const
Reflect a point about the support line stored in a reflecting segment cache.
bool getGroundfactors(const std::deque< std::deque< TYSIntersection >> &tabIntersectSegments, const std::deque< OPoint3D > &pathPoints2D, double hs, double hr, double &Gs, double &Gm, double &Gr) const
Get ground factors for source, middle and receptor zones for a reflected path.
bool computeGroundFactorReceiverZone(const std::deque< std::deque< TYSIntersection >> &tabIntersectSegments, const std::deque< OPoint3D > &pathPoints2D, double hr, double &Gr) const
Compute the ground factor of the receptor zone for a reflected path.
bool computeGroundFactorSourceZone(const std::deque< std::deque< TYSIntersection >> &tabIntersectSegments, const std::deque< OPoint3D > &pathPoints2D, double hs, double &Gs) const
Compute the ground factor of the source zone for a reflected path.
bool buildReflectionPathForCombination(const std::vector< const TYSIntersection * > &currentCombination, const std::deque< OPoint3D > &imageSourcesList, const std::deque< TYSIntersection > &tabIntersect, const OSegment3D &ray, const tympan::AcousticSource &source, TYTabChemin9613Solver &TabChemins, double distance) const
Build a reflection path from the current reflecting segment combination.
OSpectreOctave calculDz(const double z, const double C2, const OSpectreOctave &C3, const OSpectreOctave &Kmeteo, const OSpectreOctave &zmin) const override
Compute Dz, the barrier attenuation for each octave band in dB.
bool buildReflectionPath(const std::vector< const TYSIntersection * > &barrierCombination, const std::deque< OPoint3D > &imageSourcesList, const std::deque< OPoint3D > &reflectionPointsList, const OSegment3D &directRay, const tympan::AcousticSource &source, TYTabChemin9613Solver &TabChemins, double distance) const
Build a reflection path from a valid reflecting segment combination.
bool buildReflectionPoints(const std::vector< const TYSIntersection * > &currentCombination, const std::deque< OPoint3D > &imageSourcesList, const OPoint3D &receptorPoint, std::deque< OPoint3D > &reflectionPointsList) const
Build the list of reflection points associated with a reflecting segment combination.
OSpectreOctave calculKmeteo(const bool vertical, const double d_SS, const double d_SR, const double d, const double z, const double e, const OSpectreOctave &z_min) const override
Compute Kmeteo, the correction factor for meteorological effects.
Acoustic model for the 9613Solver.
TYSolver9613Solver & _solver
Reference to the solver.
bool computeGZone(const OPoint3D &ptDebut, const OPoint3D &ptFin, double &GZone, double &dpZone, const std::deque< TYSIntersection > &tabIntersect) const
Compute GZone and dpZone for the segment between ptDebut and ptFin.
bool computeSegmentEdgesHeights(double &hauteurA, double &hauteurB, const OSegment3D &meanSlope, const OSegment3D &ray) const
Compute heights relative to real ground, of the edges of a segment.
std::unique_ptr< AtmosphericConditions > _pSolverAtmos
void meanSlope(const OSegment3D &director, OSegment3D &slope) const
Create a segment corresponding to the projection of "director" segment on the ground.
OSpectreOctave _Absorption
absorption Spectrum
OPoint3D _pt
The starting point of this step.
Definition: TYEtape.h:109
ACOUSTIC_EVENT_TYPES _type
Acoustic event type.
Definition: TYEtape.h:108
9613 Solver version 2024
void selectFaces(std::deque< TYSIntersection > &tabIntersect, const OSegment3D &rayon, const string &sourceVolumeId)
Delegate to _faceSelector the build of the array of intersections.
Definition: TYSolver.cpp:148
Describes building material.
Definition: entities.hpp:64
ComplexSpectrum spectrum
Spectrum to store acoustic values at different frequencies.
Definition: entities.hpp:82
Describes an acoustic source.
Definition: entities.hpp:394
string volume_id
Volume id.
Definition: entities.hpp:404
SourceDirectivityInterface * directivity
Pointer to the source directivity.
Definition: entities.hpp:403
virtual Spectrum lwAdjustment(Vector direction, double distance)=0
< Pure virtual method to return directivity of the Source
bool intersectSegmentsFastInTheirPlane(const OSegment3D &segA, const OSegment3D &segB, OPoint3D &pt, double seuilConfondus)
size_t readReflectionOrderFromConfig(const std::string &filePath, size_t defaultValue)
Cache geometry associated with one reflecting segment.
Data structure for intersections.
OSegment3D segInter[2]