1079 lines
51 KiB
C#
1079 lines
51 KiB
C#
//------------------------------------------------------------------------------------------------------------------
|
|
// Volumetric Fog & Mist 2
|
|
// Created by Kronnect
|
|
//------------------------------------------------------------------------------------------------------------------
|
|
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using UnityEngine.Serialization;
|
|
|
|
|
|
namespace VolumetricFogAndMist2 {
|
|
|
|
|
|
public enum MaskTextureBrushMode {
|
|
AddFog = 0,
|
|
RemoveFog = 1,
|
|
ColorFog = 2
|
|
}
|
|
|
|
|
|
public enum FoWUpdateMethod {
|
|
MainThread,
|
|
BackgroundThread
|
|
}
|
|
|
|
public partial class VolumetricFog : MonoBehaviour {
|
|
|
|
public bool enableFogOfWar;
|
|
public Vector3 fogOfWarCenter;
|
|
public bool fogOfWarIsLocal;
|
|
public Vector3 fogOfWarSize = new Vector3(1024, 0, 1024);
|
|
public bool fogOfWarShowCoverage;
|
|
|
|
[FormerlySerializedAs("fogOfWarTextureSize")]
|
|
[Range(32, 2048)] public int fogOfWarTextureWidth = 256;
|
|
[Range(32, 2048)] public int fogOfWarTextureHeight;
|
|
[Tooltip("Delay before the fog alpha is restored. A value of 0 keeps the fog cleared forever.")]
|
|
[Range(0, 100)] public float fogOfWarRestoreDelay;
|
|
[Range(0, 25)] public float fogOfWarRestoreDuration = 2f;
|
|
[Range(0, 1)] public float fogOfWarSmoothness = 1f;
|
|
public bool fogOfWarBlur;
|
|
|
|
const int MAX_SIMULTANEOUS_TRANSITIONS = 64000;
|
|
bool canDestroyFOWTexture;
|
|
float now;
|
|
|
|
#region In-Editor fog of war painter
|
|
|
|
public bool maskEditorEnabled;
|
|
public MaskTextureBrushMode maskBrushMode = MaskTextureBrushMode.RemoveFog;
|
|
public Color maskBrushColor = Color.white;
|
|
[Range(1, 128)] public int maskBrushWidth = 20;
|
|
[Range(0, 1)] public float maskBrushFuzziness = 0.5f;
|
|
[Range(0, 1)] public float maskBrushOpacity = 0.15f;
|
|
|
|
#endregion
|
|
|
|
[SerializeField]
|
|
Texture2D _fogOfWarTexture;
|
|
|
|
public Vector3 anchoredFogOfWarCenter => fogOfWarIsLocal ? transform.position + fogOfWarCenter : fogOfWarCenter;
|
|
|
|
|
|
public Texture2D fogOfWarTexture {
|
|
get { return _fogOfWarTexture; }
|
|
set {
|
|
if (_fogOfWarTexture != value) {
|
|
if (value != null) {
|
|
_fogOfWarTexture = value;
|
|
canDestroyFOWTexture = false;
|
|
ReloadFogOfWarTexture();
|
|
} else {
|
|
if (canDestroyFOWTexture && _fogOfWarTexture != null) {
|
|
DestroyImmediate(_fogOfWarTexture);
|
|
}
|
|
_fogOfWarTexture = null;
|
|
canDestroyFOWTexture = false;
|
|
}
|
|
if (fogMat != null) {
|
|
fogMat.SetTexture(ShaderParams.FogOfWarTexture, _fogOfWarTexture);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
Color32[] fogOfWarColorBuffer;
|
|
|
|
struct FogOfWarTransition {
|
|
public bool enabled;
|
|
public int x, y;
|
|
public float startTime, startDelay;
|
|
public float duration;
|
|
public int initialAlpha;
|
|
public int targetAlpha;
|
|
|
|
public int restoreToAlpha;
|
|
public float restoreDelay;
|
|
public float restoreDuration;
|
|
}
|
|
|
|
FogOfWarTransition[] fowTransitionList;
|
|
int lastTransitionPos;
|
|
Dictionary<int, int> fowTransitionIndices;
|
|
Stack<int> fowFreeIndices;
|
|
bool requiresTextureUpload;
|
|
bool backgroundThreadBusy;
|
|
Material fowBlurMat;
|
|
RenderTexture fowBlur1, fowBlur2;
|
|
static readonly object _lock = new object();
|
|
|
|
|
|
void FogOfWarInit () {
|
|
if (fogOfWarTextureHeight == 0) {
|
|
fogOfWarTextureHeight = fogOfWarTextureWidth;
|
|
}
|
|
if (fowTransitionList == null || fowTransitionList.Length != MAX_SIMULTANEOUS_TRANSITIONS) {
|
|
fowTransitionList = new FogOfWarTransition[MAX_SIMULTANEOUS_TRANSITIONS];
|
|
}
|
|
if (fowFreeIndices == null) {
|
|
fowFreeIndices = new Stack<int>(MAX_SIMULTANEOUS_TRANSITIONS);
|
|
} else {
|
|
fowFreeIndices.Clear();
|
|
}
|
|
for (int k = MAX_SIMULTANEOUS_TRANSITIONS - 1; k >= 0; k--) {
|
|
fowFreeIndices.Push(k);
|
|
}
|
|
|
|
InitTransitions();
|
|
|
|
if (_fogOfWarTexture == null) {
|
|
FogOfWarUpdateTexture();
|
|
} else if (enableFogOfWar && (fogOfWarColorBuffer == null || fogOfWarColorBuffer.Length == 0)) {
|
|
ReloadFogOfWarTexture();
|
|
}
|
|
backgroundThreadBusy = false;
|
|
}
|
|
|
|
void InitTransitions () {
|
|
lock (_lock) {
|
|
if (fowTransitionIndices == null) {
|
|
fowTransitionIndices = new Dictionary<int, int>(MAX_SIMULTANEOUS_TRANSITIONS);
|
|
} else {
|
|
fowTransitionIndices.Clear();
|
|
}
|
|
lastTransitionPos = -1;
|
|
}
|
|
}
|
|
|
|
void FogOfWarDestroy () {
|
|
if (canDestroyFOWTexture && _fogOfWarTexture != null) {
|
|
DestroyImmediate(_fogOfWarTexture);
|
|
}
|
|
if (fowBlur1 != null) {
|
|
fowBlur1.Release();
|
|
}
|
|
if (fowBlur2 != null) {
|
|
fowBlur2.Release();
|
|
}
|
|
if (fowBlurMat != null) {
|
|
DestroyImmediate(fowBlurMat);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Reloads the current contents of the fog of war texture
|
|
/// </summary>
|
|
public void ReloadFogOfWarTexture () {
|
|
if (_fogOfWarTexture == null || profile == null) return;
|
|
fogOfWarTextureWidth = _fogOfWarTexture.width;
|
|
fogOfWarTextureHeight = _fogOfWarTexture.height;
|
|
EnsureTextureIsReadable(_fogOfWarTexture);
|
|
fogOfWarColorBuffer = _fogOfWarTexture.GetPixels32();
|
|
InitTransitions();
|
|
if (!enableFogOfWar) {
|
|
enableFogOfWar = true;
|
|
UpdateMaterialPropertiesNow();
|
|
}
|
|
}
|
|
|
|
|
|
void EnsureTextureIsReadable (Texture2D tex) {
|
|
#if UNITY_EDITOR
|
|
string path = UnityEditor.AssetDatabase.GetAssetPath(tex);
|
|
if (string.IsNullOrEmpty(path))
|
|
return;
|
|
UnityEditor.TextureImporter imp = UnityEditor.AssetImporter.GetAtPath(path) as UnityEditor.TextureImporter;
|
|
if (imp != null && !imp.isReadable) {
|
|
imp.isReadable = true;
|
|
imp.SaveAndReimport();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void FogOfWarUpdateTexture () {
|
|
if (!enableFogOfWar || !Application.isPlaying)
|
|
return;
|
|
int width = GetScaledSize(fogOfWarTextureWidth, 1.0f);
|
|
int height = GetScaledSize(fogOfWarTextureHeight, 1.0f);
|
|
if (_fogOfWarTexture == null || _fogOfWarTexture.width != width || _fogOfWarTexture.height != height) {
|
|
_fogOfWarTexture = new Texture2D(width, height, TextureFormat.RGBA32, false, true);
|
|
_fogOfWarTexture.hideFlags = HideFlags.DontSave;
|
|
_fogOfWarTexture.filterMode = FilterMode.Bilinear;
|
|
_fogOfWarTexture.wrapMode = TextureWrapMode.Clamp;
|
|
canDestroyFOWTexture = true;
|
|
ResetFogOfWar();
|
|
}
|
|
}
|
|
|
|
int GetScaledSize (int size, float factor) {
|
|
size = (int)(size / factor);
|
|
size /= 4;
|
|
if (size < 1)
|
|
size = 1;
|
|
return size * 4;
|
|
}
|
|
|
|
void UpdateFogOfWarMaterialBoundsProperties () {
|
|
Vector3 fogOfWarCenter = anchoredFogOfWarCenter;
|
|
fogMat.SetVector(ShaderParams.FogOfWarCenter, fogOfWarCenter);
|
|
fogMat.SetVector(ShaderParams.FogOfWarSize, fogOfWarSize);
|
|
Vector3 ca = fogOfWarCenter - 0.5f * fogOfWarSize;
|
|
fogMat.SetVector(ShaderParams.FogOfWarCenterAdjusted, new Vector4(ca.x / fogOfWarSize.x, 1f, ca.z / (fogOfWarSize.z + 0.0001f), 0));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates fog of war transitions and uploads texture changes to GPU if required
|
|
/// </summary>
|
|
public void UpdateFogOfWar (bool forceUpload = false) {
|
|
if (!enableFogOfWar || _fogOfWarTexture == null)
|
|
return;
|
|
|
|
if (forceUpload) {
|
|
requiresTextureUpload = true;
|
|
}
|
|
|
|
int tw = _fogOfWarTexture.width;
|
|
now = Time.time;
|
|
|
|
lock (_lock) {
|
|
for (int k = 0; k <= lastTransitionPos; k++) {
|
|
FogOfWarTransition fw = fowTransitionList[k];
|
|
if (!fw.enabled)
|
|
continue;
|
|
float elapsed = now - fw.startTime - fw.startDelay;
|
|
if (elapsed > 0) {
|
|
float t = fw.duration <= 0 ? 1 : elapsed / fw.duration;
|
|
if (t < 0) t = 0; else if (t > 1f) t = 1f;
|
|
int alpha = (int)(fw.initialAlpha + (fw.targetAlpha - fw.initialAlpha) * t);
|
|
int colorPos = fw.y * tw + fw.x;
|
|
fogOfWarColorBuffer[colorPos].a = (byte)alpha;
|
|
requiresTextureUpload = true;
|
|
if (t >= 1f) {
|
|
// Add refill slot if needed
|
|
if (fw.targetAlpha != fw.restoreToAlpha && fw.restoreDelay > 0) {
|
|
fowTransitionList[k].duration = fw.restoreDuration;
|
|
fowTransitionList[k].startTime = now;
|
|
fowTransitionList[k].startDelay = fw.restoreDelay;
|
|
fowTransitionList[k].initialAlpha = fw.targetAlpha;
|
|
fowTransitionList[k].targetAlpha = fw.restoreToAlpha;
|
|
fowTransitionList[k].restoreDelay = 0;
|
|
fowTransitionList[k].restoreDuration = 0;
|
|
} else {
|
|
fowTransitionList[k].enabled = false;
|
|
fowFreeIndices.Push(k);
|
|
int key = fw.y * 64000 + fw.x;
|
|
fowTransitionIndices.Remove(key);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (requiresTextureUpload) {
|
|
if (!backgroundThreadBusy) {
|
|
requiresTextureUpload = false;
|
|
UploadFogOfWarTextureEditsToGPU();
|
|
}
|
|
}
|
|
|
|
if (fogOfWarIsLocal) {
|
|
UpdateFogOfWarMaterialBoundsProperties();
|
|
}
|
|
}
|
|
|
|
|
|
void UploadFogOfWarTextureEditsToGPU () {
|
|
_fogOfWarTexture.SetPixels32(fogOfWarColorBuffer);
|
|
_fogOfWarTexture.Apply();
|
|
|
|
// Smooth texture
|
|
if (fogOfWarBlur) {
|
|
SetFowBlurTexture();
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
if (!Application.isPlaying) {
|
|
UnityEditor.EditorUtility.SetDirty(_fogOfWarTexture);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SetFowBlurTexture () {
|
|
if (fogMat == null)
|
|
return;
|
|
|
|
if (fowBlurMat == null) {
|
|
fowBlurMat = new Material(Shader.Find("Hidden/VolumetricFog2/FoWBlur"));
|
|
}
|
|
|
|
if (fowBlur1 == null || fowBlur1.width != _fogOfWarTexture.width || fowBlur2 == null || fowBlur2.width != _fogOfWarTexture.width) {
|
|
CreateFoWBlurRTs();
|
|
}
|
|
fowBlur1.DiscardContents();
|
|
Graphics.Blit(_fogOfWarTexture, fowBlur1, fowBlurMat, 0);
|
|
fowBlur2.DiscardContents();
|
|
Graphics.Blit(fowBlur1, fowBlur2, fowBlurMat, 1);
|
|
fogMat.SetTexture(ShaderParams.FogOfWarTexture, fowBlur2);
|
|
}
|
|
|
|
|
|
void CreateFoWBlurRTs () {
|
|
if (fowBlur1 != null) {
|
|
fowBlur1.Release();
|
|
}
|
|
if (fowBlur2 != null) {
|
|
fowBlur2.Release();
|
|
}
|
|
RenderTextureDescriptor desc = new RenderTextureDescriptor(_fogOfWarTexture.width, _fogOfWarTexture.height, RenderTextureFormat.ARGB32, 0);
|
|
fowBlur1 = new RenderTexture(desc);
|
|
fowBlur2 = new RenderTexture(desc);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the alpha value of the fog of war at world position creating a transition from current alpha value to specified target alpha. It takes into account FogOfWarCenter and FogOfWarSize.
|
|
/// Note that only x and z coordinates are used. Y (vertical) coordinate is ignored.
|
|
/// </summary>
|
|
/// <param name="worldPosition">in world space coordinates.</param>
|
|
/// <param name="radius">radius of application in world units.</param>
|
|
/// <param name="fogNewAlpha">target alpha value.</param>
|
|
/// <param name="duration">duration of transition in seconds (0 = apply fogNewAlpha instantly).</param>
|
|
public void SetFogOfWarAlpha (Vector3 worldPosition, float radius, float fogNewAlpha, float duration = 0) {
|
|
SetFogOfWarAlpha(worldPosition, radius, fogNewAlpha, true, duration, fogOfWarSmoothness, fogOfWarRestoreDelay, fogOfWarRestoreDuration);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Changes the alpha value of the fog of war at world position creating a transition from current alpha value to specified target alpha. It takes into account FogOfWarCenter and FogOfWarSize.
|
|
/// Note that only x and z coordinates are used. Y (vertical) coordinate is ignored.
|
|
/// </summary>
|
|
/// <param name="worldPosition">in world space coordinates.</param>
|
|
/// <param name="radius">radius of application in world units.</param>
|
|
/// <param name="fogNewAlpha">target alpha value.</param>
|
|
/// <param name="duration">duration of transition in seconds (0 = apply fogNewAlpha instantly).</param>
|
|
/// <param name="smoothness">border smoothness (0 = sharp borders, 1 = smooth transition)</param>
|
|
public void SetFogOfWarAlpha (Vector3 worldPosition, float radius, float fogNewAlpha, float duration, float smoothness) {
|
|
SetFogOfWarAlpha(worldPosition, radius, fogNewAlpha, true, duration, smoothness, fogOfWarRestoreDelay, fogOfWarRestoreDuration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the alpha value of the fog of war at world position creating a transition from current alpha value to specified target alpha. It takes into account FogOfWarCenter and FogOfWarSize.
|
|
/// Note that only x and z coordinates are used. Y (vertical) coordinate is ignored.
|
|
/// </summary>
|
|
/// <param name="worldPosition">in world space coordinates.</param>
|
|
/// <param name="radius">radius of application in world units.</param>
|
|
/// <param name="blendAlpha">if new alpha is combined with preexisting alpha value or replaced.</param>
|
|
/// <param name="fogNewAlpha">target alpha value.</param>
|
|
/// <param name="duration">duration of transition in seconds (0 = apply fogNewAlpha instantly).</param>
|
|
/// <param name="smoothness">border smoothness (0 = sharp borders, 1 = smooth transition)</param>
|
|
/// <param name="updateMethod">if method should run on main thread (immediate effect) or in a background thread (update can be slower but doesn't affect main thread performance)</param>
|
|
public void SetFogOfWarAlpha (Vector3 worldPosition, float radius, float fogNewAlpha, float duration, float smoothness, FoWUpdateMethod updateMethod = FoWUpdateMethod.BackgroundThread, bool blendAlpha = true) {
|
|
SetFogOfWarAlpha(worldPosition, radius, fogNewAlpha, blendAlpha, duration, smoothness, fogOfWarRestoreDelay, fogOfWarRestoreDuration, restoreToAlphaValue: 1f, updateMethod);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the alpha value of the fog of war at world position creating a transition from current alpha value to specified target alpha. It takes into account FogOfWarCenter and FogOfWarSize.
|
|
/// Note that only x and z coordinates are used. Y (vertical) coordinate is ignored.
|
|
/// </summary>
|
|
/// <param name="worldPosition">in world space coordinates.</param>
|
|
/// <param name="radius">radius of application in world units.</param>
|
|
/// <param name="fogNewAlpha">target alpha value.</param>
|
|
/// <param name="blendAlpha">if new alpha is combined with preexisting alpha value or replaced.</param>
|
|
/// <param name="duration">duration of transition in seconds (0 = apply fogNewAlpha instantly).</param>
|
|
/// <param name="smoothness">border smoothness.</param>
|
|
/// <param name="restoreDelay">delay before the fog alpha is restored. Pass 0 to keep change forever.</param>
|
|
/// <param name="restoreDuration">restore duration in seconds.</param>
|
|
/// <param name="restoreToAlphaValue">final alpha value when fog restores.</param>
|
|
/// <param name="updateMethod">if method should run on main thread (immediate effect) or in a background thread (update can be slower but doesn't affect main thread performance)</param>
|
|
public void SetFogOfWarAlpha (Vector3 worldPosition, float radius, float fogNewAlpha, bool blendAlpha, float duration, float smoothness, float restoreDelay, float restoreDuration, float restoreToAlphaValue = 1f, FoWUpdateMethod updateMethod = FoWUpdateMethod.BackgroundThread) {
|
|
|
|
if (_fogOfWarTexture == null || fogOfWarColorBuffer == null || fogOfWarColorBuffer.Length == 0)
|
|
return;
|
|
|
|
int tw = _fogOfWarTexture.width;
|
|
int th = _fogOfWarTexture.height;
|
|
now = Time.time;
|
|
|
|
if (updateMethod == FoWUpdateMethod.BackgroundThread && Application.isPlaying) {
|
|
System.Threading.Tasks.Task.Run(() => {
|
|
lock (_lock) {
|
|
try {
|
|
backgroundThreadBusy = true;
|
|
internal_SetFogOfWarAlpha(tw, th, worldPosition, radius, fogNewAlpha, blendAlpha, duration, smoothness, restoreDelay, restoreDuration, restoreToAlphaValue);
|
|
}
|
|
finally {
|
|
backgroundThreadBusy = false;
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
internal_SetFogOfWarAlpha(tw, th, worldPosition, radius, fogNewAlpha, blendAlpha, duration, smoothness, restoreDelay, restoreDuration, restoreToAlphaValue);
|
|
}
|
|
|
|
void internal_SetFogOfWarAlpha (int tw, int th, Vector3 worldPosition, float radius, float fogNewAlpha, bool blendAlpha = false, float duration = 0, float smoothness = 0, float restoreDelay = 0, float restoreDuration = 2, float restoreToAlphaValue = 1f) {
|
|
|
|
|
|
Vector3 fogOfWarCenter = anchoredFogOfWarCenter;
|
|
|
|
float tx = (worldPosition.x - fogOfWarCenter.x) / fogOfWarSize.x + 0.5f;
|
|
if (tx < 0 || tx > 1f)
|
|
return;
|
|
float tz = (worldPosition.z - fogOfWarCenter.z) / fogOfWarSize.z + 0.5f;
|
|
if (tz < 0 || tz > 1f)
|
|
return;
|
|
|
|
int px = (int)(tx * tw);
|
|
int pz = (int)(tz * th);
|
|
float sm = 0.0001f + smoothness;
|
|
byte newAlpha8 = (byte)(fogNewAlpha * 255);
|
|
float tr = radius / fogOfWarSize.z;
|
|
int delta = (int)(th * tr);
|
|
int deltaSqr = delta * delta;
|
|
byte restoreAlpha = (byte)(255 * restoreToAlphaValue);
|
|
|
|
int minR = Mathf.Max(0, pz - delta);
|
|
int maxR = Mathf.Min(th - 1, pz + delta);
|
|
int minC = Mathf.Max(0, px - delta);
|
|
int maxC = Mathf.Min(tw - 1, px + delta);
|
|
|
|
for (int r = minR; r <= maxR; r++) {
|
|
int rOffset = r * tw;
|
|
int rDistSqr = (pz - r) * (pz - r);
|
|
for (int c = minC; c <= maxC; c++) {
|
|
int cDistSqr = (px - c) * (px - c);
|
|
int distanceSqr = rDistSqr + cDistSqr;
|
|
if (distanceSqr <= deltaSqr) {
|
|
int colorBufferPos = rOffset + c;
|
|
Color32 colorBuffer = fogOfWarColorBuffer[colorBufferPos];
|
|
if (!blendAlpha) {
|
|
colorBuffer.a = 255;
|
|
}
|
|
distanceSqr = deltaSqr - distanceSqr;
|
|
float t = (float)distanceSqr / (deltaSqr * sm);
|
|
t = 1f - t;
|
|
if (t < 0) {
|
|
t = 0;
|
|
} else if (t > 1f) {
|
|
t = 1f;
|
|
}
|
|
byte targetAlpha = (byte)(newAlpha8 + (colorBuffer.a - newAlpha8) * t);
|
|
if (targetAlpha < 255 && (colorBuffer.a != targetAlpha || restoreDelay > 0)) {
|
|
if (duration > 0) {
|
|
AddFogOfWarTransitionSlot(c, r, colorBuffer.a, targetAlpha, 0, duration, restoreAlpha, restoreDelay, restoreDuration);
|
|
} else {
|
|
colorBuffer.a = targetAlpha;
|
|
fogOfWarColorBuffer[colorBufferPos] = colorBuffer;
|
|
requiresTextureUpload = true;
|
|
if (restoreDelay > 0) {
|
|
AddFogOfWarTransitionSlot(c, r, targetAlpha, restoreAlpha, restoreDelay, restoreDuration, targetAlpha, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the alpha value of the fog of war within bounds creating a transition from current alpha value to specified target alpha. It takes into account FogOfWarCenter and FogOfWarSize.
|
|
/// Note that only x and z coordinates are used. Y (vertical) coordinate is ignored.
|
|
/// </summary>
|
|
/// <param name="bounds">in world space coordinates.</param>
|
|
/// <param name="fogNewAlpha">target alpha value.</param>
|
|
/// <param name="duration">duration of transition in seconds (0 = apply fogNewAlpha instantly).</param>
|
|
public void SetFogOfWarAlpha (Bounds bounds, float fogNewAlpha, float duration = 0) {
|
|
SetFogOfWarAlpha(bounds, fogNewAlpha, false, duration, fogOfWarSmoothness, fogOfWarRestoreDelay, fogOfWarRestoreDuration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the alpha value of the fog of war within bounds creating a transition from current alpha value to specified target alpha. It takes into account FogOfWarCenter and FogOfWarSize.
|
|
/// Note that only x and z coordinates are used. Y (vertical) coordinate is ignored.
|
|
/// </summary>
|
|
/// <param name="bounds">in world space coordinates.</param>
|
|
/// <param name="fogNewAlpha">target alpha value.</param>
|
|
/// <param name="duration">duration of transition in seconds (0 = apply fogNewAlpha instantly).</param>
|
|
/// <param name="smoothness">border smoothness.</param>
|
|
public void SetFogOfWarAlpha (Bounds bounds, float fogNewAlpha, float duration, float smoothness) {
|
|
SetFogOfWarAlpha(bounds, fogNewAlpha, false, duration, smoothness, fogOfWarRestoreDelay, fogOfWarRestoreDuration);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Changes the alpha value of the fog of war within bounds creating a transition from current alpha value to specified target alpha. It takes into account FogOfWarCenter and FogOfWarSize.
|
|
/// Note that only x and z coordinates are used. Y (vertical) coordinate is ignored.
|
|
/// </summary>
|
|
/// <param name="bounds">in world space coordinates.</param>
|
|
/// <param name="fogNewAlpha">target alpha value (0-1).</param>
|
|
/// <param name="blendAlpha">if new alpha is combined with preexisting alpha value or replaced.</param>
|
|
/// <param name="duration">duration of transition in seconds (0 = apply fogNewAlpha instantly).</param>
|
|
/// <param name="smoothness">border smoothness.</param>
|
|
/// <param name="fuzzyness">randomization of border noise.</param>
|
|
/// <param name="restoreDelay">delay before the fog alpha is restored. Pass 0 to keep change forever.</param>
|
|
/// <param name="restoreDuration">restore duration in seconds.</param>
|
|
/// <param name="restoreToAlpha">alpha value (0-1) for the fog when restore is completed.</param>
|
|
public void SetFogOfWarAlpha (Bounds bounds, float fogNewAlpha, bool blendAlpha, float duration, float smoothness, float restoreDelay, float restoreDuration, float restoreToAlpha = 1f, FoWUpdateMethod updateMethod = FoWUpdateMethod.BackgroundThread) {
|
|
if (_fogOfWarTexture == null || fogOfWarColorBuffer == null || fogOfWarColorBuffer.Length == 0)
|
|
return;
|
|
|
|
int tw = _fogOfWarTexture.width;
|
|
int th = _fogOfWarTexture.height;
|
|
now = Time.time;
|
|
|
|
if (updateMethod == FoWUpdateMethod.BackgroundThread && Application.isPlaying) {
|
|
System.Threading.Tasks.Task.Run(() => {
|
|
lock (_lock) {
|
|
try {
|
|
backgroundThreadBusy = true;
|
|
internal_SetFogOfWarAlpha(tw, th, bounds, fogNewAlpha, blendAlpha, duration, smoothness, restoreDelay, restoreDuration, restoreToAlpha);
|
|
}
|
|
finally {
|
|
backgroundThreadBusy = false;
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
internal_SetFogOfWarAlpha(tw, th, bounds, fogNewAlpha, blendAlpha, duration, smoothness, restoreDelay, restoreDuration, restoreToAlpha);
|
|
}
|
|
|
|
void internal_SetFogOfWarAlpha (int tw, int th, Bounds bounds, float fogNewAlpha, bool blendAlpha, float duration = 0, float smoothness = 0, float restoreDelay = 0, float restoreDuration = 2, float restoreToAlpha = 1f) {
|
|
|
|
Vector3 fogOfWarCenter = anchoredFogOfWarCenter;
|
|
Vector3 worldPosition = bounds.center;
|
|
|
|
float tx = (worldPosition.x - fogOfWarCenter.x) / fogOfWarSize.x + 0.5f;
|
|
if (tx < 0 || tx > 1f)
|
|
return;
|
|
float tz = (worldPosition.z - fogOfWarCenter.z) / fogOfWarSize.z + 0.5f;
|
|
if (tz < 0 || tz > 1f)
|
|
return;
|
|
|
|
int px = (int)(tx * tw);
|
|
int pz = (int)(tz * th);
|
|
byte newAlpha8 = (byte)(fogNewAlpha * 255);
|
|
float trz = bounds.extents.z / fogOfWarSize.z;
|
|
float trx = bounds.extents.x / fogOfWarSize.x;
|
|
float aspect1 = trx > trz ? 1f : trz / trx;
|
|
float aspect2 = trx > trz ? trx / trz : 1f;
|
|
int deltaz = (int)(th * trz);
|
|
int deltazSqr = deltaz * deltaz;
|
|
int deltax = (int)(tw * trx);
|
|
int deltaxSqr = deltax * deltax;
|
|
float sm = 0.0001f + smoothness;
|
|
byte restoreAlpha = (byte)(restoreToAlpha * 255);
|
|
|
|
int minR = Mathf.Max(0, pz - deltaz);
|
|
int maxR = Mathf.Min(th - 1, pz + deltaz);
|
|
int minC = Mathf.Max(0, px - deltax);
|
|
int maxC = Mathf.Min(tw - 1, px + deltax);
|
|
|
|
for (int r = minR; r <= maxR; r++) {
|
|
int rOffset = r * tw;
|
|
int distancezSqr = (pz - r) * (pz - r);
|
|
distancezSqr = deltazSqr - distancezSqr;
|
|
float t1 = (float)distancezSqr * aspect1 / (deltazSqr * sm);
|
|
|
|
for (int c = minC; c <= maxC; c++) {
|
|
int distancexSqr = (px - c) * (px - c);
|
|
int colorBufferPos = rOffset + c;
|
|
Color32 colorBuffer = fogOfWarColorBuffer[colorBufferPos];
|
|
if (!blendAlpha) colorBuffer.a = 255;
|
|
|
|
distancexSqr = deltaxSqr - distancexSqr;
|
|
float t2 = (float)distancexSqr * aspect2 / (deltaxSqr * sm);
|
|
float t = t1 < t2 ? t1 : t2;
|
|
t = 1f - t;
|
|
if (t < 0) t = 0; else if (t > 1f) t = 1f;
|
|
byte targetAlpha = (byte)(newAlpha8 + (colorBuffer.a - newAlpha8) * t);
|
|
if (targetAlpha < 255 && (colorBuffer.a != targetAlpha || restoreDelay > 0)) {
|
|
if (duration > 0) {
|
|
AddFogOfWarTransitionSlot(c, r, colorBuffer.a, targetAlpha, 0, duration, restoreAlpha, restoreDelay, restoreDuration);
|
|
} else {
|
|
colorBuffer.a = targetAlpha;
|
|
fogOfWarColorBuffer[colorBufferPos] = colorBuffer;
|
|
requiresTextureUpload = true;
|
|
if (restoreDelay > 0) {
|
|
AddFogOfWarTransitionSlot(c, r, targetAlpha, restoreAlpha, restoreDelay, restoreDuration, restoreAlpha, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Changes the alpha value of the fog of war within collider creating a transition from current alpha value to specified target alpha. It takes into account FogOfWarCenter and FogOfWarSize.
|
|
/// Note that only x and z coordinates are used. Y (vertical) coordinate is ignored.
|
|
/// </summary>
|
|
/// <param name="collider">collider used to define the shape of the area where fog of war alpha will be set. Collider must be convex.</param>
|
|
/// <param name="fogNewAlpha">target alpha value (0-1).</param>
|
|
/// <param name="blendAlpha">if new alpha is combined with preexisting alpha value or replaced.</param>
|
|
/// <param name="duration">duration of transition in seconds (0 = apply fogNewAlpha instantly).</param>
|
|
/// <param name="smoothness">border smoothness.</param>
|
|
/// <param name="restoreDelay">delay before the fog alpha is restored. Pass 0 to keep change forever.</param>
|
|
/// <param name="restoreDuration">restore duration in seconds.</param>
|
|
/// <param name="restoreToAlpha">alpha value (0-1) for the fog when restore is completed.</param>
|
|
public void SetFogOfWarAlpha (Collider collider, float fogNewAlpha, bool blendAlpha = false, float duration = 0, float smoothness = 0, float restoreDelay = 0, float restoreDuration = 2, float restoreToAlpha = 1f) {
|
|
if (_fogOfWarTexture == null || fogOfWarColorBuffer == null || fogOfWarColorBuffer.Length == 0)
|
|
return;
|
|
|
|
Vector3 fogOfWarCenter = anchoredFogOfWarCenter;
|
|
|
|
Bounds bounds = collider.bounds;
|
|
Vector3 worldPosition = bounds.center;
|
|
float tx = (worldPosition.x - fogOfWarCenter.x) / fogOfWarSize.x + 0.5f;
|
|
if (tx < 0 || tx > 1f)
|
|
return;
|
|
float tz = (worldPosition.z - fogOfWarCenter.z) / fogOfWarSize.z + 0.5f;
|
|
if (tz < 0 || tz > 1f)
|
|
return;
|
|
|
|
now = Time.time;
|
|
int tw = _fogOfWarTexture.width;
|
|
int th = _fogOfWarTexture.height;
|
|
int px = (int)(tx * tw);
|
|
int pz = (int)(tz * th);
|
|
byte newAlpha8 = (byte)(fogNewAlpha * 255);
|
|
float trz = bounds.extents.z / fogOfWarSize.z;
|
|
float trx = bounds.extents.x / fogOfWarSize.x;
|
|
float aspect1 = trx > trz ? 1f : trz / trx;
|
|
float aspect2 = trx > trz ? trx / trz : 1f;
|
|
int deltaz = (int)(th * trz);
|
|
int deltazSqr = deltaz * deltaz;
|
|
int deltax = (int)(tw * trx);
|
|
int deltaxSqr = deltax * deltax;
|
|
float sm = 0.0001f + smoothness;
|
|
byte restoreAlpha = (byte)(restoreToAlpha * 255);
|
|
|
|
|
|
Vector3 wpos = bounds.min;
|
|
wpos.y = bounds.center.y;
|
|
for (int rr = 0; rr <= deltaz * 2; rr++) {
|
|
int r = pz - deltaz + rr;
|
|
if (r > 0 && r < th - 1) {
|
|
int distancezSqr = (pz - r) * (pz - r);
|
|
distancezSqr = deltazSqr - distancezSqr;
|
|
float t1 = (float)distancezSqr * aspect1 / (deltazSqr * sm);
|
|
wpos.z = bounds.min.z + bounds.size.z * rr / (deltaz * 2f);
|
|
for (int cc = 0; cc <= deltax * 2; cc++) {
|
|
int c = px - deltax + cc;
|
|
if (c > 0 && c < tw - 1) {
|
|
wpos.x = bounds.min.x + bounds.size.x * cc / (deltax * 2f);
|
|
Vector3 colliderPos = collider.ClosestPoint(wpos);
|
|
if (colliderPos != wpos) continue; // point is outside collider
|
|
|
|
int distancexSqr = (px - c) * (px - c);
|
|
int colorBufferPos = r * tw + c;
|
|
Color32 colorBuffer = fogOfWarColorBuffer[colorBufferPos];
|
|
if (!blendAlpha) colorBuffer.a = 255;
|
|
distancexSqr = deltaxSqr - distancexSqr;
|
|
float t2 = (float)distancexSqr * aspect2 / (deltaxSqr * sm);
|
|
float t = t1 < t2 ? t1 : t2;
|
|
t = 1f - t;
|
|
if (t < 0) t = 0; else if (t > 1f) t = 1f;
|
|
byte targetAlpha = (byte)(newAlpha8 + (colorBuffer.a - newAlpha8) * t);
|
|
if (targetAlpha < 255 && (colorBuffer.a != targetAlpha || restoreDelay > 0)) {
|
|
if (duration > 0) {
|
|
AddFogOfWarTransitionSlot(c, r, colorBuffer.a, targetAlpha, 0, duration, restoreAlpha, restoreDelay, restoreDuration);
|
|
} else {
|
|
colorBuffer.a = targetAlpha;
|
|
fogOfWarColorBuffer[colorBufferPos] = colorBuffer;
|
|
requiresTextureUpload = true;
|
|
if (restoreDelay > 0) {
|
|
AddFogOfWarTransitionSlot(c, r, targetAlpha, restoreAlpha, restoreDelay, restoreDuration, restoreAlpha, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Changes the alpha value of the fog of war within bounds creating a transition from current alpha value to specified target alpha. It takes into account FogOfWarCenter and FogOfWarSize.
|
|
/// Note that only x and z coordinates are used. Y (vertical) coordinate is ignored.
|
|
/// </summary>
|
|
/// <param name="go">gameobject used to define the shape of the area where fog of war alpha will be set. The gameobject must have a mesh associated.</param>
|
|
/// <param name="fogNewAlpha">target alpha value (0-1).</param>
|
|
/// <param name="duration">duration of transition in seconds (0 = apply fogNewAlpha instantly).</param>
|
|
/// <param name="restoreDelay">delay before the fog alpha is restored. Pass 0 to keep change forever.</param>
|
|
/// <param name="restoreDuration">restore duration in seconds.</param>
|
|
/// <param name="restoreToAlpha">alpha value (0-1) for the fog when restore is completed.</param>
|
|
public void SetFogOfWarAlpha (GameObject go, float fogNewAlpha, float duration = 0, float restoreDelay = 0, float restoreDuration = 2, float restoreToAlpha = 1f, FoWUpdateMethod updateMethod = FoWUpdateMethod.BackgroundThread) {
|
|
if (_fogOfWarTexture == null || fogOfWarColorBuffer == null || fogOfWarColorBuffer.Length == 0)
|
|
return;
|
|
|
|
if (go == null) return;
|
|
|
|
int tw = _fogOfWarTexture.width;
|
|
int th = _fogOfWarTexture.height;
|
|
now = Time.time;
|
|
|
|
MeshRenderer meshRenderer = go.GetComponentInChildren<MeshRenderer>();
|
|
if (meshRenderer == null) {
|
|
Debug.LogError("No MeshRenderer found on this object.");
|
|
return;
|
|
}
|
|
|
|
Bounds bounds = meshRenderer.bounds;
|
|
|
|
MeshFilter mf = meshRenderer.GetComponent<MeshFilter>();
|
|
if (mf == null) {
|
|
Debug.LogError("No MeshFilter found on this object.");
|
|
return;
|
|
}
|
|
Mesh mesh = mf.sharedMesh;
|
|
if (mesh == null) {
|
|
Debug.LogError("No Mesh found on this object.");
|
|
return;
|
|
}
|
|
if (mesh.GetTopology(0) != MeshTopology.Triangles) {
|
|
Debug.LogError("Only triangle topology is supported by this tool.");
|
|
return;
|
|
}
|
|
|
|
// Get triangle info
|
|
int[] indices = mesh.triangles;
|
|
Vector3[] vertices = mesh.vertices;
|
|
|
|
int verticesLength = vertices.Length;
|
|
Transform t = meshRenderer.transform;
|
|
for (int k = 0; k < verticesLength; k++) {
|
|
vertices[k] = t.TransformPoint(vertices[k]);
|
|
}
|
|
|
|
if (updateMethod == FoWUpdateMethod.BackgroundThread && Application.isPlaying) {
|
|
System.Threading.Tasks.Task.Run(() => {
|
|
lock (_lock) {
|
|
try {
|
|
backgroundThreadBusy = true;
|
|
internal_SetFogOfWarAlpha(tw, th, bounds, indices, vertices, fogNewAlpha, duration, restoreDelay, restoreDuration, restoreToAlpha);
|
|
}
|
|
finally {
|
|
backgroundThreadBusy = false;
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
internal_SetFogOfWarAlpha(tw, th, bounds, indices, vertices, fogNewAlpha, duration, restoreDelay, restoreDuration, restoreToAlpha);
|
|
}
|
|
|
|
|
|
void internal_SetFogOfWarAlpha (int tw, int th, Bounds bounds, int[] indices, Vector3[] vertices, float fogNewAlpha, float duration = 0, float restoreDelay = 0, float restoreDuration = 2, float restoreToAlpha = 1f) {
|
|
|
|
Vector3 fogOfWarCenter = anchoredFogOfWarCenter;
|
|
Vector3 worldPosition = bounds.center;
|
|
float tx = (worldPosition.x - fogOfWarCenter.x) / fogOfWarSize.x + 0.5f;
|
|
if (tx < 0 || tx > 1f)
|
|
return;
|
|
float tz = (worldPosition.z - fogOfWarCenter.z) / fogOfWarSize.z + 0.5f;
|
|
if (tz < 0 || tz > 1f)
|
|
return;
|
|
|
|
byte newAlpha8 = (byte)(fogNewAlpha * 255);
|
|
byte restoreAlpha = (byte)(restoreToAlpha * 255);
|
|
|
|
int indicesLength = indices.Length;
|
|
|
|
Vector2[] triangles = new Vector2[indicesLength];
|
|
for (int k = 0; k < indicesLength; k += 3) {
|
|
triangles[k].x = vertices[indices[k]].x;
|
|
triangles[k].y = vertices[indices[k]].z;
|
|
triangles[k + 1].x = vertices[indices[k + 1]].x;
|
|
triangles[k + 1].y = vertices[indices[k + 1]].z;
|
|
triangles[k + 2].x = vertices[indices[k + 2]].x;
|
|
triangles[k + 2].y = vertices[indices[k + 2]].z;
|
|
}
|
|
int index = 0;
|
|
|
|
int px = (int)(tx * tw);
|
|
int pz = (int)(tz * th);
|
|
float trz = bounds.extents.z / fogOfWarSize.z;
|
|
float trx = bounds.extents.x / fogOfWarSize.x;
|
|
int deltaz = (int)(th * trz);
|
|
int deltax = (int)(tw * trx);
|
|
int r0 = pz - deltaz;
|
|
if (r0 < 1) r0 = 1; else if (r0 >= th) r0 = th - 1;
|
|
int r1 = pz + deltaz;
|
|
if (r1 < 1) r1 = 1; else if (r1 >= th) r1 = th - 1;
|
|
int c0 = px - deltax;
|
|
if (c0 < 1) c0 = 1; else if (c0 >= tw) c0 = tw - 1;
|
|
int c1 = px + deltax;
|
|
if (c1 < 1) c1 = 1; else if (c1 >= tw) c1 = tw - 1;
|
|
|
|
Vector2 v0 = triangles[index];
|
|
Vector2 v1 = triangles[index + 1];
|
|
Vector2 v2 = triangles[index + 2];
|
|
|
|
for (int r = r0; r <= r1; r++) {
|
|
int rr = r * tw;
|
|
float wz = (((r + 0.5f) / th) - 0.5f) * fogOfWarSize.z + fogOfWarCenter.z;
|
|
for (int c = c0; c <= c1; c++) {
|
|
float wx = (((c + 0.5f) / tw) - 0.5f) * fogOfWarSize.x + fogOfWarCenter.x;
|
|
// Check if any triangle contains this position
|
|
for (int i = 0; i < indicesLength; i += 3) {
|
|
if (PointInTriangle(wx, wz, v0.x, v0.y, v1.x, v1.y, v2.x, v2.y)) {
|
|
int colorBufferPos = rr + c;
|
|
Color32 colorBuffer = fogOfWarColorBuffer[colorBufferPos];
|
|
if (colorBuffer.a != newAlpha8 || restoreDelay > 0) {
|
|
if (duration > 0) {
|
|
AddFogOfWarTransitionSlot(c, r, colorBuffer.a, newAlpha8, 0, duration, restoreAlpha, restoreDelay, restoreDuration);
|
|
} else {
|
|
colorBuffer.a = newAlpha8;
|
|
fogOfWarColorBuffer[colorBufferPos] = colorBuffer;
|
|
requiresTextureUpload = true;
|
|
if (restoreDelay > 0) {
|
|
AddFogOfWarTransitionSlot(c, r, newAlpha8, restoreAlpha, restoreDelay, restoreDuration, restoreAlpha, 0, 0);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
} else {
|
|
index += 3;
|
|
index %= indicesLength;
|
|
v0 = triangles[index];
|
|
v1 = triangles[index + 1];
|
|
v2 = triangles[index + 2];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
float Sign (float p1x, float p1z, float p2x, float p2z, float p3x, float p3z) {
|
|
return (p1x - p3x) * (p2z - p3z) - (p2x - p3x) * (p1z - p3z);
|
|
}
|
|
|
|
bool PointInTriangle (float x, float z, float v1x, float v1z, float v2x, float v2z, float v3x, float v3z) {
|
|
float d1 = Sign(x, z, v1x, v1z, v2x, v2z);
|
|
float d2 = Sign(x, z, v2x, v2z, v3x, v3z);
|
|
float d3 = Sign(x, z, v3x, v3z, v1x, v1z);
|
|
|
|
bool has_neg = (d1 < 0) || (d2 < 0) || (d3 < 0);
|
|
bool has_pos = (d1 > 0) || (d2 > 0) || (d3 > 0);
|
|
|
|
return !(has_neg && has_pos);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Restores fog of war to full opacity
|
|
/// </summary>
|
|
/// <param name="worldPosition">World position.</param>
|
|
/// <param name="radius">Radius.</param>
|
|
/// <param name="alpha">Alpha value (1f = fully opaque)</param>
|
|
public void ResetFogOfWarAlpha (Vector3 worldPosition, float radius, float alpha = 1f) {
|
|
if (_fogOfWarTexture == null || fogOfWarColorBuffer == null || fogOfWarColorBuffer.Length == 0)
|
|
return;
|
|
|
|
Vector3 fogOfWarCenter = anchoredFogOfWarCenter;
|
|
|
|
float tx = (worldPosition.x - fogOfWarCenter.x) / fogOfWarSize.x + 0.5f;
|
|
if (tx < 0 || tx > 1f)
|
|
return;
|
|
float tz = (worldPosition.z - fogOfWarCenter.z) / fogOfWarSize.z + 0.5f;
|
|
if (tz < 0 || tz > 1f)
|
|
return;
|
|
|
|
int tw = _fogOfWarTexture.width;
|
|
int th = _fogOfWarTexture.height;
|
|
int px = (int)(tx * tw);
|
|
int pz = (int)(tz * th);
|
|
float tr = radius / fogOfWarSize.z;
|
|
int delta = (int)(th * tr);
|
|
int deltaSqr = delta * delta;
|
|
byte fogAlpha = (byte)(alpha * 255);
|
|
for (int r = pz - delta; r <= pz + delta; r++) {
|
|
if (r > 0 && r < th - 1) {
|
|
for (int c = px - delta; c <= px + delta; c++) {
|
|
if (c > 0 && c < tw - 1) {
|
|
int distanceSqr = (pz - r) * (pz - r) + (px - c) * (px - c);
|
|
if (distanceSqr <= deltaSqr) {
|
|
int colorBufferPos = r * tw + c;
|
|
Color32 colorBuffer = fogOfWarColorBuffer[colorBufferPos];
|
|
colorBuffer.a = fogAlpha;
|
|
fogOfWarColorBuffer[colorBufferPos] = colorBuffer;
|
|
requiresTextureUpload = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
requiresTextureUpload = true;
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
/// Restores fog of war to full opacity
|
|
/// </summary>
|
|
public void ResetFogOfWarAlpha (Bounds bounds, float alpha = 1f) {
|
|
ResetFogOfWarAlpha(bounds.center, bounds.extents.x, bounds.extents.z, alpha);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restores fog of war to full opacity
|
|
/// </summary>
|
|
public void ResetFogOfWarAlpha (Vector3 position, Vector3 size, float alpha = 1f) {
|
|
ResetFogOfWarAlpha(position, size.x * 0.5f, size.z * 0.5f, alpha);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restores fog of war to full opacity
|
|
/// </summary>
|
|
/// <param name="position">Position in world space.</param>
|
|
/// <param name="extentsX">Half of the length of the rectangle in X-Axis.</param>
|
|
/// <param name="extentsZ">Half of the length of the rectangle in Z-Axis.</param>
|
|
/// <param name="alpha">Alpha value (1f = fully opaque)</param>
|
|
public void ResetFogOfWarAlpha (Vector3 position, float extentsX, float extentsZ, float alpha = 1f) {
|
|
if (_fogOfWarTexture == null || fogOfWarColorBuffer == null || fogOfWarColorBuffer.Length == 0)
|
|
return;
|
|
|
|
Vector3 fogOfWarCenter = anchoredFogOfWarCenter;
|
|
|
|
float tx = (position.x - fogOfWarCenter.x) / fogOfWarSize.x + 0.5f;
|
|
if (tx < 0 || tx > 1f)
|
|
return;
|
|
float tz = (position.z - fogOfWarCenter.z) / fogOfWarSize.z + 0.5f;
|
|
if (tz < 0 || tz > 1f)
|
|
return;
|
|
|
|
int tw = _fogOfWarTexture.width;
|
|
int th = _fogOfWarTexture.height;
|
|
int px = (int)(tx * tw);
|
|
int pz = (int)(tz * th);
|
|
float trz = extentsZ / fogOfWarSize.z;
|
|
float trx = extentsX / fogOfWarSize.x;
|
|
int deltaz = (int)(th * trz);
|
|
int deltax = (int)(tw * trx);
|
|
byte fogAlpha = (byte)(alpha * 255);
|
|
for (int r = pz - deltaz; r <= pz + deltaz; r++) {
|
|
if (r > 0 && r < th - 1) {
|
|
for (int c = px - deltax; c <= px + deltax; c++) {
|
|
if (c > 0 && c < tw - 1) {
|
|
int colorBufferPos = r * tw + c;
|
|
Color32 colorBuffer = fogOfWarColorBuffer[colorBufferPos];
|
|
colorBuffer.a = fogAlpha;
|
|
fogOfWarColorBuffer[colorBufferPos] = colorBuffer;
|
|
requiresTextureUpload = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public void ResetFogOfWar (float alpha = 1f) {
|
|
if (_fogOfWarTexture == null)
|
|
return;
|
|
int h = _fogOfWarTexture.height;
|
|
int w = _fogOfWarTexture.width;
|
|
int newLength = h * w;
|
|
if (fogOfWarColorBuffer == null || fogOfWarColorBuffer.Length != newLength) {
|
|
fogOfWarColorBuffer = new Color32[newLength];
|
|
}
|
|
Color32 opaque = new Color32(255, 255, 255, (byte)(alpha * 255));
|
|
for (int k = 0; k < newLength; k++) {
|
|
fogOfWarColorBuffer[k] = opaque;
|
|
}
|
|
UploadFogOfWarTextureEditsToGPU();
|
|
InitTransitions();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or set fog of war state as a Color32 buffer. The alpha channel stores the transparency of the fog at that position (0 = no fog, 1 = opaque).
|
|
/// </summary>
|
|
public Color32[] fogOfWarTextureData {
|
|
get {
|
|
return fogOfWarColorBuffer;
|
|
}
|
|
set {
|
|
enableFogOfWar = true;
|
|
fogOfWarColorBuffer = value;
|
|
if (value == null || _fogOfWarTexture == null)
|
|
return;
|
|
if (value.Length != _fogOfWarTexture.width * _fogOfWarTexture.height)
|
|
return;
|
|
UploadFogOfWarTextureEditsToGPU();
|
|
}
|
|
}
|
|
|
|
void AddFogOfWarTransitionSlot (int x, int y, byte initialAlpha, byte targetAlpha, float delay, float duration, byte restoreToAlpha, float restoreDelay, float restoreDuration) {
|
|
|
|
// Check if this slot exists
|
|
int key = y * 64000 + x;
|
|
if (fowTransitionIndices.TryGetValue(key, out int index)) {
|
|
// slot already exists
|
|
if (fowTransitionList[index].enabled) {
|
|
if (fowTransitionList[index].x != x || fowTransitionList[index].y != y) {
|
|
index = -1;
|
|
} else {
|
|
if (fowTransitionList[index].targetAlpha <= targetAlpha && fowTransitionList[index].restoreToAlpha == restoreToAlpha && fowTransitionList[index].restoreDelay == restoreDelay && fowTransitionList[index].restoreDuration == restoreDuration) {
|
|
// transition running already to target alpha
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
index = -1;
|
|
}
|
|
|
|
if (index < 0) {
|
|
if (!fowFreeIndices.TryPop(out index)) return;
|
|
fowTransitionIndices[key] = index;
|
|
if (index >= lastTransitionPos) {
|
|
lastTransitionPos = index;
|
|
}
|
|
}
|
|
|
|
fowTransitionList[index].x = x;
|
|
fowTransitionList[index].y = y;
|
|
fowTransitionList[index].duration = duration;
|
|
fowTransitionList[index].startTime = now;
|
|
fowTransitionList[index].startDelay = delay;
|
|
fowTransitionList[index].initialAlpha = initialAlpha;
|
|
fowTransitionList[index].targetAlpha = targetAlpha;
|
|
fowTransitionList[index].restoreToAlpha = restoreToAlpha;
|
|
fowTransitionList[index].restoreDelay = restoreDelay;
|
|
fowTransitionList[index].restoreDuration = restoreDuration;
|
|
|
|
fowTransitionList[index].enabled = true;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Gets the current alpha value of the Fog of War at a given world position
|
|
/// </summary>
|
|
/// <returns>The fog of war alpha.</returns>
|
|
/// <param name="worldPosition">World position.</param>
|
|
public float GetFogOfWarAlpha (Vector3 worldPosition) {
|
|
if (fogOfWarColorBuffer == null || fogOfWarColorBuffer.Length == 0 || _fogOfWarTexture == null)
|
|
return 1f;
|
|
|
|
float tx = (worldPosition.x - fogOfWarCenter.x) / fogOfWarSize.x + 0.5f;
|
|
if (tx < 0 || tx > 1f)
|
|
return 1f;
|
|
float tz = (worldPosition.z - fogOfWarCenter.z) / fogOfWarSize.z + 0.5f;
|
|
if (tz < 0 || tz > 1f)
|
|
return 1f;
|
|
|
|
int tw = _fogOfWarTexture.width;
|
|
int th = _fogOfWarTexture.height;
|
|
int px = (int)(tx * tw);
|
|
int pz = (int)(tz * th);
|
|
int colorBufferPos = pz * tw + px;
|
|
if (colorBufferPos < 0 || colorBufferPos >= fogOfWarColorBuffer.Length)
|
|
return 1f;
|
|
return fogOfWarColorBuffer[colorBufferPos].a / 255f;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
} |