using System; using System.Collections.Generic; using System.Text; using EzySlice; using UnityEngine; using UnityEngine.SceneManagement; using Plane = EzySlice.Plane; public class WallCutter : MonoBehaviour { public GameObject pointPrefab; public Transform parrentSplitWall; private List points = new List(); private LineRenderer lineRenderer; private List pointsObjects = new List(); private List hulls = new List(); private List newHulls = new List(); private List allHulls = new List(); public List newMatForWalls; private Transform parrent; private WallType wallType = WallType.None; private float distanceToCloseFigure = 0.05f; [HideInInspector] public GameObject divideObject; public bool IsDivideMode { get; set; } = false; public bool CanDivide { get; set; } private bool isClosedFigure = false; void Start() { // Инициализация LineRenderer BuildingRoom.Instance.OnEnableDivideMode += OnDivedeModeChange; BuildingRoom.Instance.OnViewTypeChange += OnViewChange; lineRenderer = GetComponent(); } void Update() { if (IsDivideMode) { if (Input.GetMouseButtonDown(0)) { RaycastHit hit; Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out hit)) { if (hit.transform.gameObject == divideObject) { Vector3 newPoint = new Vector3(hit.point.x, hit.point.y, hit.point.z); if (points.Count >= 15) return; if (points.Count == 0 || CheckDistanceToPoint(newPoint)) { points.Add(newPoint); CreatePointObject(hit.point); // Создаем визуальный объект точки UpdateLineRenderer(); } } } } if (Input.GetKeyDown(KeyCode.Space)) { if (points.Count > 2) { if (Vector3.Distance(points[0], points[points.Count - 1]) > distanceToCloseFigure + 0.1f) { isClosedFigure = false; AddBoundaryPointFromFirstToSecond(); AddBoundaryPointFromLastToSecondLast(); UpdateLineRenderer(false); } else { isClosedFigure = true; UpdateLineRenderer(true); } CutMesh(); CheckAndCombineHulls(); } else if (points.Count == 2) { isClosedFigure = false; CutMesh(); CheckAndCombineHulls(); } ClearAllPoints(true); } } } private bool CheckDistanceToPoint(Vector3 newPoint) { foreach (var point in points) { if (Vector3.Distance(point, newPoint) < distanceToCloseFigure) return false; } return true; } private void UpdateLineRenderer(bool checkClosure = false) { bool isClosed = checkClosure && Vector3.Distance(points[points.Count - 1], points[0]) < distanceToCloseFigure; lineRenderer.positionCount = points.Count + (isClosedFigure ? 1 : 0); for (int i = 0; i < points.Count; i++) { Vector3 point = points[i]; lineRenderer.SetPosition(i, point); } if (isClosedFigure) { Vector3 firstPoint = points[0]; lineRenderer.SetPosition(points.Count, firstPoint); } } private void CreatePointObject(Vector3 position) { GameObject pointObject = Instantiate(pointPrefab); pointObject.transform.position = position; pointsObjects.Add(pointObject); } private void AddBoundaryPointFromFirstToSecond() { Vector3 point1 = points[0]; Vector3 point2 = points[1]; Vector3 direction = (point2 - point1).normalized; if (Vector3.Distance(point2, point1) < distanceToCloseFigure) { points[1] = points[0]; return; } Bounds wallBounds = divideObject.GetComponent().bounds; Vector3 boundaryPoint; if (FindClosestIntersectionWithWallBoundary(point1, direction, wallBounds, out boundaryPoint)) { points.Insert(1, boundaryPoint); CreatePointObject(boundaryPoint); } } private void AddBoundaryPointFromLastToSecondLast() { if (points.Count < 2) return; Vector3 lastPoint = points[points.Count - 1]; Vector3 secondLastPoint = points[points.Count - 2]; Vector3 direction = (secondLastPoint - lastPoint).normalized; Bounds wallBounds = divideObject.GetComponent().bounds; Vector3 boundaryPoint; if (FindClosestIntersectionWithWallBoundary(lastPoint, direction, wallBounds, out boundaryPoint)) { points.Add(boundaryPoint); CreatePointObject(boundaryPoint); } } private bool FindClosestIntersectionWithWallBoundary(Vector3 origin, Vector3 direction, Bounds bounds, out Vector3 closestIntersection) { closestIntersection = Vector3.zero; float closestDistance = float.MaxValue; bool foundIntersection = false; Vector3[] faceCenters = { bounds.center + new Vector3(bounds.extents.x, 0, 0), bounds.center - new Vector3(bounds.extents.x, 0, 0), bounds.center + new Vector3(0, bounds.extents.y, 0), bounds.center - new Vector3(0, bounds.extents.y, 0), bounds.center + new Vector3(0, 0, bounds.extents.z), bounds.center - new Vector3(0, 0, bounds.extents.z) }; Vector3[] faceNormals = { Vector3.right, Vector3.left, Vector3.up, Vector3.down, Vector3.forward, Vector3.back }; for (int i = 0; i < faceCenters.Length; i++) { Vector3 planeNormal = faceNormals[i]; Vector3 planePoint = faceCenters[i]; float denominator = Vector3.Dot(planeNormal, -direction); if (Mathf.Abs(denominator) > 1e-6) { Vector3 diff = planePoint - origin; float t = Vector3.Dot(diff, planeNormal) / denominator; if (t >= 0) { Vector3 intersection = origin + t * -direction; if (bounds.Contains(intersection) && t < closestDistance) { closestDistance = t; closestIntersection = intersection; foundIntersection = true; } } } } return foundIntersection; } public void CutMesh() { hulls.Clear(); hulls.Add(divideObject); int hullCounter = 0; for (int i = 0; i < points.Count - 1; i++) { Vector3 point1 = points[i]; Vector3 point2 = points[i + 1]; Vector3 normal = Vector3.Cross(point2 - point1, GetSurface(divideObject)).normalized; Vector3 position = (point1 + point2) / 2.0f; newHulls = new List(); foreach (GameObject hull in hulls) { SlicedHull slicedHull = hull.Slice(position, normal); if (slicedHull != null) { GameObject upperHull = slicedHull.CreateUpperHull(hull, divideObject.GetComponent().material); GameObject lowerHull = slicedHull.CreateLowerHull(hull, divideObject.GetComponent().material); AddComponentsToHull(upperHull, "UpperHull_" + hullCounter++); AddComponentsToHull(lowerHull, "LowerHull_" + hullCounter++); upperHull.transform.position = divideObject.transform.position; lowerHull.transform.position = divideObject.transform.position; newHulls.Add(upperHull); newHulls.Add(lowerHull); allHulls.Add(upperHull); allHulls.Add(lowerHull); } else { newHulls.Add(hull); allHulls.Add(hull); } } hulls = newHulls; } } private void CheckAndCombineHulls() { List insideHulls = new List(); List outsideHulls = new List(); if (hulls.Count < 2) { return; } GameObject currentHull = hulls[0]; insideHulls.Add(currentHull); int index = 0; while (index < insideHulls.Count) { currentHull = insideHulls[index]; for (int i = 1; i < hulls.Count; i++) { GameObject neighborHull = hulls[i]; if (!insideHulls.Contains(neighborHull) && !IsLineBetweenHulls(currentHull, neighborHull)) { insideHulls.Add(neighborHull); } } index++; } foreach (var hull in hulls) { if (!insideHulls.Contains(hull)) { outsideHulls.Add(hull); } } if (insideHulls.Count <= 0) { ClearAllPoints(true); IsDivideMode = false; return; } if (outsideHulls.Count <= 0) { ClearAllPoints(true); IsDivideMode = false; return; } GameObject inside = null; GameObject outSide = null; if (insideHulls.Count > 0) { GameObject insideCombinedHull = CombineMeshes(new HashSet(insideHulls), "InsideHull"); insideCombinedHull.transform.SetParent(parrent); AddComponentsToWalls(insideCombinedHull, 0); inside = insideCombinedHull; } if (outsideHulls.Count > 0) { GameObject outsideCombinedHull = CombineMeshes(new HashSet(outsideHulls), "OutsideHull"); outsideCombinedHull.transform.SetParent(parrent); AddComponentsToWalls(outsideCombinedHull, 1); outSide = outsideCombinedHull; } SaveChangeModel.Instance.SaveChangeSplitMesh(divideObject, inside, outSide); points.Clear(); ClearAllPoints(); lineRenderer.positionCount = 0; IsDivideMode = false; } private GameObject CombineMeshes(HashSet group, string name) { if (group.Count == 0) { return null; } List meshFilters = new List(); foreach (var hull in group) { meshFilters.Add(hull.GetComponent()); } CombineInstance[] combine = new CombineInstance[meshFilters.Count]; for (int i = 0; i < meshFilters.Count; i++) { combine[i].mesh = meshFilters[i].sharedMesh; combine[i].transform = meshFilters[i].transform.localToWorldMatrix; } GameObject combinedObject = new GameObject(name); MeshFilter combinedMeshFilter = combinedObject.AddComponent(); combinedMeshFilter.mesh = new Mesh(); combinedMeshFilter.mesh.CombineMeshes(combine, true, true); MeshRenderer combinedMeshRenderer = combinedObject.AddComponent(); combinedMeshRenderer.material = divideObject.GetComponent().material; MeshCollider combinedMeshCollider = combinedObject.AddComponent(); combinedMeshCollider.sharedMesh = combinedMeshFilter.mesh; return combinedObject; } private bool IsLineBetweenHulls(GameObject hull1, GameObject hull2) { if (points.Count == 2) return true; Vector3 center1 = hull1.GetComponent().bounds.center; Vector3 center2 = hull2.GetComponent().bounds.center; Vector2 localCenter1 = center1; Vector2 localCenter2 = center2; for (int i = 0; i < points.Count - 1; i++) { Vector3 p1 = points[i]; Vector3 p2 = points[i + 1]; Vector2 localP1 = p1; Vector2 localP2 = p2; switch (wallType) { case WallType.WallX: localP1 = new Vector2(p1.y, p1.z); localP2 = new Vector3(p2.y, p2.z); localCenter1 = new Vector2(center1.y, center1.z); localCenter2 = new Vector2(center2.y, center2.z); break; case WallType.WallY: localP1 = new Vector3(p1.x,p1.z); localP2 = new Vector3(p2.x,p2.z); localCenter1 = new Vector2(center1.x, center1.z); localCenter2 = new Vector2(center2.x, center2.z); break; case WallType.WallZ: localP1 = new Vector3(p1.x,p1.y); localP2 = new Vector3(p2.x, p2.y); localCenter1 = new Vector2(center1.x, center1.y); localCenter2 = new Vector2(center2.x, center2.y); break; case WallType.None: ClearAllPoints(); IsDivideMode = false; break; } Line3D l1 = new Line3D(localCenter1, localCenter2); Line3D l2 = new Line3D(localP1, localP2); LineIntersection3D line = new LineIntersection3D(); if(line.AreLinesIntersecting3D(l1,l2)) { return true; } } if (isClosedFigure) { Vector3 lastPoint = points[points.Count - 1]; Vector3 firstPoint = points[0]; Line3D l2 = new Line3D(lastPoint, firstPoint); Line3D l1 = new Line3D(center1, center2); LineIntersection3D line = new LineIntersection3D(); if (line.AreLinesIntersecting3D(l1,l2)) { return true; } } return false; } private void AddComponentsToHull(GameObject hull, string name) { if (hull.GetComponent() == null) { MeshFilter meshFilter = hull.AddComponent(); meshFilter.mesh = hull.GetComponent().mesh; } if (hull.GetComponent() == null) { MeshRenderer meshRenderer = hull.AddComponent(); meshRenderer.material = divideObject.GetComponent().material; } if (hull.GetComponent() == null) { MeshCollider collider = hull.AddComponent(); collider.sharedMesh = hull.GetComponent().mesh; } hull.name = name; } private void ClearAllPoints(bool isDisable = false) { foreach (var point in pointsObjects) { Destroy(point); } isClosedFigure = false; pointsObjects.Clear(); lineRenderer.loop = false; lineRenderer.positionCount = 0; foreach (var hull in allHulls) { Destroy(hull); } allHulls = new List(); if (!isDisable) { divideObject.SetActive(false); } } public void OnDivedeModeChange(GameObject gameObject) { IsDivideMode = !IsDivideMode; divideObject = gameObject; parrent = BuildingRoom.Instance.CurrentRoon.transform; ClearAllPoints(true); lineRenderer.loop = false; points.Clear(); lineRenderer.positionCount = 0; } public void AddComponentsToWalls(GameObject wall, int materialIndex) { wall.tag = divideObject.tag; wall.GetComponent().material = newMatForWalls[materialIndex]; wall.AddComponent(); wall.GetComponent().convex = false; wall.GetComponent().providesContacts = true; } private void OnDestroy() { BuildingRoom.Instance.OnEnableDivideMode -= OnDivedeModeChange; } public Vector3 GetSurface(GameObject surfaceObject) { Vector3 averageNormal = Vector3.zero; MeshFilter meshFilter = surfaceObject.GetComponent(); if (meshFilter != null) { Mesh mesh = meshFilter.mesh; Vector3[] normals = mesh.normals; Transform transform = surfaceObject.transform; averageNormal = Vector3.zero; foreach (var normal in normals) { averageNormal += transform.TransformDirection(normal); } averageNormal.Normalize(); wallType = DetermineOrientation(averageNormal, surfaceObject); } return averageNormal; } private WallType DetermineOrientation(Vector3 normal, GameObject obj) { float threshold = 0.7f; if (Mathf.Abs(normal.y) > threshold) { if (normal.y > 0) { return WallType.WallY; } else { return WallType.WallY; } } else if (Mathf.Abs(normal.x) > threshold) { return WallType.WallX; } else if (Mathf.Abs(normal.z) > threshold) { return WallType.WallZ; } return WallType.None; } public void OnViewChange(ViewType type) { if (type == ViewType.Walk) { IsDivideMode = false; ClearAllPoints(true); } } } public struct Line3D { public Vector2 start; public Vector2 end; public Line3D(Vector2 start, Vector2 end) { this.start = start; this.end = end; } } public class LineIntersection3D { private const float epsilon = 1e-6f; private bool AreLinesIntersectingInXY(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4) { return AreLinesIntersecting(new Vector2(p1.x, p1.y), new Vector2(p2.x, p2.y), new Vector2(p3.x, p3.y), new Vector2(p4.x, p4.y)); } private bool AreLinesIntersecting(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4) { float denominator = (p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x); if (Math.Abs(denominator) < epsilon) { return false; } float t = ((p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x)) / denominator; float u = ((p1.x - p3.x) * (p1.y - p2.y) - (p1.y - p3.y) * (p1.x - p2.x)) / denominator; return t >= 0 && t <= 1 && u >= 0 && u <= 1; } public bool AreLinesIntersecting3D(Line3D line1, Line3D line2) { bool intersectXY = AreLinesIntersectingInXY(line1.start, line1.end, line2.start, line2.end); return (intersectXY); } } public enum WallType { None, WallZ, WallX, WallY, }