Skip to content
Snippets Groups Projects
CoastlineAgent.cs 7.79 KiB
Newer Older
Felix Buenen's avatar
Felix Buenen committed
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using UnityEditorInternal;
Felix Buenen's avatar
Felix Buenen committed
using UnityEngine;
using UnityEngine.Rendering;
Felix Buenen's avatar
Felix Buenen committed

Felix Buenen's avatar
Felix Buenen committed
public class CoastlineAgent
Felix Buenen's avatar
Felix Buenen committed
{
Felix Buenen's avatar
Felix Buenen committed
    private CoastlineGenerator coastlineGenerator;
Felix Buenen's avatar
Felix Buenen committed
    private float tokensRemaining;
    private Vector2 position, attractor, repulsor, prefDirection;
    private int actionRadius;
    private bool useCustomRadius = false;
    private List<Vector2> borderPart;
Felix Buenen's avatar
Felix Buenen committed

    private System.Func<Vector2, bool> borderFilter;

    public int Tokens { get => tokens; }
    public int ActionRadius { get => actionRadius; }

    public CoastlineAgent(CoastlineGenerator generator, Vector2 position, int tokens, List<Vector2> borderPart, int actionRadius = -1)
Felix Buenen's avatar
Felix Buenen committed
    {
Felix Buenen's avatar
Felix Buenen committed
        coastlineGenerator = generator;
Felix Buenen's avatar
Felix Buenen committed

Felix Buenen's avatar
Felix Buenen committed
        this.tokens = tokens;
        this.tokensRemaining = tokens;
        this.position = position;
        this.actionRadius = actionRadius;
        this.borderPart = borderPart;

        // calculate preferred direction
        Vector2 tangent = (borderPart[borderPart.Count - 1] - borderPart[0]).normalized;
        Vector2 bitangent = new Vector2(-tangent.y, tangent.x);

        if(bitangent == Vector2.zero) // in case of the first agent, just create any random pref direction
        {
            prefDirection = Random.insideUnitCircle;
            prefDirection.Normalize();
        }
        else
        {
            prefDirection = RotateVector(bitangent, Random.Range(-70.0f, 70.0f));
        }


        if (this.actionRadius > 0)
        {
            useCustomRadius = true;
            borderFilter = p => (p - position).sqrMagnitude <= actionRadius * actionRadius;
        }
        else
        {
            borderFilter = p => (p - position).sqrMagnitude <= coastlineGenerator.SqrActionRadius;
        }
Felix Buenen's avatar
Felix Buenen committed

Felix Buenen's avatar
Felix Buenen committed
        // create random attractor / repulsor and make sure they point at different directions
        Vector2 direction = Random.insideUnitCircle * coastlineGenerator.ActionRadius;
        attractor = position + direction;
        repulsor = position + RotateVector(-direction, Random.Range(-90f, 90));
Felix Buenen's avatar
Felix Buenen committed
    }

    public bool UpdateLand()
    {
        if (tokensRemaining <= 0) return false;
Felix Buenen's avatar
Felix Buenen committed

        Vector2 pos = GetRandomPosition();
        if(pos == new Vector2(0, 0))
        {
            tokensRemaining--;
            return true;
        }
Felix Buenen's avatar
Felix Buenen committed

Felix Buenen's avatar
Felix Buenen committed
        Vector2 initPos = new Vector2(-1, -1);
        Vector2 highestPos = initPos;
        float highestScore = 0.0f;
        // score 
        for (int x = -1; x <= 1; x++)
Felix Buenen's avatar
Felix Buenen committed
        {
Felix Buenen's avatar
Felix Buenen committed
            for (int y = -1; y <= 1; y++)
            {
                Vector2 currPos = new Vector2((int)pos.x + x, (int)pos.y + y);

                if (!PositionInBounds(currPos)) continue;
Felix Buenen's avatar
Felix Buenen committed
                int currIndex = (int)(currPos.x + currPos.y * coastlineGenerator.TerrainSize);
                if (coastlineGenerator.landBitField[currIndex]) continue;

                float score = Score(currPos);
                if (score > highestScore || highestPos == initPos)
                {
                    highestScore = score;
                    highestPos = currPos;
                }
            }
Felix Buenen's avatar
Felix Buenen committed
        }

        if (highestPos == initPos || pos == Vector2.zero)
        {
            // couldn't find a decent neighbor
            tokensRemaining--;
            return true;
        }
        
        if (PositionInBounds(highestPos))
        {
            coastlineGenerator.coastText.SetPixel((int)highestPos.x, (int)highestPos.y, Color.grey);
            int highestIndex = (int)highestPos.x + (int)highestPos.y * coastlineGenerator.TerrainSize;
            coastlineGenerator.landBitField[highestIndex] = true;
Felix Buenen's avatar
Felix Buenen committed

            coastlineGenerator.borderPoints.Add(highestPos);

            if (!borderPart.Contains(highestPos)) borderPart.Add(highestPos);
            coastlineGenerator.RemoveNonBoundaryPixels(highestPos);
        }
        else Debug.Log("Whoops");
Felix Buenen's avatar
Felix Buenen committed

        tokensRemaining--;
        return true;
Felix Buenen's avatar
Felix Buenen committed
    }

Felix Buenen's avatar
Felix Buenen committed
    private float Score(Vector2 pos)
Felix Buenen's avatar
Felix Buenen committed
    {
Felix Buenen's avatar
Felix Buenen committed
        int terrainSize = coastlineGenerator.TerrainSize;
        float mapHorizDist = (pos.x < terrainSize * 0.5f ? pos.x : terrainSize - pos.x);
        float mapVertDist = (pos.y < terrainSize * 0.5f ? pos.y : terrainSize - pos.y);
        float mapDist = Mathf.Min(mapHorizDist, mapVertDist);
Felix Buenen's avatar
Felix Buenen committed

        return ((repulsor - pos).sqrMagnitude * 1f - (attractor - pos).sqrMagnitude * 1f) + 3 * mapDist * mapDist;
    }

    private bool PositionInBounds(Vector2 pos)
    {
        return pos.x >= 0 || pos.y >= 0 || pos.x <= (coastlineGenerator.TerrainSize - 1) || pos.y <= (coastlineGenerator.TerrainSize - 1);
    }

    private bool PositionOnEdge(Vector2 pos)
    {
        return pos.x == 0 || pos.y == 0 || pos.x == (coastlineGenerator.TerrainSize - 1) || pos.y == (coastlineGenerator.TerrainSize - 1);
    }

    public Vector2 GetRandomPosition()
    {
        if (borderPart.Count == 0)
        {
            //Debug.Log("borderpart count is zero!");
            return Vector2.zero;
        }
        int randIndex = Random.Range(0, borderPart.Count);
        Vector2 pos = borderPart[randIndex];

        if (PositionOnEdge(pos)) return Vector2.zero;
        bool inland = true;

        for (int x = -1; x <= 1; x++)
        {
            for (int y = -1; y <= 1; y++)
            {
                if (x == 0 && y == 0) continue;

                if (!PositionInBounds(pos + new Vector2(x, y))) continue;

                int index = (int)((pos.x + x) + (pos.y + y) * coastlineGenerator.TerrainSize);
                if (!coastlineGenerator.landBitField[index])
                {
                    inland = false;
                    break;
                }
            }

            if(!inland)
            {
                break;
            }
        }

        if (inland)
        {
            // not a valid border point anymore, remove
            borderPart.Remove(pos);
            coastlineGenerator.borderPoints.Remove(pos);
            Vector2 oldPos = pos;

            // find new border point in preferred direction
            pos = TraverseToCoastline(oldPos);
            //pos = new Vector2(-1, -1);
            if(pos == new Vector2(-1, -1))
            {
                return Vector2.zero;
            }
            else if (!borderPart.Contains(pos)) borderPart.Add(pos);
        }

        return pos;
    }

    public Vector2 TraverseToCoastline(Vector2 startPos)
    {
        Vector2 pos = new Vector2(-1, -1);

        if (PositionOnEdge(startPos)) return pos;
        Vector2 prevPos = pos;
        bool foundCoastPoint = false;
        Vector2 oldPos = startPos;
            Vector2Int newPos = new Vector2Int((int) (oldPos.x + prefDirection.x), (int) (oldPos.y + prefDirection.y));

            if (!PositionInBounds(pos)) return pos;
            int idx = coastlineGenerator.TerrainSize * newPos.y + newPos.x;

            if(!coastlineGenerator.landBitField[idx])
            {
                foundCoastPoint = true;
                pos = prevPos;
            }
            else
            {
                //Debug.Log("not a coastline!");
                //Debug.Log("pref dir x: " + prefDirection.x);
                //Debug.Log("pref dir y: " + prefDirection.y);
            }
            oldPos = oldPos + prefDirection;

            //foundCoastPoint = coastlineGenerator.borderPoints.Contains(possibleCoastPoint);

            //if (foundCoastPoint) pos = possibleCoastPoint;
        while (!foundCoastPoint && maxCounter < 100);

        return pos;
    }

    private static Vector2 RotateVector(Vector2 v, float degrees)
    {
        float rad = degrees * Mathf.Deg2Rad;
        float ca = Mathf.Cos(rad);
        float sa = Mathf.Sin(rad);

        return new Vector2(ca * v.x - sa * v.y, sa * v.x + ca * v.y);
Felix Buenen's avatar
Felix Buenen committed
    }
}