Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
TerrainChunker.cs 3.55 KiB
using System.Diagnostics;
using UnityEngine;

/// <summary>
/// Divide the terrain generation into 9 "chunks".
/// Each chunk is responsible for the generation of its
/// terrain object and only its terrain object.
/// 
/// A chunk may be moved when the player moves, such that a 3x3
/// grid of chunks surrounds the player at any time, and that
/// there's a minimum of one full terrain between the player and
/// the void.
/// 
/// Once a chunk is moved, it will regenerate its terrain object.
/// This will be done by a thread/job.
/// </summary>
public abstract class TerrainChunker : MonoBehaviour
{
    #region Variables
    [Header("Autogenerated values")]
    [Tooltip("Current grid position of the chunker")]
    public Vector2Int gridPosition;

    public Vector2 crawlOffset;

    public TerrainMaster master;

    public float TerrainSize => terrainWidth;

    protected Terrain terrain;

    protected TerrainData data;

    protected int heightResolution = 513;

    protected float terrainWidth = 1000;

    protected float heightScale;

    private bool initialGen = true;

    private bool lookForCompletion = false;

    private Stopwatch createTimer = new();
    #endregion

    public void Initialize(TerrainMaster master, Vector2Int gridPosition,
        Vector2 crawlOffset)
    {
        this.master = master;
        this.gridPosition = gridPosition;
        this.crawlOffset = crawlOffset;

        gameObject.name = $"Chunker {gridPosition}";

        data = Instantiate(Resources.Load<TerrainData>("Template"));

        heightScale = data.size.y / master.terrainHeight;
        data.size = new(data.size.x, master.terrainHeight, data.size.z);

        heightResolution = data.heightmapResolution;
        terrainWidth = data.size.x;

        UpdateChunk(gridPosition, true);
    }

    public void UpdateChunk(Vector2Int newGridPosition, bool forceUpdate = false)
    {
        if (newGridPosition != gridPosition || forceUpdate)
        {
            gridPosition = newGridPosition;

            // Now we update the terrain
            UpdateTerrain();
        }
    }

    private void UpdateTerrain()
    {
        print($"Updating {gameObject.name} terrain.");

        createTimer.Start();

        CreateShapeGenerator();
        lookForCompletion = true;
    }

    private void ApplyTerrain(float[,] heights)
    {
        createTimer.Stop();
        print($"Apply {gameObject.name} terrain. " +
            $"Took {createTimer.Elapsed} with mode {master.mode}.");

        // Calculate position of game object
        Vector2 flatPosition = (Vector2)gridPosition * terrainWidth;
        Vector3 worldPosition = new(flatPosition.x, 0, flatPosition.y);

        // Set heights of terrain data.
        master.shapeApplier.ApplyHeights(transform, terrain, data,
           worldPosition, heights);
    }

    private void Update()
    {
        if (lookForCompletion && CheckShapeDone() && !CheckShapeCancelled())
        {
            ApplyTerrain(GetHeights());
            lookForCompletion = false;

            if (initialGen)
            {
                initialGen = false;
                master.ChunkInitGenerate();
            }
        }
    }

    #region Shape generation
    protected abstract bool CheckShapeDone();

    protected abstract bool CheckShapeCancelled();

    protected abstract void CreateShapeGenerator();

    protected abstract float[,] GetHeights();
    #endregion
    #region Texture generation
    protected abstract float[,,] GetAlphamap();
    #endregion

    private void OnDestroy()
    {
        CleanUp();
    }

    protected abstract void CleanUp();
}