Newer
Older
Hierarchical-Task-Network-Unity-3D / Assets / Scripts / ShopKeeper / SectionHandler.cs
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;
    }
}