Files
2026-02-28 12:43:44 +08:00

295 lines
12 KiB
C#

//Copyright(c)2020 Procedural Worlds Pty Limited
using System.Collections.Generic;
using UnityEngine;
using GeNa.Core.FlowAnalyzer;
namespace GeNa.Core
{
[ExecuteAlways]
public class RiverFlow : MonoBehaviour
{
[HideInInspector]
public List<Vector3> m_startPositions = new List<Vector3>();
[Header("River Flow Calculation Parameters")]
public bool m_selectTerrainOnly = true;
[Min(0.0f)]
public float m_seaLevel = 25.0f;
[Range(0.05f, 3.0f)]
public float m_startFlow = 0.15f;
[Range(0.5f, 1.5f)]
public float YScale = 1.0f;
[Range(0.8f, 2.5f)]
public float SimplifyEpsilon = 1.25f;
[Header("Visual Preferences")]
public Color SimpleRiverPathColor = Color.yellow;
public bool ShowSimpleRiverPath = true;
public bool ShowRiverConnections = true;
public bool AutoAnalyze = false;
private bool ShowOriginalRiverPaths = false;
private Color RiverPathColor = Color.blue;
private bool ShowRiverVelocity = false;
private bool ShowFlowDirectionMap = false;
[HideInInspector]
public float selectionDiameter = 3.0f;
ElevationFlowAnalyzer m_flowAnalyzer = null;
private List<RootPathNode> m_riverPaths = null;
public List<RootPathNode> m_simpliedPaths = null;
private bool m_simplifiedParmsChanged = false;
private float epsilonScale = 1.0f;
private GeNaAPI.GeNaSplineAPI m_genaAPI = new GeNaAPI.GeNaSplineAPI();
private void OnEnable()
{
m_seaLevel = GeNaEvents.GetSeaLevel(m_seaLevel);
}
public void CreateRiverFlowsClicked()
{
if (m_startPositions.Count < 1)
return;
Terrain terrain = GetTerrainAtPoint(m_startPositions[0]);
if (terrain == null)
return;
m_flowAnalyzer = new ElevationFlowAnalyzer(terrain);
List<FlowFromNode> startNodes = CreateFlowNodes();
m_riverPaths = m_flowAnalyzer.CreateRiverPaths(startNodes, m_seaLevel);
float width = Terrain.activeTerrain.terrainData.heightmapResolution;
Vector3 size = Terrain.activeTerrain.terrainData.size;
epsilonScale = (size / width).x;
//m_flowAnalyzer.SimplifyPaths(m_riverPaths, new Vector3(1.0f, YScale, 1.0f), SimplifyEpsilon * epsilonScale);
if (m_genaAPI == null)
m_genaAPI = new GeNaAPI.GeNaSplineAPI();
m_simplifiedParmsChanged = true;
}
private Terrain GetTerrainAtPoint(Vector3 point)
{
Terrain[] terrains = Terrain.activeTerrains;
if (terrains.Length < 1)
return null;
Terrain curTerrain = terrains[0];
Vector3 terrainPos = GetTerrainCenter(curTerrain);
float curDistance = (terrainPos - point).sqrMagnitude;
for (int i = 1; i < terrains.Length; i++)
{
terrainPos = GetTerrainCenter(terrains[i]);
float distance = (terrainPos - point).sqrMagnitude;
if (distance < curDistance)
{
curDistance = distance;
curTerrain = terrains[i];
}
}
return curTerrain;
}
private Vector3 GetTerrainCenter(Terrain terrain)
{
return terrain.GetPosition() + terrain.terrainData.size * 0.5f;
}
public void CreateSplinesClicked()
{
m_genaAPI.GenerateGeNaSpline(m_simpliedPaths);
}
public void ClearClicked()
{
m_flowAnalyzer = null;
m_riverPaths = null;
m_simpliedPaths = null;
m_startPositions.Clear();
if (m_genaAPI != null)
{
//m_genaAPI.Clear();
}
}
List<FlowFromNode> CreateFlowNodes()
{
List<FlowFromNode> startNodes = new List<FlowFromNode>();
for (int i = 0; i < m_startPositions.Count; i++)
{
float height = 0.0f;
Vector2Int gridLoc = m_flowAnalyzer.GetGridLocation(m_startPositions[i], out height);
//Debug.Log($"Creating startNode for {m_startPositions[i] - Terrain.activeTerrain.GetPosition()} at gridLoc {gridLoc} = {m_flowAnalyzer.GetWorldPosition(gridLoc)}");
FlowFromNode node = new FlowFromNode(height, gridLoc.x, gridLoc.y, m_startFlow);
startNodes.Add(node);
}
return startNodes;
}
public void AddPoint(Vector3 point)
{
int index = GetPointIndex(point);
if (index < 0)
{
m_startPositions.Add(point);
CheckAutoAnalyze(true);
}
}
void CheckAutoAnalyze(bool pointAdded)
{
if (AutoAnalyze && pointAdded)
{
CreateRiverFlowsClicked();
}
else if (m_riverPaths != null)
{
for (int i = m_riverPaths.Count - 1; i >= 0; i--)
{
int index = GetPointIndex(m_riverPaths[i].Position);
if (index < 0)
{
m_riverPaths.RemoveAt(i);
m_simpliedPaths.RemoveAt(i);
}
}
}
}
public void RemovePoint(Vector3 point)
{
int index = GetPointIndex(point);
if (index >= 0)
{
m_startPositions.RemoveAt(index);
CheckAutoAnalyze(false);
}
}
int GetPointIndex(Vector3 point)
{
for (int i = 0; i < m_startPositions.Count; i++)
{
if ((m_startPositions[i] - point).magnitude <= selectionDiameter)
return i;
}
return -1;
}
float lastEpsilon = -99.0f;
float lastYScale = -99.0f;
private void OnDrawGizmosSelected()
{
if (m_riverPaths == null)
return;
if (ShowFlowDirectionMap)
OnDrawGizmosFlowDirectionMap();
if (ShowOriginalRiverPaths)
OnDrawGizmosRiverPaths();
if (m_simpliedPaths == null || lastEpsilon != SimplifyEpsilon)
m_simplifiedParmsChanged = true;
if (lastYScale != YScale)
m_simplifiedParmsChanged = true;
lastEpsilon = SimplifyEpsilon;
lastYScale = YScale;
OnDrawGizmosSimplifiedRivers();
}
private void OnDrawGizmosRiverPaths()
{
foreach (RootPathNode rootNode in m_riverPaths)
{
Vector3 curPos = rootNode.Position + Vector3.up * rootNode.Flow * 1.0f;
foreach (PathNode pathNode in rootNode.Path)
{
Gizmos.color = RiverPathColor;
Gizmos.DrawLine(curPos, pathNode.Position + Vector3.up * pathNode.Flow * 2.0f);
if (ShowRiverConnections)
{
if (pathNode.ConnectedTo != null)
{
Gizmos.color = new Color(0.4f, 0.6f, 1.0f);
Vector3 pos = pathNode.ConnectedTo.Position;
Gizmos.DrawWireCube(pos, Vector3.one);
}
if (pathNode.ConnectedFrom != null)
{
Gizmos.color = Color.magenta;
Vector3 pos = pathNode.ConnectedFrom.Position;
Gizmos.DrawWireCube(pos, Vector3.one);
}
}
Gizmos.color = Color.cyan;
if (ShowRiverVelocity)
{
Vector3 velocity = pathNode.Velocity;
Vector3 target = curPos + velocity;
Gizmos.DrawLine(curPos, target);
}
curPos = pathNode.Position + Vector3.up * pathNode.Flow * 2.0f;
}
}
}
void OnDrawGizmosSimplifiedRivers()
{
if (m_flowAnalyzer == null)
return;
if (m_simpliedPaths == null)
m_simplifiedParmsChanged = true;
if (m_simplifiedParmsChanged)
{
Vector3 scale = new Vector3(1.0f, YScale, 1.0f);
m_simpliedPaths = m_flowAnalyzer.SimplifyedPaths(m_riverPaths, scale, SimplifyEpsilon * epsilonScale);
m_simplifiedParmsChanged = false;
}
if (!ShowSimpleRiverPath)
return;
foreach (RootPathNode rootNode in m_simpliedPaths)
{
Vector3 scale = new Vector3(1.0f, YScale, 1.0f);
//List<ElevationFlowAnalyzer.PathNode> nodes = Simplify.DouglasPeucker(rootNode.Path, scale, SimplifyEpsilon * epsilonScale);
List<PathNode> nodes = rootNode.Path;
Gizmos.color = SimpleRiverPathColor;
Vector3 curPos = nodes[0].Position;
Gizmos.DrawWireCube(curPos, Vector3.one * 0.25f);
foreach (PathNode node in nodes)
{
if (node != rootNode)
{
Gizmos.color = SimpleRiverPathColor;
Vector3 point = node.Position;
Gizmos.DrawLine(curPos, point);
curPos = point;
Gizmos.DrawWireCube(curPos, Vector3.one * 0.25f);
}
if (ShowRiverConnections)
{
if (node.ConnectedTo != null)
{
Gizmos.color = new Color(0.4f, 0.6f, 1.0f);
Vector3 pos = node.ConnectedTo.Position;
Gizmos.DrawWireCube(pos, Vector3.one);
}
if (node.ConnectedFrom != null)
{
Gizmos.color = Color.magenta;
Vector3 pos = node.ConnectedFrom.Position;
Gizmos.DrawWireCube(pos, Vector3.one);
}
}
}
}
}
private void OnDrawGizmosFlowDirectionMap()
{
if (m_flowAnalyzer == null || m_flowAnalyzer.m_flowDirectionMap == null)
return;
Vector3[,] dirMap = m_flowAnalyzer.m_flowDirectionMap;
int width = dirMap.GetLength(0);
Terrain terrain = Terrain.activeTerrain;
if (terrain == null)
return;
Transform terrainTransform = terrain.transform;
Vector3 terrainPosition = terrainTransform.position;
float scale = terrain.terrainData.size.x / (width - 1);
Vector3 tPos = terrain.GetPosition();
Color shortColor = new Color(0.5f, 0.2f, 0.0f);
Color longColor = new Color(1.0f, 1.0f, 0.0f);
Gizmos.color = Color.grey;
for (int r = 1; r < width - 1; r++)
{
for (int c = 1; c < width - 1; c++)
{
//if (dirMap[r, c].sqrMagnitude >= 0.001f)
{
float x = c * scale + tPos.x;
float z = r * scale + tPos.z;
float y = terrain.SampleHeight(new Vector3(x, 1000.0f, z)) + terrainPosition.y;
Vector3 pos = new Vector3(x, y, z);
if (y >= m_seaLevel)
Gizmos.DrawLine(pos, pos + dirMap[r, c]);
}
}
}
}
}
}