RoomBuilder.cs 51 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480
  1. using UnityEngine;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. public enum BuildingMode
  5. {
  6. PlacingWalls,
  7. PlacingDoor,
  8. AddingDoorToExistingWall
  9. }
  10. public class RoomBuilder : MonoBehaviour
  11. {
  12. [Header("Building Settings")]
  13. [SerializeField] private float snapTolerance = 1f;
  14. [SerializeField] private Material wallMaterial;
  15. [SerializeField] private Material previewMaterial;
  16. [SerializeField] private float wallHeight = 3f;
  17. [SerializeField] private float wallThickness = 0.2f;
  18. [Header("Door Settings")]
  19. [SerializeField] private float doorWidth = 1.5f;
  20. [SerializeField] private Material doorFrameMaterial;
  21. [Header("Visual Feedback")]
  22. [SerializeField] private GameObject wallPointPrefab;
  23. [SerializeField] private LineRenderer previewLine;
  24. private List<Vector3> currentRoomPoints = new List<Vector3>();
  25. private List<GameObject> wallSegments = new List<GameObject>();
  26. private List<GameObject> wallPointMarkers = new List<GameObject>();
  27. private bool isBuildingRoom = false;
  28. private bool isPlacingDoor = false;
  29. private BuildingMode currentBuildingMode = BuildingMode.PlacingWalls;
  30. private Camera playerCamera;
  31. private float lastRoomCompletionTime = 0f;
  32. private const float ROOM_DETECTION_COOLDOWN = 1.0f; // 1 second cooldown
  33. private GameObject previewWall;
  34. private Room currentRoom;
  35. // Events
  36. public System.Action<Room> OnRoomCompleted;
  37. public System.Action<string> OnBuildingError;
  38. private void Start()
  39. {
  40. playerCamera = Camera.main;
  41. if (previewLine == null)
  42. {
  43. CreatePreviewLine();
  44. }
  45. }
  46. private void Update()
  47. {
  48. if (GameManager.Instance != null && !GameManager.Instance.IsBuilding())
  49. return;
  50. HandleInput();
  51. UpdatePreview();
  52. }
  53. private void OnGUI()
  54. {
  55. // Show building mode information
  56. if (GameManager.Instance != null && GameManager.Instance.IsBuilding())
  57. {
  58. GUIStyle style = new GUIStyle();
  59. style.fontSize = 16;
  60. style.normal.textColor = Color.white;
  61. string modeText = "";
  62. string instructionText = "";
  63. if (isBuildingRoom)
  64. {
  65. modeText = "Building Room";
  66. instructionText = "Left-click to place wall points | Right-click to complete room";
  67. }
  68. else if (isPlacingDoor)
  69. {
  70. modeText = "Placing Door";
  71. instructionText = "Click on a wall to place door";
  72. }
  73. else if (currentBuildingMode == BuildingMode.AddingDoorToExistingWall)
  74. {
  75. modeText = "Adding Door to Wall";
  76. instructionText = "Click on existing wall to add door | ESC to cancel | Tab to toggle modes";
  77. }
  78. else
  79. {
  80. modeText = "Ready to Build";
  81. instructionText = "Left-click to start room | Tab to add door to existing wall | Auto-detects enclosed rooms";
  82. }
  83. GUI.Label(new Rect(10, 10, 400, 30), $"Mode: {modeText}", style);
  84. GUI.Label(new Rect(10, 35, 400, 30), instructionText, style);
  85. }
  86. }
  87. #region Input Handling
  88. private void HandleInput()
  89. {
  90. // Only handle building inputs when we're supposed to be building
  91. if (GameManager.Instance == null || !GameManager.Instance.IsBuilding())
  92. return;
  93. // Building mode switching - use Tab instead of D to avoid camera conflict
  94. if (Input.GetKeyDown(KeyCode.Tab) && !isBuildingRoom && !isPlacingDoor)
  95. {
  96. currentBuildingMode = BuildingMode.AddingDoorToExistingWall;
  97. Debug.Log("Switched to door placement mode - Click on existing walls to add doors");
  98. }
  99. else if (Input.GetKeyDown(KeyCode.Tab) && currentBuildingMode == BuildingMode.AddingDoorToExistingWall)
  100. {
  101. currentBuildingMode = BuildingMode.PlacingWalls;
  102. Debug.Log("Switched to wall building mode");
  103. }
  104. if (Input.GetMouseButtonDown(0)) // Left click
  105. {
  106. Debug.Log($"Mouse click detected - isPlacingDoor={isPlacingDoor}, currentBuildingMode={currentBuildingMode}");
  107. if (currentBuildingMode == BuildingMode.AddingDoorToExistingWall)
  108. {
  109. TryAddDoorToExistingWall();
  110. }
  111. else if (isPlacingDoor)
  112. {
  113. TryPlaceDoor();
  114. }
  115. else
  116. {
  117. TryPlaceWallPoint();
  118. }
  119. }
  120. else if (Input.GetMouseButtonDown(1)) // Right click
  121. {
  122. if (isBuildingRoom)
  123. {
  124. // Instead of forcing completion, try to auto-complete smartly
  125. // Only allow manual completion if we have a valid room shape
  126. if (currentRoomPoints.Count >= 3)
  127. {
  128. TryCompleteRoom();
  129. }
  130. else
  131. {
  132. OnBuildingError?.Invoke("Need at least 3 points to complete room manually");
  133. }
  134. }
  135. }
  136. else if (Input.GetKeyDown(KeyCode.Escape))
  137. {
  138. if (currentBuildingMode == BuildingMode.AddingDoorToExistingWall)
  139. {
  140. currentBuildingMode = BuildingMode.PlacingWalls;
  141. Debug.Log("Cancelled door placement mode");
  142. }
  143. else
  144. {
  145. CancelBuilding();
  146. }
  147. }
  148. }
  149. private Vector3 GetMouseWorldPosition()
  150. {
  151. Ray ray = playerCamera.ScreenPointToRay(Input.mousePosition);
  152. if (Physics.Raycast(ray, out RaycastHit hit))
  153. {
  154. return hit.point;
  155. }
  156. else
  157. {
  158. // Fallback: project to ground plane (y = 0)
  159. Plane groundPlane = new Plane(Vector3.up, Vector3.zero);
  160. if (groundPlane.Raycast(ray, out float distance))
  161. {
  162. return ray.GetPoint(distance);
  163. }
  164. }
  165. return Vector3.zero;
  166. }
  167. #endregion
  168. #region Wall Building
  169. private void TryPlaceWallPoint()
  170. {
  171. Vector3 mousePos = GetMouseWorldPosition();
  172. if (!isBuildingRoom)
  173. {
  174. // Check if we're starting near an existing wall
  175. Vector3 snapPoint = FindWallSnapPoint(mousePos);
  176. if (snapPoint != Vector3.zero)
  177. {
  178. Debug.Log($"Starting room from existing wall at: {snapPoint}");
  179. StartRoom(snapPoint);
  180. }
  181. else
  182. {
  183. StartRoom(mousePos);
  184. }
  185. }
  186. else
  187. {
  188. // Check if we're close to the first point (completing the room)
  189. if (Vector3.Distance(mousePos, currentRoomPoints[0]) < snapTolerance)
  190. {
  191. AutoCompleteRoom();
  192. return;
  193. }
  194. // Check if we're clicking on an existing wall to complete the room
  195. Vector3 wallSnapPoint = FindWallSnapPoint(mousePos);
  196. if (wallSnapPoint != Vector3.zero)
  197. {
  198. Debug.Log($"Completing room by connecting to existing wall at: {wallSnapPoint}");
  199. AddWallPoint(wallSnapPoint);
  200. // If we started from an existing wall and now connect to another existing wall,
  201. // we can complete the room with just 2 points (wall-to-wall connection)
  202. if (currentRoomPoints.Count >= 2)
  203. {
  204. Debug.Log("Wall-to-wall connection detected, triggering comprehensive room detection");
  205. // Instead of AutoCompleteRoom, use the comprehensive room detection
  206. CheckForNewEnclosedRoomsGlobal();
  207. return;
  208. }
  209. }
  210. AddWallPoint(mousePos);
  211. // Auto-check if room is now enclosed (only for non-wall-snap cases)
  212. if (IsRoomEnclosed(currentRoomPoints))
  213. {
  214. AutoCompleteRoom();
  215. }
  216. // ADDITIONAL: Force completion check if we have enough points and walls
  217. if (currentRoomPoints.Count >= 3)
  218. {
  219. Debug.Log("Checking for forced room completion with sufficient points");
  220. // Get all walls and see if we have enough to complete a room
  221. List<GameObject> allWalls = new List<GameObject>();
  222. GameObject[] allGameObjects = FindObjectsByType<GameObject>(FindObjectsSortMode.None);
  223. foreach (GameObject obj in allGameObjects)
  224. {
  225. if (obj != null && obj.GetComponent<Renderer>() != null &&
  226. (obj.name.Contains("Wall") || obj.name.Contains("wall")))
  227. {
  228. allWalls.Add(obj);
  229. }
  230. }
  231. if (allWalls.Count >= 4) // Enough walls for a room
  232. {
  233. Debug.Log("Sufficient walls detected, forcing room completion");
  234. CheckForNewEnclosedRoomsGlobal();
  235. }
  236. }
  237. }
  238. }
  239. private void AutoCompleteRoom()
  240. {
  241. // Check if room already has a door
  242. if (DoesRoomHaveDoor())
  243. {
  244. Debug.Log("Room already has a door, completing without door placement.");
  245. CompleteRoomWithoutDoor();
  246. }
  247. else
  248. {
  249. Debug.Log("Room completed, needs door placement.");
  250. CompleteRoom();
  251. }
  252. }
  253. private Vector3 FindWallSnapPoint(Vector3 clickPosition)
  254. {
  255. float snapDistance = 2.0f; // Distance to snap to existing walls
  256. GameObject closestWall = null;
  257. float closestDistance = float.MaxValue;
  258. // Check all existing wall segments
  259. foreach (GameObject wall in wallSegments)
  260. {
  261. if (wall == null || wall.name == "Door Opening" || wall.name == "Door Frame Post") continue;
  262. Renderer wallRenderer = wall.GetComponent<Renderer>();
  263. if (wallRenderer == null) continue;
  264. // Check distance to wall bounds
  265. Bounds bounds = wallRenderer.bounds;
  266. float distance = bounds.SqrDistance(clickPosition);
  267. if (distance < closestDistance && distance < snapDistance * snapDistance)
  268. {
  269. closestWall = wall;
  270. closestDistance = distance;
  271. }
  272. }
  273. if (closestWall != null)
  274. {
  275. // Find the closest point on the wall surface to snap to
  276. Transform wallTransform = closestWall.transform;
  277. Vector3 wallScale = wallTransform.localScale;
  278. Vector3 wallCenter = wallTransform.position;
  279. // Calculate wall endpoints based on wall's forward direction (length direction)
  280. Vector3 wallDirection = wallTransform.forward;
  281. float wallLength = wallScale.z; // Length is in Z direction after LookRotation
  282. Vector3 wallStart = wallCenter - wallDirection * wallLength / 2f;
  283. Vector3 wallEnd = wallCenter + wallDirection * wallLength / 2f;
  284. // Project click position onto the wall line to find closest point
  285. Vector3 wallVector = wallEnd - wallStart;
  286. Vector3 clickVector = clickPosition - wallStart;
  287. float t = Mathf.Clamp01(Vector3.Dot(clickVector, wallVector) / wallVector.sqrMagnitude);
  288. Vector3 snapPoint = wallStart + t * wallVector;
  289. // Ensure snap point is at ground level
  290. snapPoint.y = 0f;
  291. Debug.Log($"Found wall snap point: {snapPoint} on wall from {wallStart} to {wallEnd}");
  292. return snapPoint;
  293. }
  294. return Vector3.zero; // No wall found to snap to
  295. }
  296. private bool DoesRoomHaveDoor()
  297. {
  298. // Check if any wall segments in current room area have door openings or door frames
  299. foreach (GameObject segment in wallSegments)
  300. {
  301. if (segment != null && (segment.name == "Door Opening" || segment.name == "Door Frame Post"))
  302. {
  303. return true;
  304. }
  305. }
  306. return false;
  307. }
  308. private bool IsRoomEnclosed(List<Vector3> points)
  309. {
  310. // Check if the current points form a closed shape
  311. if (points.Count < 3) return false;
  312. // Check if we're close to starting point or clicked on existing wall
  313. Vector3 lastPoint = points[points.Count - 1];
  314. Vector3 firstPoint = points[0];
  315. return Vector3.Distance(lastPoint, firstPoint) < snapTolerance * 2f;
  316. }
  317. private void CompleteRoomWithoutDoor()
  318. {
  319. // Complete the room without entering door placement mode
  320. if (currentRoomPoints.Count > 2)
  321. {
  322. Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1];
  323. Vector3 firstPoint = currentRoomPoints[0];
  324. if (Vector3.Distance(lastPoint, firstPoint) > 0.1f)
  325. {
  326. CreateWallSegment(lastPoint, firstPoint);
  327. }
  328. }
  329. Debug.Log("Room completed without additional door (existing door found).");
  330. // Clean up any very short or overlapping wall segments
  331. CleanupWallSegments();
  332. // Finalize the room immediately
  333. FinishRoom();
  334. // Reset building state
  335. ResetBuildingState();
  336. }
  337. private void StartRoom(Vector3 startPoint)
  338. {
  339. Debug.Log("Starting room construction");
  340. isBuildingRoom = true;
  341. currentRoomPoints.Clear();
  342. currentRoomPoints.Add(startPoint);
  343. // Create visual marker for start point
  344. if (wallPointPrefab != null)
  345. {
  346. GameObject marker = Instantiate(wallPointPrefab, startPoint, Quaternion.identity);
  347. marker.GetComponent<Renderer>().material.color = Color.green; // Start point is green
  348. wallPointMarkers.Add(marker);
  349. }
  350. }
  351. private void AddWallPoint(Vector3 newPoint)
  352. {
  353. currentRoomPoints.Add(newPoint);
  354. // Create wall segment from previous point to new point
  355. CreateWallSegment(currentRoomPoints[currentRoomPoints.Count - 2], newPoint);
  356. // Create visual marker
  357. if (wallPointPrefab != null)
  358. {
  359. GameObject marker = Instantiate(wallPointPrefab, newPoint, Quaternion.identity);
  360. wallPointMarkers.Add(marker);
  361. }
  362. Debug.Log($"Added wall point {currentRoomPoints.Count}: {newPoint}");
  363. // Check if this wall placement has created any new enclosed rooms
  364. CheckForNewEnclosedRooms();
  365. // Additional comprehensive check if we have enough points
  366. if (currentRoomPoints.Count >= 3)
  367. {
  368. Debug.Log("Also triggering global room detection as backup");
  369. CheckForNewEnclosedRoomsGlobal();
  370. }
  371. // SIMPLE FALLBACK: If we have 3+ points and are near existing walls, try to complete
  372. if (currentRoomPoints.Count >= 3)
  373. {
  374. Vector3 firstPoint = currentRoomPoints[0];
  375. Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1];
  376. // Simple distance check - if start and end are close to existing walls, complete the room
  377. if (IsNearExistingWall(firstPoint) && IsNearExistingWall(lastPoint))
  378. {
  379. Debug.Log("Simple room completion: Start and end points near existing walls");
  380. ForceSimpleRoomCompletion();
  381. }
  382. }
  383. }
  384. private bool IsNearExistingWall(Vector3 point)
  385. {
  386. GameObject[] allObjects = FindObjectsByType<GameObject>(FindObjectsSortMode.None);
  387. foreach (GameObject obj in allObjects)
  388. {
  389. if (obj != null && obj.name.Contains("Wall") && obj.GetComponent<Renderer>() != null)
  390. {
  391. if (Vector3.Distance(point, obj.transform.position) < 3.0f)
  392. {
  393. return true;
  394. }
  395. }
  396. }
  397. return false;
  398. }
  399. private void ForceSimpleRoomCompletion()
  400. {
  401. Debug.Log("Forcing simple room completion");
  402. // Create a simple room from current points
  403. if (currentRoomPoints.Count >= 3)
  404. {
  405. List<Vector3> roomArea = new List<Vector3>(currentRoomPoints);
  406. // Check if it has a door
  407. bool hasDoor = DoesAreaHaveDoor(roomArea);
  408. if (!hasDoor)
  409. {
  410. Debug.Log("Simple room has no door - forcing door placement");
  411. ForceRoomDoorPlacement(roomArea);
  412. }
  413. else
  414. {
  415. Debug.Log("Simple room has door - completing");
  416. CompleteDetectedRoom(roomArea);
  417. }
  418. }
  419. }
  420. private void TryCompleteRoom()
  421. {
  422. if (currentRoomPoints.Count < 3)
  423. {
  424. OnBuildingError?.Invoke("Room needs at least 3 points");
  425. return;
  426. }
  427. Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1];
  428. Vector3 firstPoint = currentRoomPoints[0];
  429. // Check if there's a clear line to the first point
  430. if (IsValidClosingWall(lastPoint, firstPoint))
  431. {
  432. CompleteRoom();
  433. }
  434. else
  435. {
  436. OnBuildingError?.Invoke("Cannot complete room: No clear line to start point");
  437. }
  438. }
  439. private void CompleteRoom()
  440. {
  441. // Add final wall segment to close the room
  442. if (currentRoomPoints.Count > 2)
  443. {
  444. Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1];
  445. Vector3 firstPoint = currentRoomPoints[0];
  446. if (Vector3.Distance(lastPoint, firstPoint) > 0.1f)
  447. {
  448. CreateWallSegment(lastPoint, firstPoint);
  449. }
  450. }
  451. Debug.Log("Room walls completed. Now place a door.");
  452. // Clean up any very short or overlapping wall segments
  453. CleanupWallSegments();
  454. // Start door placement phase
  455. isBuildingRoom = false;
  456. isPlacingDoor = true;
  457. // Change marker colors to indicate door placement phase
  458. foreach (var marker in wallPointMarkers)
  459. {
  460. if (marker != null)
  461. marker.GetComponent<Renderer>().material.color = Color.blue;
  462. }
  463. }
  464. private void CleanupWallSegments()
  465. {
  466. // Remove any walls that are too short or overlapping
  467. for (int i = wallSegments.Count - 1; i >= 0; i--)
  468. {
  469. if (wallSegments[i] == null) continue;
  470. Transform wallTransform = wallSegments[i].transform;
  471. Vector3 wallScale = wallTransform.localScale;
  472. // Remove walls that are too short (likely overlapping or error segments)
  473. if (wallScale.z < 0.2f) // Z is the length dimension
  474. {
  475. Debug.Log($"Removing short wall segment: {wallScale.z}");
  476. DestroyImmediate(wallSegments[i]);
  477. wallSegments.RemoveAt(i);
  478. }
  479. }
  480. }
  481. private bool IsValidClosingWall(Vector3 from, Vector3 to)
  482. {
  483. // For now, just check distance (can be improved with obstacle checking)
  484. return Vector3.Distance(from, to) < 20f; // Max wall length
  485. }
  486. private void CreateWallSegment(Vector3 from, Vector3 to)
  487. {
  488. // Ensure points are at ground level (y = 0) for consistency
  489. from.y = 0f;
  490. to.y = 0f;
  491. Vector3 center = (from + to) / 2f;
  492. Vector3 direction = to - from;
  493. float length = direction.magnitude;
  494. // Skip very short wall segments to prevent overlapping
  495. if (length < 0.1f)
  496. {
  497. Debug.LogWarning($"Skipping very short wall segment: {length}");
  498. return;
  499. }
  500. GameObject wall = GameObject.CreatePrimitive(PrimitiveType.Cube);
  501. wall.name = "Wall Segment";
  502. // Anchor wall to floor - position at ground level with wall extending upward
  503. wall.transform.position = new Vector3(center.x, wallHeight / 2f, center.z);
  504. wall.transform.rotation = Quaternion.LookRotation(direction);
  505. wall.transform.localScale = new Vector3(wallThickness, wallHeight, length);
  506. if (wallMaterial != null)
  507. {
  508. wall.GetComponent<Renderer>().material = wallMaterial;
  509. }
  510. // Add collider for pathfinding obstacles but make non-clickable
  511. Collider wallCollider = wall.GetComponent<Collider>();
  512. if (wallCollider != null)
  513. {
  514. wallCollider.isTrigger = false;
  515. // Walls should block navigation but not be clickable for UI
  516. wall.layer = LayerMask.NameToLayer("Default"); // Ensure it's on a non-UI layer
  517. }
  518. wallSegments.Add(wall);
  519. // After adding any wall, check if it might have completed an enclosed room
  520. // (but only if we're not already in a specific building mode)
  521. if (!isPlacingDoor && currentBuildingMode != BuildingMode.AddingDoorToExistingWall)
  522. {
  523. CheckForNewEnclosedRoomsGlobal();
  524. }
  525. }
  526. #endregion
  527. #region Door Placement
  528. private void TryPlaceDoor()
  529. {
  530. Vector3 mousePos = GetMouseWorldPosition();
  531. Debug.Log($"Trying to place door at mouse position: {mousePos}");
  532. // Find the closest wall segment
  533. GameObject closestWall = FindClosestWallSegment(mousePos);
  534. if (closestWall != null)
  535. {
  536. Debug.Log($"Found wall for door placement: {closestWall.name}");
  537. PlaceDoor(closestWall, mousePos);
  538. FinishRoom(); // This should reset the building state
  539. // Extra safety: force reset building state immediately
  540. Debug.Log("Building state reset.");
  541. return; // Exit immediately after placing door
  542. }
  543. else
  544. {
  545. Debug.LogWarning("No suitable wall found for door placement");
  546. OnBuildingError?.Invoke("Click closer to a wall to place the door (not too close to corners)");
  547. }
  548. }
  549. private void TryAddDoorToExistingWall()
  550. {
  551. Vector3 mousePos = GetMouseWorldPosition();
  552. Debug.Log($"Trying to add door to existing wall at: {mousePos}");
  553. // Find the closest existing wall segment (not door-related)
  554. GameObject targetWall = null;
  555. float closestDistance = float.MaxValue;
  556. foreach (GameObject wall in wallSegments)
  557. {
  558. if (wall == null || wall.name == "Door Opening" || wall.name == "Door Frame Post") continue;
  559. Renderer wallRenderer = wall.GetComponent<Renderer>();
  560. if (wallRenderer == null) continue;
  561. Bounds bounds = wallRenderer.bounds;
  562. float distance = bounds.SqrDistance(mousePos);
  563. if (distance < closestDistance && distance < 16f) // Max click distance squared
  564. {
  565. targetWall = wall;
  566. closestDistance = distance;
  567. }
  568. }
  569. if (targetWall != null)
  570. {
  571. Debug.Log($"Adding door to existing wall: {targetWall.name}");
  572. PlaceDoor(targetWall, mousePos);
  573. // Switch back to normal building mode
  574. currentBuildingMode = BuildingMode.PlacingWalls;
  575. Debug.Log("Door added successfully, switched back to wall building mode");
  576. }
  577. else
  578. {
  579. OnBuildingError?.Invoke("Click closer to an existing wall to add a door");
  580. }
  581. }
  582. private GameObject FindClosestWallSegment(Vector3 position)
  583. {
  584. GameObject closest = null;
  585. float closestDistance = float.MaxValue;
  586. foreach (GameObject wall in wallSegments)
  587. {
  588. if (wall == null) continue;
  589. // Get the wall bounds
  590. Renderer wallRenderer = wall.GetComponent<Renderer>();
  591. if (wallRenderer == null) continue;
  592. Bounds bounds = wallRenderer.bounds;
  593. float distance = bounds.SqrDistance(position);
  594. // Check if click is within reasonable distance and not too close to wall ends
  595. if (distance < closestDistance && distance < 16f) // Max click distance squared (4^2)
  596. {
  597. // Additional check: make sure we're not too close to wall endpoints
  598. Transform wallTransform = wall.transform;
  599. Vector3 wallDirection = wallTransform.forward;
  600. float wallLength = wallTransform.localScale.z;
  601. Vector3 wallStart = wallTransform.position - wallDirection * wallLength / 2f;
  602. Vector3 wallEnd = wallTransform.position + wallDirection * wallLength / 2f;
  603. float distToStart = Vector3.Distance(position, wallStart);
  604. float distToEnd = Vector3.Distance(position, wallEnd);
  605. // Only allow door placement if not too close to wall ends
  606. if (distToStart > doorWidth * 0.75f && distToEnd > doorWidth * 0.75f)
  607. {
  608. closestDistance = distance;
  609. closest = wall;
  610. }
  611. }
  612. }
  613. return closest;
  614. }
  615. private void PlaceDoor(GameObject wallSegment, Vector3 clickPosition)
  616. {
  617. // Create door opening by modifying the wall
  618. Transform wallTransform = wallSegment.transform;
  619. Vector3 wallScale = wallTransform.localScale;
  620. Vector3 wallCenter = wallTransform.position;
  621. // Calculate wall endpoints correctly based on wall's forward direction (length direction)
  622. Vector3 wallDirection = wallTransform.forward;
  623. float wallLength = wallScale.z; // Length is in Z direction after LookRotation
  624. Vector3 wallStart = wallCenter - wallDirection * wallLength / 2f;
  625. Vector3 wallEnd = wallCenter + wallDirection * wallLength / 2f;
  626. // Project click position onto the wall line to find closest point
  627. Vector3 wallVector = wallEnd - wallStart;
  628. Vector3 clickVector = clickPosition - wallStart;
  629. float t = Mathf.Clamp01(Vector3.Dot(clickVector, wallVector) / wallVector.sqrMagnitude);
  630. Vector3 doorPosition = wallStart + t * wallVector;
  631. // Validate door position - ensure it's not too close to wall ends
  632. float distFromStart = Vector3.Distance(doorPosition, wallStart);
  633. float distFromEnd = Vector3.Distance(doorPosition, wallEnd);
  634. float minDistance = doorWidth * 0.6f;
  635. if (distFromStart < minDistance || distFromEnd < minDistance)
  636. {
  637. // Adjust door position to be away from wall ends
  638. if (distFromStart < distFromEnd)
  639. {
  640. doorPosition = wallStart + wallDirection * minDistance;
  641. }
  642. else
  643. {
  644. doorPosition = wallEnd - wallDirection * minDistance;
  645. }
  646. }
  647. Debug.Log($"Door placed at: {doorPosition} on wall from {wallStart} to {wallEnd}");
  648. // Remove original wall and create two wall segments with a gap
  649. wallSegments.Remove(wallSegment);
  650. DestroyImmediate(wallSegment);
  651. CreateWallWithDoor(wallStart, wallEnd, doorPosition);
  652. }
  653. private void CreateWallWithDoor(Vector3 wallStart, Vector3 wallEnd, Vector3 doorPosition)
  654. {
  655. Vector3 wallDirection = (wallEnd - wallStart).normalized;
  656. float doorHalfWidth = doorWidth / 2f;
  657. Vector3 door1End = doorPosition - wallDirection * doorHalfWidth;
  658. Vector3 door2Start = doorPosition + wallDirection * doorHalfWidth;
  659. // Create wall segment before door (if long enough)
  660. float leftSegmentLength = Vector3.Distance(wallStart, door1End);
  661. if (leftSegmentLength > 0.5f)
  662. {
  663. CreateWallSegment(wallStart, door1End);
  664. }
  665. // Create wall segment after door (if long enough)
  666. float rightSegmentLength = Vector3.Distance(door2Start, wallEnd);
  667. if (rightSegmentLength > 0.5f)
  668. {
  669. CreateWallSegment(door2Start, wallEnd);
  670. }
  671. // Create door frame markers (simplified)
  672. CreateDoorFrame(doorPosition, wallDirection);
  673. }
  674. private void CreateDoorFrame(Vector3 position, Vector3 direction)
  675. {
  676. Debug.Log($"Creating door frame at position: {position}, direction: {direction}");
  677. // Create simple door frame markers - two posts on sides of the opening
  678. float frameHeight = wallHeight * 0.85f; // Door frame slightly lower than wall
  679. // Fix rotation: Use the direction itself (not perpendicular) for door post positioning
  680. // This positions posts along the wall direction, not perpendicular to it
  681. Vector3 postDirection = direction.normalized;
  682. // Position posts precisely at the door opening edges along the wall
  683. Vector3 leftPost = position - postDirection * doorWidth / 2f;
  684. Vector3 rightPost = position + postDirection * doorWidth / 2f;
  685. // Ensure door posts are exactly at ground level like walls
  686. leftPost.y = 0f;
  687. rightPost.y = 0f;
  688. Debug.Log($"Door posts positioned at: Left={leftPost}, Right={rightPost}, Height={frameHeight}");
  689. CreateDoorPost(leftPost, frameHeight);
  690. CreateDoorPost(rightPost, frameHeight);
  691. // Create a subtle ground-level door opening indicator
  692. GameObject doorOpening = new GameObject("Door Opening");
  693. doorOpening.transform.position = new Vector3(position.x, 0.01f, position.z); // Just above ground
  694. // Add a simple flat rectangle for the door opening - perpendicular to wall direction
  695. GameObject indicator = GameObject.CreatePrimitive(PrimitiveType.Cube);
  696. indicator.transform.SetParent(doorOpening.transform);
  697. indicator.transform.localPosition = Vector3.zero;
  698. // Rotate indicator to be perpendicular to the wall (across the door opening)
  699. Vector3 perpendicular = Vector3.Cross(direction, Vector3.up).normalized;
  700. indicator.transform.localRotation = Quaternion.LookRotation(perpendicular);
  701. indicator.transform.localScale = new Vector3(doorWidth, 0.02f, 0.1f); // Thin flat indicator
  702. Renderer indicatorRenderer = indicator.GetComponent<Renderer>();
  703. if (indicatorRenderer != null)
  704. {
  705. indicatorRenderer.material.color = new Color(0.2f, 0.8f, 0.2f, 0.7f); // Semi-transparent green
  706. }
  707. indicator.name = "Door Indicator";
  708. // Make door indicator non-clickable by removing collider
  709. Collider indicatorCollider = indicator.GetComponent<Collider>();
  710. if (indicatorCollider != null)
  711. {
  712. DestroyImmediate(indicatorCollider);
  713. }
  714. wallSegments.Add(doorOpening);
  715. }
  716. private void CreateDoorPost(Vector3 position, float height)
  717. {
  718. GameObject post = GameObject.CreatePrimitive(PrimitiveType.Cube);
  719. post.name = "Door Frame Post";
  720. // Position door posts to match wall anchoring (anchored to floor)
  721. // Ensure Y position matches how walls are positioned at wallHeight/2
  722. post.transform.position = new Vector3(position.x, height / 2f, position.z);
  723. post.transform.localScale = new Vector3(0.15f, height, 0.15f); // Slightly thinner posts
  724. if (doorFrameMaterial != null)
  725. {
  726. post.GetComponent<Renderer>().material = doorFrameMaterial;
  727. }
  728. else
  729. {
  730. post.GetComponent<Renderer>().material.color = new Color(0.4f, 0.2f, 0.05f); // Darker brown
  731. }
  732. // Make door posts non-clickable but solid
  733. Collider postCollider = post.GetComponent<Collider>();
  734. if (postCollider != null)
  735. {
  736. postCollider.isTrigger = false; // Keep solid for visual barrier
  737. post.layer = LayerMask.NameToLayer("Default"); // Non-clickable layer
  738. }
  739. wallSegments.Add(post);
  740. }
  741. private void FinishRoom()
  742. {
  743. // Use the stored room data if we have it (from auto-detection), otherwise create new
  744. if (currentRoom == null)
  745. {
  746. currentRoom = new Room
  747. {
  748. roomPoints = new List<Vector3>(currentRoomPoints),
  749. wallObjects = new List<GameObject>(wallSegments),
  750. isComplete = true,
  751. roomType = RoomType.Generic
  752. };
  753. }
  754. else
  755. {
  756. // Update the existing room with completion status
  757. currentRoom.isComplete = true;
  758. currentRoom.hasEntrance = true;
  759. }
  760. lastRoomCompletionTime = Time.time; // Set completion time for cooldown
  761. Debug.Log($"Room completed with door placed");
  762. // Clean up visual markers
  763. foreach (GameObject marker in wallPointMarkers)
  764. {
  765. if (marker != null)
  766. DestroyImmediate(marker);
  767. }
  768. // Notify completion
  769. OnRoomCompleted?.Invoke(currentRoom);
  770. // Reset building state
  771. ResetBuildingState();
  772. }
  773. #endregion
  774. #region Preview and Visual Feedback
  775. private void UpdatePreview()
  776. {
  777. if (!isBuildingRoom || currentRoomPoints.Count == 0)
  778. {
  779. if (previewLine != null)
  780. previewLine.enabled = false;
  781. return;
  782. }
  783. Vector3 mousePos = GetMouseWorldPosition();
  784. // Update preview line
  785. if (previewLine != null)
  786. {
  787. previewLine.enabled = true;
  788. previewLine.positionCount = 2;
  789. previewLine.SetPosition(0, currentRoomPoints[currentRoomPoints.Count - 1]);
  790. previewLine.SetPosition(1, mousePos);
  791. // Change color based on snap distance to first point
  792. if (currentRoomPoints.Count > 2 && Vector3.Distance(mousePos, currentRoomPoints[0]) < snapTolerance)
  793. {
  794. previewLine.material.color = Color.green; // Can close room
  795. }
  796. else
  797. {
  798. previewLine.material.color = Color.white; // Normal building
  799. }
  800. }
  801. }
  802. private void CreatePreviewLine()
  803. {
  804. GameObject previewObj = new GameObject("Preview Line");
  805. previewLine = previewObj.AddComponent<LineRenderer>();
  806. previewLine.material = new Material(Shader.Find("Sprites/Default"));
  807. previewLine.startColor = Color.white;
  808. previewLine.endColor = Color.white;
  809. previewLine.startWidth = 0.1f;
  810. previewLine.enabled = false;
  811. }
  812. #endregion
  813. #region Utility Methods
  814. private void CancelBuilding()
  815. {
  816. if (isBuildingRoom || isPlacingDoor)
  817. {
  818. Debug.Log("Cancelling room construction");
  819. // Clean up
  820. foreach (GameObject wall in wallSegments)
  821. {
  822. if (wall != null)
  823. DestroyImmediate(wall);
  824. }
  825. foreach (GameObject marker in wallPointMarkers)
  826. {
  827. if (marker != null)
  828. DestroyImmediate(marker);
  829. }
  830. // Reset state
  831. isBuildingRoom = false;
  832. isPlacingDoor = false;
  833. currentRoomPoints.Clear();
  834. wallSegments.Clear();
  835. wallPointMarkers.Clear();
  836. if (previewLine != null)
  837. previewLine.enabled = false;
  838. }
  839. }
  840. public bool IsBuilding()
  841. {
  842. return isBuildingRoom || isPlacingDoor;
  843. }
  844. private void ResetBuildingState()
  845. {
  846. Debug.Log($"ResetBuildingState() called - Before: isBuildingRoom={isBuildingRoom}, isPlacingDoor={isPlacingDoor}, currentBuildingMode={currentBuildingMode}");
  847. // Reset all building-related state variables
  848. isBuildingRoom = false;
  849. isPlacingDoor = false;
  850. currentBuildingMode = BuildingMode.PlacingWalls;
  851. currentRoomPoints.Clear();
  852. wallSegments.Clear();
  853. wallPointMarkers.Clear();
  854. if (previewLine != null)
  855. previewLine.enabled = false;
  856. Debug.Log($"ResetBuildingState() completed - After: isBuildingRoom={isBuildingRoom}, isPlacingDoor={isPlacingDoor}, currentBuildingMode={currentBuildingMode}");
  857. }
  858. private void CheckForNewEnclosedRooms()
  859. {
  860. Debug.Log("CheckForNewEnclosedRooms() called");
  861. // Skip room detection if we just completed a room recently (prevents immediate re-detection)
  862. if (Time.time - lastRoomCompletionTime < ROOM_DETECTION_COOLDOWN)
  863. {
  864. Debug.Log("Skipping room detection due to cooldown period");
  865. return;
  866. }
  867. // Skip if we're already placing a door
  868. if (isPlacingDoor)
  869. {
  870. Debug.Log("Skipping room detection - already placing door");
  871. return;
  872. }
  873. // Get all wall segments from multiple sources to ensure we find ALL walls
  874. List<GameObject> allWalls = new List<GameObject>();
  875. // Method 1: Include walls from our wallSegments list
  876. List<GameObject> roomBuilderWalls = wallSegments.Where(wall =>
  877. wall != null &&
  878. wall.name == "Wall Segment" &&
  879. wall.GetComponent<Renderer>() != null
  880. ).ToList();
  881. allWalls.AddRange(roomBuilderWalls);
  882. // Method 2: Find all GameObjects that look like walls in the scene
  883. GameObject[] allGameObjects = FindObjectsByType<GameObject>(FindObjectsSortMode.None);
  884. foreach (GameObject obj in allGameObjects)
  885. {
  886. if (obj != null && obj.GetComponent<Renderer>() != null &&
  887. (obj.name.Contains("Wall") || obj.name.Contains("wall")) &&
  888. !allWalls.Contains(obj))
  889. {
  890. allWalls.Add(obj);
  891. }
  892. }
  893. Debug.Log($"Found {roomBuilderWalls.Count} walls from RoomBuilder, {allWalls.Count} total walls in scene");
  894. // Debug: List all walls found
  895. foreach (var wall in allWalls)
  896. {
  897. if (wall != null)
  898. {
  899. Debug.Log($"Wall found: {wall.name} at position {wall.transform.position}");
  900. }
  901. }
  902. // Find enclosed areas using the wall network
  903. List<List<Vector3>> enclosedAreas = FindEnclosedAreas(allWalls);
  904. Debug.Log($"FindEnclosedAreas returned {enclosedAreas.Count} areas");
  905. foreach (var area in enclosedAreas)
  906. {
  907. Debug.Log($"Checking area with {area.Count} points");
  908. if (IsValidRoom(area))
  909. {
  910. // Check if this room has any doors
  911. bool hasDoor = DoesAreaHaveDoor(area);
  912. Debug.Log($"Valid room found with {area.Count} points. Has door: {hasDoor}");
  913. if (!hasDoor)
  914. {
  915. Debug.Log($"Found new enclosed room without door - forcing door placement");
  916. ForceRoomDoorPlacement(area);
  917. return; // Handle one room at a time
  918. }
  919. else
  920. {
  921. Debug.Log($"Found enclosed room with existing door - completing room");
  922. CompleteDetectedRoom(area);
  923. }
  924. }
  925. else
  926. {
  927. Debug.Log($"Area not valid as room (too small or insufficient points)");
  928. }
  929. }
  930. }
  931. private List<List<Vector3>> FindEnclosedAreas(List<GameObject> walls)
  932. {
  933. Debug.Log("FindEnclosedAreas called");
  934. List<List<Vector3>> enclosedAreas = new List<List<Vector3>>();
  935. // Instead of just checking current room points, do a comprehensive wall network analysis
  936. if (walls.Count >= 3) // Need at least 3 walls to form a room
  937. {
  938. Debug.Log("Attempting comprehensive wall network analysis");
  939. // Use the global polygon detection method
  940. enclosedAreas = DetectClosedPolygonsFromWalls(walls);
  941. }
  942. // Also check if current room points form a potential room (original logic)
  943. if (currentRoomPoints.Count >= 3)
  944. {
  945. Debug.Log("Checking current room points for completion");
  946. Vector3 firstPoint = currentRoomPoints[0];
  947. Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1];
  948. // Check if there are existing walls that could complete the loop
  949. foreach (GameObject wall in walls)
  950. {
  951. if (CouldWallCompleteRoom(wall, firstPoint, lastPoint))
  952. {
  953. Debug.Log("Found wall that could complete current room");
  954. List<Vector3> completedRoom = new List<Vector3>(currentRoomPoints);
  955. enclosedAreas.Add(completedRoom);
  956. break;
  957. }
  958. }
  959. }
  960. Debug.Log($"FindEnclosedAreas returning {enclosedAreas.Count} areas");
  961. return enclosedAreas;
  962. }
  963. private bool CouldWallCompleteRoom(GameObject wall, Vector3 roomStart, Vector3 roomEnd)
  964. {
  965. if (wall == null) return false;
  966. // Get wall endpoints
  967. Transform wallTransform = wall.transform;
  968. Vector3 wallScale = wallTransform.localScale;
  969. Vector3 wallCenter = wallTransform.position;
  970. Vector3 wallDirection = wallTransform.forward;
  971. float wallLength = wallScale.z;
  972. Vector3 wallStart = wallCenter - wallDirection * wallLength / 2f;
  973. Vector3 wallEnd = wallCenter + wallDirection * wallLength / 2f;
  974. // Check if either end of the room is close to either end of this wall
  975. float tolerance = snapTolerance * 2f;
  976. return (Vector3.Distance(roomStart, wallStart) < tolerance ||
  977. Vector3.Distance(roomStart, wallEnd) < tolerance) &&
  978. (Vector3.Distance(roomEnd, wallStart) < tolerance ||
  979. Vector3.Distance(roomEnd, wallEnd) < tolerance);
  980. }
  981. private bool IsValidRoom(List<Vector3> area)
  982. {
  983. if (area.Count < 3) return false;
  984. // Calculate area - rooms should have minimum area
  985. float area2D = CalculatePolygonArea(area);
  986. return area2D > 4f; // Minimum room area
  987. }
  988. private float CalculatePolygonArea(List<Vector3> points)
  989. {
  990. if (points.Count < 3) return 0f;
  991. float area = 0f;
  992. for (int i = 0; i < points.Count; i++)
  993. {
  994. Vector3 current = points[i];
  995. Vector3 next = points[(i + 1) % points.Count];
  996. area += (current.x * next.z - next.x * current.z);
  997. }
  998. return Mathf.Abs(area) / 2f;
  999. }
  1000. private bool DoesAreaHaveDoor(List<Vector3> area)
  1001. {
  1002. // Check if there are any door openings or door frame posts within the room area
  1003. foreach (GameObject segment in wallSegments)
  1004. {
  1005. if (segment != null && (segment.name == "Door Opening" || segment.name == "Door Frame Post"))
  1006. {
  1007. Vector3 doorPos = segment.transform.position;
  1008. if (IsPointInPolygon(doorPos, area))
  1009. {
  1010. return true;
  1011. }
  1012. }
  1013. }
  1014. return false;
  1015. }
  1016. private bool IsPointInPolygon(Vector3 point, List<Vector3> polygon)
  1017. {
  1018. // Simple ray casting algorithm for 2D point-in-polygon test
  1019. bool inside = false;
  1020. for (int i = 0, j = polygon.Count - 1; i < polygon.Count; j = i++)
  1021. {
  1022. Vector3 pi = polygon[i];
  1023. Vector3 pj = polygon[j];
  1024. if (((pi.z > point.z) != (pj.z > point.z)) &&
  1025. (point.x < (pj.x - pi.x) * (point.z - pi.z) / (pj.z - pi.z) + pi.x))
  1026. {
  1027. inside = !inside;
  1028. }
  1029. }
  1030. return inside;
  1031. }
  1032. private void ForceRoomDoorPlacement(List<Vector3> roomArea)
  1033. {
  1034. Debug.Log("Forcing door placement for enclosed room");
  1035. // Switch to door placement mode
  1036. isBuildingRoom = false;
  1037. isPlacingDoor = true;
  1038. currentBuildingMode = BuildingMode.PlacingDoor;
  1039. // Store the room data
  1040. currentRoom = new Room
  1041. {
  1042. roomPoints = roomArea,
  1043. isComplete = false
  1044. };
  1045. // Clear current room points since we're now in door mode
  1046. currentRoomPoints.Clear();
  1047. OnBuildingError?.Invoke("Room detected! Click on a wall to place a door.");
  1048. }
  1049. private void CompleteDetectedRoom(List<Vector3> roomArea)
  1050. {
  1051. Debug.Log("Completing room with existing door");
  1052. // Create and complete the room
  1053. Room detectedRoom = new Room
  1054. {
  1055. roomPoints = roomArea,
  1056. isComplete = true,
  1057. hasEntrance = true
  1058. };
  1059. // Fire completion event
  1060. OnRoomCompleted?.Invoke(detectedRoom);
  1061. // Reset building state
  1062. ResetBuildingState();
  1063. }
  1064. private void CheckForNewEnclosedRoomsGlobal()
  1065. {
  1066. // This is a more comprehensive room detection that works with all existing walls
  1067. // It finds enclosed areas using all wall segments, not just current room points
  1068. List<GameObject> actualWalls = wallSegments.Where(wall =>
  1069. wall != null &&
  1070. wall.name == "Wall Segment" &&
  1071. wall.GetComponent<Renderer>() != null
  1072. ).ToList();
  1073. if (actualWalls.Count < 3) return; // Need at least 3 walls to form a room
  1074. Debug.Log($"Checking for enclosed rooms with {actualWalls.Count} walls");
  1075. // Use a more sophisticated approach to detect closed polygons
  1076. List<List<Vector3>> detectedRooms = DetectClosedPolygonsFromWalls(actualWalls);
  1077. Debug.Log($"Found {detectedRooms.Count} potential rooms");
  1078. foreach (var roomArea in detectedRooms)
  1079. {
  1080. Debug.Log($"Checking room with {roomArea.Count} points, area: {CalculatePolygonArea(roomArea)}");
  1081. if (IsValidRoom(roomArea))
  1082. {
  1083. bool hasDoor = DoesAreaHaveDoor(roomArea);
  1084. Debug.Log($"Valid room found. Has door: {hasDoor}");
  1085. if (!hasDoor)
  1086. {
  1087. Debug.Log($"Detected new enclosed room without door - forcing door placement");
  1088. ForceRoomDoorPlacement(roomArea);
  1089. return; // Handle one room at a time
  1090. }
  1091. else
  1092. {
  1093. Debug.Log($"Room already has door - completing");
  1094. CompleteDetectedRoom(roomArea);
  1095. return;
  1096. }
  1097. }
  1098. }
  1099. Debug.Log("No valid enclosed rooms detected");
  1100. }
  1101. private List<List<Vector3>> DetectClosedPolygonsFromWalls(List<GameObject> walls)
  1102. {
  1103. List<List<Vector3>> closedPolygons = new List<List<Vector3>>();
  1104. Debug.Log($"Starting simplified room detection with {walls.Count} walls");
  1105. // Simple approach: Look for rectangular rooms formed by connecting existing walls
  1106. // This is more reliable than complex graph algorithms for your use case
  1107. // Check if current room points connect existing walls to form a room
  1108. if (currentRoomPoints.Count >= 2)
  1109. {
  1110. Vector3 firstPoint = currentRoomPoints[0];
  1111. Vector3 lastPoint = currentRoomPoints[currentRoomPoints.Count - 1];
  1112. Debug.Log($"Checking if room points form enclosed area: {currentRoomPoints.Count} points");
  1113. Debug.Log($"First point: {firstPoint}, Last point: {lastPoint}");
  1114. // Check if we can find existing walls that connect our start and end points
  1115. bool startsOnWall = IsPointOnAnyWall(firstPoint, walls);
  1116. bool endsOnWall = IsPointOnAnyWall(lastPoint, walls);
  1117. Debug.Log($"First point on wall: {startsOnWall}, Last point on wall: {endsOnWall}");
  1118. if (startsOnWall && endsOnWall && currentRoomPoints.Count >= 3)
  1119. {
  1120. // Check if there's a path of existing walls that could complete the room
  1121. if (CanWallsCompleteRoom(firstPoint, lastPoint, walls))
  1122. {
  1123. Debug.Log("Found completed room using wall connections!");
  1124. List<Vector3> completedRoom = new List<Vector3>(currentRoomPoints);
  1125. closedPolygons.Add(completedRoom);
  1126. }
  1127. }
  1128. }
  1129. Debug.Log($"Simplified detection found {closedPolygons.Count} rooms");
  1130. return closedPolygons;
  1131. }
  1132. private bool IsPointOnAnyWall(Vector3 point, List<GameObject> walls)
  1133. {
  1134. float tolerance = 2.0f; // Distance tolerance for "on wall"
  1135. foreach (GameObject wall in walls)
  1136. {
  1137. if (wall == null) continue;
  1138. // Check if point is close to this wall
  1139. Bounds wallBounds = wall.GetComponent<Renderer>().bounds;
  1140. if (wallBounds.SqrDistance(point) < tolerance * tolerance)
  1141. {
  1142. Debug.Log($"Point {point} is near wall {wall.name}");
  1143. return true;
  1144. }
  1145. }
  1146. return false;
  1147. }
  1148. private bool CanWallsCompleteRoom(Vector3 startPoint, Vector3 endPoint, List<GameObject> walls)
  1149. {
  1150. // Simple check: if we have walls forming the outer boundary and our points connect them,
  1151. // we likely have a completed room
  1152. // Count how many walls we have that could form room boundaries
  1153. int boundaryWalls = 0;
  1154. foreach (GameObject wall in walls)
  1155. {
  1156. if (wall != null && wall.name.Contains("Wall"))
  1157. {
  1158. boundaryWalls++;
  1159. }
  1160. }
  1161. Debug.Log($"Found {boundaryWalls} potential boundary walls");
  1162. // If we have sufficient boundary walls and our points connect them, assume room completion
  1163. return boundaryWalls >= 4; // At least 4 walls needed for a room
  1164. }
  1165. private Vector3 RoundVector3(Vector3 vector, float precision)
  1166. {
  1167. return new Vector3(
  1168. Mathf.Round(vector.x / precision) * precision,
  1169. Mathf.Round(vector.y / precision) * precision,
  1170. Mathf.Round(vector.z / precision) * precision
  1171. );
  1172. }
  1173. private List<Vector3> FindSmallestCycle(Dictionary<Vector3, List<Vector3>> graph, Vector3 start, HashSet<Vector3> globalVisited)
  1174. {
  1175. if (globalVisited.Contains(start)) return null;
  1176. // DFS to find the shortest cycle starting from this point
  1177. HashSet<Vector3> pathVisited = new HashSet<Vector3>();
  1178. List<Vector3> path = new List<Vector3>();
  1179. if (DFSFindCycle(graph, start, start, pathVisited, path))
  1180. {
  1181. return new List<Vector3>(path);
  1182. }
  1183. return null;
  1184. }
  1185. private bool DFSFindCycle(Dictionary<Vector3, List<Vector3>> graph, Vector3 current, Vector3 target, HashSet<Vector3> pathVisited, List<Vector3> path)
  1186. {
  1187. // Check if we found a cycle back to start - use exact equality since we're using rounded coordinates
  1188. if (path.Count > 0 && path.Count >= 3)
  1189. {
  1190. // Check if current point connects back to target in the graph
  1191. if (graph.ContainsKey(current) && graph[current].Contains(target))
  1192. {
  1193. Debug.Log($"Found cycle! Path length: {path.Count}");
  1194. return true;
  1195. }
  1196. }
  1197. if (path.Count > 6) return false; // Limit search depth to avoid infinite loops
  1198. pathVisited.Add(current);
  1199. path.Add(current);
  1200. if (graph.ContainsKey(current))
  1201. {
  1202. foreach (var neighbor in graph[current])
  1203. {
  1204. // Don't revisit the previous node (except when completing cycle)
  1205. if (path.Count == 1 ||
  1206. (!pathVisited.Contains(neighbor)) ||
  1207. (path.Count >= 3 && neighbor.Equals(target)))
  1208. {
  1209. if (DFSFindCycle(graph, neighbor, target, pathVisited, path))
  1210. return true;
  1211. }
  1212. }
  1213. }
  1214. pathVisited.Remove(current);
  1215. path.RemoveAt(path.Count - 1);
  1216. return false;
  1217. }
  1218. #endregion
  1219. }
  1220. // Data classes
  1221. [System.Serializable]
  1222. public class Room
  1223. {
  1224. public List<Vector3> roomPoints = new List<Vector3>();
  1225. public List<GameObject> wallObjects = new List<GameObject>();
  1226. public bool isComplete = false;
  1227. public RoomType roomType = RoomType.Generic;
  1228. public Vector3 doorPosition;
  1229. public bool hasEntrance = false;
  1230. public bool hasReception = false;
  1231. }
  1232. public enum RoomType
  1233. {
  1234. Generic,
  1235. Reception,
  1236. GuestRoom,
  1237. Restaurant,
  1238. Kitchen,
  1239. Storage,
  1240. Bathroom,
  1241. Lobby
  1242. }