959e80cf72
assets upload description.
614 lines
20 KiB
C#
614 lines
20 KiB
C#
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using AwesomeTechnologies.Utility;
|
|
using AwesomeTechnologies.VegetationStudio;
|
|
#if UNITY_EDITOR
|
|
using UnityEditor;
|
|
#endif
|
|
using UnityEngine;
|
|
#if UNITY_POST_PROCESSING_STACK_V2
|
|
using UnityEngine.Rendering.PostProcessing;
|
|
#endif
|
|
|
|
|
|
namespace AwesomeTechnologies.VegetationSystem.Biomes
|
|
{
|
|
[System.Serializable]
|
|
public class Node
|
|
{
|
|
public bool Selected;
|
|
public Vector3 Position;
|
|
public bool OverrideWidth;
|
|
public float CustomWidth = 2f;
|
|
public bool Active = true;
|
|
public bool DisableEdge;
|
|
}
|
|
[ExecuteInEditMode]
|
|
public class BiomeMaskArea : MonoBehaviour
|
|
{
|
|
public List<Node> Nodes = new List<Node>();
|
|
public bool ClosedArea = true;
|
|
public bool ShowArea = true;
|
|
public bool ShowHandles = true;
|
|
public string MaskName = "";
|
|
private bool _needInit;
|
|
public string Id;
|
|
public BiomeType BiomeType;
|
|
public LayerMask GroundLayerMask;
|
|
public AnimationCurve BlendCurve = new AnimationCurve();
|
|
public AnimationCurve InverseBlendCurve = new AnimationCurve();
|
|
|
|
public AnimationCurve TextureBlendCurve = new AnimationCurve();
|
|
public float BlendDistance = 5f;
|
|
public float NoiseScale = 20;
|
|
public bool UseNoise = true;
|
|
private PolygonBiomeMask _currentMaskArea;
|
|
|
|
private Vector3 _lastPosition;
|
|
private Quaternion _lastRotation;
|
|
private Vector3 _lastLossyScale;
|
|
|
|
|
|
public void UpdateBiomeMask()
|
|
{
|
|
if (!enabled || !gameObject.activeSelf) return;
|
|
List<Vector3> worldSpaceNodeList = GetWorldSpaceNodePositions();
|
|
List<bool> disableEdgeList = GetDisableEdgeList();
|
|
PolygonBiomeMask maskArea = new PolygonBiomeMask {BiomeType = BiomeType, BlendDistance = BlendDistance, UseNoise = UseNoise, NoiseScale = NoiseScale};
|
|
maskArea.AddPolygon(worldSpaceNodeList,disableEdgeList);
|
|
|
|
if (!ValidateAnimationCurve(BlendCurve))
|
|
{
|
|
BlendCurve = CreateResetAnimationCurve();
|
|
}
|
|
|
|
if (!ValidateAnimationCurve(InverseBlendCurve))
|
|
{
|
|
InverseBlendCurve = CreateResetAnimationCurve();
|
|
}
|
|
|
|
if (!ValidateAnimationCurve(TextureBlendCurve))
|
|
{
|
|
TextureBlendCurve = CreateResetAnimationCurve();
|
|
}
|
|
|
|
maskArea.SetCurve(BlendCurve.GenerateCurveArray(4096));
|
|
maskArea.SetInverseCurve(InverseBlendCurve.GenerateCurveArray(4096));
|
|
maskArea.SetTextureCurve(TextureBlendCurve.GenerateCurveArray(4096));
|
|
|
|
if (_currentMaskArea != null)
|
|
{
|
|
VegetationStudioManager.RemoveBiomeMask(_currentMaskArea);
|
|
_currentMaskArea = null;
|
|
}
|
|
|
|
_currentMaskArea = maskArea;
|
|
VegetationStudioManager.AddBiomeMask(maskArea);
|
|
|
|
RefreshPostProcessVolume();
|
|
}
|
|
|
|
|
|
public bool ValidateAnimationCurve(AnimationCurve curve)
|
|
{
|
|
float sample = curve.Evaluate(0.5f);
|
|
if (float.IsNaN(sample))
|
|
{
|
|
#if UNITY_EDITOR
|
|
EditorUtility.DisplayDialog("Curve error",
|
|
"There is a problem with one of the biome curves. It will be reset", "OK");
|
|
#endif
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
AnimationCurve CreateResetAnimationCurve()
|
|
{
|
|
AnimationCurve curve = new AnimationCurve();
|
|
curve.AddKey(0, 0.5f);
|
|
curve.AddKey(1, 0.5f);
|
|
return curve;
|
|
}
|
|
|
|
|
|
private List<bool> GetDisableEdgeList()
|
|
{
|
|
List<bool> disableEdgeList = new List<bool>();
|
|
for (int i = 0; i <= Nodes.Count - 1; i++)
|
|
{
|
|
disableEdgeList.Add(Nodes[i].DisableEdge);
|
|
}
|
|
|
|
return disableEdgeList;
|
|
}
|
|
|
|
// ReSharper disable once UnusedMember.Local
|
|
void Start()
|
|
{
|
|
_lastPosition = transform.position;
|
|
_lastRotation = transform.rotation;
|
|
_lastLossyScale = transform.lossyScale;
|
|
|
|
if (Nodes.Count == 0)
|
|
{
|
|
CreateDefaultNodes();
|
|
}
|
|
}
|
|
|
|
// ReSharper disable once UnusedMember.Local
|
|
void OnDisable()
|
|
{
|
|
if (_currentMaskArea != null)
|
|
{
|
|
VegetationStudioManager.RemoveBiomeMask(_currentMaskArea);
|
|
_currentMaskArea = null;
|
|
}
|
|
}
|
|
|
|
// ReSharper disable once UnusedMember.Local
|
|
void Update()
|
|
{
|
|
if (!Application.isPlaying || _needInit)
|
|
{
|
|
if (_lastPosition != transform.position || _lastRotation != transform.rotation || _needInit || _lastLossyScale != transform.lossyScale)
|
|
{
|
|
PositionNodes();
|
|
_lastPosition = transform.position;
|
|
_lastRotation = transform.rotation;
|
|
_lastLossyScale = transform.lossyScale;
|
|
_needInit = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void Reset()
|
|
{
|
|
if (Id == "") Id = System.Guid.NewGuid().ToString();
|
|
ClearNodes();
|
|
CreateDefaultNodes();
|
|
BlendCurve.AddKey(0, 0);
|
|
BlendCurve.AddKey(1, 1);
|
|
|
|
InverseBlendCurve.AddKey(0, 1);
|
|
InverseBlendCurve.AddKey(1, 0);
|
|
|
|
TextureBlendCurve.AddKey(0, 0);
|
|
TextureBlendCurve.AddKey(1, 1);
|
|
}
|
|
|
|
public void ClearNodes()
|
|
{
|
|
Nodes.Clear();
|
|
}
|
|
|
|
void CreateDefaultNodes()
|
|
{
|
|
Bounds tempBounds = new Bounds(new Vector3(0, 0, 0), new Vector3(6f, 1f, 6f));
|
|
ClearNodes();
|
|
for (int i = 0; i <= 3; i++)
|
|
{
|
|
Node node = new Node();
|
|
switch (i)
|
|
{
|
|
case 0:
|
|
node.Position = new Vector3(tempBounds.extents.x, tempBounds.extents.y, tempBounds.extents.z);
|
|
break;
|
|
case 1:
|
|
node.Position = new Vector3(-tempBounds.extents.x, tempBounds.extents.y, tempBounds.extents.z);
|
|
break;
|
|
case 2:
|
|
node.Position = new Vector3(-tempBounds.extents.x, tempBounds.extents.y, -tempBounds.extents.z);
|
|
break;
|
|
case 3:
|
|
node.Position = new Vector3(tempBounds.extents.x, tempBounds.extents.y, -tempBounds.extents.z);
|
|
break;
|
|
}
|
|
Nodes.Add(node);
|
|
}
|
|
PositionNodes();
|
|
}
|
|
|
|
public void DeleteNode(Node node)
|
|
{
|
|
Nodes.Remove(node);
|
|
}
|
|
|
|
public void AddNodesToEnd(Vector3[] worldPositions)
|
|
{
|
|
for (int i = 0; i <= worldPositions.Length - 1; i++)
|
|
{
|
|
AddNodeToEnd(worldPositions[i]);
|
|
}
|
|
}
|
|
|
|
public void AddNodesToEnd(Vector3[] worldPositions, bool[] disableEdges)
|
|
{
|
|
for (int i = 0; i <= worldPositions.Length - 1; i++)
|
|
{
|
|
AddNodeToEnd(worldPositions[i],disableEdges[i]);
|
|
}
|
|
}
|
|
|
|
public void AddNodesToEnd(Vector3[] worldPositions, float[] customWidth, bool[] active)
|
|
{
|
|
for (int i = 0; i <= worldPositions.Length - 1; i++)
|
|
{
|
|
AddNodeToEnd(worldPositions[i], customWidth[i], active[i]);
|
|
}
|
|
}
|
|
|
|
public void AddNodesToEnd(Vector3[] worldPositions, float[] customWidth, bool[] active, bool[] disableEdges)
|
|
{
|
|
for (int i = 0; i <= worldPositions.Length - 1; i++)
|
|
{
|
|
AddNodeToEnd(worldPositions[i], customWidth[i], active[i],disableEdges[i]);
|
|
}
|
|
}
|
|
|
|
public void AddNodeToEnd(Vector3 worldPosition)
|
|
{
|
|
Node node = new Node { Position = transform.InverseTransformPoint(worldPosition) };
|
|
Nodes.Add(node);
|
|
}
|
|
|
|
public void AddNodeToEnd(Vector3 worldPosition, bool disableEdge)
|
|
{
|
|
Node node = new Node
|
|
{
|
|
Position = transform.InverseTransformPoint(worldPosition),
|
|
DisableEdge = disableEdge
|
|
};
|
|
Nodes.Add(node);
|
|
}
|
|
|
|
public void AddNodeToEnd(Vector3 worldPosition, float customWidth, bool active)
|
|
{
|
|
Node node = new Node
|
|
{
|
|
Position = transform.InverseTransformPoint(worldPosition),
|
|
CustomWidth = customWidth,
|
|
OverrideWidth = true,
|
|
Active = active
|
|
};
|
|
Nodes.Add(node);
|
|
}
|
|
|
|
public void AddNodeToEnd(Vector3 worldPosition, float customWidth, bool active, bool disableEdge)
|
|
{
|
|
Node node = new Node
|
|
{
|
|
Position = transform.InverseTransformPoint(worldPosition),
|
|
CustomWidth = customWidth,
|
|
OverrideWidth = true,
|
|
Active = active,
|
|
DisableEdge = disableEdge
|
|
};
|
|
Nodes.Add(node);
|
|
}
|
|
|
|
// ReSharper disable once UnusedMember.Local
|
|
void OnEnable()
|
|
{
|
|
if (Id == "") Id = System.Guid.NewGuid().ToString();
|
|
_needInit = true;
|
|
}
|
|
|
|
public void PositionNodes()
|
|
{
|
|
for (int i = 0; i <= Nodes.Count - 1; i++)
|
|
{
|
|
Ray ray = new Ray(transform.TransformPoint(Nodes[i].Position) + new Vector3(0, 2000f, 0), Vector3.down);
|
|
|
|
var hits = Physics.RaycastAll(ray).OrderBy(h => h.distance).ToArray();
|
|
for (int j = 0; j <= hits.Length - 1; j++)
|
|
{
|
|
if (hits[j].collider is TerrainCollider || GroundLayerMask.Contains(hits[j].collider.gameObject.layer))
|
|
{
|
|
Nodes[i].Position = transform.InverseTransformPoint(hits[j].point);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateBiomeMask();
|
|
}
|
|
|
|
public void AddNode(Vector3 worldPosition)
|
|
{
|
|
if (Nodes.Count == 0)
|
|
{
|
|
AddNodeToEnd(worldPosition);
|
|
return;
|
|
}
|
|
|
|
Node closestNode = FindClosestNode(worldPosition);
|
|
Node nextNode = GetNextNode(closestNode);
|
|
Node previousNode = GetPreviousNode(closestNode);
|
|
|
|
LineSegment3D nextSegment = new LineSegment3D(transform.TransformPoint(closestNode.Position), transform.TransformPoint(nextNode.Position));
|
|
LineSegment3D previousSegment = new LineSegment3D(transform.TransformPoint(closestNode.Position), transform.TransformPoint(previousNode.Position));
|
|
|
|
float nextSegmentDistance = nextSegment.DistanceTo(worldPosition);
|
|
float previousSegmentDistance = previousSegment.DistanceTo(worldPosition);
|
|
|
|
Node node = new Node { Position = transform.InverseTransformPoint(worldPosition) };
|
|
|
|
int currentNodeIndex = GetNodeIndex(closestNode);
|
|
|
|
if (nextSegmentDistance < previousSegmentDistance)
|
|
{
|
|
if (currentNodeIndex == Nodes.Count - 1)
|
|
{
|
|
Nodes.Add(node);
|
|
}
|
|
else
|
|
{
|
|
Nodes.Insert(currentNodeIndex + 1, node);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Nodes.Insert(currentNodeIndex, node);
|
|
}
|
|
}
|
|
|
|
public int GetNodeIndex(Node node)
|
|
{
|
|
int nodeIndex = 0;
|
|
for (int i = 0; i <= Nodes.Count - 1; i++)
|
|
{
|
|
if (Nodes[i] == node)
|
|
{
|
|
nodeIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
return nodeIndex;
|
|
}
|
|
|
|
public List<Vector3> GetWorldSpaceNodePositions()
|
|
{
|
|
List<Vector3> worldSpaceNodeList = new List<Vector3>();
|
|
|
|
for (int i = 0; i <= Nodes.Count - 1; i++)
|
|
{
|
|
worldSpaceNodeList.Add(transform.TransformPoint(Nodes[i].Position));
|
|
}
|
|
return worldSpaceNodeList;
|
|
}
|
|
public Node GetNextNode(Node node)
|
|
{
|
|
int nodeIndex = 0;
|
|
for (int i = 0; i <= Nodes.Count - 1; i++)
|
|
{
|
|
if (Nodes[i] == node)
|
|
{
|
|
nodeIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nodeIndex == Nodes.Count - 1)
|
|
{
|
|
return Nodes[0];
|
|
}
|
|
else
|
|
{
|
|
return Nodes[nodeIndex + 1];
|
|
}
|
|
}
|
|
|
|
public Node GetPreviousNode(Node node)
|
|
{
|
|
int nodeIndex = 0;
|
|
for (int i = 0; i <= Nodes.Count - 1; i++)
|
|
{
|
|
if (Nodes[i] == node)
|
|
{
|
|
nodeIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (nodeIndex == 0)
|
|
{
|
|
return Nodes[Nodes.Count - 1];
|
|
}
|
|
else
|
|
{
|
|
return Nodes[nodeIndex - 1];
|
|
}
|
|
}
|
|
|
|
public Node FindClosestNode(Vector3 worldPosition)
|
|
{
|
|
Node returnNode = null;
|
|
float smallestDistance = float.MaxValue;
|
|
|
|
for (int i = 0; i <= Nodes.Count - 1; i++)
|
|
{
|
|
float distance = Vector3.Distance(worldPosition, transform.TransformPoint(Nodes[i].Position));
|
|
if (distance < smallestDistance)
|
|
{
|
|
smallestDistance = distance;
|
|
returnNode = Nodes[i];
|
|
}
|
|
}
|
|
|
|
return returnNode;
|
|
}
|
|
|
|
void DrawGizmos()
|
|
{
|
|
#if UNITY_EDITOR
|
|
if (MaskName != "")
|
|
{
|
|
GUIStyle stLabel = new GUIStyle(EditorStyles.whiteLabel);
|
|
Handles.Label(GetMaskCenter(), MaskName + "(" + BiomeType + ")", stLabel);
|
|
}
|
|
else
|
|
{
|
|
GUIStyle stLabel = new GUIStyle(EditorStyles.whiteLabel);
|
|
Handles.Label(GetMaskCenter(), BiomeType.ToString(), stLabel);
|
|
}
|
|
#endif
|
|
if (ShowArea)
|
|
{
|
|
|
|
|
|
#if UNITY_EDITOR
|
|
Gizmos.color = new Color(1f, 1f, 0, 1f);
|
|
Camera sceneviewCamera = SceneViewDetector.GetCurrentSceneViewCamera();
|
|
if (!sceneviewCamera) return;
|
|
|
|
for (int i = 0; i <= Nodes.Count - 1; i++)
|
|
{
|
|
var distance = Vector3.Distance(sceneviewCamera.transform.position, transform.TransformPoint(Nodes[i].Position));
|
|
|
|
if (distance < 200)
|
|
{
|
|
Gizmos.color = !Nodes[i].DisableEdge ? new Color(0f, 1f, 0f, 0.9f) : new Color(1f, 0f, 0f, 0.9f);
|
|
|
|
if (Nodes[i].Selected) Gizmos.color = new Color(1f, 1f, 1f, 1f);
|
|
|
|
Gizmos.DrawSphere(transform.TransformPoint(Nodes[i].Position), 0.02f * distance);
|
|
}
|
|
}
|
|
|
|
Gizmos.color = new Color(1f, 1f, 1f, 1f);
|
|
|
|
if (Nodes.Count > 1)
|
|
{
|
|
for (int i = 0; i <= Nodes.Count - 1; i++)
|
|
{
|
|
if (i == Nodes.Count - 1)
|
|
{
|
|
if (ClosedArea)
|
|
{
|
|
Gizmos.DrawLine(transform.TransformPoint(Nodes[0].Position), transform.TransformPoint(Nodes[i].Position));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Gizmos.DrawLine(transform.TransformPoint(Nodes[i].Position), transform.TransformPoint(Nodes[i + 1].Position));
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
}
|
|
}
|
|
|
|
public virtual void OnDrawGizmosSelected()
|
|
{
|
|
if (!VegetationStudioManager.ShowBiomes) DrawGizmos();
|
|
}
|
|
|
|
public virtual void OnDrawGizmos()
|
|
{
|
|
if (VegetationStudioManager.ShowBiomes) DrawGizmos();
|
|
}
|
|
|
|
Vector3 GetMaskCenter()
|
|
{
|
|
List<Vector3> worldPositions = GetWorldSpaceNodePositions();
|
|
return (GetMeanVector(worldPositions.ToArray()));
|
|
}
|
|
|
|
private Vector3 GetMeanVector(Vector3[] positions)
|
|
{
|
|
if (positions.Length == 0)
|
|
return Vector3.zero;
|
|
float x = 0f;
|
|
float y = 0f;
|
|
float z = 0f;
|
|
foreach (Vector3 pos in positions)
|
|
{
|
|
x += pos.x;
|
|
y += pos.y;
|
|
z += pos.z;
|
|
}
|
|
return new Vector3(x / positions.Length, y / positions.Length, z / positions.Length);
|
|
}
|
|
|
|
// ReSharper disable once UnusedMember.Local
|
|
|
|
public void RefreshPostProcessVolume()
|
|
{
|
|
#if UNITY_POST_PROCESSING_STACK_V2
|
|
PostProcessProfileInfo postProcessProfileInfo =
|
|
VegetationStudioManager.GetPostProcessProfileInfo(BiomeType);
|
|
RefreshPostProcessVolume(postProcessProfileInfo, VegetationStudioManager.GetPostProcessingLayer());
|
|
#endif
|
|
}
|
|
#if UNITY_POST_PROCESSING_STACK_V2
|
|
public void RefreshPostProcessVolume(PostProcessProfileInfo postProcessProfileInfo, LayerMask postProcessLayer)
|
|
{
|
|
gameObject.layer = postProcessLayer;
|
|
|
|
if (postProcessProfileInfo == null)
|
|
{
|
|
PostProcessVolume postProcessVolume = gameObject.GetComponent<PostProcessVolume>();
|
|
if (postProcessVolume)
|
|
{
|
|
DestroyImmediate(postProcessVolume);
|
|
}
|
|
|
|
MeshCollider meshCollider = gameObject.GetComponent<MeshCollider>();
|
|
if (meshCollider)
|
|
{
|
|
DestroyImmediate(meshCollider);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PostProcessVolume postProcessVolume = gameObject.GetComponent<PostProcessVolume>();
|
|
if (!postProcessVolume)
|
|
{
|
|
postProcessVolume = gameObject.AddComponent<PostProcessVolume>();
|
|
}
|
|
|
|
postProcessVolume.blendDistance = postProcessProfileInfo.BlendDistance;
|
|
postProcessVolume.priority = postProcessProfileInfo.Priority;
|
|
postProcessVolume.weight = postProcessProfileInfo.Weight;
|
|
postProcessVolume.profile = postProcessProfileInfo.PostProcessProfile;
|
|
postProcessVolume.enabled = postProcessProfileInfo.Enabled;
|
|
|
|
MeshCollider meshCollider = gameObject.GetComponent<MeshCollider>();
|
|
if (!meshCollider)
|
|
{
|
|
meshCollider = gameObject.AddComponent<MeshCollider>();
|
|
}
|
|
|
|
meshCollider.convex = true;
|
|
meshCollider.enabled = postProcessProfileInfo.Enabled;
|
|
meshCollider.isTrigger = true;
|
|
|
|
Vector3[] polygonPoints = new Vector3[Nodes.Count];
|
|
for (int i = 0; i <= Nodes.Count - 1; i++)
|
|
{
|
|
polygonPoints[i] = Nodes[i].Position;
|
|
}
|
|
meshCollider.sharedMesh = MeshUtils.ExtrudeMeshFromPolygon(polygonPoints, postProcessProfileInfo.VolumeHeight);
|
|
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
public static class UnityExtensions
|
|
{
|
|
/// <summary>
|
|
/// Extension method to check if a layer is in a layermask
|
|
/// </summary>
|
|
/// <param name="mask"></param>
|
|
/// <param name="layer"></param>
|
|
/// <returns></returns>
|
|
public static bool Contains(this LayerMask mask, int layer)
|
|
{
|
|
return mask == (mask | (1 << layer));
|
|
}
|
|
}
|
|
}
|