Files
Barrack-Unity/Assets/Scripts/Game/WorldFiller.cs
T

929 lines
28 KiB
C#
Raw Normal View History

2026-08-12 00:52:57 -04:00
using UnityEngine;
2026-08-19 13:18:26 -04:00
using System.Collections.Generic;
2026-08-12 00:52:57 -04:00
public class WorldFiller : MonoBehaviour
{
public Player player;
public SpriteRenderer gameBoardSpriteRenderer;
public GameObject wallPrefab;
2026-08-28 18:23:35 -04:00
public BallSpawner ballSpawner;
public AudioClip areaDestroyed;
private AudioSource audioSource;
2026-08-19 13:18:26 -04:00
[SerializeField]
private float fallbackBarThicknessWorldUnits = 0.08f;
[SerializeField]
private float geometryEpsilon = 0.0001f;
2026-08-19 13:38:47 -04:00
[SerializeField]
private float wallPaddingPixelsPerSide = 1.0f;
2026-08-12 00:52:57 -04:00
private Bounds gameBoardBounds;
private Vector2?[] wallPositions;
private int wallCount = 0;
2026-08-25 22:24:07 -04:00
private Queue<GameObject> spawnedWalls = new Queue<GameObject>();
2026-08-19 13:18:26 -04:00
private List<Rect> freeAreas = new List<Rect>();
private float boardArea;
2026-08-12 00:52:57 -04:00
// Start is called once before the first execution of Update after the MonoBehaviour is created
void Start()
{
2026-08-28 18:23:35 -04:00
audioSource = GetComponent<AudioSource>();
2026-08-12 00:52:57 -04:00
gameBoardBounds = gameBoardSpriteRenderer.bounds;
2026-08-19 13:18:26 -04:00
InitializeFreeAreas();
2026-08-12 00:52:57 -04:00
ClearWalls();
}
public void ClearWalls()
{
wallPositions = new Vector2?[2];
wallPositions[0] = null;
wallPositions[1] = null;
wallCount = 0;
}
2026-08-25 22:24:07 -04:00
public void ClearSpawnedWalls()
{
while (spawnedWalls.Count > 0)
{
GameObject wall = spawnedWalls.Dequeue();
if (wall != null)
{
Destroy(wall);
}
}
spawnedWalls.Clear();
2026-08-27 17:14:18 -04:00
InitializeFreeAreas();
2026-08-25 22:24:07 -04:00
}
2026-08-12 00:52:57 -04:00
public void SetWallPosition(Vector2 position)
{
if (wallCount >= wallPositions.Length)
{
Debug.LogError("Index out of bounds for wall positions.");
return;
}
wallPositions[wallCount] = position;
wallCount++;
SpawnWall();
}
public void SpawnWall()
{
// If both wall positions are set, spawn a wall.
if (wallPositions[0] != null && wallPositions[1] != null)
{
2026-08-19 13:18:26 -04:00
Vector2 wallPointA = wallPositions[0].Value;
Vector2 wallPointB = wallPositions[1].Value;
if (Vector2.Distance(wallPointA, wallPointB) <= geometryEpsilon)
{
Debug.LogWarning("Wall points are too close. Skipping wall spawn.");
ClearWalls();
return;
}
2026-08-12 00:52:57 -04:00
// Get all the balls in the scene.
var ballSpawner = FindAnyObjectByType<BallSpawner>();
2026-08-19 13:18:26 -04:00
var balls = ballSpawner != null ? ballSpawner.GetBalls() : null;
bool isVerticalLine = Mathf.Abs(wallPointA.x - wallPointB.x) <= Mathf.Abs(wallPointA.y - wallPointB.y);
float lineCenter = isVerticalLine ? (wallPointA.x + wallPointB.x) * 0.5f : (wallPointA.y + wallPointB.y) * 0.5f;
float lineMin = isVerticalLine ? Mathf.Min(wallPointA.y, wallPointB.y) : Mathf.Min(wallPointA.x, wallPointB.x);
float lineMax = isVerticalLine ? Mathf.Max(wallPointA.y, wallPointB.y) : Mathf.Max(wallPointA.x, wallPointB.x);
float barThickness = GetCurrentBarThickness(isVerticalLine);
float halfBarThickness = barThickness * 0.5f;
2026-08-12 00:52:57 -04:00
2026-08-19 13:18:26 -04:00
Rect activeArea = FindActiveArea((wallPointA + wallPointB) * 0.5f, isVerticalLine, lineCenter, lineMin, lineMax);
DetermineSideOccupancy(
balls,
activeArea,
isVerticalLine,
lineCenter,
halfBarThickness,
out bool sideAHasBall,
out bool sideBHasBall
);
2026-08-12 00:52:57 -04:00
2026-08-19 13:18:26 -04:00
Rect targetWallRect;
// Spawn scenarios:
// 1) Both sides empty => fill the whole available region.
// 2) Only one side empty => fill that free side plus the bar strip.
// 3) Both sides occupied => spawn only along the wall line, with bar thickness.
if (!sideAHasBall && !sideBHasBall)
{
targetWallRect = activeArea;
}
else if (sideAHasBall && sideBHasBall)
{
if (isVerticalLine)
{
targetWallRect = Rect.MinMaxRect(
lineCenter - halfBarThickness,
lineMin,
lineCenter + halfBarThickness,
lineMax
);
}
else
{
targetWallRect = Rect.MinMaxRect(
lineMin,
lineCenter - halfBarThickness,
lineMax,
lineCenter + halfBarThickness
);
}
}
else
{
if (isVerticalLine)
{
// Side A is left, side B is right for vertical lines.
targetWallRect = !sideAHasBall
? Rect.MinMaxRect(activeArea.xMin, activeArea.yMin, lineCenter + halfBarThickness, activeArea.yMax)
: Rect.MinMaxRect(lineCenter - halfBarThickness, activeArea.yMin, activeArea.xMax, activeArea.yMax);
}
else
{
// Side A is bottom, side B is top for horizontal lines.
targetWallRect = !sideAHasBall
? Rect.MinMaxRect(activeArea.xMin, activeArea.yMin, activeArea.xMax, lineCenter + halfBarThickness)
: Rect.MinMaxRect(activeArea.xMin, lineCenter - halfBarThickness, activeArea.xMax, activeArea.yMax);
}
}
if (!TryGetRectIntersection(targetWallRect, activeArea, out Rect clampedWallRect))
{
Debug.LogWarning("Could not create a valid wall rectangle inside the active free area.");
ClearWalls();
return;
}
GameObject wall = Instantiate(wallPrefab, Vector3.zero, Quaternion.identity);
var wallRenderer = wall.GetComponentInChildren<SpriteRenderer>();
if (wallRenderer == null)
{
Debug.LogError("Wall prefab is missing a SpriteRenderer in children.");
Destroy(wall);
ClearWalls();
return;
}
2026-08-19 13:38:47 -04:00
Rect boardRect = Rect.MinMaxRect(
gameBoardBounds.min.x,
gameBoardBounds.min.y,
gameBoardBounds.max.x,
gameBoardBounds.max.y
);
float wallPaddingWorldUnits = PixelsToWorldUnits(wallPaddingPixelsPerSide, wallRenderer);
Rect paddedWallRect = ExpandRectInsideBounds(clampedWallRect, wallPaddingWorldUnits, boardRect);
ApplyWallTransform(wall.transform, wallRenderer, paddedWallRect);
2026-08-25 22:24:07 -04:00
spawnedWalls.Enqueue(wall);
2026-08-19 13:18:26 -04:00
var wallBounds = wallRenderer.bounds;
2026-08-19 13:38:47 -04:00
UpdateFreeAreas(paddedWallRect);
2026-08-19 13:18:26 -04:00
// Check if there are yummies, multipliers or the shark intersecting the spawned wall.
// Yummies and multipliers are awarded to the player if they are intersected by the wall.
// The shark is killed if it is intersected by the wall.
var yummies = GameObject.FindGameObjectsWithTag("Yummy");
var multipliers = GameObject.FindGameObjectsWithTag("Multiplier");
var shark = GameObject.FindGameObjectWithTag("Shark");
foreach (var yummy in yummies)
{
if (yummy != null && wallBounds.Intersects(yummy.GetComponent<SpriteRenderer>().bounds))
{
// Award the yummy to the player.
YummyBase yummyBase = yummy.GetComponent<YummyBase>();
if (yummyBase != null)
{
yummyBase.AwardToPlayer(player);
}
else
{
Debug.LogError("YummyBase component not found on yummy object.");
}
}
}
foreach (var multiplier in multipliers)
{
if (multiplier != null && wallBounds.Intersects(multiplier.GetComponent<SpriteRenderer>().bounds))
{
// Award the multiplier to the player.
Multiplier multiplierComponent = multiplier.GetComponent<Multiplier>();
if (multiplierComponent != null)
{
multiplierComponent.AwardToPlayer(player);
}
else
{
Debug.LogError("Multiplier component not found on multiplier object.");
}
}
}
if (shark != null && wallBounds.Intersects(shark.GetComponent<SpriteRenderer>().bounds))
{
// Kill the shark.
shark.GetComponent<Shark>().KillShark();
}
2026-08-12 00:52:57 -04:00
// Destroy all bar segments after we are done.
GameObject[] barSegments = GameObject.FindGameObjectsWithTag("Bar");
foreach (GameObject barSegment in barSegments)
{
Destroy(barSegment);
}
2026-08-19 13:18:26 -04:00
// Let the player know that the wall has been spawned.
int areaPercentage = boardArea > geometryEpsilon
2026-08-19 13:38:47 -04:00
? Mathf.RoundToInt((paddedWallRect.width * paddedWallRect.height / boardArea) * 100.0f)
2026-08-19 13:18:26 -04:00
: 0;
player.OnWallSpawned(areaPercentage);
2026-08-12 00:52:57 -04:00
// Clear the pending wall line.
ClearWalls();
}
}
2026-08-19 13:18:26 -04:00
2026-08-25 22:24:07 -04:00
public void InitializeFreeAreas()
2026-08-19 13:18:26 -04:00
{
gameBoardBounds = gameBoardSpriteRenderer.bounds;
Rect boardRect = Rect.MinMaxRect(
gameBoardBounds.min.x,
gameBoardBounds.min.y,
gameBoardBounds.max.x,
gameBoardBounds.max.y
);
freeAreas.Clear();
freeAreas.Add(boardRect);
boardArea = boardRect.width * boardRect.height;
}
2026-08-28 17:44:31 -04:00
public bool IsPositionInFreeRegion(Vector2 position)
2026-08-27 17:14:18 -04:00
{
if (freeAreas.Count == 0 && gameBoardSpriteRenderer != null)
{
InitializeFreeAreas();
}
for (int i = 0; i < freeAreas.Count; i++)
{
if (RectContainsWithTolerance(freeAreas[i], position))
{
return true;
}
}
return false;
}
2026-08-28 18:23:35 -04:00
public float GetAreaPercentOfFreeRegion(Vector2 position)
2026-08-28 17:44:31 -04:00
{
if (freeAreas.Count == 0 && gameBoardSpriteRenderer != null)
{
InitializeFreeAreas();
}
for (int i = 0; i < freeAreas.Count; i++)
{
if (RectContainsWithTolerance(freeAreas[i], position))
{
return (freeAreas[i].width * freeAreas[i].height) / boardArea;
}
}
return -1.0f;
}
2026-08-28 18:23:35 -04:00
public Rect? GetFreeRegionOfPosition(Vector2 position)
{
if (freeAreas.Count == 0 && gameBoardSpriteRenderer != null)
{
InitializeFreeAreas();
}
for (int i = 0; i < freeAreas.Count; i++)
{
if (RectContainsWithTolerance(freeAreas[i], position))
{
return freeAreas[i];
}
}
return null;
}
public bool FillFreeRegion(Rect freeRegion)
{
if ((freeAreas.Count == 0 || boardArea <= geometryEpsilon) && gameBoardSpriteRenderer != null)
{
InitializeFreeAreas();
}
if (!TryGetMatchingFreeArea(freeRegion, out Rect regionToFill))
{
Debug.LogWarning("Requested region is not currently a free area.");
return false;
}
if (!TrySpawnWallInRect(regionToFill))
{
return false;
}
audioSource.PlayOneShot(areaDestroyed);
return true;
}
public Queue<GameObject> GetBallsInSameFreeRegion(Vector2 position)
{
Queue<GameObject> ballsInSameRegion = new Queue<GameObject>();
if (ballSpawner == null)
{
return ballsInSameRegion;
}
var targetRegion = GetFreeRegionOfPosition(position);
if (targetRegion == null)
{
return ballsInSameRegion;
}
foreach (var ball in ballSpawner.GetBalls())
{
var ballRegion = GetFreeRegionOfPosition(ball.transform.position);
if (ballRegion != null && ballRegion.Value == targetRegion.Value)
{
ballsInSameRegion.Enqueue(ball);
}
}
return ballsInSameRegion;
}
2026-08-27 17:14:18 -04:00
public Vector2 GetRandomPositionInFreeAreas(Bounds? clearingArea = null)
{
if (freeAreas.Count == 0 && gameBoardSpriteRenderer != null)
{
InitializeFreeAreas();
}
List<Rect> candidateAreas = new List<Rect>(freeAreas);
if (clearingArea.HasValue)
{
Bounds bounds = clearingArea.Value;
Rect clearingRect = Rect.MinMaxRect(bounds.min.x, bounds.min.y, bounds.max.x, bounds.max.y);
candidateAreas = GetAreasExcludingRect(candidateAreas, clearingRect);
}
float totalArea = 0.0f;
for (int i = 0; i < candidateAreas.Count; i++)
{
totalArea += candidateAreas[i].width * candidateAreas[i].height;
}
if (totalArea <= geometryEpsilon)
{
Debug.LogWarning("No free area available to generate a random position.");
if (gameBoardSpriteRenderer != null)
{
return gameBoardSpriteRenderer.bounds.center;
}
return Vector2.zero;
}
float targetArea = Random.Range(0.0f, totalArea);
float accumulatedArea = 0.0f;
for (int i = 0; i < candidateAreas.Count; i++)
{
Rect area = candidateAreas[i];
accumulatedArea += area.width * area.height;
if (targetArea <= accumulatedArea)
{
return new Vector2(
Random.Range(area.xMin, area.xMax),
Random.Range(area.yMin, area.yMax)
);
}
}
Rect fallbackArea = candidateAreas[candidateAreas.Count - 1];
return fallbackArea.center;
}
2026-08-19 13:18:26 -04:00
private float GetCurrentBarThickness(bool isVerticalLine)
{
var barSegments = GameObject.FindGameObjectsWithTag("Bar");
foreach (var barSegment in barSegments)
{
if (barSegment == null)
{
continue;
}
var spriteRenderer = barSegment.GetComponent<SpriteRenderer>();
if (spriteRenderer == null)
{
continue;
}
float thickness = isVerticalLine
? spriteRenderer.bounds.size.x
: spriteRenderer.bounds.size.y;
if (thickness > geometryEpsilon)
{
return thickness;
}
}
return fallbackBarThicknessWorldUnits;
}
private Rect FindActiveArea(
Vector2 lineMidpoint,
bool isVerticalLine,
float lineCenter,
float lineMin,
float lineMax)
{
if (freeAreas.Count == 0)
{
InitializeFreeAreas();
}
for (int i = 0; i < freeAreas.Count; i++)
{
if (RectContainsWithTolerance(freeAreas[i], lineMidpoint))
{
return freeAreas[i];
}
}
Rect lineRect = isVerticalLine
? Rect.MinMaxRect(lineCenter - geometryEpsilon, lineMin, lineCenter + geometryEpsilon, lineMax)
: Rect.MinMaxRect(lineMin, lineCenter - geometryEpsilon, lineMax, lineCenter + geometryEpsilon);
float bestOverlap = -1.0f;
Rect fallback = freeAreas[0];
for (int i = 0; i < freeAreas.Count; i++)
{
if (!TryGetRectIntersection(freeAreas[i], lineRect, out Rect overlap))
{
continue;
}
float overlapArea = overlap.width * overlap.height;
if (overlapArea > bestOverlap)
{
bestOverlap = overlapArea;
fallback = freeAreas[i];
}
}
return fallback;
}
private void DetermineSideOccupancy(
Queue<GameObject> balls,
Rect activeArea,
bool isVerticalLine,
float lineCenter,
float halfBarThickness,
out bool sideAHasBall,
out bool sideBHasBall)
{
sideAHasBall = false;
sideBHasBall = false;
if (balls == null)
{
return;
}
foreach (var ball in balls)
{
if (ball == null || !ball.activeInHierarchy)
{
continue;
}
Vector2 ballPosition = ball.transform.position;
if (!RectContainsWithTolerance(activeArea, ballPosition))
{
continue;
}
if (isVerticalLine)
{
if (ballPosition.x < lineCenter - halfBarThickness)
{
sideAHasBall = true;
}
else if (ballPosition.x > lineCenter + halfBarThickness)
{
sideBHasBall = true;
}
else
{
if (ballPosition.x <= lineCenter)
{
sideAHasBall = true;
}
else
{
sideBHasBall = true;
}
}
}
else
{
if (ballPosition.y < lineCenter - halfBarThickness)
{
sideAHasBall = true;
}
else if (ballPosition.y > lineCenter + halfBarThickness)
{
sideBHasBall = true;
}
else
{
if (ballPosition.y <= lineCenter)
{
sideAHasBall = true;
}
else
{
sideBHasBall = true;
}
}
}
if (sideAHasBall && sideBHasBall)
{
return;
}
}
}
2026-08-28 18:23:35 -04:00
private bool TryGetMatchingFreeArea(Rect requestedRegion, out Rect matchedRegion)
{
for (int i = 0; i < freeAreas.Count; i++)
{
if (RectsApproximatelyEqual(freeAreas[i], requestedRegion))
{
matchedRegion = freeAreas[i];
return true;
}
}
matchedRegion = default;
return false;
}
private bool RectsApproximatelyEqual(Rect a, Rect b)
{
return Mathf.Abs(a.xMin - b.xMin) <= geometryEpsilon
&& Mathf.Abs(a.yMin - b.yMin) <= geometryEpsilon
&& Mathf.Abs(a.xMax - b.xMax) <= geometryEpsilon
&& Mathf.Abs(a.yMax - b.yMax) <= geometryEpsilon;
}
private bool TrySpawnWallInRect(Rect targetRect)
{
if (wallPrefab == null)
{
Debug.LogError("Wall prefab is not assigned.");
return false;
}
if (gameBoardSpriteRenderer == null)
{
Debug.LogError("Game board SpriteRenderer is not assigned.");
return false;
}
gameBoardBounds = gameBoardSpriteRenderer.bounds;
Rect boardRect = Rect.MinMaxRect(
gameBoardBounds.min.x,
gameBoardBounds.min.y,
gameBoardBounds.max.x,
gameBoardBounds.max.y
);
if (!TryGetRectIntersection(targetRect, boardRect, out Rect clampedRect))
{
Debug.LogWarning("Target wall rectangle is outside the game board bounds.");
return false;
}
GameObject wall = Instantiate(wallPrefab, Vector3.zero, Quaternion.identity);
var wallRenderer = wall.GetComponentInChildren<SpriteRenderer>();
if (wallRenderer == null)
{
Debug.LogError("Wall prefab is missing a SpriteRenderer in children.");
Destroy(wall);
return false;
}
float wallPaddingWorldUnits = PixelsToWorldUnits(wallPaddingPixelsPerSide, wallRenderer);
Rect paddedWallRect = ExpandRectInsideBounds(clampedRect, wallPaddingWorldUnits, boardRect);
ApplyWallTransform(wall.transform, wallRenderer, paddedWallRect);
spawnedWalls.Enqueue(wall);
UpdateFreeAreas(paddedWallRect);
ResolveWallIntersections(wallRenderer.bounds);
DestroyBarSegments();
if (boardArea <= geometryEpsilon)
{
boardArea = boardRect.width * boardRect.height;
}
int areaPercentage = boardArea > geometryEpsilon
? Mathf.RoundToInt((paddedWallRect.width * paddedWallRect.height / boardArea) * 100.0f)
: 0;
if (player != null)
{
player.OnWallSpawned(areaPercentage);
}
return true;
}
private void ResolveWallIntersections(Bounds wallBounds)
{
// Yummies and multipliers are awarded if intersected by the spawned wall.
var yummies = GameObject.FindGameObjectsWithTag("Yummy");
var multipliers = GameObject.FindGameObjectsWithTag("Multiplier");
var shark = GameObject.FindGameObjectWithTag("Shark");
foreach (var yummy in yummies)
{
if (yummy == null)
{
continue;
}
var yummyRenderer = yummy.GetComponent<SpriteRenderer>();
if (yummyRenderer == null || !wallBounds.Intersects(yummyRenderer.bounds))
{
continue;
}
YummyBase yummyBase = yummy.GetComponent<YummyBase>();
if (yummyBase != null)
{
yummyBase.AwardToPlayer(player);
}
else
{
Debug.LogError("YummyBase component not found on yummy object.");
}
}
foreach (var multiplier in multipliers)
{
if (multiplier == null)
{
continue;
}
var multiplierRenderer = multiplier.GetComponent<SpriteRenderer>();
if (multiplierRenderer == null || !wallBounds.Intersects(multiplierRenderer.bounds))
{
continue;
}
Multiplier multiplierComponent = multiplier.GetComponent<Multiplier>();
if (multiplierComponent != null)
{
multiplierComponent.AwardToPlayer(player);
}
else
{
Debug.LogError("Multiplier component not found on multiplier object.");
}
}
if (shark == null)
{
return;
}
var sharkRenderer = shark.GetComponent<SpriteRenderer>();
if (sharkRenderer == null || !wallBounds.Intersects(sharkRenderer.bounds))
{
return;
}
Shark sharkComponent = shark.GetComponent<Shark>();
if (sharkComponent != null)
{
sharkComponent.KillShark();
}
}
private void DestroyBarSegments()
{
GameObject[] barSegments = GameObject.FindGameObjectsWithTag("Bar");
foreach (GameObject barSegment in barSegments)
{
Destroy(barSegment);
}
}
2026-08-19 13:18:26 -04:00
private void ApplyWallTransform(Transform wallTransform, SpriteRenderer wallRenderer, Rect targetRect)
{
Vector2 baseSize = wallRenderer.bounds.size;
if (baseSize.x <= geometryEpsilon || baseSize.y <= geometryEpsilon)
{
Debug.LogError("Cannot scale wall: base sprite size is invalid.");
return;
}
Vector3 currentScale = wallTransform.localScale;
float scaleX = currentScale.x * (targetRect.width / baseSize.x);
float scaleY = currentScale.y * (targetRect.height / baseSize.y);
wallTransform.localScale = new Vector3(scaleX, scaleY, currentScale.z);
wallTransform.position = new Vector3(targetRect.center.x, targetRect.center.y, wallTransform.position.z);
}
private void UpdateFreeAreas(Rect occupiedArea)
{
List<Rect> updatedAreas = new List<Rect>();
for (int i = 0; i < freeAreas.Count; i++)
{
Rect freeArea = freeAreas[i];
if (!TryGetRectIntersection(freeArea, occupiedArea, out Rect overlap))
{
updatedAreas.Add(freeArea);
continue;
}
// Left remainder.
AddValidRect(updatedAreas, Rect.MinMaxRect(freeArea.xMin, freeArea.yMin, overlap.xMin, freeArea.yMax));
// Right remainder.
AddValidRect(updatedAreas, Rect.MinMaxRect(overlap.xMax, freeArea.yMin, freeArea.xMax, freeArea.yMax));
// Bottom remainder.
AddValidRect(updatedAreas, Rect.MinMaxRect(overlap.xMin, freeArea.yMin, overlap.xMax, overlap.yMin));
// Top remainder.
AddValidRect(updatedAreas, Rect.MinMaxRect(overlap.xMin, overlap.yMax, overlap.xMax, freeArea.yMax));
}
freeAreas = updatedAreas;
}
2026-08-27 17:14:18 -04:00
private List<Rect> GetAreasExcludingRect(List<Rect> sourceAreas, Rect excludedArea)
{
List<Rect> remainingAreas = new List<Rect>();
for (int i = 0; i < sourceAreas.Count; i++)
{
Rect sourceArea = sourceAreas[i];
if (!TryGetRectIntersection(sourceArea, excludedArea, out Rect overlap))
{
AddValidRect(remainingAreas, sourceArea);
continue;
}
// Split the source area around the overlap and keep only non-overlapping parts.
AddValidRect(remainingAreas, Rect.MinMaxRect(sourceArea.xMin, sourceArea.yMin, overlap.xMin, sourceArea.yMax));
AddValidRect(remainingAreas, Rect.MinMaxRect(overlap.xMax, sourceArea.yMin, sourceArea.xMax, sourceArea.yMax));
AddValidRect(remainingAreas, Rect.MinMaxRect(overlap.xMin, sourceArea.yMin, overlap.xMax, overlap.yMin));
AddValidRect(remainingAreas, Rect.MinMaxRect(overlap.xMin, overlap.yMax, overlap.xMax, sourceArea.yMax));
}
return remainingAreas;
}
2026-08-19 13:18:26 -04:00
private void AddValidRect(List<Rect> target, Rect candidate)
{
if (candidate.width <= geometryEpsilon || candidate.height <= geometryEpsilon)
{
return;
}
target.Add(candidate);
}
private bool TryGetRectIntersection(Rect a, Rect b, out Rect intersection)
{
float xMin = Mathf.Max(a.xMin, b.xMin);
float xMax = Mathf.Min(a.xMax, b.xMax);
float yMin = Mathf.Max(a.yMin, b.yMin);
float yMax = Mathf.Min(a.yMax, b.yMax);
if (xMax - xMin <= geometryEpsilon || yMax - yMin <= geometryEpsilon)
{
intersection = default;
return false;
}
intersection = Rect.MinMaxRect(xMin, yMin, xMax, yMax);
return true;
}
private bool RectContainsWithTolerance(Rect rect, Vector2 point)
{
return point.x >= rect.xMin - geometryEpsilon
&& point.x <= rect.xMax + geometryEpsilon
&& point.y >= rect.yMin - geometryEpsilon
&& point.y <= rect.yMax + geometryEpsilon;
}
2026-08-19 13:38:47 -04:00
private float PixelsToWorldUnits(float pixels, SpriteRenderer referenceRenderer)
{
float pixelsPerUnit = 100.0f;
if (referenceRenderer != null && referenceRenderer.sprite != null && referenceRenderer.sprite.pixelsPerUnit > 0.0f)
{
pixelsPerUnit = referenceRenderer.sprite.pixelsPerUnit;
}
else if (gameBoardSpriteRenderer != null && gameBoardSpriteRenderer.sprite != null && gameBoardSpriteRenderer.sprite.pixelsPerUnit > 0.0f)
{
pixelsPerUnit = gameBoardSpriteRenderer.sprite.pixelsPerUnit;
}
return pixels / pixelsPerUnit;
}
private Rect ExpandRectInsideBounds(Rect rect, float padding, Rect boundsRect)
{
if (padding <= geometryEpsilon)
{
return rect;
}
Rect expanded = Rect.MinMaxRect(
rect.xMin - padding,
rect.yMin - padding,
rect.xMax + padding,
rect.yMax + padding
);
if (TryGetRectIntersection(expanded, boundsRect, out Rect clampedExpanded))
{
return clampedExpanded;
}
return rect;
}
2026-08-12 00:52:57 -04:00
}