// MIT License - Copyright (c) 2025 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace Samples.UnityHelpers.SpatialStructures { using System.Collections.Generic; using UnityEngine; using WallstopStudios.UnityHelpers.Core.DataStructure.Adapters; using WallstopStudios.UnityHelpers.Core.Extension; /// /// Demonstrates when to pick gridless (Vector2) versus grid-aware (Grid + FastVector3Int) hull helpers. /// Attach this component to any GameObject, assign a Grid reference for the tile-based example, and press Play. /// [DisallowMultipleComponent] public sealed class HullUsageDemo : MonoBehaviour { private const float HullLineDuration = 8f; [Header("Gridless (Vector2)")] [SerializeField] private Vector2 gridlessBounds = new Vector2(10f, 5f); [SerializeField] [Range(4, 32)] private int gridlessEdgeSamplesPerSide = 8; [Header("Grid-aware (Grid + FastVector3Int)")] [SerializeField] private Grid grid; [SerializeField] private Vector2Int gridFootprint = new Vector2Int(6, 4); [SerializeField] [Range(3, 12)] private int gridHullNeighbors = 5; private void Start() { List gridlessHull = RunGridlessExample(); DrawGridlessHull(gridlessHull); List gridHull = RunGridAwareExample(); DrawGridAwareHull(gridHull); } private List RunGridlessExample() { List pointCloud = CreateGridlessPointCloud(); UnityExtensions.ConcaveHullOptions options = UnityExtensions .ConcaveHullOptions.Default.WithStrategy( UnityExtensions.ConcaveHullStrategy.EdgeSplit ) .WithBucketSize(Mathf.Max(16, pointCloud.Count / 2)) .WithAngleThreshold(65f); List hull = pointCloud.BuildConcaveHull(options); Debug.Log( $"[HullUsageDemo] Gridless hull: {hull.Count} vertices from {pointCloud.Count} samples using {options.Strategy}." ); return hull; } private List CreateGridlessPointCloud() { List points = new List(); float halfWidth = gridlessBounds.x * 0.5f; float halfHeight = gridlessBounds.y * 0.5f; int horizontalSamples = Mathf.Max(2, gridlessEdgeSamplesPerSide); for (int i = 0; i < horizontalSamples; i++) { float t = i / (float)(horizontalSamples - 1); float x = Mathf.Lerp(-halfWidth, halfWidth, t); points.Add(new Vector2(x, -halfHeight)); points.Add(new Vector2(x * 0.7f, halfHeight)); } for (int i = 1; i < horizontalSamples - 1; i++) { float t = i / (float)(horizontalSamples - 1); float y = Mathf.Lerp(-halfHeight * 0.75f, halfHeight * 0.75f, t); points.Add(new Vector2(-halfWidth, y)); points.Add(new Vector2(halfWidth, y * 0.4f)); } points.Add(new Vector2(0f, halfHeight + 1.25f)); points.Add(new Vector2(-halfWidth * 0.2f, halfHeight + 0.75f)); points.Add(new Vector2(halfWidth * 0.2f, halfHeight + 0.75f)); return points; } private void DrawGridlessHull(IReadOnlyList hull) { if (hull == null || hull.Count < 2) { return; } List loop = new List(hull.Count); for (int i = 0; i < hull.Count; i++) { Vector2 point = hull[i]; loop.Add(new Vector3(point.x, point.y, 0f)); } DrawLoop(loop, Color.cyan); } private List RunGridAwareExample() { if (grid == null) { Debug.LogWarning( "[HullUsageDemo] Assign a Grid reference to show the grid-aware hull example." ); return new List(); } List tileSamples = CreateGridPointCloud(); UnityExtensions.ConcaveHullOptions options = UnityExtensions .ConcaveHullOptions.Default.WithStrategy(UnityExtensions.ConcaveHullStrategy.Knn) .WithNearestNeighbors(Mathf.Max(3, gridHullNeighbors)); List hull = tileSamples.BuildConcaveHull(grid, options); Debug.Log( $"[HullUsageDemo] Grid-aware hull: {hull.Count} tiles from {tileSamples.Count} samples using Grid \"{grid.name}\"." ); return hull; } private List CreateGridPointCloud() { int width = Mathf.Max(3, gridFootprint.x); int height = Mathf.Max(3, gridFootprint.y); List tiles = new List(width * height); for (int x = 0; x < width; x++) { tiles.Add(new FastVector3Int(x, 0, 0)); tiles.Add(new FastVector3Int(x, height - 1, 0)); } for (int y = 1; y < height - 1; y++) { tiles.Add(new FastVector3Int(0, y, 0)); tiles.Add(new FastVector3Int(width - 1, y, 0)); } tiles.Add(new FastVector3Int(width / 2, height / 2, 0)); tiles.Add(new FastVector3Int(width / 2 - 1, height / 2, 0)); tiles.Add(new FastVector3Int(width / 2, height / 2 - 1, 0)); return tiles; } private void DrawGridAwareHull(IReadOnlyList hull) { if (grid == null || hull == null || hull.Count < 2) { return; } List loop = new List(hull.Count); for (int i = 0; i < hull.Count; i++) { Vector3 worldPoint = grid.CellToWorld((Vector3Int)hull[i]); loop.Add(worldPoint); } DrawLoop(loop, Color.yellow); } private static void DrawLoop(IReadOnlyList points, Color color) { if (points == null || points.Count < 2) { return; } for (int i = 0; i < points.Count; i++) { Vector3 start = points[i]; Vector3 end = points[(i + 1) % points.Count]; Debug.DrawLine(start, end, color, HullLineDuration); } } } }