Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
TerrainChunker.cs 3.37 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
{
    [Header("Autogenerated values")]
	[Tooltip("Current grid position of the chunker")]
    public Vector2Int gridPosition;

    public Vector2 crawlOffset;

    public TerrainMaster master;

    protected Terrain terrain;

    protected TerrainData data;

    protected int heightResolution = 513;

    protected float terrainWidth = 1000;

    protected float heightScale;

    private bool lookForCompletion = false;

    private Stopwatch createTimer = new();

    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();

        CreateRunning();
        lookForCompletion = true;
    }

    protected abstract bool CheckIfFinished();

    protected abstract bool CheckIfCancelled();

    /// <summary>
    /// Checks and deals with any running.
    /// </summary>
    //protected abstract void CancelRunning();

    protected abstract void CreateRunning();

    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.
        TerrainHeightApplier.singleton.ApplyHeights(transform, terrain, data,
           worldPosition, heights);
    }

    private void Update()
    {
        if (lookForCompletion && CheckIfFinished() && !CheckIfCancelled())
        {
            ApplyTerrain(RetrieveFromRunning());
            lookForCompletion = false;
        }
    }

    protected abstract float[,] RetrieveFromRunning();


    private void OnDestroy()
    {
        CleanUp();
    }

    protected abstract void CleanUp();

    public float GetTerrainSize()
    {
        return terrainWidth;
    }
}