Example #1
0
        /// <summary>
        /// Can we place this Feature at the provided position?
        ///
        /// </summary>
        /// <returns><c>true</c>, if the Feature could be placed, <c>false</c> otherwise.</returns>
        /// <param name="placedSections">The grid, containing all the Sections we wish to avoid.</param>
        /// <param name="gridPosition">The grid position we want to place the Feature at.</param>
        /// <param name="localPosition">The location of the Section we want to place at "gridPosition".</param>
        /// <param name="rotation">Rotation.</param>
        public bool CanPlace(ReadOnlyGrid placedSections, Vector2Int gridPosition, Vector2Int localPosition, int rotation)
        {
            foreach (Vector2Int sectionPosition in Elements.Keys)
            {
                // Shift this Section's position over by the offset, "localPosition".
                // This places "localPosition" at (0, 0).
                // Then rotate the point by the provided rotation.
                //
                // Because "localPosition" is provided in the same coordinates as "sectionPosition",
                // the resulting position is already normalized.
                Vector2Int rotatedPoint = Rotate(sectionPosition - localPosition, rotation);

                if (placedSections[gridPosition + rotatedPoint] != null)
                {
                    return(false);
                }
            }

            return(true);
        }
Example #2
0
 public bool CanPlace(ReadOnlyGrid placedSections, Configuration configuration)
 {
     return(CanPlace(placedSections, configuration.gridPosition, configuration.localPosition, configuration.rotation));
 }
Example #3
0
        /// <summary>
        /// Returns a list of valid configurations at the provided grid position.
        ///
        /// A configuration is uniquely defined by the grid position, the local position (which
        /// represents the specific Section we place at the adjacent grid position), and the
        /// rotation used. Additional information is provided to aid in selecting an interesting
        /// configuration in generation (such as the direction we connected in).
        ///
        /// A configuration is considered valid if every Section within this Feature can be
        /// placed onto the Grid without overlapping any existing Grid Sections.
        ///
        ///
        ///
        /// TODO find some way to optimize this, if it's possible to do so. Maybe a cache of recently checked
        /// positions, placed in the CanConnect method? This would amortize O(n^2) checks into O(n)? unique
        /// lookups, but O(n^2) comparisons still.
        ///
        /// Another idea: statically maintain a list of "internal" Sections within each Feature, and ignore those
        /// as seed positions within the CheckPlacement method. The thinking being internal points would never have
        /// any external ability to connect. Theoretically still O(n^2) (case study: space-filling curves have n^2
        /// surface area and take up n^2 space), but in practice, most Features are likely to have a linear exposed
        /// area.
        ///
        /// Tangent from the above idea. Space filling curves like the spiral or comb have O(n^2) surface area, but
        /// need "thin" or "small" connection points to be satisfied. Because we try to place Features off of a single
        /// Section, we aren't being smart about how it can connect, so we allow bad cases like this. If we take the
        /// neighboring e.g. 2-3 Sections in the Grid, we can try fitting small polygons within the Feature instead of
        /// individual Sections. If the small polygon is logarithmic in size [width * height = O(log^2(n))], then the
        /// "gaps" in the space filling curve must be at least logarithmic as well [O(sqrt(log^2(n))) == O(log(n))]. This
        /// might mean that the worst-case behavior is avoided, because the large gaps might force the number of Sections
        /// to be O(n log n) instead of O(n^2).
        ///
        /// Another idea: Change the iteration order of the CanPlace method to check nearby Sections first, to
        /// increase the likelihood of breaking out early when checking for overlaps. Or check Grid sections near
        /// the bounding box? Some heuristics might be faster than others.
        ///
        /// Another idea: Do a bounding box check first, if there's a way to do a cheap check in the global grid.
        ///
        /// Another idea: Keep track if this Feature has rotational symmetry (either 2 way or 4 way). If so, we can
        /// memoize the different rotation checks for up to 4x speedup in 4-way rotationally symmetrical cases.
        ///
        /// </summary>
        /// <returns>A List containing all valid configurations found.</returns>
        /// <param name="placedSections">The grid, containing the positions of Sections already placed.</param>
        /// <param name="gridPosition">The position in the grid we want to place the Feature at.</param>
        public List <Configuration> CanConnect(ReadOnlyGrid placedSections, Vector2Int gridPosition)
        {
            Section?maybeGridSection = placedSections[gridPosition];

            if (maybeGridSection == null)
            {
                // Empty Section is invalid, we always need to be passed in a full one.
                return(new List <Configuration>());
            }
            Section gridSection = maybeGridSection.Value;

            // A cache of the configurations we've seen while trying to place this Feature.
            //
            // Two configurations are considered equal if their gridPosition+localPosition and rotation are equal.
            //
            // This provides up to 4x speedup. Can be achieved with dense Features (approaching filled square
            // or a checkerboard pattern).
            //
            // Motivating 2x2 Feature example: checking if the top-right corner can be placed North of a given
            // Section is equivalent to checking if the bottom-left corner can be placed West of the same Section.
            // With larger dense Features, Sections away from the edges will produce up to 4 duplicate placement checks.
            Dictionary <Configuration, bool> cache = new Dictionary <Configuration, bool>();

            // Build up all possible valid configurations we'll return
            List <Configuration> toReturn = new List <Configuration>();

            // Try to place the Feature at all adjacent grid positions
            CheckPlacements(gridPosition + Vector2Int.up);
            CheckPlacements(gridPosition + Vector2Int.right);
            CheckPlacements(gridPosition + Vector2Int.down);
            CheckPlacements(gridPosition + Vector2Int.left);

            return(toReturn);

            // Checks all possible configurations of placing this Feature down at
            // a given grid position. Add them to the returning list
            void CheckPlacements(Vector2Int pos)
            {
                // If the provided input position already has a Section, then trivially
                // any Section we try to place here will conflict. So we return early.
                if (placedSections[pos] != null)
                {
                    return;
                }

                // Else try every possible local position.
                foreach (KeyValuePair <Vector2Int, Section> element in Elements)
                {
                    // And every possible rotational variant for each.
                    for (int rot = 0; rot < 4; rot++)
                    {
                        Configuration configuration = new Configuration(pos, element.Key, rot);

                        // Have we already tried this configuration? Check the cache.
                        if (!cache.TryGetValue(configuration, out bool canPlace))
                        {
                            // If it's not in there, compute it manually and add it.
                            canPlace             = CanPlace(placedSections, configuration);
                            cache[configuration] = canPlace;
                        }
                        else
                        {
                            Debug.Log("Cache hit!");
                        }

                        // If the configuration is valid (no overlaps w/ grid), add it
                        if (canPlace)
                        {
                            toReturn.Add(configuration);
                        }
                    }
                }
            }
        }