/// <summary>
        /// Called whenever the currently selected building is changed to update the panel display.
        /// </summary>
        /// <param name="building">Newly selected building</param>
        internal void SelectionChanged(BuildingInfo building)
        {
            // Set current building.
            currentBuilding = building;

            // Safety first!
            if (currentBuilding != null)
            {
                string buildingName = building.name;

                // Get available calculation packs for this building.
                popPacks   = PopData.instance.GetPacks(building);
                floorPacks = FloorData.instance.Packs;

                // Get current and default packs for this item.
                currentPopPack   = (PopDataPack)PopData.instance.ActivePack(building);
                currentFloorPack = (FloorDataPack)FloorData.instance.ActivePack(building);
                PopDataPack   defaultPopPack   = (PopDataPack)PopData.instance.CurrentDefaultPack(building);
                FloorDataPack defaultFloorPack = (FloorDataPack)FloorData.instance.CurrentDefaultPack(building);

                // Update multiplier before we do any other calcs.
                multCheck.isChecked = Multipliers.instance.HasOverride(buildingName);
                currentMult         = Multipliers.instance.ActiveMultiplier(building);


                // Build pop pack menu.
                popMenu.items = new string[popPacks.Length];
                for (int i = 0; i < popMenu.items.Length; ++i)
                {
                    popMenu.items[i] = popPacks[i].displayName;

                    // Check for default name match,
                    if (popPacks[i].name.Equals(defaultPopPack.name))
                    {
                        popMenu.items[i] += Translations.Translate("RPR_PCK_DEF");
                    }

                    // Set menu selection to current pack if it matches.
                    if (popPacks[i].name.Equals(currentPopPack.name))
                    {
                        popMenu.selectedIndex = i;
                    }
                }

                // Set population pack to current pack.
                UpdatePopSelection(currentPopPack);

                // Build floor pack menu.
                floorMenu.items = new string[floorPacks.Length];
                for (int i = 0; i < floorPacks.Length; ++i)
                {
                    floorMenu.items[i] = floorPacks[i].displayName;

                    // Check for default name match,
                    if (floorPacks[i].name.Equals(defaultFloorPack.name))
                    {
                        floorMenu.items[i] += Translations.Translate("RPR_PCK_DEF");
                    }

                    // Set menu selection to current pack if it matches.
                    if (floorPacks[i].name.Equals(currentFloorPack.name))
                    {
                        floorMenu.selectedIndex = i;

                        // Force pack selection update.
                        UpdateFloorSelection(i);
                    }
                }

                // Update legacy panel for private building AIs (volumetric panel is updated by menu selection change above).
                if (building.GetAI() is PrivateBuildingAI)
                {
                    legacyPanel.SelectionChanged(building);
                }

                // Is this a school building (need to do school building after pop and floor packs are updated)?
                if (building.GetAI() is SchoolAI)
                {
                    // Yes - school building.  Set current pack.
                    currentSchoolPack = (SchoolDataPack)SchoolData.instance.ActivePack(building);

                    // Are we using custom school settings?
                    if (ModSettings.enableSchoolProperties)
                    {
                        // Yes - extend panel height and show school panel.
                        volumetricPanel.relativePosition = new Vector2(0f, SchoolCalcY);
                        applyButton.relativePosition     = new Vector2(ApplyX, SchoolSaveY);

                        // Get available school packs for this building.
                        schoolPacks = SchoolData.instance.GetPacks(building);

                        // Get current and default packs for this item.
                        currentSchoolPack = (SchoolDataPack)SchoolData.instance.ActivePack(building);
                        SchoolDataPack defaultSchoolPack = (SchoolDataPack)SchoolData.instance.CurrentDefaultPack(building);

                        // Build school pack menu.
                        schoolMenu.items = new string[schoolPacks.Length];
                        for (int i = 0; i < schoolMenu.items.Length; ++i)
                        {
                            schoolMenu.items[i] = schoolPacks[i].displayName;

                            // Check for default name match,
                            if (schoolPacks[i].name.Equals(defaultSchoolPack.name))
                            {
                                schoolMenu.items[i] += Translations.Translate("RPR_PCK_DEF");
                            }

                            // Set menu selection to current pack if it matches.
                            if (schoolPacks[i].name.Equals(currentSchoolPack.name))
                            {
                                schoolMenu.selectedIndex = i;

                                // Force pack selection update.
                                UpdateSchoolSelection(i);
                            }
                        }

                        // Set multiplier value.
                        multSlider.value = currentMult;

                        schoolPanel.Show();
                    }
                    else
                    {
                        // It's a school, but we're not using custom school settings, so use the non-school layout.
                        volumetricPanel.relativePosition = new Vector2(0f, BaseCalcY);
                        applyButton.relativePosition     = new Vector2(ApplyX, BaseSaveY);
                        schoolPanel.Hide();
                    }
                }
                else
                {
                    // Not a school building - use non-school layout.
                    currentSchoolPack = null;
                    volumetricPanel.relativePosition = new Vector2(0f, BaseCalcY);
                    applyButton.relativePosition     = new Vector2(ApplyX, BaseSaveY);
                    schoolPanel.Hide();
                }
            }
        }
        /// <summary>
        /// Perform and display volumetric calculations for the currently selected building.
        /// </summary>
        /// <param name="building">Selected building prefab</param>
        /// <param name="levelData">Population (level) calculation data to apply to calculations</param>
        /// <param name="floorData">Floor calculation data to apply to calculations</param>
        /// <param name="schoolData">School calculation data to apply to calculations</param>
        /// <param name="schoolData">Multiplier to apply to calculations</param>
        internal void CalculateVolumetric(BuildingInfo building, LevelData levelData, FloorDataPack floorData, SchoolDataPack schoolData, float multiplier)
        {
            // Safety first!
            if (building == null)
            {
                return;
            }

            // Reset message label.
            messageLabel.text = string.Empty;

            // Perform calculations.
            // Get floors and allocate area an number of floor labels.
            SortedList <int, float> floors = PopData.instance.VolumetricFloors(building.m_generatedInfo, floorData, out float totalArea);

            floorAreaLabel.text = totalArea.ToString("N0", LocaleManager.cultureInfo);
            numFloorsLabel.text = floors.Count.ToString();

            // Get total units.
            int totalUnits = PopData.instance.VolumetricPopulation(building.m_generatedInfo, levelData, floorData, multiplier, floors, totalArea);

            // Floor labels list.
            List <string> floorLabels = new List <string>();

            // What we call our units for this building.
            string unitName;

            switch (building.GetService())
            {
            case ItemClass.Service.Residential:
                // Residential - households.
                unitName = Translations.Translate("RPR_CAL_UNI_HOU");
                break;

            case ItemClass.Service.Education:
                // Education - students.
                unitName = Translations.Translate("RPR_CAL_UNI_STU");
                break;

            default:
                // Default - workplaces.
                unitName = Translations.Translate("RPR_CAL_UNI_WOR");
                break;
            }

            // See if we're using area calculations for numbers of units, i.e. areaPer is at least one.
            if (levelData.areaPer > 0)
            {
                // Determine area percentage to use for calculations (inverse of empty area percentage).
                float areaPercent = 1 - (levelData.emptyPercent / 100f);

                // Create new floor area labels by iterating through each floor.
                for (int i = 0; i < floors.Count; ++i)
                {
                    // StringBuilder, because we're doing a fair bit of manipulation here.
                    StringBuilder floorString = new StringBuilder("Floor ");

                    // Floor number
                    floorString.Append(i + 1);
                    floorString.Append(" " + Translations.Translate("RPR_CAL_VOL_ARA") + " ");
                    floorString.Append(floors[i].ToString("N0"));

                    // See if we're calculating units per individual floor.
                    if (!levelData.multiFloorUnits)
                    {
                        // Number of units on this floor - always rounded down.
                        int floorUnits = (int)((floors[i] * areaPercent) / levelData.areaPer);
                        // Adjust by multiplier (after rounded calculation above).
                        floorUnits = (int)(floorUnits * multiplier);

                        // Add extra info to label.
                        floorString.Append(" (");
                        floorString.Append(floorUnits.ToString("N0"));
                        floorString.Append(" ");
                        floorString.Append(unitName);
                        floorString.Append(")");
                    }

                    // Add new floor label item with results for this calculation.
                    floorLabels.Add(floorString.ToString());
                }
            }

            // Do we have a current school selection, and are we using school property overrides?
            if (schoolData != null && ModSettings.enableSchoolProperties)
            {
                // Yes - calculate and display school worker breakdown.
                int[] workers = SchoolData.instance.CalcWorkers(schoolData, totalUnits);
                schoolWorkerLabel.Show();
                schoolWorkerLabel.text = workers[0] + " / " + workers[1] + " / " + workers[2] + " / " + workers[3];

                // Calculate construction cost to display.
                int cost = SchoolData.instance.CalcCost(schoolData, totalUnits);
                ColossalFramework.Singleton <EconomyManager> .instance.m_EconomyWrapper.OnGetConstructionCost(ref cost, building.m_class.m_service, building.m_class.m_subService, building.m_class.m_level);

                // Calculate maintenance cost to display.
                int maintenance = SchoolData.instance.CalcMaint(schoolData, totalUnits) * 100;
                ColossalFramework.Singleton <EconomyManager> .instance.m_EconomyWrapper.OnGetMaintenanceCost(ref maintenance, building.m_class.m_service, building.m_class.m_subService, building.m_class.m_level);

                float displayMaint = Mathf.Abs(maintenance * 0.0016f);

                // And display school cost breakdown.
                costLabel.Show();
                costLabel.text = cost.ToString((!(displayMaint >= 10f)) ? Settings.moneyFormat : Settings.moneyFormatNoCents, LocaleManager.cultureInfo) + " / " + displayMaint.ToString((!(displayMaint >= 10f)) ? Settings.moneyFormat : Settings.moneyFormatNoCents, LocaleManager.cultureInfo);

                // Enforce school floors list position.
                ResetFloorListPosition();
            }
            else
            {
                // No - hide school worker breakdown and cost labels.
                schoolWorkerLabel.Hide();
                costLabel.Hide();

                // Enforce default floors list position.
                ResetFloorListPosition();
            }

            // Allocate our new list of labels to the floors list (via an interim fastlist to avoid race conditions if we 'build' manually directly into floorsList).
            FastList <object> fastList = new FastList <object>()
            {
                m_buffer = floorLabels.ToArray(),
                m_size   = floorLabels.Count
            };

            floorsList.rowsData = fastList;

            // Display total unit calculation result.
            switch (building.GetService())
            {
            case ItemClass.Service.Residential:
                // Residential building.
                totalJobsLabel.Hide();
                totalStudentsLabel.Hide();
                totalHomesLabel.Show();
                totalHomesLabel.text = totalUnits.ToString("N0", LocaleManager.cultureInfo);
                break;

            case ItemClass.Service.Education:
                // School building.
                totalHomesLabel.Hide();
                totalJobsLabel.Hide();
                totalStudentsLabel.Show();
                totalStudentsLabel.text = totalUnits.ToString("N0", LocaleManager.cultureInfo);
                break;

            default:
                // Workplace building.
                totalHomesLabel.Hide();
                totalStudentsLabel.Hide();
                totalJobsLabel.Show();
                totalJobsLabel.text = totalUnits.ToString("N0", LocaleManager.cultureInfo);
                break;
            }

            // Display commercial visit count, or hide the label if not commercial.
            if (building.GetAI() is CommercialBuildingAI)
            {
                visitCountLabel.Show();
                visitCountLabel.text = RealisticVisitplaceCount.PreviewVisitCount(building, totalUnits).ToString();
            }
            else
            {
                visitCountLabel.Hide();
            }

            // Display production count, or hide the label if not a production building.
            if (building.GetAI() is PrivateBuildingAI privateAI && (privateAI is OfficeBuildingAI || privateAI is IndustrialBuildingAI || privateAI is IndustrialExtractorAI))
            {
                productionLabel.Show();
                productionLabel.text = privateAI.CalculateProductionCapacity(building.GetClassLevel(), new ColossalFramework.Math.Randomizer(), building.GetWidth(), building.GetLength()).ToString();
            }