using System; using System.Collections.Generic; using System.Xml.Serialization; using TMPro; using UnityEngine; using UnityEngine.InputSystem; public class SectionHandler : WorldInteractableObject { //Variables [Header("Section Settings:")] [SerializeField] private int currentAmount; [SerializeField] private int maxAmount; [SerializeField] private Product product; [SerializeField] private SectionPosition[] sectionPositions; private string sectionName; [SerializeField] private TextMeshProUGUI tmp; [SerializeField] private GameObject shopOwner; [Header("Instancing") ] [SerializeField] private bool GPUInstancing; [SerializeField, HideInInspector] private List<SectionCapacity> sectionCapacities; [SerializeField, HideInInspector] private Mesh mesh; [SerializeField, HideInInspector] private Material material; [SerializeField, HideInInspector] private float objectScale; private BoxCollider boxCollider; private List<SectionCapacity> sortedCapacities; [Serializable] private struct SectionPosition { [SerializeField] private Transform transform; public Transform Transform => transform; public bool IsOccupied { get; set; } } //Buffers private GraphicsBuffer argsBuffer; private GraphicsBuffer instanceDataBuffer; //Materials Properites private MaterialPropertyBlock materialProps; //Properties public int CurrentAmount { get => currentAmount; set { currentAmount = Mathf.Clamp(value, 0, maxAmount); } } public int MaxAmount => maxAmount; public BoxCollider BoxCollider => boxCollider; public Product Product => product; public string SectionName => sectionName; void Awake() { base.Awake(); boxCollider = TryGetComponent(out BoxCollider collider) ? collider : null; if (boxCollider == null) Debug.LogError($"The component {nameof(boxCollider)} on the object {gameObject.name}"); if (GPUInstancing) { sortedCapacities = new(sectionCapacities); sortedCapacities.Sort(); // Optional: Validate no duplicate thresholds for (int i = 1; i < sortedCapacities.Count; i++) { if (sortedCapacities[i].amountThreshold == sortedCapacities[i - 1].amountThreshold) Debug.LogError($"Duplicate threshold: {sortedCapacities[i].amountThreshold}"); } } sectionName = product.name; } // Start is called once before the first execution of Update after the MonoBehaviour is created void Start() { currentAmount = maxAmount; //tmp.text = product.ItemName; if (GetPositionsForCurrentAmount(currentAmount, out Positions positions)) UpdateGPUInstances(positions); } //Removes a product quantity amount public void RemoveQuantity(int value) { int val = currentAmount <= 0 ? 0 : currentAmount -= value; currentAmount = val; if (GetPositionsForCurrentAmount(currentAmount, out Positions positions)) UpdateGPUInstances(positions); } public bool GetPositionsForCurrentAmount(int currentAmount, out Positions positions) { if (!GPUInstancing || sortedCapacities == null || sortedCapacities.Count == 0) { positions = null; return false; } // Binary search for efficiency (list is sorted) int index = sortedCapacities.FindLastIndex( sc => sc.amountThreshold <= currentAmount); positions = (index >= 0) ? sortedCapacities[index].positions : null; return positions != null; } //Reffils the stock of the product public void Refill(int value) => currentAmount = value; public void OnTriggerEnter(Collider other) { shopOwner = other.CompareTag("Owner") ? other.gameObject : null; } public void OnTriggerExit(Collider other) { shopOwner = other.CompareTag("Owner") ? null : shopOwner; } public void UpdateGPUInstances(Positions layout) { if (!GPUInstancing || mesh == null || material == null) return; // Get the correct positions for current amount if (layout == null || layout.list == null || layout.list.Length == 0) { Debug.LogWarning("No positions found for current capacity."); return; } // Build per-instance data ProductData[] productDataArray = new ProductData[layout.list.Length]; for (int i = 0; i < layout.list.Length; i++) { Vector3 localPos = transform.InverseTransformPoint(layout.list[i]); productDataArray[i] = new ProductData(localPos, objectScale); } // Release previous buffers instanceDataBuffer?.Release(); argsBuffer?.Release(); // Create new instance data buffer int stride = System.Runtime.InteropServices.Marshal.SizeOf(typeof(ProductData)); instanceDataBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, productDataArray.Length, stride); instanceDataBuffer.SetData(productDataArray); // Create args buffer: 5 uints uint[] args = new uint[5] { mesh.GetIndexCount(0), (uint)productDataArray.Length, mesh.GetIndexStart(0), mesh.GetBaseVertex(0), 0 }; argsBuffer = new GraphicsBuffer(GraphicsBuffer.Target.IndirectArguments, 1, args.Length * sizeof(uint)); argsBuffer.SetData(args); // Setup material props materialProps ??= new MaterialPropertyBlock(); materialProps.SetBuffer("_PerInstanceData", instanceDataBuffer); } private void Update() { if(currentAmount != 0 && GPUInstancing) { Graphics.RenderMeshIndirect( new RenderParams(material) { worldBounds = new Bounds(transform.position, boxCollider.bounds.size), matProps = materialProps }, mesh, argsBuffer ); } if (Keyboard.current[Key.Space].wasPressedThisFrame) { RemoveQuantity(10); } } private bool IsOccupied(out SectionPosition sectionPosition) { foreach (SectionPosition sc in sectionPositions) { if (!sc.IsOccupied) { sectionPosition = sc; ; return false; } } sectionPosition = new SectionPosition(); return true; } private Vector3 RequestPosition() { if (!IsOccupied(out SectionPosition sectionPosition)) return sectionPosition.Transform.position; return Vector3.zero; } }