fbpx
using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEngine;
using Random = UnityEngine.Random;

public class TerrainGenerator : MonoBehaviour
{
    [Header("Common Settings")]
    [SerializeField] [Range(1, 35)] int width = 12;
    [SerializeField] [Range(1, 35)] int height = 8;
    [SerializeField] [ReadOnly] int _lastSeed;
    [SerializeField] int _seed;
    
    [Header("Perlin Settings")]
    [SerializeField] bool _usePerlin;
    [SerializeField] int horizontalScroll;
    [SerializeField] int verticalScroll;
    [SerializeField] [Range(1, 20)] float scale = 20f;

    [Header("Tiles")]
    [SerializeField] float[] _tileHeights;
    [SerializeField] float _borderHeight = 1f;
    [SerializeField] List<GameObject> terrainTilePrefabs;
    [SerializeField] GameObject terrainTileBorderPrefab;

    [ReadOnly] public List<GameObject> terrainTiles = new();
    int _lastSizeX;
    int _lastSizeZ;

    void OnValidate()
    {
        if (_tileHeights.Length != terrainTilePrefabs.Count)
            Array.Resize(ref _tileHeights, terrainTilePrefabs.Count);
        
        if (_seed != _lastSeed || width != _lastSizeX || height != _lastSizeZ)
        {
            _lastSeed = _seed;
            GenerateTerrain();
        }
    }

    void DestroyExistingTerrain()
    {
        if (terrainTiles == null)
            return;
        terrainTiles = GetComponentsInChildren<MeshRenderer>().Select(t => t.gameObject).ToList();
        foreach (var tile in terrainTiles)
        {
            if (Application.isPlaying)
                Destroy(tile.gameObject);
            else
                DestroyImmediate(tile.gameObject);
        }

        terrainTiles.Clear();
    }

    public static float Map(float value, float originalMin, float originalMax, float targetMin, float targetMax)
    {
        return (value - originalMin) / (originalMax - originalMin) * (targetMax - targetMin) + targetMin;
    }

    [ContextMenu("Generate Terrain")]
    public void GenerateTerrain()
    {
        DestroyExistingTerrain();
        Random.InitState(_seed);
        var randomOffsetX = Random.value * 100f;
        var randomOffsetZ = Random.value * 100f;

        for (var x = -width; x < width; x++)
        for (var z = -height; z < height; z++)
        {
            int terrainIndex = Random.Range(0, terrainTilePrefabs.Count);
            if (_usePerlin)
            {
                var xCoord = (x + randomOffsetX + horizontalScroll) / width * scale;
                var zCoord = (z + randomOffsetZ + verticalScroll) / height * scale;

                var y = Mathf.PerlinNoise(xCoord, zCoord);
                terrainIndex = (int) Map(y, 0, 1, 0, terrainTilePrefabs.Count);
                terrainIndex = Mathf.Clamp(terrainIndex, 0, terrainTilePrefabs.Count - 1);
            }

            GameObject terrainTile = terrainTilePrefabs[terrainIndex];

            var isBorder = x == -width || x == width - 1 || z == -height || z == height - 1;
            if (isBorder)
                terrainTile = terrainTileBorderPrefab;
            var tile = Instantiate(terrainTile, new Vector3(x, 0, z), Quaternion.identity, transform);
            terrainTiles.Add(tile);
            if (isBorder)
                tile.transform.localPosition += Vector3.up * _borderHeight;
            else
                tile.transform.localPosition += Vector3.up * _tileHeights[terrainIndex];
        }
    }
}