diff --git a/Assets/Scripts/Simulator/Components/Conveyor/ConveyorManager.cs b/Assets/Scripts/Simulator/Components/Conveyor/ConveyorManager.cs index cba49f25..906d732b 100644 --- a/Assets/Scripts/Simulator/Components/Conveyor/ConveyorManager.cs +++ b/Assets/Scripts/Simulator/Components/Conveyor/ConveyorManager.cs @@ -4,6 +4,7 @@ using Simulator.Data.Transport; using System; using System.Collections.Generic; using System.Linq; +using Unity.Collections; using UnityEngine; using UnityEngine.Splines; using UVC.Core; @@ -25,21 +26,30 @@ public class ConveyorManager : SingletonScene [Header("Root & Visual")] [SerializeField] private Transform rootParent; // 생성될 경로의 부모(없으면 빌더 자신) [SerializeField] private float tangentScale = 0.25f; // 곡률(탄젠트 길이 비율) - [SerializeField] - ConveyorComponent splineprefab; - [SerializeField] - GameObject cube; + [SerializeField] private ConveyorComponent splineprefab; + [SerializeField] private GameObject junction; + + [Header("Corner Modules")] // ▼ 추가: 코너 프리팹 & 각도 허용 오차 + [SerializeField] private GameObject corner45Prefab; + [SerializeField] private GameObject corner90Prefab; + [SerializeField, Range(1f, 30f)] private float angleTolerance = 10f; public Dictionary Nodes = new Dictionary(); - public Dictionary<(string,string), ConveyorComponent> Paths = new Dictionary<(string, string), ConveyorComponent>(); + public Dictionary<(string, string), ConveyorComponent> Paths = new Dictionary<(string, string), ConveyorComponent>(); + + // ▼ 추가: station 연결성 계산용 테이블 + // stationName -> (경로 키, station에서 맞닿는 cap index(0=from,1=to), 반대편 노드명) + private readonly Dictionary pathKey, int capIndex, string otherNode)>> _stationAdj + = new Dictionary, int, string)>>(); + public DataMapper conveyorStartDataMapper; public DataMapper conveyorCompleteDataMapper; protected override void Init() { var conveyorStartDataMask = new DataMask(); - conveyorStartDataMask.ObjectName = "conveyor"; // AGV 객체의 이름을 설정합니다. - conveyorStartDataMask.ObjectIdKey = "component_id"; // AGV의 고유 식별자로 사용할 키를 설정합니다. + conveyorStartDataMask.ObjectName = "conveyor"; + conveyorStartDataMask.ObjectIdKey = "component_id"; conveyorStartDataMask["component_id"] = ""; conveyorStartDataMask["event_name"] = ""; conveyorStartDataMask["timestamp"] = new DateTime(); @@ -48,13 +58,13 @@ public class ConveyorManager : SingletonScene ["entity_id_original"] = "", ["start_node"] = "", ["end_node"] = "", - ["travel_time"]=0.0f + ["travel_time"] = 0.0f }; - conveyorStartDataMapper=new DataMapper(conveyorStartDataMask); + conveyorStartDataMapper = new DataMapper(conveyorStartDataMask); var conveyorCompleteDataMask = new DataMask(); - conveyorCompleteDataMask.ObjectName = "conveyor"; // AGV 객체의 이름을 설정합니다. - conveyorCompleteDataMask.ObjectIdKey = "component_id"; // AGV의 고유 식별자로 사용할 키를 설정합니다. + conveyorCompleteDataMask.ObjectName = "conveyor"; + conveyorCompleteDataMask.ObjectIdKey = "component_id"; conveyorCompleteDataMask["component_id"] = ""; conveyorCompleteDataMask["event_name"] = ""; conveyorCompleteDataMask["timestamp"] = new DateTime(); @@ -69,27 +79,29 @@ public class ConveyorManager : SingletonScene public void Build(List datas) { - if (datas==null||datas.Count <= 0) - { - return; - } + if (datas == null || datas.Count <= 0) return; + var data = datas[0]; if (rootParent == null) rootParent = transform; + Nodes.Clear(); + Paths.Clear(); + _stationAdj.Clear(); // ▲ 추가: 초기화 + if (data.nodes != null) { foreach (var n in data.nodes) - { Nodes[n.name] = n; - } } foreach (var path in data.paths) - { CreatePathGOWithSpline(path, Nodes[path.from_node], Nodes[path.to_node]); - } + SpawnJunction(); + // ▲ 추가: station의 연결성(직선/코너) 기반 cap 분리 & 코너 배치 + ProcessStations(); + SubscribeConveyor(data); } @@ -100,27 +112,18 @@ public class ConveyorManager : SingletonScene mqttConfigConveyorStart.SetDataMapper(conveyorStartDataMapper); mqttConfigConveyorStart.SetHandler(OnSetData); DataRepository.Instance.MqttReceiver.Add(mqttConfigConveyorStart); - /* - DataRepository.Instance.MqttReceiver.AddTopic($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/node_completed"); - var mqttConfigConveyorComplete = new MqttSubscriptionConfig($"simulation/{SimulationConfig.SimulationCode}/components/+/{data.name}/+/node_completed"); - mqttConfigConveyorComplete.SetDataMapper(conveyorCompleteDataMapper); - mqttConfigConveyorComplete.SetHandler(OnUnSetData); - DataRepository.Instance.MqttReceiver.Add(mqttConfigConveyorComplete); - */ } private void CreatePathGOWithSpline(ConveyorPath path, ConveyorNode fromNode, ConveyorNode toNode) { TryGetNodePos(fromNode.name, out Vector3 fromPos); TryGetNodePos(toNode.name, out Vector3 toPos); - string goName = path.name; var go = Instantiate(splineprefab); - Paths.Add((fromNode.name,toNode.name), go); + Paths.Add((fromNode.name, toNode.name), go); go.transform.SetParent(rootParent, worldPositionStays: false); var container = go.GetComponent(); - var spline = new Spline(); Vector3 dir = (toPos - fromPos); @@ -146,26 +149,100 @@ public class ConveyorManager : SingletonScene container.Spline = spline; var mesher = go.GetComponent(); - if(string.Equals(fromNode.node_type, "junction")) - { + + // ▼ 변경: junction만 즉시 cap 분리, station은 후처리에서 판단 + if (string.Equals(fromNode.node_type, "junction")) mesher.DetachCap(0); - } if (string.Equals(toNode.node_type, "junction")) - { mesher.DetachCap(1); + + mesher.UpdateCaps(); + mesher.Rebuild(); + + // ▼ 추가: station 인접 정보 기록(후처리에서 cap/코너 결정) + if (string.Equals(fromNode.node_type, "station")) + AddStationAdj(fromNode.name, (fromNode.name, toNode.name), capIndex: 0, otherNode: toNode.name); + + if (string.Equals(toNode.node_type, "station")) + AddStationAdj(toNode.name, (fromNode.name, toNode.name), capIndex: 1, otherNode: fromNode.name); + } + + // ▼ 추가: station 인접 정보 수집 + private void AddStationAdj(string stationName, (string, string) pathKey, int capIndex, string otherNode) + { + if (!_stationAdj.TryGetValue(stationName, out var list)) + { + list = new List<(ValueTuple, int, string)>(); + _stationAdj[stationName] = list; } + list.Add((pathKey, capIndex, otherNode)); + } + + // ▼ 추가: station의 연결성이 직선인지 코너인지 판정하고 cap/코너를 적용 + private void ProcessStations() + { + foreach (var kv in _stationAdj) + { + string station = kv.Key; + var adj = kv.Value; + + // 연결 경로가 2개일 때만 직선/코너 판정(기타 케이스는 유지) + if (adj.Count != 2) continue; + + if (!TryGetNodePos(station, out var stationPos)) continue; + + var (pathA, capA, otherA) = adj[0]; + var (pathB, capB, otherB) = adj[1]; + + if (!TryGetNodePos(otherA, out var otherAPos)) continue; + if (!TryGetNodePos(otherB, out var otherBPos)) continue; + + Vector3 vA = (otherAPos - stationPos); vA.y = 0f; + Vector3 vB = (otherBPos - stationPos); vB.y = 0f; + + if (vA.sqrMagnitude < 1e-6f || vB.sqrMagnitude < 1e-6f) continue; + + float angle = Vector3.SignedAngle(vA.normalized, vB.normalized, Vector3.up); // 0~180 + float straightThreshold = 180f - angleTolerance; // 예: tol=10 → 170° + + Debug.Log(angle); + bool isStraight = Mathf.Abs(angle) >= straightThreshold; // 거의 반대 방향이면 직선 + if (isStraight) + { + // 직선: cap 유지 + continue; + } + + // 코너: station에 맞닿는 두 cap 분리 + if (Paths.TryGetValue(pathA, out var convA)) + DetachAndRebuild(convA, capA); + if (Paths.TryGetValue(pathB, out var convB)) + DetachAndRebuild(convB, capB); + + var corner = Instantiate(corner90Prefab); + corner.transform.position = stationPos; + corner.transform.rotation = Quaternion.Euler(new Vector3(-90, -angle, 0)); + } + } + + // ▼ 추가: cap 분리 & 리빌드 헬퍼 + private void DetachAndRebuild(ConveyorComponent comp, int capIndex) + { + var mesher = comp.GetComponent(); + if (mesher == null) return; + mesher.DetachCap(capIndex); mesher.UpdateCaps(); mesher.Rebuild(); } private void SpawnJunction() { - foreach(var node in Nodes) + foreach (var node in Nodes) { if (string.Equals(node.Value.node_type, "junction")) { - var module = Instantiate(cube); - module.transform.position=ToVector3(node.Value.position); + var module = Instantiate(junction); + module.transform.position = ToVector3(node.Value.physical.position); } } } @@ -176,33 +253,19 @@ public class ConveyorManager : SingletonScene DataObject? obj = idata as DataObject; if (obj == null) return; - //Debug.Log($"OnUpdateData:{obj}, {obj["data"]}"); - var data = obj.GetDataObject("data"); - var fromNodeName = data.GetString("start_node"); - var toNodeName = data.GetString("end_node"); - ConveyorComponent? conveyor = Paths[(fromNodeName,toNodeName)]; - conveyor.SetModelData(obj); - } - private void OnUnSetData(IDataObject idata) - { - if (idata == null) return; - - DataObject? obj = idata as DataObject; - if (obj == null) return; - //Debug.Log($"OnUpdateData:{obj}, {obj["data"]}"); var data = obj.GetDataObject("data"); var fromNodeName = data.GetString("start_node"); var toNodeName = data.GetString("end_node"); ConveyorComponent? conveyor = Paths[(fromNodeName, toNodeName)]; - conveyor.UnSetModelData(obj); + conveyor.SetModelData(obj); } private bool TryGetNodePos(string nodeName, out Vector3 pos) { if (!string.IsNullOrWhiteSpace(nodeName) && Nodes.TryGetValue(nodeName, out ConveyorNode node)) { - pos = ToVector3(node.position); + pos = ToVector3(node.physical.position); return true; } @@ -212,6 +275,7 @@ public class ConveyorManager : SingletonScene private Vector3 ToVector3(Position p) { + Debug.Log(p); return new Vector3(p.x, p.z, p.y); } } \ No newline at end of file