/// <summary> /// /// update method for the planner view /// /// </summary> protected override void customUpdate() { // check for a path dragging operation if (selectedConnector != null) { GameObject[] objs = GameObject.FindGameObjectsWithTag("connectiondrag"); foreach (GameObject go in objs) { Destroy(go); } Customer[] customers = connectorMap[selectedConnector]; Vector3 positionA = (customers[0] == null) ? ScaleInScene(new Vector3(plan.paths[selectedPathIndex].warehouse.address.x, 1, plan.paths[selectedPathIndex].warehouse.address.z)) : ScaleInScene(new Vector3(customers[0].address.x, 1, customers[0].address.z)); Vector3 positionB = (customers[1] == null) ? ScaleInScene(new Vector3(plan.paths[selectedPathIndex].warehouse.address.x, 1, plan.paths[selectedPathIndex].warehouse.address.z)) : ScaleInScene(new Vector3(customers[1].address.x, 1, customers[1].address.z)); // get mouse position float[] getlocation = mouseRayPoint(); // draw cylinders CreateCylinderBetweenPoints(positionA, new Vector3(getlocation[0], 1, getlocation[2]), 0.16f, Color.gray, 0, "connectiondrag"); CreateCylinderBetweenPoints(positionB, new Vector3(getlocation[0], 1, getlocation[2]), 0.16f, Color.gray, 0, "connectiondrag"); } // up listener for dragging (end of the drag) if (Input.GetMouseButtonUp(0) && selectedConnector != null) { // check if there is a selected customer at the end of dragging Customer customer = customerAtMouse(); if (customer != null) { if (customer.selected) { // get the id of the customer int customerId = customer.id; // get the position to insert into the VehiclePath Customer connectorStartingCustomer = connectorMap[selectedConnector][0]; int position = 0; // not the starting segment from the warehouse if (connectorStartingCustomer != null) { List <CustomerDelivery> customers = plan.paths[selectedPathIndex].customers; for (int i = 0; i < customers.Count; i++) { if (connectorStartingCustomer.id == customers[i].id) { position = i + 1; } } } // insert the customer Capture.Log("ManualPathAddedDrag;AfterCustomerIndex=" + position, Capture.PLANNER); insertCustomer(customerId, position); } } // cleans up connection drags objects GameObject[] objs = GameObject.FindGameObjectsWithTag("connectiondrag"); foreach (GameObject go in objs) { Destroy(go); } // set the selected connector selectedConnector = null; } // right click to remove a customer in a path if (Input.GetMouseButtonDown(1) && selectedPathIndex != -1) { // check for a customer Customer customer = customerAtMouse(); if (customer != null) { int customerID = customer.id; int pathindex = -1; // get its position in the delivery path List <CustomerDelivery> currentPath = plan.paths[selectedPathIndex].customers; for (int i = 0; i < currentPath.Count; i++) { if (currentPath[i].id == customerID) { pathindex = i; } } // should have found the index if (pathindex != -1) { string position = ""; try { Address address = plan.paths[selectedPathIndex].customers[pathindex].address; position += address.x + "," + address.z; } catch (Exception e) { Debug.Log(e); } // remove the customer plan.paths[selectedPathIndex].customers.RemoveAt(pathindex); RefreshPaths(); // recalculate the path PlanPathCalculation pathPathCalculation = new PlanPathCalculation(plan.paths[selectedPathIndex]); pathPathCalculation.calculate(); manualPathCapacityRemaining = pathPathCalculation.getTotalCapacityRemaining(); manualPathRangeRemaining = pathPathCalculation.getTotalRangeRemaining(); Capture.Log("ManualPathRemove;VehicleIndex=" + selectedPathIndex + ";RemovedIndex=" + customerID + ";RemovedPosition=" + position + ";VehicleName=" + plan.paths[selectedPathIndex].vehicle.tag + ";" + JsonConvert.SerializeObject(plan) + ";" + planCalculation.getLogString(), Capture.PLANNER); playClick(); } } } // left click to select a customer or connection if (Input.GetMouseButtonDown(0) && selectedPathIndex != -1) { Customer customer = customerAtMouse(); // check if the customer is selected if (customer != null) { if (customer.selected) { int index = customer.id; insertCustomer(index, -1); } } else { // check for a selected connector, it will return null if no connector // is selected selectedConnector = connectionAtMouse(); } } // mouse over for customers in the planner view if ((!Input.GetMouseButton(0) && !Input.GetMouseButton(1)) || selectedConnector != null) { // get highlighted customer object[] highlighted = customerAtMousePosition(); // remove house labels HideHouseLabels(); if (highlighted == null) { GameObject.Find("customerhighlight").transform.position = new Vector3(1000, 1000, 1000); } else { // highlight customer GameObject obj = (GameObject)highlighted[0]; Customer customer = (Customer)highlighted[1]; // if customer is selected (only difference from the business view implementation) if (customer.selected) { Vector3 pos = obj.transform.position; Vector3 scale = obj.transform.localScale; GameObject.Find("customerhighlight").transform.position = new Vector3(pos.x, 0.02f, pos.z); GameObject.Find("customerhighlight").transform.localScale = new Vector3(1 + customer.weight / 4f, 0.01f * scale.y, 1 + customer.weight / 4f); ShowHouseLabel(customer); } } } // remove all connection highlight objects GameObject[] highlightobjs = GameObject.FindGameObjectsWithTag("connectionhighlight"); foreach (GameObject obj in highlightobjs) { Destroy(obj); } // if a path is selected and there is not a selected connector for dragging if (selectedPathIndex != -1 && selectedConnector == null) { // get the connection at the mouse location GameObject connector = connectionAtMouse(); if (connector != null) { Customer[] customers = connectorMap[connector]; Vector3 positionA = (customers[0] == null) ? ScaleInScene(new Vector3(plan.paths[selectedPathIndex].warehouse.address.x, 1, plan.paths[selectedPathIndex].warehouse.address.z)) : ScaleInScene(new Vector3(customers[0].address.x, 1, customers[0].address.z)); Vector3 positionB = (customers[1] == null) ? ScaleInScene(new Vector3(plan.paths[selectedPathIndex].warehouse.address.x, 1, plan.paths[selectedPathIndex].warehouse.address.z)) : ScaleInScene(new Vector3(customers[1].address.x, 1, customers[1].address.z)); CreateCylinderBetweenPoints(positionA, positionB, 0.48f, new Color(1.0f, 0.5f, 0.3f), 0, "connectionhighlight"); } } }
/// <summary> /// inserts a Customer into the selected VehiclePath /// </summary> /// <param name="customerId">customer id</param> /// <param name="position">position to insert in the current VehiclePath</param> private void insertCustomer(int customerId, int position) { // gets the current delivery path List <CustomerDelivery> currentPath = plan.paths[selectedPathIndex].customers; if (currentPath.Count == 0) { currentPath = new List <CustomerDelivery>(); } // if -1, add to the end of the path if (position == -1) { position = currentPath.Count; } // check all paths to check if a Customer is selected by another path bool alreadySelected = false; for (int j = 0; j < plan.paths.Count; j++) { for (int i = 0; i < plan.paths[j].customers.Count; i++) { if (plan.paths[j].customers[i].id == customerId) { alreadySelected = true; } } } // if not selected if (!alreadySelected) { // add costomer from the scenario CustomerDelivery customerPath = scenario.getCustomer(customerId).clone(); plan.paths[selectedPathIndex].customers.Insert(position, customerPath); // calculate the path metrics with new inserted node PlanPathCalculation planPathCalculation = new PlanPathCalculation(plan.paths[selectedPathIndex]); planPathCalculation.calculate(); // if valid if (planPathCalculation.isValid()) { string addedposition = ""; try { Address addedAddress = customerPath.address; addedposition += addedAddress.x + "," + addedAddress.z; } catch (Exception e) { Debug.Log(e); } RefreshPaths(); manualPathCapacityRemaining = planPathCalculation.getTotalCapacityRemaining(); manualPathRangeRemaining = planPathCalculation.getTotalRangeRemaining(); debugStr = "Path updated"; Capture.Log("ManualPathAdded;VehicleIndex=" + selectedPathIndex + ";AddedIndex=" + customerId + ";AddedPosition=" + addedposition + ";VehicleTag=" + plan.paths[selectedPathIndex].vehicle.tag + ";" + JsonConvert.SerializeObject(plan) + ";" + planCalculation.getLogString(), Capture.PLANNER); playClick(); } else if (planPathCalculation.getTotalRangeRemaining() < 0) // too long { plan.paths[selectedPathIndex].customers.RemoveAt(position); debugStr = "Path : range to long"; Capture.Log("ManualPathAdded;RangeTooLong", Capture.PLANNER); ShowMsg("Path too long", true); } else if (planPathCalculation.getTotalCapacityRemaining() < 0) // too high of capacity { plan.paths[selectedPathIndex].customers.RemoveAt(position); debugStr = "Path : weight constraint reached"; Capture.Log("ManualPathAdded;PayloadConstraint", Capture.PLANNER); ShowMsg("Path weight too high", true); } else if (planPathCalculation.getTotalTime() > 24.0) // too long in duration { plan.paths[selectedPathIndex].customers.RemoveAt(position); debugStr = "Path : duration to long"; Capture.Log("ManualPathAdded;DurationTooLong", Capture.PLANNER); ShowMsg("Path is longer than 24 h", true); } else if (!planPathCalculation.deliveredFoodinTime()) // food not delivered in time { plan.paths[selectedPathIndex].customers.RemoveAt(position); debugStr = "Path : food not delivered in time"; Capture.Log("ManualPathAdded;FoodTimeConstraint", Capture.PLANNER); ShowMsg("Food is not in time", true); } } else { debugStr = "Customer has delivery"; ShowMsg("Customer has delivery", true); } }
/// <summary> /// /// GUI controls specific to the planner /// /// </summary> protected override void OnGUICustom() { // get screen size and set scroll window height Rect rect = Camera.main.pixelRect; int scrollheight = (int)Math.Max(rect.height - 250, 200); // right panel where users can select plans to open, add a button to open a plan int counter = 0; teamPlansRect = new Rect(rect.width - 180, 10, 170, scrollheight + 20); GUI.Box(teamPlansRect, new GUIContent("Team Plans", "Select a Plan To Open")); scrollPositionOperationplans = GUI.BeginScrollView(new Rect(rect.width - 174, 40, 164, scrollheight), scrollPositionOperationplans, new Rect(0, 0, 140, 20 + loadedPlans.Count * 21)); // for each plan id loaded, add a button to open the plan, if not loaded fully, display it // but unhighlight it and remove the event on the button foreach (int id in loadedPlans.Keys) { // check if fully loaded bool loaded = (loadedPlans[id].plan != null); string tag = (!loaded ? " *" : "") + loadedPlans[id].tag; GUI.color = loaded ? Color.white : Color.gray; if (GUI.Button(new Rect(0, 10 + 20 * counter, 164, 20), tag)) { if (loaded) { userSelectedPlan = loadedPlans[id].plan; OpenPlan(loadedPlans[id].plan); } } GUI.color = Color.white; counter += 1; } GUI.EndScrollView(); // add a toolbar button to remove all plan paths if (GUI.Button(removeAllPathsRect, new GUIContent(GUIHelpers.removeimage, "Remove All Paths"))) { playClick(); RemovePaths(); Capture.Log("RemoveAllPaths;" + JsonConvert.SerializeObject(plan) + ";" + planCalculation.getLogString(), Capture.PLANNER); } // submit plan button to your team, it will open a popup for confirmation submitRect = new Rect(192, rect.height - 56, 80, 24); if (GUI.Button(submitRect, new GUIContent("Submit", "Submit the Plan to Your Team"))) { // check that the plan has delivery customers int totalCustomers = 0; foreach (DataObjects.VehicleDelivery p in plan.paths) { totalCustomers += p.customers.Count; } // open popup if (totalCustomers > 0) { GUIAssets.PopupButton.showing = true; GUIAssets.PopupButton.popupPanelID = "SubmitCanvas"; GameObject.Find("SubmitCanvas").GetComponent <Canvas>().enabled = true; } else { ShowMsg("Empty Plan", true); } } // add AI button if the session includes it if (Startup.isAI) { aiButtonRect = new Rect(10, rect.height - 210, 32, 32); if (GUI.Button(aiButtonRect, new GUIContent(GUIHelpers.aiimage, "AI Agent to Generate Plan Alternatives"))) { vehicleDashboardView = false; runAIAgent(); Capture.Log("RunPathAgent", Capture.PLANNER); } } // add controls for the purchased vehicle list on the left int heightVehicleScroll = (int)rect.height - 270; // labels for database vehicles GUI.Box(new Rect(10, 50, 272, heightVehicleScroll), "Purchased Designs"); GUI.color = Color.white; int yInc = 28; // scroll window for all plan vehicles vehicleRect = new Rect(10, 80, 272, heightVehicleScroll - 48); scrollPositionVehicles = GUI.BeginScrollView(vehicleRect, scrollPositionVehicles, new Rect(0, 0, 272, 20 + plan.paths.Count * yInc)); // add GUI controls for vehicle paths for (int i = 0; i < plan.paths.Count; i++) { string vehicletag = plan.paths[i].vehicle.tag; double cost = plan.paths[i].vehicle.cost; double range = plan.paths[i].vehicle.range; double velocity = plan.paths[i].vehicle.velocity; double payload = plan.paths[i].vehicle.payload; string tooltipStr = "Selects " + vehicletag + " , range = " + range.ToString("0.0") + " mi , capacity = " + payload.ToString("0") + " lb , $" + cost.ToString("0"); GUI.color = Color.white; if (selectedPathIndex == i) { GUI.color = Color.cyan; } // select a vehicle to highlight and edit it if (GUI.Button(new Rect(26, 10 + i * yInc, 164, 24), new GUIContent(vehicletag, tooltipStr))) { vehicleDashboardView = false; if (selectedPathIndex == i) { selectedPathIndex = -1; } else { selectedPathIndex = i; PlanPathCalculation planPathCalculation = new PlanPathCalculation(plan.paths[i]); planPathCalculation.calculate(); manualPathRangeRemaining = planPathCalculation.getTotalRangeRemaining(); manualPathCapacityRemaining = planPathCalculation.getTotalCapacityRemaining(); } RefreshPaths(); playClick(); Capture.Log("SelectPath;" + i + ";" + vehicletag, Capture.PLANNER); } // draw vehicle metrics as bar charts GUI.color = Color.white; GUI.Box(new Rect(192, 14 + 16 + i * yInc - (float)(16 * range / maxVehicleRange), 12, 16 * (float)(range / maxVehicleRange)), "", Assets.GUIHelpers.whiteStyle); GUI.Box(new Rect(206, 14 + 16 + i * yInc - (float)(16 * payload / maxVehicleCapacity), 12, 16 * (float)(payload / maxVehicleCapacity)), "", Assets.GUIHelpers.lightgrayStyle); GUI.Box(new Rect(220, 14 + 16 + i * yInc - (float)(16 * cost / maxVehicleCost), 12, 16 * (float)(cost / maxVehicleCost)), "", Assets.GUIHelpers.grayRedStyle); GUI.color = Color.white; // timing issue in Unity when deleting vehicles, I think this is fixed // since the remove button in below this in the code now try { if (plan.paths[i].customers.Count == 0) { GUI.color = new Color(0.2f, 0.2f, 0.2f); } } catch (Exception e) { Debug.Log(e); } // add the remove path button // only show if the dashboard view is not shown, since mouse actions go through if (!vehicleDashboardView) { // adds the remove button to the view if (GUI.Button(new Rect(234, 10 + i * yInc, 24, 24), new GUIContent(GUIHelpers.removeimage, "Remove the Path"))) { // set the remaining variable to the full values manualPathRangeRemaining = plan.paths[i].vehicle.range; manualPathCapacityRemaining = plan.paths[i].vehicle.payload; plan.paths[i].customers.Clear(); selectedPathIndex = i; // remove and add path to show highlighted path RefreshPaths(); playClick(); Capture.Log("VehiclePathRemoved;" + i + ";" + plan.paths[i].vehicle.tag + ";" + JsonConvert.SerializeObject(plan) + ";" + planCalculation.getLogString(), Capture.PLANNER); } } GUI.color = Color.white; if (GUI.Button(new Rect(0, 10 + i * yInc, 24, 24), new GUIContent("-", "Remove Design from Purchased List"))) { selectedPathIndex = -1; plan.paths.RemoveAt(i); // refresh the path lines RefreshPaths(); playClick(); Capture.Log("VehicleRemoved;" + i + ";" + vehicletag + ";" + JsonConvert.SerializeObject(plan) + ";" + planCalculation.getLogString(), Capture.PLANNER); ShowMsg(vehicletag + " Removed", false); if (plan.paths.Count == 0) { vehicleDashboardView = true; } } } GUI.EndScrollView(); // show the dashboard view in the upper center of the window with the range and capacity // remaining for the display if (selectedPathIndex != -1) { GUI.Box(new Rect(290, 10, 360, 62), ""); GUI.Label(new Rect(384, 20, 10, 52), "0"); float maxRange = (float)plan.paths[selectedPathIndex].vehicle.range; float maxCapacity = (float)plan.paths[selectedPathIndex].vehicle.payload; GUI.Label(new Rect(505, 10, 28, 56), "\n" + maxRange.ToString("0.0") + "\n" + maxCapacity.ToString("0.0")); GUI.Box(new Rect(399, 30, 1, 36), "", Assets.GUIHelpers.whiteStyle); GUI.Box(new Rect(501, 30, 1, 36), "", Assets.GUIHelpers.whiteStyle); GUI.Label(new Rect(294, 10, 390, 56), "\n Range (mi)\n Weight (lb)"); GUI.Label(new Rect(542, 10, 390, 56), "\n Remaining=" + manualPathRangeRemaining.ToString("0.0") + "\n Remaining=" + manualPathCapacityRemaining.ToString("0")); GUI.color = Color.cyan; GUI.Label(new Rect(294, 10, 390, 56), plan.paths[selectedPathIndex].vehicle.tag + "\n\n"); GUI.Box(new Rect(400, 36, (float)(100f * (1 - manualPathRangeRemaining / maxRange)), 10), "", Assets.GUIHelpers.whiteStyle); GUI.Box(new Rect(400, 50, (float)(100f * (1 - manualPathCapacityRemaining / maxCapacity)), 10), "", Assets.GUIHelpers.whiteStyle); GUI.color = Color.white; } // add a button to toggle the information help panel GUI.color = Color.white; if (GUI.Button(infoRect, new GUIContent(GUIHelpers.infoimage, "Toggle Information Panel"))) { showHelpInfoPanel = !showHelpInfoPanel; playClick(); GameObject.Find("helpcanvasplanner").GetComponent <Canvas>().enabled = showHelpInfoPanel; Capture.Log("ToggleInfoPanel:" + showHelpInfoPanel, Capture.PLANNER); } // add a button to toggle the information help panel vehicleSelectionRect = new Rect(244, 52, 28, 28); if (GUI.Button(vehicleSelectionRect, new GUIContent(GUIHelpers.dashboardimage, "Add a Design From the Design Team"))) { vehicleDashboardView = !vehicleDashboardView; if (vehicleDashboardView) { selectedPathIndex = -1; } playClick(); Capture.Log("ShowVehicleList;" + vehicleDashboardView, Capture.PLANNER); } // add the team design selection list if (vehicleDashboardView) { // multiple boxes adds opacity for (int i = 0; i < 4; i++) { GUI.Box(new Rect(200, 94, 260, 400), "Team Designs"); } scrollPositionVehicleSelection = GUI.BeginScrollView(new Rect(210, 130, 260, 300), scrollPositionVehicleSelection, new Rect(0, 0, 240, 30 * teamVehicles.Count)); // add all team vehicles to the list int counterdash = 0; foreach (Vehicle vehicle in teamVehicles) { string tooltipStr = "Add design " + vehicle.tag + " , range = " + vehicle.range.ToString("0.0") + " mi , capacity = " + vehicle.payload.ToString("0") + " lb , $" + vehicle.cost.ToString("0"); // add the plus button to add a vehicle to the purchased list if (GUI.Button(new Rect(0, counterdash * 30, 28, 28), new GUIContent("+", tooltipStr))) { DataObjects.VehicleDelivery p = simplePath(); p.vehicle = vehicle; try { p.warehouse = scenario.warehouse; } catch (Exception e) { Debug.Log(e); } plan.paths.Add(p); RefreshPaths(); playClick(); Capture.Log("VehicleAdd;" + vehicle.tag + ";" + JsonConvert.SerializeObject(plan) + ";" + planCalculation.getLogString(), Capture.PLANNER); ShowMsg(vehicle.tag + " Added", false); } // add the vehicle tag GUI.Label(new Rect(30, counterdash * 30, 164, 28), vehicle.tag); // add the metric bars GUI.color = Color.white; GUI.Box(new Rect(186, (counterdash + 1) * 30 - 8 - 16 * (float)(vehicle.range / maxVehicleRange), 12, 16 * (float)(vehicle.range / maxVehicleRange)), "", Assets.GUIHelpers.whiteStyle); GUI.Box(new Rect(202, (counterdash + 1) * 30 - 8 - 16 * (float)(vehicle.payload / maxVehicleCapacity), 12, 16 * (float)(vehicle.payload / maxVehicleCapacity)), "", Assets.GUIHelpers.lightgrayStyle); GUI.Box(new Rect(218, (counterdash + 1) * 30 - 8 - 16 * (float)(vehicle.cost / maxVehicleCost), 12, 16 * (float)(vehicle.cost / maxVehicleCost)), "", Assets.GUIHelpers.grayRedStyle); GUI.color = Color.white; counterdash += 1; } GUI.EndScrollView(); // add the hide button at the bottom of the view if (GUI.Button(new Rect(250, 456, 140, 28), "Hide")) { vehicleDashboardView = false; } } // lock the camera view if the dashboard view is opened or the user is // dragging a selected component MoveCamera.lockView = vehicleDashboardView || (selectedConnector != null); }