LCOV - code coverage report
Current view: top level - simulation/helpers - Walls.js (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 41 0.0 %
Date: 2023-04-02 12:52:40 Functions: 0 3 0.0 %

          Line data    Source code
       1             : /**
       2             :  * Returns the wall piece entities needed to construct a wall between start.pos and end.pos. Assumes start.pos != end.pos.
       3             :  * The result is an array of objects, each one containing the following information about a single wall piece entity:
       4             :  *   - 'template': the template name of the entity
       5             :  *   - 'pos': position of the entity, as an object with keys 'x' and 'z'
       6             :  *   - 'angle': orientation of the entity, as an angle in radians
       7             :  *
       8             :  * All the pieces in the resulting array are ordered left-to-right (or right-to-left) as they appear in the physical wall.
       9             :  *
      10             :  * @param placementData Object that associates the wall piece template names with information about those kinds of pieces.
      11             :  *                        Expects placementData[templateName].templateData to contain the parsed template information about
      12             :  *                        the template whose filename is <i>templateName</i>.
      13             :  * @param wallSet Object that primarily holds the template names for the supported wall pieces in this set (under the
      14             :  *                  'templates' key), as well as the min and max allowed overlap factors (see GetWallSegmentsRec). Expected
      15             :  *                  to contain template names for keys "long" (long wall segment), "medium" (medium wall segment), "short"
      16             :  *                  (short wall segment), "tower" (intermediate tower between wall segments), "gate" (replacement for long
      17             :  *                  walls).
      18             :  * @param start Object holding the starting position of the wall. Must contain keys 'x' and 'z'.
      19             :  * @param end   Object holding the ending position of the wall. Must contains keys 'x' and 'z'.
      20             :  */
      21             : function GetWallPlacement(placementData, wallSet, start, end)
      22             : {
      23           0 :         let candidateSegments = ["long", "medium", "short"].map(size => ({
      24             :                 "template": wallSet.templates[size],
      25             :                 "len": placementData[wallSet.templates[size]].templateData.wallPiece.length
      26             :         }));
      27             : 
      28           0 :         let towerWidth = placementData[wallSet.templates.tower].templateData.wallPiece.length;
      29             : 
      30           0 :         let dir = {
      31             :                 "x": end.pos.x - start.pos.x,
      32             :                 "z": end.pos.z - start.pos.z
      33             :         };
      34             : 
      35           0 :         let len = Math.sqrt(dir.x * dir.x + dir.z * dir.z);
      36             : 
      37             :         // we'll need room for at least our starting and ending towers to fit next to eachother
      38           0 :         if (len <= towerWidth)
      39           0 :                 return [];
      40             : 
      41           0 :         let placement = GetWallSegmentsRec(
      42             :                 len,
      43             :                 candidateSegments,
      44             :                 wallSet.minTowerOverlap,
      45             :                 wallSet.maxTowerOverlap,
      46             :                 towerWidth,
      47             :                 0, []
      48             :         );
      49             : 
      50             :         // TODO: make sure intermediate towers are spaced out far enough for their obstructions to not overlap, implying that
      51             :         // tower's wallpiece lengths should be > their obstruction width, which is undesirable because it prevents towers with
      52             :         // wide bases
      53           0 :         if (!placement)
      54             :         {
      55           0 :                 error("No placement possible for distance=" +
      56             :                         Math.round(len * 1000) / 1000.0 +
      57             :                         ", minOverlap=" + wallSet.minTowerOverlap +
      58             :                         ", maxOverlap=" + wallSet.maxTowerOverlap);
      59             : 
      60           0 :                 return [];
      61             :         }
      62             : 
      63             :         // List of chosen candidate segments
      64           0 :         let placedEntities = placement.segments;
      65             : 
      66             :         // placement.r is the remaining distance to target without towers (must be <= (N-1) * towerWidth)
      67           0 :         let spacing = placement.r / (2 * placedEntities.length);
      68             : 
      69           0 :         let dirNormalized = { "x": dir.x / len, "z": dir.z / len };
      70             : 
      71             :         // Angle of this wall segment (relative to world-space X/Z axes)
      72           0 :         let angle = -Math.atan2(dir.z, dir.x);
      73             : 
      74           0 :         let progress = 0;
      75           0 :         let result = [];
      76             : 
      77           0 :         for (let i = 0; i < placedEntities.length; ++i)
      78             :         {
      79           0 :                 let placedEntity = placedEntities[i];
      80             : 
      81           0 :                 result.push({
      82             :                         "template": placedEntity.template,
      83             :                         "pos": {
      84             :                                 "x": start.pos.x + (progress + spacing + placedEntity.len/2) * dirNormalized.x,
      85             :                                 "z": start.pos.z + (progress + spacing + placedEntity.len/2) * dirNormalized.z
      86             :                         },
      87             :                         "angle": angle,
      88             :                 });
      89             : 
      90           0 :                 if (i < placedEntities.length - 1)
      91             :                 {
      92           0 :                         result.push({
      93             :                                 "template": wallSet.templates.tower,
      94             :                                 "pos": {
      95             :                                         "x": start.pos.x + (progress + placedEntity.len + 2 * spacing) * dirNormalized.x,
      96             :                                         "z": start.pos.z + (progress + placedEntity.len + 2 * spacing) * dirNormalized.z
      97             :                                 },
      98             :                                 "angle": angle,
      99             :                         });
     100             :                 }
     101             : 
     102           0 :                 progress += placedEntity.len + 2 * spacing;
     103             :         }
     104             : 
     105           0 :         return result;
     106             : }
     107             : 
     108             : /**
     109             :  * Helper function for GetWallPlacement. Finds a list of wall segments and the corresponding remaining spacing/overlap
     110             :  * distance "r" that will suffice to construct a wall of the given distance. It is understood that two extra towers will
     111             :  * be placed centered at the starting and ending points of the wall.
     112             :  *
     113             :  * @param d Total distance between starting and ending points (constant throughout calls).
     114             :  * @param candidateSegments List of candidate segments (constant throughout calls). Should be ordered longer-to-shorter
     115             :  *                            for better execution speed.
     116             :  * @param minOverlap Minimum overlap factor (constant throughout calls). Must have a value between 0 (meaning walls are
     117             :  *                     not allowed to overlap towers) and 1 (meaning they're allowed to overlap towers entirely).
     118             :  *                     Must be <= maxOverlap.
     119             :  * @param maxOverlap Maximum overlap factor (constant throughout calls). Must have a value between 0 (meaning walls are
     120             :  *                     not allowed to overlap towers) and 1 (meaning they're allowed to overlap towers entirely).
     121             :  *                     Must be >= minOverlap.
     122             :  * @param t Length of a single tower (constant throughout calls). Acts as buffer space for wall segments (see comments).
     123             :  * @param distSoFar Sum of all the wall segments' lengths in 'segments'.
     124             :  * @param segments Current list of wall segments placed.
     125             :  */
     126             : function GetWallSegmentsRec(d, candidateSegments, minOverlap, maxOverlap, t, distSoFar, segments)
     127             : {
     128             :         // The idea is to find a number N of wall segments (excluding towers) so that the sum of their lengths adds up to a
     129             :         // value that is within certain bounds of the distance 'd' between the starting and ending points of the wall. This
     130             :         // creates either a positive or negative 'buffer' of space, that can be compensated for by spacing the wall segments
     131             :         // out away from each other, or inwards, overlapping each other. The spaces or overlaps can then be covered up by
     132             :         // placing towers on top of them. In this way, the same set of wall segments can be used to span a wider range of
     133             :         // target distances.
     134             :         //
     135             :         // In this function, it is understood that two extra towers will be placed centered at the starting and ending points.
     136             :         // They are allowed to contribute to the buffer space.
     137             :         //
     138             :         // The buffer space equals the difference between d and the sum of the lengths of all the wall segments, and is denoted
     139             :         // 'r' for 'remaining space'. Positive values of r mean that the walls will need to be spaced out, negative values of r
     140             :         // mean that they will need to overlap. Clearly, there are limits to how far wall segments can be spaced out or
     141             :         // overlapped, depending on how much 'buffer space' each tower provides, and how far 'into' towers the wall segments are
     142             :         // allowed to overlap.
     143             :         //
     144             :         // Let 't' signify the width of a tower. When there are N wall segments, then the maximum distance that can be covered
     145             :         // using only these walls (plus the towers covering up any gaps) is achieved when the walls and towers touch outer-border-
     146             :         // to-outer-border. Therefore, the maximum value of r is then given by:
     147             :         //
     148             :         //   rMax = t/2 + (N-1)*t + t/2
     149             :         //        = N*t
     150             :         //
     151             :         // where the two half-tower widths are buffer space contributed by the implied towers on the starting and ending points.
     152             :         // Similarly, a value rMin = -N*t can be derived for the minimal value of r. Note that a value of r = 0 means that the
     153             :         // wall segment lengths add up to exactly d, meaning that each one starts and ends right in the center of a tower.
     154             :         //
     155             :         // Thus, we establish:
     156             :         //   -Nt <= r <= Nt
     157             :         //
     158             :         // We can further generalize this by adding in parameters to control the depth to within which wall segments are allowed to
     159             :         // overlap with a tower. The bounds above assume that a wall segment is allowed to overlap across the entire range of 0
     160             :         // (not overlapping at all, as in the upper boundary) to 1 (overlapping maximally, as in the lower boundary).
     161             :         //
     162             :         // By requiring that walls overlap towers to a degree of at least 0 < minOverlap <= 1, it is clear that this lowers the
     163             :         // distance that can be maximally reached by the same set of wall segments, compared to the value of minOverlap = 0 that
     164             :         // we assumed to initially find Nt.
     165             :         //
     166             :         // Consider a value of minOverlap = 0.5, meaning that any wall segment must protrude at least halfway into towers; in this
     167             :         // situation, wall segments must at least touch boundaries or overlap mutually, implying that the sum of their lengths
     168             :         // must equal or exceed 'd', establishing an upper bound of 0 for r.
     169             :         // Similarly, consider a value of minOverlap = 1, meaning that any wall segment must overlap towers maximally; this situation
     170             :         // is equivalent to the one for finding the lower bound -Nt on r.
     171             :         //
     172             :         // With the implicit value minOverlap = 0 that yielded the upper bound Nt above, simple interpolation and a similar exercise
     173             :         // for maxOverlap, we find:
     174             :         //   (1-2*maxOverlap) * Nt <= r <= (1-2*minOverlap) * Nt
     175             :         //
     176             :         // To find N segments that satisfy this requirement, we try placing L, M and S wall segments in turn and continue recursively
     177             :         // as long as the value of r is not within the bounds. If continuing recursively returns an impossible configuration, we
     178             :         // backtrack and try a wall segment of the next length instead. Note that we should prefer to use the long segments first since
     179             :         // they can be replaced by gates.
     180             : 
     181           0 :         for (let candSegment of candidateSegments)
     182             :         {
     183           0 :                 segments.push(candSegment);
     184             : 
     185           0 :                 let newDistSoFar = distSoFar + candSegment.len;
     186           0 :                 let r = d - newDistSoFar;
     187             : 
     188           0 :                 let rLowerBound = (1 - 2 * maxOverlap) * segments.length * t;
     189           0 :                 let rUpperBound = (1 - 2 * minOverlap) * segments.length * t;
     190             : 
     191           0 :                 if (r < rLowerBound)
     192             :                 {
     193             :                         // we've allocated too much wall length, pop the last segment and try the next
     194             :                         //warn("Distance so far exceeds target, trying next level");
     195           0 :                         segments.pop();
     196           0 :                         continue;
     197             :                 }
     198           0 :                 else if (r > rUpperBound)
     199             :                 {
     200           0 :                         let recursiveResult = GetWallSegmentsRec(d, candidateSegments, minOverlap, maxOverlap, t, newDistSoFar, segments);
     201           0 :                         if (!recursiveResult)
     202             :                         {
     203             :                                 // recursive search with this piece yielded no results, pop it and try the next one
     204           0 :                                 segments.pop();
     205           0 :                                 continue;
     206             :                         }
     207             : 
     208           0 :                         return recursiveResult;
     209             :                 }
     210             : 
     211           0 :                 return { "segments": segments, "r": r };
     212             :         }
     213             : 
     214           0 :         return false;
     215             : }
     216             : 
     217           0 : Engine.RegisterGlobal("GetWallPlacement", GetWallPlacement);

Generated by: LCOV version 1.14