using UnityEngine;
using System;
using System.Text;
using System.Collections.Generic;
using TGS.Geom;
using System.Globalization;
namespace TGS
{
public partial class TerrainGridSystem : MonoBehaviour
{
///
/// Complete array of states and cells and the territory name they belong to.
///
[NonSerialized]
public List cells;
[SerializeField]
int _numCells = 3;
///
/// Gets or sets the desired number of cells in irregular topology.
///
public int numCells
{
get { return _numCells; }
set
{
if (_numCells != value)
{
_numCells = Mathf.Clamp(value, 2, 20000);
needGenerateMap = true;
isDirty = true;
}
}
}
[SerializeField]
bool _showCells = true;
///
/// Toggle cells frontiers visibility.
///
public bool showCells
{
get
{
return _showCells;
}
set
{
if (value != _showCells)
{
_showCells = value;
isDirty = true;
if (cellLayer != null)
{
cellLayer.SetActive(_showCells);
ClearLastOver();
}
else if (_showCells)
{
Redraw();
}
}
}
}
[SerializeField]
Vector2
_cellSize;
///
/// Gets current individual cell size or sets user defined cell size
///
public Vector2 cellSize
{
get
{
return _cellSize;
}
set
{
if (value != _cellSize)
{
_cellSize = value;
SetScaleByCellSize();
isDirty = true;
needGenerateMap = true;
}
}
}
[SerializeField]
[ColorUsage(true, true)]
Color
_cellBorderColor = new Color(0, 1, 0, 1.0f);
///
/// Cells border color
///
public Color cellBorderColor
{
get
{
return _cellBorderColor;
}
set
{
if (value != _cellBorderColor)
{
_cellBorderColor = value;
isDirty = true;
if (cellsThinMat != null && _cellBorderColor != cellsThinMat.color)
{
cellsThinMat.color = _cellBorderColor;
}
if (cellsGeoMat != null && _cellBorderColor != cellsGeoMat.color)
{
cellsGeoMat.color = _cellBorderColor;
}
}
}
}
[SerializeField]
bool _cellCustomBorderThickness;
///
/// Enables cells custom border thickness
///
public bool cellCustomBorderThickness
{
get
{
return _cellCustomBorderThickness;
}
set
{
if (value != _cellCustomBorderThickness)
{
_cellCustomBorderThickness = value;
UpdateMaterialThickness();
if (_showCells)
DrawCellBorders();
isDirty = true;
}
}
}
[SerializeField]
float
_cellBorderThickness = 1f;
///
/// Cells border thickness
///
public float cellBorderThickness
{
get
{
return _cellBorderThickness;
}
set
{
if (value != _cellBorderThickness)
{
_cellCustomBorderThickness = true;
_cellBorderThickness = Mathf.Max(0.0001f, value);
UpdateMaterialThickness();
if (_showCells)
DrawCellBorders();
isDirty = true;
}
}
}
public float cellBorderAlpha
{
get
{
return _cellBorderColor.a;
}
set
{
if (_cellBorderColor.a != value)
{
cellBorderColor = new Color(_cellBorderColor.r, _cellBorderColor.g, _cellBorderColor.b, Mathf.Clamp01(value));
}
}
}
[SerializeField]
float _cellFillPadding;
///
/// Padding applied to cell fill with color/texture
///
public float cellFillPadding
{
get
{
return _cellFillPadding;
}
set
{
if (value != _cellFillPadding)
{
_cellFillPadding = value;
if (_showCells)
{
Redraw();
}
isDirty = true;
}
}
}
[SerializeField]
[Tooltip("Instead of using the highlight color, use the cell color if it's set")]
bool _cellHighlightUseCellColor;
///
/// Instead of using the highlight color, use the cell color if it's set
///
public bool cellHighlightUseCellColor
{
get
{
return _cellHighlightUseCellColor;
}
set
{
if (value != _cellHighlightUseCellColor)
{
_cellHighlightUseCellColor = value;
isDirty = true;
}
}
}
[SerializeField]
[ColorUsage(true, true)]
[Tooltip("Fill color to use when the mouse hovers a cell's region")]
Color
_cellHighlightColor = new Color(1, 0, 0, 0.8f);
///
/// Fill color to use when the mouse hovers a cell's region.
///
public Color cellHighlightColor
{
get
{
return _cellHighlightColor;
}
set
{
if (value != _cellHighlightColor)
{
_cellHighlightColor = value;
isDirty = true;
if (hudMatCellOverlay != null && _cellHighlightColor != hudMatCellOverlay.color)
{
hudMatCellOverlay.color = _cellHighlightColor;
}
if (hudMatCellGround != null && _cellHighlightColor != hudMatCellGround.color)
{
hudMatCellGround.color = _cellHighlightColor;
}
}
}
}
[SerializeField]
[ColorUsage(true, true)]
Color
_cellHighlightColor2 = new Color(0, 1, 0, 0.8f);
///
/// Alternate fill color to use when the mouse hovers a cell's region.
///
public Color cellHighlightColor2
{
get
{
return _cellHighlightColor2;
}
set
{
if (value != _cellHighlightColor2)
{
_cellHighlightColor2 = value;
isDirty = true;
if (hudMatCellOverlay != null)
{
hudMatCellOverlay.SetColor(ShaderParams.Color2, _cellHighlightColor2);
}
if (hudMatCellGround != null)
{
hudMatCellGround.SetColor(ShaderParams.Color2, _cellHighlightColor2);
}
}
}
}
[SerializeField]
[Range(0, 0.5f)]
[Tooltip("Width of the highlight border(only box grids)")]
float _cellHighlightBorderSize;
///
/// Width of the highlight border (only box grids)
///
public float cellHighlightBorderSize
{
get
{
return _cellHighlightBorderSize;
}
set
{
if (value != _cellHighlightBorderSize)
{
_cellHighlightBorderSize = value;
isDirty = true;
if (hudMatCellOverlay != null)
{
hudMatCellOverlay.SetFloat(ShaderParams.HighlightBorderSize, 0.5f - _cellHighlightBorderSize);
}
if (hudMatCellGround != null)
{
hudMatCellGround.SetFloat(ShaderParams.HighlightBorderSize, 0.5f - _cellHighlightBorderSize);
}
}
}
}
[SerializeField]
[ColorUsage(true, true)]
Color
_cellHighlightBorderColor = Color.white;
///
/// Color for the highlight border
///
public Color cellHighlightBorderColor
{
get
{
return _cellHighlightBorderColor;
}
set
{
if (value != _cellHighlightBorderColor)
{
_cellHighlightBorderColor = value;
isDirty = true;
if (hudMatCellOverlay != null)
{
hudMatCellOverlay.SetColor(ShaderParams.HighlightBorderColor, _cellHighlightBorderColor);
}
if (hudMatCellGround != null)
{
hudMatCellGround.SetColor(ShaderParams.HighlightBorderColor, _cellHighlightBorderColor);
}
}
}
}
[SerializeField]
bool _cellHighlightNonVisible = true;
///
/// Gets or sets whether invisible cells should also be highlighted when pointer is over them
///
public bool cellHighlightNonVisible
{
get { return _cellHighlightNonVisible; }
set
{
if (_cellHighlightNonVisible != value)
{
_cellHighlightNonVisible = value;
isDirty = true;
}
}
}
///
/// Returns Cell under mouse position or null if none.
///
public Cell cellHighlighted { get { return _cellHighlighted; } }
///
/// Returns current highlighted cell index.
///
public int cellHighlightedIndex { get { return _cellHighlightedIndex; } }
///
/// Returns Cell index which has been clicked
///
public int cellLastClickedIndex { get { return _cellLastClickedIndex; } }
[SerializeField]
bool _cellsFlat;
///
/// Gets or sets if cells are rendered as horizontal flat surfaces ignoring any terrain slope.
///
public bool cellsFlat
{
get { return _cellsFlat; }
set
{
if (_cellsFlat != value)
{
_cellsFlat = value;
if (!Application.isPlaying)
{
Redraw(true);
}
else
{
issueRedraw = RedrawType.Full;
}
}
}
}
[SerializeField]
Texture2D _gridFlatMask;
///
/// Gets or sets the "flat" mask. The alpha component of this texture is used to determine if a cell should be rendered flat or adapt to terrain otherwise.
///
public Texture2D gridFlatMask
{
get { return _gridFlatMask; }
set
{
if (_gridFlatMask != value)
{
_gridFlatMask = value;
isDirty = true;
ReloadFlatMask();
}
}
}
[SerializeField]
float _cellsMaxSlope = 1f;
///
/// Gets or sets the cells max slope. Cells with a greater slope will be hidden.
///
/// The cells max slope.
public float cellsMaxSlope
{
get { return _cellsMaxSlope; }
set
{
if (_cellsMaxSlope != value)
{
_cellsMaxSlope = value;
needUpdateTerritories = true;
if (!Application.isPlaying)
{
Redraw(true);
}
else
{
issueRedraw = RedrawType.Full;
}
}
}
}
[SerializeField]
float _cellsMaxHeightDifference;
///
/// Gets or sets the cells max height different between vertices. Cells with a greater difference than this threshold will be hidden.
///
/// The cells max height difference.
public float cellsMaxHeightDifference
{
get { return _cellsMaxHeightDifference; }
set
{
if (_cellsMaxHeightDifference != value)
{
_cellsMaxHeightDifference = Mathf.Max(0, value);
needUpdateTerritories = true;
if (!Application.isPlaying)
{
Redraw(true);
}
else
{
issueRedraw = RedrawType.Full;
}
}
}
}
[SerializeField]
float _cellsMinimumAltitude;
///
/// Gets or sets the minimum cell altitude. Useful to hide cells under certain altitude, for instance, under water.
///
public float cellsMinimumAltitude
{
get { return _cellsMinimumAltitude; }
set
{
if (_cellsMinimumAltitude != value)
{
_cellsMinimumAltitude = value;
issueRedraw = RedrawType.Full;
Redraw(true);
}
}
}
[SerializeField]
bool _cellsMinimumAltitudeClampVertices;
///
/// Clamps vertices below the minimum altitude
///
public bool cellsMinimumAltitudeClampVertices
{
get { return _cellsMinimumAltitudeClampVertices; }
set
{
if (_cellsMinimumAltitudeClampVertices != value)
{
_cellsMinimumAltitudeClampVertices = value;
issueRedraw = RedrawType.Full;
Redraw(true);
}
}
}
[SerializeField]
float _cellsMaximumAltitude = 0f;
///
/// Gets or sets the maximum cell altitude. Useful to hide cells above certain altitude.
///
public float cellsMaximumAltitude
{
get { return _cellsMaximumAltitude; }
set
{
if (_cellsMaximumAltitude != value)
{
_cellsMaximumAltitude = value;
issueRedraw = RedrawType.Full;
Redraw(true);
}
}
}
[SerializeField]
bool _cellsMaximumAltitudeClampVertices;
///
/// Clamps vertices above the maximum altitude
///
public bool cellsMaximumAltitudeClampVertices
{
get { return _cellsMaximumAltitudeClampVertices; }
set
{
if (_cellsMaximumAltitudeClampVertices != value)
{
_cellsMaximumAltitudeClampVertices = value;
issueRedraw = RedrawType.Full;
Redraw(true);
}
}
}
#region Public Cell Functions
[NonSerialized]
List _voronoiSites;
///
/// Sets or gets a list of Voronoi sites. Full list will be completed with random number up to numCells amount.
///
/// The voronoi sites.
public List voronoiSites
{
get { return _voronoiSites; }
set
{
if (_voronoiSites != value)
{
_voronoiSites = value;
needGenerateMap = true;
}
}
}
[SerializeField]
byte[] _voronoiSerializationData;
///
/// Gets baked Voronoi cells
///
public byte[] voronoiSerializationData
{
get { return _voronoiSerializationData; }
set
{
if (_voronoiSerializationData != value)
{
_voronoiSerializationData = value;
isDirty = true;
}
}
}
public bool hasBakedVoronoi
{
get
{
return _voronoiSerializationData != null && _voronoiSerializationData.Length > 0;
}
}
///
/// Returns the index of a cell in the cells array by its reference.
///
public int CellGetIndex(Cell cell)
{
if (cell == null)
return -1;
return cell.index;
}
///
/// Returns the index of a cell by its row and column numbers.
///
/// The get index.
/// Row.
/// Column.
/// If set to true row and column values will be clamped inside current grid size (in case their values exceed the number of rows or columns). If set to false, it will wrap around edges.
public int CellGetIndex(int row, int column, bool clampToBorders = true)
{
if (_gridTopology != GridTopology.Box && _gridTopology != GridTopology.Hexagonal)
{
Debug.LogWarning("Grid topology does not support row/column indexing.");
return -1;
}
if (clampToBorders)
{
row = Mathf.Clamp(row, 0, _cellRowCount - 1);
column = Mathf.Clamp(column, 0, _cellColumnCount - 1);
}
else
{
row = (row + _cellRowCount) % _cellRowCount;
column = (column + _cellColumnCount) % _cellColumnCount;
}
return row * _cellColumnCount + column;
}
///
/// Returns the cell index of a cell at a given position (in local or world space) in the grid
///
public int CellGetIndex(Vector3 position, bool worldSpace = false)
{
Cell cell = CellGetAtPosition(position, worldSpace);
if (cell == null) return -1;
return CellGetIndex(cell);
}
///
/// Colorize specified region of a cell by indexes.
///
/// The generated color surface positioned and oriented over the given cell.
/// Cell index.
/// If the colored surface is shown or not.
/// Texture to be used.
public GameObject CellToggleRegionSurface(int cellIndex, bool visible, Texture2D texture)
{
return CellToggleRegionSurface(cellIndex, visible, Color.white, false, texture, Misc.Vector2one, Misc.Vector2zero, 0, false);
}
///
/// Colorize specified region of a cell by indexes.
///
/// The generated color surface positioned and oriented over the given cell.
/// Cell index.
/// If the colored surface is shown or not.
/// Color. Can be partially transparent.
/// If set to true any cached surface will be destroyed and regenerated. Usually you pass false to improve performance.
public GameObject CellToggleRegionSurface(int cellIndex, bool visible, Color color, bool refreshGeometry = false)
{
return CellToggleRegionSurface(cellIndex, visible, color, refreshGeometry, null, Misc.Vector2one, Misc.Vector2zero, 0, false);
}
///
/// Colorize specified region of a cell by indexes.
///
/// The generated color surface positioned and oriented over the given cell.
/// Cell index.
/// If the colored surface is shown or not.
/// Color. Can be partially transparent.
/// If set to true any cached surface will be destroyed and regenerated. Usually you pass false to improve performance.
/// The index of the texture configured in the list of textures of the inspector.
public GameObject CellToggleRegionSurface(int cellIndex, bool visible, Color color, bool refreshGeometry, int textureIndex)
{
Texture2D texture = null;
if (textureIndex >= 0 && textureIndex < textures.Length)
{
texture = textures[textureIndex];
}
return CellToggleRegionSurface(cellIndex, visible, color, refreshGeometry, texture, Misc.Vector2one, Misc.Vector2zero, 0, false);
}
///
/// Colorize specified region of a cell by indexes.
///
/// The generated color surface positioned and oriented over the given cell.
/// Cell index.
/// If the colored surface is shown or not.
/// Color. Can be partially transparent.
/// If set to true any cached surface will be destroyed and regenerated. Usually you pass false to improve performance.
/// An optional texture. If you pass a color different than white, the texture will be tinted using that color.
public GameObject CellToggleRegionSurface(int cellIndex, bool visible, Color color, bool refreshGeometry, Texture2D texture)
{
return CellToggleRegionSurface(cellIndex, visible, color, refreshGeometry, texture, Misc.Vector2one, Misc.Vector2zero, 0, false);
}
///
/// Colorize specified region of a cell by indexes.
///
/// The generated color surface positioned and oriented over the given cell.
/// Cell index.
/// If the colored surface is shown or not.
/// Color. Can be partially transparent.
/// If set to true any cached surface will be destroyed and regenerated. Usually you pass false to improve performance.
/// An optional texture. If you pass a color different than white, the texture will be tinted using that color.
/// Texture scale.
/// Texture offset.
/// Texture rotation.
public GameObject CellToggleRegionSurface(int cellIndex, bool visible, Color color, bool refreshGeometry, Texture2D texture, Vector2 textureScale, Vector2 textureOffset, float textureRotation, bool rotateInLocalSpace)
{
return CellToggleRegionSurface(cellIndex, visible, color, refreshGeometry, texture, textureScale, textureOffset, textureRotation, false, rotateInLocalSpace);
}
///
/// Colorize specified region of a cell by indexes.
///
/// The generated color surface positioned and oriented over the given cell.
/// Cell.
/// If the colored surface is shown or not.
/// Color. Can be partially transparent.
/// If set to true any cached surface will be destroyed and regenerated. Usually you pass false to improve performance.
/// An optional texture. If you pass a color different than white, the texture will be tinted using that color.
/// Texture scale.
/// Texture offset.
/// Texture rotation.
/// If set to true the colored surface will be shown on top of objects.
public GameObject CellToggleRegionSurface(Cell cell, bool visible, Color color, bool refreshGeometry, Texture2D texture, Vector2 textureScale, Vector2 textureOffset, float textureRotation, bool overlay, bool rotateInLocalSpace)
{
int cellIndex = CellGetIndex(cell);
return CellToggleRegionSurface(cellIndex, visible, color, refreshGeometry, texture, textureScale, textureOffset, textureRotation, overlay, rotateInLocalSpace);
}
///
/// Colorize specified region of a cell by indexes.
///
/// The generated color surface positioned and oriented over the given cell.
/// Cell index.
/// If the colored surface is shown or not.
/// Color. Can be partially transparent.
/// If set to true any cached surface will be destroyed and regenerated. Usually you pass false to improve performance.
/// An optional texture. If you pass a color different than white, the texture will be tinted using that color.
/// Texture scale.
/// Texture offset.
/// Texture rotation.
/// If set to true the colored surface will be shown on top of objects.
public GameObject CellToggleRegionSurface(int cellIndex, bool visible, Color color, bool refreshGeometry, Texture2D texture, Vector2 textureScale, Vector2 textureOffset, float textureRotation, bool overlay, bool rotateInLocalSpace)
{
if (needGenerateMap || needResortCells || issueRedraw != RedrawType.None)
{
CheckGridChanges();
}
if (cellIndex < 0 || cells == null || cellIndex >= cells.Count || cells[cellIndex] == null)
return null;
if (!visible)
{
CellHideRegionSurface(cellIndex);
return null;
}
int cacheIndex = GetCacheIndexForCellRegion(cellIndex);
GameObject surf;
bool existsInCache = surfaces.TryGetValue(cacheIndex, out surf);
if (existsInCache && surf == null)
{
surfaces.Remove(cacheIndex);
existsInCache = false;
}
if (refreshGeometry && existsInCache)
{
surfaces.Remove(cacheIndex);
DestroyImmediate(surf);
surf = null;
}
Region region = cells[cellIndex].region;
// Should the surface be recreated?
if (surf != null)
{
if (texture != null && (textureScale != region.customTextureScale || textureOffset != region.customTextureOffset || textureRotation != region.customTextureRotation || region.customTextureScale.x == 0))
{
// we check if customTextureScale.x == 0 to ensure the material is of texture type so if it was colored before and now it's being textured, we need to regenerate UVs
surfaces.Remove(cacheIndex);
DestroyImmediate(surf);
surf = null;
}
}
// If it exists, activate and check proper material, if not create surface
bool isHighlighted = cellHighlightedIndex == cellIndex && _highlightEffect != HighlightEffect.None;
Material surfMaterial;
if (surf != null)
{
Material coloredMat = overlay ? coloredMatOverlayCell : coloredMatGroundCell;
Material texturizedMat = overlay ? texturizedMatOverlayCell : texturizedMatGroundCell;
if (!surf.activeSelf)
{
surf.SetActive(true);
}
// Check if material is ok
Renderer renderer = surf.GetComponent();
surfMaterial = renderer.sharedMaterial;
if ((texture == null && !surfMaterial.name.Equals(coloredMat.name)) || (texture != null && !surfMaterial.name.Equals(texturizedMat.name))
|| (surfMaterial.color != color && !isHighlighted) || (texture != null && (region.customMaterial == null || region.customMaterial.mainTexture != texture)))
{
Material goodMaterial = GetColoredTexturedMaterialForCell(color, texture, overlay);
region.customMaterial = goodMaterial;
ApplyMaterialToSurface(region, goodMaterial);
}
}
else
{
surfMaterial = GetColoredTexturedMaterialForCell(color, texture, overlay);
surf = GenerateCellRegionSurface(cellIndex, surfMaterial, textureScale, textureOffset, textureRotation, rotateInLocalSpace);
if (surf == null)
return null;
region.customMaterial = surfMaterial;
if (texture != null)
{
region.customTextureOffset = textureOffset;
region.customTextureRotation = textureRotation;
region.customTextureScale = textureScale;
region.customRotateInLocalSpace = rotateInLocalSpace;
}
}
// If it was highlighted, highlight it again
if (isHighlighted && region.customMaterial != null && _highlightedObj != null)
{
if (hudMatCell.HasProperty(ShaderParams.MainTex))
{
if (region.customMaterial != null)
{
hudMatCell.mainTexture = region.customMaterial.mainTexture;
}
else
{
hudMatCell.mainTexture = null;
}
}
surf.GetComponent().sharedMaterial = hudMatCell;
_highlightedObj = surf;
}
// Optimization: if color alpha is zero, disable the entire surface
if (color.a <= 0)
{
CellHideRegionSurface(cellIndex);
}
return surf;
}
///
/// Uncolorize/hide specified cell by index in the cells collection.
///
public void CellHideRegionSurface(int cellIndex)
{
if (_cellHighlightedIndex != cellIndex || _highlightedObj == null)
{
int cacheIndex = GetCacheIndexForCellRegion(cellIndex);
GameObject surf;
if (surfaces.TryGetValue(cacheIndex, out surf))
{
if (surf == null)
{
surfaces.Remove(cacheIndex);
}
else
{
surf.SetActive(false);
}
}
}
cells[cellIndex].region.customMaterial = null;
}
///
/// Uncolorize/hide specified all cells.
///
public void CellHideRegionSurfaces()
{
int cellsCount = cells.Count;
for (int k = 0; k < cellsCount; k++)
{
CellHideRegionSurface(k);
}
}
///
/// Colors a cell and fades it out for "duration" in seconds.
///
public void CellFadeOut(Cell cell, Color color, float duration = 2f)
{
int cellIndex = CellGetIndex(cell);
CellFadeOut(cellIndex, color, duration);
}
///
/// Colors a cell and fades it out during "duration" in seconds.
///
public void CellFadeOut(int cellIndex, Color color, float duration = 2f, int repetitions = 1)
{
CellAnimate(FaderStyle.FadeOut, cellIndex, Misc.ColorNull, color, duration, repetitions);
}
///
/// Fades out a list of cells with "color" and "duration" in seconds.
///
public void CellFadeOut(List cellIndices, Color color, float duration = 2f, int repetitions = 1)
{
if (cellIndices == null)
return;
int cellCount = cellIndices.Count;
for (int k = 0; k < cellCount; k++)
{
CellAnimate(FaderStyle.FadeOut, cellIndices[k], Misc.ColorNull, color, duration, repetitions);
}
}
///
/// Flashes a cell with "color" and "duration" in seconds.
///
public void CellFlash(int cellIndex, Color color, float duration = 2f, int repetitions = 1)
{
CellAnimate(FaderStyle.Flash, cellIndex, Misc.ColorNull, color, duration, repetitions);
}
///
/// Flashes a cell with "color" and "duration" in seconds.
///
public void CellFlash(Cell cell, Color color, float duration = 2f, int repetitions = 1)
{
int cellIndex = CellGetIndex(cell);
CellAnimate(FaderStyle.Flash, cellIndex, Misc.ColorNull, color, duration, repetitions);
}
///
/// Flashes a list of cells with "color" and "duration" in seconds.
///
public void CellFlash(List cellIndices, Color color, float duration = 2f, int repetitions = 1)
{
if (cellIndices == null)
return;
int cellCount = cellIndices.Count;
for (int k = 0; k < cellCount; k++)
{
CellAnimate(FaderStyle.Flash, cellIndices[k], Misc.ColorNull, color, duration, repetitions);
}
}
///
/// Temporarily colors a cell for "duration" in seconds.
///
public void CellColorTemp(int cellIndex, Color color, float duration = 2f)
{
CellAnimate(FaderStyle.ColorTemp, cellIndex, Misc.ColorNull, color, duration, 1);
}
///
/// Temporarily colors a cell for "duration" in seconds.
///
public void CellColorTemp(Cell cell, Color color, float duration = 2f, int repetitions = 1)
{
int cellIndex = CellGetIndex(cell);
CellAnimate(FaderStyle.ColorTemp, cellIndex, Misc.ColorNull, color, duration, repetitions);
}
///
/// Temporarily colors a list of cells for "duration" in seconds.
///
public void CellColorTemp(List cellIndices, Color color, float duration = 2f, int repetitions = 1)
{
if (cellIndices == null)
return;
int cellCount = cellIndices.Count;
for (int k = 0; k < cellCount; k++)
{
CellAnimate(FaderStyle.ColorTemp, cellIndices[k], Misc.ColorNull, color, duration, repetitions);
}
}
///
/// Blinks a cell with colors "color1" and "color2" and "duration" in seconds.
///
public void CellBlink(Cell cell, Color color1, Color color2, float duration = 2f, int repetitions = 1)
{
int cellIndex = CellGetIndex(cell);
CellAnimate(FaderStyle.Blink, cellIndex, color1, color2, duration, repetitions);
}
///
/// Blinks a cell with colors "color1" and "color2" and "duration" in seconds.
///
public void CellBlink(int cellIndex, Color color1, Color color2, float duration = 2f, int repetitions = 1)
{
CellAnimate(FaderStyle.Blink, cellIndex, color1, color2, duration, repetitions);
}
///
/// Blinks a list of cells with colors "color1" and "color2" and "duration" in seconds.
///
public void CellBlink(List cellIndices, Color color1, Color color2, float duration = 2f, int repetitions = 1)
{
if (cellIndices == null)
return;
int cellCount = cellIndices.Count;
for (int k = 0; k < cellCount; k++)
{
CellAnimate(FaderStyle.Blink, cellIndices[k], color1, color2, duration, repetitions);
}
}
///
/// Flashes a cell from "initialColor" to "color" and "duration" in seconds.
///
public void CellFlash(Cell cell, Color initialColor, Color color, float duration = 2f, int repetitions = 1)
{
int cellIndex = CellGetIndex(cell);
CellAnimate(FaderStyle.Flash, cellIndex, initialColor, color, duration, repetitions);
}
///
/// Flashes a cell from "initialColor" to "color" and "duration" in seconds.
///
public void CellFlash(int cellIndex, Color initialColor, Color color, float duration = 2f, int repetitions = 1)
{
CellAnimate(FaderStyle.Flash, cellIndex, initialColor, color, duration, repetitions);
}
///
/// Flashes a list of cells from "initialColor" to "color" and "duration" in seconds.
///
public void CellFlash(List cellIndices, Color initialColor, Color color, float duration = 2f, int repetitions = 1)
{
if (cellIndices == null)
return;
int cellCount = cellIndices.Count;
for (int k = 0; k < cellCount; k++)
{
CellAnimate(FaderStyle.Flash, cellIndices[k], initialColor, color, duration, repetitions);
}
}
///
/// Blinks a cell with "color" and "duration" in seconds.
///
public void CellBlink(Cell cell, Color color, float duration = 2f, int repetitions = 1)
{
int cellIndex = CellGetIndex(cell);
CellAnimate(FaderStyle.Blink, cellIndex, Misc.ColorNull, color, duration, repetitions);
}
///
/// Blinks a cell with "color" and "duration" in seconds.
///
public void CellBlink(int cellIndex, Color color, float duration = 2f, int repetitions = 1)
{
CellAnimate(FaderStyle.Blink, cellIndex, Misc.ColorNull, color, duration, repetitions);
}
///
/// Blinks a list of cells with "color" and "duration" in seconds.
///
public void CellBlink(List cellIndices, Color color, float duration = 2f, int repetitions = 1)
{
if (cellIndices == null)
return;
int cellCount = cellIndices.Count;
for (int k = 0; k < cellCount; k++)
{
CellAnimate(FaderStyle.Blink, cellIndices[k], Misc.ColorNull, color, duration, repetitions);
}
}
///
/// Creates a gameobject with the border for the given cell.
///
public GameObject CellDrawBorder(Cell cell, Color color = default(Color), float thickness = 0, float expand = 1f)
{
if (cell == null || cell.region == null) return null;
List dummyList = new List();
dummyList.Add(cell.index);
return CellDrawBorder(dummyList, color, thickness, expand);
}
///
/// Creates a gameobject with the border for the given cell.
///
public GameObject CellDrawBorder(int cellIndex, Color color = default(Color), float thickness = 0, float expand = 1f)
{
if (!ValidCellIndex(cellIndex)) return null;
List dummyList = new List();
dummyList.Add(cellIndex);
Cell cell = cells[cellIndex];
if (cell.region.customBorderGameObject != null)
{
DestroyImmediate(cell.region.customBorderGameObject);
}
cell.region.customBorderGameObject = CellDrawBorder(dummyList, color, thickness, expand);
return cell.region.customBorderGameObject;
}
///
/// Destroys a custom border previous drawn using CellDrawBorder
///
public void CellDestroyBorder(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return;
Cell cell = cells[cellIndex];
if (cell.region.customBorderGameObject != null)
{
DestroyImmediate(cell.region.customBorderGameObject);
}
}
public GameObject CellDrawBorder(List cells, Color color = default(Color), float thickness = 1f, float expand = 1f)
{
GetCellIndices(cells, tempListCells);
return CellDrawBorder(tempListCells, color, thickness, expand);
}
readonly Dictionary customBorderHit = new Dictionary();
public GameObject CellDrawBorder(List cellIndices, Color color = default(Color), float thickness = 1f, float expand = 1f)
{
Region region;
if (cellIndices == null) return null;
if (cellIndices.Count > 1)
{
region = new Region(null, false);
region.isFlat = _cellsFlat;
customBorderHit.Clear();
foreach (int cellIndex in cellIndices)
{
if (!ValidCellIndex(cellIndex)) continue;
Cell cell = cells[cellIndex];
foreach (Segment segment in cell.region.segments)
{
customBorderHit.TryGetValue(segment, out int count);
count++;
customBorderHit[segment] = count;
}
}
foreach (KeyValuePair kvp in customBorderHit)
{
if (kvp.Value == 1)
{
region.segments.Add(kvp.Key);
}
}
}
else
{
region = cells[cellIndices[0]].region;
}
if (expand != 1f)
{
// compute center
Vector2 center = Vector2.zero;
int count = 0;
int segCount = region.segments.Count;
for (int k = 0; k < segCount; k++)
{
Segment s = region.segments[k];
center.x += (float)s.start.x;
center.y += (float)s.start.y;
center.x += (float)s.end.x;
center.y += (float)s.end.y;
count += 2;
}
center /= count;
// expand segments
for (int k = 0; k < segCount; k++)
{
Segment s = region.segments[k];
float sx = ((float)s.start.x - center.x) * expand + center.x;
float sy = ((float)s.start.y - center.y) * expand + center.y;
float ex = ((float)s.end.x - center.x) * expand + center.x;
float ey = ((float)s.end.y - center.y) * expand + center.y;
Segment es = new Segment(new Point(sx, sy), new Point(ex, ey), s.border);
region.segments[k] = es;
}
}
TerritoryMesh tm = new TerritoryMesh();
if (!GenerateRegionMesh(tm, region, thickness)) return null;
if (color == default(Color))
{
color = Color.black;
}
Material material;
if (thickness > 1f)
{
material = territoriesThickHackMat;
}
else
{
material = GetFrontierColorMaterial(color);
}
material = Instantiate(material);
material.renderQueue--;
material.color = color;
material.DisableKeyword(ShaderParams.SKW_NEAR_CLIP_FADE);
UpdateMaterialTerritoryThickness(material, thickness);
SetStencil(material);
bool useVertexDisplacement = thickness > 1f & !canUseGeometryShaders;
Transform root = CheckTerritoriesCustomFrontiersRoot();
GameObject go = DrawTerritoryFrontier(tm, material, root, CUSTOM_BORDER_NAME, useVertexDisplacement);
return go;
}
///
/// Returns the rect enclosing the cell in local space coordinates
///
public Rect CellGetRect(int cellIndex)
{
if (cells == null || cellIndex < 0 || cellIndex >= cells.Count)
return new Rect(0, 0, 0, 0);
Rect rect = cells[cellIndex].region.rect2D;
return rect;
}
///
/// Cancels any ongoing visual effect on any cell
///
public void CancelAnimations(float fadeOutDuration = 0)
{
CancelAnimationAll(fadeOutDuration);
}
///
/// Cancels any ongoing visual effect on a cell
///
/// Cell index.
public void CellCancelAnimations(int cellIndex, float fadeOutDuration = 0)
{
CellCancelAnimation(cellIndex, fadeOutDuration);
}
///
/// Cancels any ongoing visual effect on a list of cells
///
/// Cell indices.
public void CellCancelAnimations(List cellIndices, float fadeOutDuration = 0)
{
int cellCount = cellIndices.Count;
for (int k = 0; k < cellCount; k++)
{
CellCancelAnimation(cellIndices[k], fadeOutDuration);
}
}
///
/// Returns the rect enclosing the cell in world space
///
public Bounds CellGetRectWorldSpace(int cellIndex)
{
if (cells == null || cellIndex < 0 || cellIndex >= cells.Count)
return new Bounds(Misc.Vector3zero, Misc.Vector3zero);
Rect rect = cells[cellIndex].region.rect2D;
Vector3 min = GetWorldSpacePosition(rect.min);
Vector3 max = GetWorldSpacePosition(rect.max);
Bounds bounds = new Bounds((min + max) * 0.5f, max - min);
return bounds;
}
///
/// Returns the size in normalized viewport coordinates (0..1) for the given cell if that cell was on the center of the screen
///
/// The get rect screen space.
/// Cell index.
public Vector2 CellGetViewportSize(int cellIndex)
{
Transform t = cameraMain.transform;
Vector3 oldPos = t.position;
Quaternion oldRot = t.rotation;
Plane p = new Plane(transform.forward, transform.position);
float dist = p.GetDistanceToPoint(oldPos);
Vector3 cellPos = CellGetPosition(cellIndex);
t.position = cellPos - transform.forward * dist;
t.LookAt(cellPos);
Vector3 cellRectMin = transform.TransformPoint(cells[cellIndex].region.rect2D.min);
Vector3 cellRectMax = transform.TransformPoint(cells[cellIndex].region.rect2D.max);
Vector3 screenMin = cameraMain.WorldToViewportPoint(cellRectMin);
Vector3 screenMax = cameraMain.WorldToViewportPoint(cellRectMax);
t.rotation = oldRot;
t.position = oldPos;
return new Vector2(Mathf.Abs(screenMax.x - screenMin.x), Mathf.Abs(screenMax.y - screenMin.y));
}
///
/// Gets the cell's center position in world space or local space.
///
public Vector3 CellGetPosition(int cellIndex, bool worldSpace = true, float elevation = 0)
{
if (!ValidCellIndex(cellIndex)) return Misc.Vector3zero;
return CellGetPosition(cells[cellIndex], worldSpace, elevation);
}
///
/// Gets the cell's center position in world space.
///
public Vector3 CellGetPosition(Cell cell, bool worldSpace = true, float elevation = 0)
{
if (cell == null)
return Misc.Vector3zero;
Vector2 cellGridCenter = cell.scaledCenter;
if (worldSpace)
{
return GetWorldSpacePosition(cellGridCenter, elevation);
}
else
{
return cellGridCenter;
}
}
///
/// Gets the cell's centroid in world space.
///
public Vector3 CellGetCentroid(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return Misc.Vector3zero;
Vector2 cellCentroid = cells[cellIndex].GetCentroid();
return GetWorldSpacePosition(cellCentroid);
}
///
/// Returns the normal at the center of a cell
///
/// The normal in world space coordinates.
/// Cell index.
public Vector3 CellGetNormal(int cellIndex)
{
if (_terrainWrapper == null || !ValidCellIndex(cellIndex)) return Misc.Vector3zero;
Vector2 cellCenter = cells[cellIndex].scaledCenter;
return _terrainWrapper.GetInterpolatedNormal(cellCenter.x + 0.5f, cellCenter.y + 0.5f);
}
///
/// Returns the number of vertices of the cell
///
public int CellGetVertexCount(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return 0;
return cells[cellIndex].region.points.Count;
}
///
/// Returns the world space position of the vertex
///
public Vector3 CellGetVertexPosition(int cellIndex, int vertexIndex)
{
if (cellIndex < 0 || cellIndex >= cells.Count || cells[cellIndex].region == null || cells[cellIndex].region.points == null || cells[cellIndex].region.points.Count <= vertexIndex)
return Misc.Vector3zero;
Vector2 localPosition = cells[cellIndex].region.points[vertexIndex];
return GetWorldSpacePosition(localPosition);
}
///
/// Returns a list of neighbour cells for specificed cell.
///
public List CellGetNeighbours(Cell cell)
{
int cellIndex = CellGetIndex(cell);
return CellGetNeighbours(cellIndex);
}
///
/// Returns the index of an ajacent cell by the side name
///
public int CellGetNeighbour(int cellIndex, CELL_SIDE side)
{
int r, c;
CELL_SIDE os;
if (!GetAdjacentCellCoordinates(cellIndex, side, out r, out c, out os))
{
return -1;
}
return CellGetIndex(r, c);
}
///
/// Returns a list of neighbour cells for specificed cell index.
///
public List CellGetNeighbours(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return null;
return cells[cellIndex].neighbours;
}
///
/// Returns a list of neighbour cells for specificed cell index.
///
public int CellGetNeighbours(int cellIndex, List neighbours)
{
if (!ValidCellIndex(cellIndex)) return 0;
if (neighbours == null)
return 0;
neighbours.Clear();
neighbours.AddRange(cells[cellIndex].neighbours);
return neighbours.Count;
}
///
/// Get a list of cells which are nearer than a given distance in cell count
///
public List CellGetNeighbours(Cell cell, int maxSteps, int cellGroupMask = -1, float maxSearchCost = 0, CanCrossCheckType canCrossCheckType = CanCrossCheckType.Default, int maxResultsCount = int.MaxValue)
{
int cellIndex = CellGetIndex(cell);
return CellGetNeighbours(cellIndex, maxSteps, cellGroupMask, maxSearchCost, canCrossCheckType, maxResultsCount);
}
///
/// Get a list of cells which are nearer than a given distance in cell count
///
public List CellGetNeighbours(int cellIndex, int maxSteps, int cellGroupMask = -1, float maxSearchCost = 0, CanCrossCheckType canCrossCheckType = CanCrossCheckType.Default, int maxResultsCount = int.MaxValue)
{
List results = new List();
CellGetNeighbours(cellIndex, maxSteps, results, cellGroupMask, maxSearchCost, canCrossCheckType, maxResultsCount);
return results;
}
///
/// Get a list of cells which are nearer than a given distance in cell count
///
public int CellGetNeighbours(int cellIndex, int maxSteps, List cellIndices, int cellGroupMask = -1, float maxSearchCost = 0, CanCrossCheckType canCrossCheckType = CanCrossCheckType.Default, int maxResultsCount = int.MaxValue, bool ignoreCellCosts = false, bool includeInvisibleCells = true)
{
if (cellIndex < 0 || cellIndex >= cells.Count || cellIndices == null)
return 0;
Cell cell = cells[cellIndex];
cellIndices.Clear();
maxSteps = Mathf.Min(BIG_INT_NUMBER, maxSteps);
int maxI = maxSteps * 2 + 1;
maxI *= maxI;
maxI--; // ignore starting cell
int dx = -1;
int dy = 0;
int y = -maxSteps;
int x = maxSteps;
cellIteration++;
GridDistanceFunction distanceFunction;
if (_gridTopology == GridTopology.Hexagonal)
{
distanceFunction = CellGetHexagonDistance;
}
else
{
distanceFunction = CellGetBoxDistance;
}
float dummyCost;
int count = 0;
for (int i = 0; i < maxI; i++)
{
int cx = x + cell.column;
int cy = y + cell.row;
if (cx >= 0 && cx < _cellColumnCount && cy >= 0 && cy < _cellRowCount)
{
int ci = CellGetIndex(cy, cx, false);
if (cells[ci].iteration == cellIteration)
{
cellIndices.Add(ci);
count++;
}
else
{
if (distanceFunction(cellIndex, ci) <= maxSteps)
{
int stepsCount = FindPath(cellIndex, ci, tempListCells, out dummyCost, maxSearchCost, maxSteps, cellGroupMask, canCrossCheckType, ignoreCellCosts, includeInvisibleCells);
if (stepsCount > 0)
{
for (int k = 0; k < stepsCount; k++)
{
cells[tempListCells[k]].iteration = cellIteration;
}
cellIndices.Add(ci);
count++;
}
}
}
if (count >= maxResultsCount) break;
}
if ((x == y) || ((x < 0) && (x == -y)) || ((x > 0) && (x == 1 - y)))
{
int t = dx;
dx = dy;
dy = -t;
}
x += dx;
y += dy;
}
return count;
}
///
/// Get a list of cells which are nearer than a given distance in cell count
///
public List CellGetNeighboursWithinRange(int cellIndex, int minSteps, int maxSteps, int cellGroupMask = -1, float maxCost = -1, bool includeInvisibleCells = true, CanCrossCheckType canCrossCheckType = CanCrossCheckType.Default, bool ignoreCellCosts = false)
{
if (cellIndex < 0 || cellIndex >= cells.Count)
return null;
minSteps = Mathf.Max(1, minSteps);
Cell cell = cells[cellIndex];
List cc = new List();
GridDistanceFunction distanceFunction;
if (_gridTopology == GridTopology.Hexagonal)
{
distanceFunction = CellGetHexagonDistance;
}
else
{
distanceFunction = CellGetBoxDistance;
}
for (int x = cell.column - maxSteps; x <= cell.column + maxSteps; x++)
{
if (x < 0 || x >= _cellColumnCount)
continue;
for (int y = cell.row - maxSteps; y <= cell.row + maxSteps; y++)
{
if (y < 0 || y >= _cellRowCount)
continue;
if (x == cell.column && y == cell.row)
continue;
int ci = CellGetIndex(y, x);
if (!includeInvisibleCells && !CellIsVisible(ci)) continue;
if (distanceFunction(cellIndex, ci) <= maxSteps)
{
List steps = FindPath(cellIndex, ci, maxCost, maxSteps, cellGroupMask, canCrossCheckType, ignoreCellCosts, includeInvisibleCells);
if (steps != null)
{
int stepsCount = steps.Count;
if (stepsCount >= minSteps)
{
cc.Add(ci);
}
}
}
}
}
return cc;
}
///
/// Adds surrounding cells to a list of given cells
///
public void CellExpandSelection(List cellIndices)
{
if (_gridTopology == GridTopology.Irregular) return;
int cellCount = cellIndices.Count;
for (int k = 0; k < cellCount; k++)
{
int cellIndex = cellIndices[k];
Cell cell = cells[cellIndex];
if (cell == null) continue;
if (_gridTopology == GridTopology.Box)
{
for (int j = 0; j < 9; j++)
{
int sr = (j / 3) - 1;
int sc = (j % 3) - 1;
int newRow = cell.row + sr;
int newColumn = cell.column + sc;
Cell newCell = CellGetAtPosition(newColumn, newRow);
if (newCell == null) continue;
if (!cellIndices.Contains(newCell.index))
{
cellIndices.Add(newCell.index);
}
}
}
else
{
for (int j = 0; j < 8; j++)
{
if (GetAdjacentCellCoordinates(cellIndex, (CELL_SIDE)j, out int newRow, out int newColumn, out CELL_SIDE otherSide))
{
Cell newCell = CellGetAtPosition(newColumn, newRow);
if (newCell == null) continue;
if (!cellIndices.Contains(newCell.index))
{
cellIndices.Add(newCell.index);
}
}
}
}
}
}
///
/// Returns cell's territory index to which it belongs to, or -1 if error.
///
public int CellGetTerritoryIndex(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return -1;
return cells[cellIndex].territoryIndex;
}
///
/// Returns cell's territory region index to which it belongs to, or -1 if error.
///
public int CellGetTerritoryRegionIndex(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return -1;
int terrIndex = cells[cellIndex].territoryIndex;
Territory terr = territories[terrIndex];
if (terr.regions.Count == 1) return 0;
Cell cell = cells[cellIndex];
List regionCells = new List();
for (int k = 0; k < terr.regions.Count; k++)
{
TerritoryGetCells(terrIndex, k, regionCells);
if (regionCells.Contains(cell))
{
return k;
}
}
return -1;
}
///
/// Returns current cell's fill color
///
public Color CellGetColor(int cellIndex)
{
if (cellIndex < 0 || cellIndex >= cells.Count || cells[cellIndex].region.customMaterial == null)
return new Color(0, 0, 0, 0);
return cells[cellIndex].region.customMaterial.color;
}
///
/// Returns current cell's fill texture
///
public Texture2D CellGetTexture(int cellIndex)
{
if (cellIndex < 0 || cellIndex >= cells.Count || cells[cellIndex].region.customMaterial == null)
return null;
return (Texture2D)cells[cellIndex].region.customMaterial.mainTexture;
}
///
/// Sets current cell's fill color. Use CellToggleRegionSurface for more options
///
public void CellSetColor(int cellIndex, Color color)
{
CellToggleRegionSurface(cellIndex, true, color, false, null, Misc.Vector2one, Misc.Vector2zero, 0, false, false);
}
///
/// Sets current cell's fill color. Use CellToggleRegionSurface for more options
///
public void CellSetColor(Cell cell, Color color)
{
int cellIndex = CellGetIndex(cell);
CellSetColor(cellIndex, color);
}
///
/// Sets cells' fill color.
///
public void CellSetColor(List cellIndices, Color color)
{
int cellCount = cellIndices.Count;
for (int k = 0; k < cellCount; k++)
{
CellToggleRegionSurface(cellIndices[k], color.a > 0, color, false, null, Misc.Vector2one, Misc.Vector2zero, 0, false, false);
}
}
///
/// Sets current cell's fill texture. Use CellToggleRegionSurface for more options
///
public void CellSetTexture(Cell cell, Texture2D texture)
{
int cellIndex = CellGetIndex(cell);
CellSetTexture(cellIndex, texture);
}
///
/// Sets current cell's fill texture. Use CellToggleRegionSurface for more options
///
public void CellSetTexture(int cellIndex, Texture2D texture)
{
if (texture != null)
{
CellToggleRegionSurface(cellIndex, true, Color.white, false, texture);
}
else
{
CellClear(cellIndex);
}
}
///
/// Sets current cell's fill texture. Use CellToggleRegionSurface for more options
///
public void CellSetTexture(Cell cell, Texture2D texture, Color tintColor)
{
int cellIndex = CellGetIndex(cell);
CellSetTexture(cellIndex, texture, tintColor);
}
///
/// Sets current cell's fill texture. Use CellToggleRegionSurface for more options
///
public void CellSetTexture(int cellIndex, Texture2D texture, Color tintColor)
{
if (texture != null)
{
CellToggleRegionSurface(cellIndex, true, tintColor, false, texture);
}
else
{
CellClear(cellIndex);
}
}
///
/// Assigns a custom material to a cell
///
public GameObject CellSetMaterial(int cellIndex, Material material)
{
if (!ValidCellIndex(cellIndex)) return null;
Color color = material.HasProperty(ShaderParams.Color) ? material.GetColor(ShaderParams.Color) : Color.white;
Texture2D tex = null;
if (material.HasProperty(ShaderParams.MainTex))
{
tex = material.GetTexture(ShaderParams.MainTex) as Texture2D;
}
else if (material.HasProperty(ShaderParams.BaseMap))
{
tex = material.GetTexture(ShaderParams.BaseMap) as Texture2D;
}
GameObject o = CellToggleRegionSurface(cellIndex, true, color, false, tex);
Region region = cells[cellIndex].region;
region.customMaterial = material;
region.customRotateInLocalSpace = false;
region.customTextureOffset = Vector2.zero;
region.customTextureRotation = 0;
region.customTextureScale = Vector2.one;
ApplyMaterialToSurface(region, material);
return o;
}
///
/// Returns current cell's fill texture index (if texture exists in textures list).
/// Texture index is from 1..32. It will return 0 if texture does not exist or it does not match any texture in the list of textures.
///
public int CellGetTextureIndex(int cellIndex)
{
if (cellIndex < 0 || cellIndex >= cells.Count || cells[cellIndex].region.customMaterial == null)
return 0;
Texture2D tex = (Texture2D)cells[cellIndex].region.customMaterial.mainTexture;
if (textures != null)
{
for (int k = 1; k < textures.Length; k++)
{
if (tex == textures[k])
return k;
}
}
return 0;
}
///
/// Returns cell's row or -1 if cellIndex is not valid.
///
public int CellGetRow(int cellIndex)
{
if (!ValidCellIndex(cellIndex))
return -1;
return cells[cellIndex].row;
}
///
/// Returns cell's column or -1 if cellIndex is not valid.
///
public int CellGetColumn(int cellIndex)
{
if (!ValidCellIndex(cellIndex))
return -1;
return cells[cellIndex].column;
}
public bool CellIsBorder(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return false;
Cell cell = cells[cellIndex];
if (_gridTopology == GridTopology.Irregular)
{
int segmentCount = cell.region.segments.Count;
for (int k = 0; k < segmentCount; k++)
{
if (cell.region.segments[k].border) return true;
}
return false;
}
return (cell.column == 0 || cell.column == _cellColumnCount - 1 || cell.row == 0 || cell.row == _cellRowCount - 1);
}
///
/// Returns the index of a territory in the territory array by its reference.
///
public int TerritoryGetIndex(Territory territory)
{
if (territory == null)
return -1;
int index;
if (territoryLookup.TryGetValue(territory, out index))
return index;
else
return -1;
}
///
/// Returns true if cell is visible
///
public bool CellIsVisible(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return false;
return cells[cellIndex].visible;
}
///
/// Merges cell2 into cell1. Cell2 is removed.
/// Only cells which are neighbours can be merged.
///
public bool CellMerge(Cell cell1, Cell cell2)
{
if (cell1 == null || cell2 == null)
return false;
if (!cell1.neighbours.Contains(cell2))
return false;
cell1.center = (cell2.center + cell1.center) / 2.0f;
// Polygon UNION operation between both regions
PolygonClipper pc = new PolygonClipper(cell1.region.polygon, cell2.region.polygon);
pc.Compute(PolygonOp.UNION);
// Remove cell2 from lists
CellRemove(cell2);
// Updates geometry data on cell1
Polygon poly = pc.subject;
cell1.region.polygon = poly;
// Update segments list
int pointsCount = poly.contours[0].points.Count;
int oldCell1SegmentsCount = cell1.region.segments.Count;
int oldCell2SegmentsCount = cell2.region.segments.Count;
List newSegments = new List(pointsCount);
Contour contour = poly.contours[0];
for (int k = 0; k < pointsCount; k++)
{
Segment s = contour.GetSegment(k);
bool found = false;
// try to find the old segment from cell1 that corresponds to this new segment in poly
for (int j = 0; j < oldCell1SegmentsCount; j++)
{
Segment o = cell1.region.segments[j];
if ((Point.EqualsBoth(o.start, s.start) && Point.EqualsBoth(o.end, s.end)) || (Point.EqualsBoth(o.end, s.start) && Point.EqualsBoth(o.start, s.end)))
{
newSegments.Add(o);
found = true;
break;
}
}
if (!found)
{
// try to find the old segment in cell2 instead
for (int j = 0; j < oldCell2SegmentsCount; j++)
{
Segment o = cell2.region.segments[j];
if ((Point.EqualsBoth(o.start, s.start) && Point.EqualsBoth(o.end, s.end)) || (Point.EqualsBoth(o.end, s.start) && Point.EqualsBoth(o.start, s.end)))
{
newSegments.Add(o);
break;
}
}
}
}
// Assign the new segment list
cell1.region.segments = newSegments;
// Refresh rect2D
CellUpdateBounds(cell1);
// Refresh neighbours
CellsUpdateNeighbours();
needResortCells = true;
// Refresh territories
if (territoriesAreUsed)
{
FindTerritoriesFrontiers();
UpdateTerritoriesBoundary();
}
if (cell1 == _cellLastOver)
{
ClearLastOver();
}
return true;
}
///
/// Removes a cell from the cells and territories lists. Note that this operation only removes cell structure but does not affect polygons - mostly internally used
///
///
public void CellRemove(int cellIndex)
{
if (cells == null || cellIndex < 0 || cellIndex >= cells.Count) return;
CellRemove(cells[cellIndex]);
}
///
/// Removes a cell from the cells and territories lists. Note that this operation only removes cell structure but does not affect polygons - mostly internally used
///
public void CellRemove(Cell cell)
{
if (cell == _cellHighlighted)
HideCellRegionHighlight();
if (cell == _cellLastOver)
{
ClearLastOver();
}
int territoryIndex = cell.territoryIndex;
if (territoryIndex >= 0 && territoryIndex < territories.Count)
{
Territory territory = territories[territoryIndex];
if (territory.cells != null && territory.cells.Contains(cell))
{
territory.cells.Remove(cell);
}
}
// remove cell from global list
int index = cells.IndexOf(cell);
if (index >= 0)
{
cells[index] = null;
}
// remove from sorted list
if (sortedCells.Contains(cell))
{
sortedCells.Remove(cell);
}
needRefreshRouteMatrix = true;
needUpdateTerritories = true;
}
///
/// Tags a cell with a user-defined integer tag. Cell can be later retrieved very quickly using CellGetWithTag.
///
public void CellSetTag(Cell cell, int tag)
{
// remove previous tag register
if (cellTagged.ContainsKey(cell.tag))
{
cellTagged.Remove(cell.tag);
}
// override existing tag
if (cellTagged.ContainsKey(tag))
{
cellTagged.Remove(tag);
}
cellTagged.Add(tag, cell);
cell.tag = tag;
}
///
/// Tags a cell with a user-defined integer tag. Cell can be later retrieved very quickly using CellGetWithTag.
///
public void CellSetTag(int cellIndex, int tag)
{
if (cellIndex < 0 || cellIndex >= cells.Count)
return;
CellSetTag(cells[cellIndex], tag);
}
///
/// Returns the tag value of a given cell.
///
public int CellGetTag(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return 0;
return cells[cellIndex].tag;
}
///
/// Retrieves Cell object with associated tag.
///
public Cell CellGetWithTag(int tag)
{
Cell cell;
if (cellTagged.TryGetValue(tag, out cell))
return cell;
return null;
}
///
/// Returns the shape/surface gameobject of the cell.
///
/// The get game object.
/// Cell index.
public GameObject CellGetGameObject(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return null;
Cell cell = cells[cellIndex];
if (cell.region.surfaceGameObject != null)
return cell.region.surfaceGameObject;
GameObject go = CellToggleRegionSurface(cellIndex, true, Misc.ColorNull);
CellToggleRegionSurface(cellIndex, false, Misc.ColorNull);
return go;
}
///
/// Extrudes the surface of a cell
///
///
///
public GameObject CellGetExtrudedGameObject(int cellIndex, float extrusionAmount = 1f)
{
if (!ValidCellIndex(cellIndex)) return null;
Poly2Tri.Polygon poly = GetPolygon(cells[cellIndex].region, ref tempPolyPoints, out int pointCount, _cellFillPadding);
if (poly == null)
return null;
GameObject cellSurface = CellGetGameObject(cellIndex);
if (cellSurface == null) return null;
return ExtrudeGameObject(cellSurface, extrusionAmount, tempPolyPoints, pointCount);
}
///
/// Returns true if a given cell can be crossed by using the pathfinding engine.
///
public bool CellGetCanCross(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return false;
return cells[cellIndex].canCross;
}
///
/// Specifies if a given cell can be crossed by using the pathfinding engine.
///
public void CellSetCanCross(int cellIndex, bool canCross)
{
if (!ValidCellIndex(cellIndex)) return;
cells[cellIndex].canCross = canCross;
needRefreshRouteMatrix = true;
}
///
/// Specifies if a list of cells can be crossed by using the pathfinding engine.
///
public void CellSetCanCross(List cellIndices, bool canCross)
{
int count = cellIndices.Count;
for (int k = 0; k < count; k++)
{
Cell cell = cells[cellIndices[k]];
cell.canCross = canCross;
}
needRefreshRouteMatrix = true;
}
///
/// Sets the additional cost of crossing an hexagon side.
///
/// Cell index.
/// Side of the hexagon.
/// Crossing cost.
public void CellSetSideCrossCost(int cellIndex, CELL_SIDE side, float cost, CELL_DIRECTION direction = CELL_DIRECTION.Both)
{
if (cellIndex < 0 || cellIndex >= cells.Count)
return;
Cell cell = cells[cellIndex];
if (direction != CELL_DIRECTION.Entering)
{
cell.SetSideCrossCost(side, cost);
}
if (direction != CELL_DIRECTION.Exiting)
{
int or, oc;
CELL_SIDE os;
if (GetAdjacentCellCoordinates(cellIndex, side, out or, out oc, out os))
{
int oindex = CellGetIndex(or, oc);
if (oindex >= 0)
{
cells[oindex].SetSideCrossCost(os, cost);
}
}
}
}
///
/// Gets the cost of crossing any hexagon side.
///
/// Cell index.
/// Side of the cell.///
/// The direction for getting the cost. Entering or exiting values are acceptable. Both will return the entering cost.
public float CellGetSideCrossCost(int cellIndex, CELL_SIDE side, CELL_DIRECTION direction = CELL_DIRECTION.Entering)
{
if (cellIndex < 0 || cellIndex >= cells.Count)
return 0;
Cell cell = cells[cellIndex];
if (direction == CELL_DIRECTION.Exiting)
{
return cell.GetSideCrossCost(side);
}
int or, oc;
CELL_SIDE os;
if (GetAdjacentCellCoordinates(cellIndex, side, out or, out oc, out os))
{
int oindex = CellGetIndex(or, oc);
return cells[oindex].GetSideCrossCost(os);
}
return 0;
}
///
/// Makes a side of a cell block the LOS.
///
/// Cell index.
/// Side of the cell.
/// Status of the block.
public void CellSetSideBlocksLOS(int cellIndex, CELL_SIDE side, bool blocks)
{
if (cellIndex < 0 || cellIndex >= cells.Count)
return;
Cell cell = cells[cellIndex];
cell.SetSideBlocksLOS(side, blocks);
int r = cell.row;
int c = cell.column;
GetAdjacentCellCoordinates(cellIndex, side, out int or, out int oc, out CELL_SIDE os);
if (or >= 0 && or < _cellRowCount && oc >= 0 && oc < _cellColumnCount)
{
int oindex = CellGetIndex(or, oc);
if (oindex >= 0)
{
cells[oindex].SetSideBlocksLOS(os, blocks);
}
}
}
///
/// Returns true if the side of a cell blocks LOS.
///
/// Cell index.
/// Side of the cell.///
public bool CellGetSideBlocksLOS(int cellIndex, CELL_SIDE side)
{
if (cellIndex < 0 || cellIndex >= cells.Count)
return false;
Cell cell = cells[cellIndex];
if (cell.GetSideBlocksLOS(side))
return true;
int r = cell.row;
int c = cell.column;
int or = r, oc = c;
CELL_SIDE os = side;
if (_gridTopology == GridTopology.Hexagonal)
{
int evenValue = _evenLayout ? 1 : 0;
switch (side)
{
case CELL_SIDE.Bottom:
or--;
os = CELL_SIDE.Top;
break;
case CELL_SIDE.Top:
or++;
os = CELL_SIDE.Bottom;
break;
case CELL_SIDE.BottomRight:
if (oc % 2 != evenValue)
{
or--;
}
oc++;
os = CELL_SIDE.TopLeft;
break;
case CELL_SIDE.TopRight:
if (oc % 2 == evenValue)
{
or++;
}
oc++;
os = CELL_SIDE.BottomLeft;
break;
case CELL_SIDE.TopLeft:
if (oc % 2 == evenValue)
{
or++;
}
oc--;
os = CELL_SIDE.BottomRight;
break;
case CELL_SIDE.BottomLeft:
if (oc % 2 != evenValue)
{
or--;
}
oc--;
os = CELL_SIDE.TopRight;
break;
}
}
else
{
switch (side)
{
case CELL_SIDE.Bottom:
or--;
os = CELL_SIDE.Top;
break;
case CELL_SIDE.Top:
or++;
os = CELL_SIDE.Bottom;
break;
case CELL_SIDE.BottomRight:
or--;
oc++;
os = CELL_SIDE.TopLeft;
break;
case CELL_SIDE.TopRight:
or++;
oc++;
os = CELL_SIDE.BottomLeft;
break;
case CELL_SIDE.TopLeft:
or++;
oc--;
os = CELL_SIDE.BottomRight;
break;
case CELL_SIDE.BottomLeft:
or--;
oc--;
os = CELL_SIDE.TopRight;
break;
case CELL_SIDE.Right:
oc++;
os = CELL_SIDE.Left;
break;
case CELL_SIDE.Left:
oc--;
os = CELL_SIDE.Right;
break;
}
}
if (or >= 0 && or < _cellRowCount && oc >= 0 && oc < _cellColumnCount)
{
int oindex = CellGetIndex(or, oc);
return cells[oindex].GetSideBlocksLOS(os);
}
else
{
return false;
}
}
///
/// Sets cost of entering or exiting a given hexagonal cell across any edge.
///
/// Cell index.
/// Crossing cost.
public void CellSetCrossCost(int cellIndex, float cost, CELL_DIRECTION direction = CELL_DIRECTION.Entering)
{
if (!ValidCellIndex(cellIndex)) return;
for (int side = 0; side < 8; side++)
{
CellSetSideCrossCost(cellIndex, (CELL_SIDE)side, cost, direction);
}
}
///
/// Returns the cost of entering or exiting a given hexagonal cell without specifying a specific edge. This method is used along CellSetCrossCost which doesn't take into account per-edge costs.
///
/// Cell index.
public float CellGetCrossCost(int cellIndex)
{
if (!ValidCellIndex(cellIndex)) return 0;
return cells[cellIndex].GetSidesCost();
}
///
/// Specifies the cell group (by default 1) used by FindPath cellGroupMask optional argument
///
public void CellSetGroup(int cellIndex, int group)
{
if (cellIndex < 0 || cellIndex >= cells.Count)
return;
if (cells[cellIndex] == null) return;
cells[cellIndex].group = group;
needRefreshRouteMatrix = true;
}
///
/// Returns cell group (default 1)
///
public int CellGetGroup(int cellIndex)
{
if (cellIndex < 0 || cellIndex >= cells.Count)
return -1;
return cells[cellIndex].group;
}
///
/// Returns the indices of all cells belonging to a group in the indices array which must be fully allocated when passed. Also the length of this array determines the maximum number of indices returned.
/// This method returns the actual number of indices returned, regardless of the length the array. This design helps reduce heap allocations.
///
public int CellGetFromGroup(int group, int[] indices)
{
if (indices == null || cells == null)
return 0;
int cellCount = cells.Count;
int count = 0;
for (int k = 0; k < cellCount && k < indices.Length; k++)
{
if (cells[k].group == group)
{
indices[count++] = k;
}
}
return count;
}
///
/// Returns the indices of all cells belonging to a group in the indices array which must be fully allocated when passed. Also the length of this array determines the maximum number of indices returned.
/// This method returns the actual number of indices returned, regardless of the length the array. This design helps reduce heap allocations.
///
public int CellGetFromGroup(int group, List indices)
{
if (indices == null || cells == null)
return 0;
int cellCount = cells.Count;
for (int k = 0; k < cellCount; k++)
{
if (cells[k].group == group)
{
indices.Add(k);
}
}
return indices.Count;
}
///
/// Specifies if a given cell is visible.
///
/// If true, cell won't be part of any territory. Territory borders will be updated.
public void CellSetVisible(int cellIndex, bool visible, bool excludeFromAnyTerritory = false)
{
if (cellIndex < 0 || cellIndex >= cells.Count)
return;
Cell cell = cells[cellIndex];
if (cell.visible == visible)
return; // nothing to do
cell.visible = visible;
if (cellIndex == _cellLastOverIndex)
{
ClearLastOver();
}
needRefreshRouteMatrix = true;
refreshCellMesh = true;
if (excludeFromAnyTerritory)
{
if (cell.territoryIndex >= 0)
{
cell.territoryIndex = -1;
}
needUpdateTerritories = true;
}
issueRedraw = RedrawType.Full;
}
CELL_SIDE GetSideByVector(Vector2 dir)
{
switch (_gridTopology)
{
case GridTopology.Box:
if (Mathf.Abs(dir.x) > Mathf.Abs(dir.y))
{
return dir.x < 0 ? CELL_SIDE.Right : CELL_SIDE.Left;
}
else
{
return dir.y < 0 ? CELL_SIDE.Top : CELL_SIDE.Bottom;
}
default:
// hexagons
if (dir.x == 0)
{
return dir.y < 0 ? CELL_SIDE.Top : CELL_SIDE.Bottom;
}
else if (dir.x < 0)
{
return dir.y < 0 ? CELL_SIDE.TopRight : CELL_SIDE.BottomRight;
}
else
{
return dir.y < 0 ? CELL_SIDE.TopLeft : CELL_SIDE.BottomLeft;
}
}
}
///
/// Sets the cost for going from one cell to another (both cells must be adjacent).
///
/// Cell start index.
/// Cell end index.
public void CellSetCrossCost(int cellStartIndex, int cellEndIndex, float cost)
{
if (cellStartIndex < 0 || cellStartIndex >= cells.Count || cellEndIndex < 0 || cellEndIndex >= cells.Count)
return;
CELL_SIDE side = GetSideByVector(cells[cellStartIndex].center - cells[cellEndIndex].center);
CellSetSideCrossCost(cellEndIndex, side, cost, CELL_DIRECTION.Entering);
}
///
/// Returns the cost of going from one cell to another (both cells must be adjacent)
///
/// The get cross cost.
/// Cell start index.
/// Cell end index.
public float CellGetCrossCost(int cellStartIndex, int cellEndIndex)
{
if (cellStartIndex < 0 || cellStartIndex >= cells.Count || cellEndIndex < 0 || cellEndIndex >= cells.Count)
return 0;
CELL_SIDE side = GetSideByVector(cells[cellStartIndex].center - cells[cellEndIndex].center);
return CellGetSideCrossCost(cellEndIndex, side, CELL_DIRECTION.Entering);
}
///
/// Specified visibility for a group of cells that lay within a given rectangle
///
/// Rect or boundary. If local space is used, coordinates must be in range (-0.5..0.5)
/// If set to true visible.
public bool CellSetVisible(Rect rect, bool visible, bool worldSpace = false)
{
return ToggleCellsVisibility(rect, visible, worldSpace);
}
///
/// Specified visibility for a group of cells that lay within a given gameObject
///
/// GameObject whose mesh or collider will be used.
/// If set to true visible.
public bool CellSetVisible(GameObject obj, bool visible)
{
Collider collider = obj.GetComponent();
if (collider != null)
return CellSetVisible(collider.bounds, visible);
Renderer renderer = obj.GetComponent();
if (renderer != null)
return CellSetVisible(renderer.bounds, visible);
return false;
}
///
/// Specified visibility for a group of cells that lay within a given gameObject
///
/// Renderer whose bounds will be used.
/// If set to true visible.
public bool CellSetVisible(Renderer renderer, bool visible)
{
return CellSetVisible(renderer.bounds, visible);
}
///
/// Specified visibility for a group of cells that lay within a given collider
///
/// Collider that provides boundary.
/// If set to true visible.
public bool CellSetVisible(Collider collider, bool visible)
{
if (collider == null)
return false;
return CellSetVisible(collider.bounds, visible);
}
///
/// Specified visibility for a group of cells that lay within a given bounds
///
/// Bounds in world space coordinates.
/// If set to true visible.
public bool CellSetVisible(Bounds bounds, bool visible)
{
Rect rect = new Rect();
Vector3 pos = bounds.min;
if (!GetLocalHitFromWorldPosition(ref pos))
return false;
rect.min = pos;
pos = bounds.max;
if (!GetLocalHitFromWorldPosition(ref pos))
return false;
rect.max = pos;
return ToggleCellsVisibility(rect, visible, false);
}
///
/// Specifies if a given cell's border is visible.
///
public void CellSetBorderVisible(int cellIndex, bool visible)
{
if (cellIndex < 0 || cellIndex >= cells.Count)
return;
Cell cell = cells[cellIndex];
cell.borderVisible = visible;
issueRedraw = RedrawType.Full;
}
///
/// Returns the state of the border visibility after CellSetBorderVisible has been called
///
/// Cell index.
public bool CellHasBorderVisible(int cellIndex)
{
if (cellIndex < 0 || cellIndex >= cells.Count)
return false;
return cells[cellIndex].borderVisible;
}
///
/// Returns the cell object under position in local or worldSpace coordinates
///
/// The get at position.
/// Position.
/// If set to true, position is given in world space coordinate units, otherwise position refer to local coordinates.
/// Optional territory index to restrict the search and make it faster.
public Cell CellGetAtPosition(Vector3 position, bool worldSpace = false, int territoryIndex = -1)
{
return GetCellAtPoint(position, worldSpace, territoryIndex);
}
///
/// Returns the indices of the cells within or under a volume
///
/// The bounds of volume or area in world space coordinates, for example the collider bounds.
/// An initialized list where results will be added
/// Optional margin that's added or substracted to the resulting area.
/// If true, not only the center of the cell but all cell vertices will be checked
public int CellGetInArea(Bounds bounds, List cellIndices, float padding = 0, bool checkAllVertices = false)
{
return GetCellInArea(bounds, cellIndices, padding, checkAllVertices);
}
///
/// Returns the indices of the cells within or under a volume
///
/// The gameobject that covers the desired cells
/// An initialized list where results will be added
/// Optional margin that's added or substracted to the resulting area.
/// If true, not only the center of the cell but all cell vertices will be checked
public int CellGetInArea(GameObject gameObject, List cellIndices, float padding = 0, bool checkAllVertices = false)
{
Collider collider = gameObject.GetComponentInChildren();
if (collider != null)
{
return GetCellInArea(collider.bounds, cellIndices, padding, checkAllVertices);
}
Renderer renderer = gameObject.GetComponent();
if (renderer != null)
{
return GetCellInArea(renderer.bounds, cellIndices, padding, checkAllVertices);
}
return 0;
}
///
/// Returns the indices of the cells within or under a volume
///
/// An initialized list where results will be added
/// Optional margin that's added or substracted to the resulting area.
/// If true, not only the center of the cell but all cell vertices will be checked
public int CellGetInArea(Vector2 localStartPos, Vector2 localEndPos, List cellIndices, float padding = 0, bool checkAllVertices = false)
{
if (cellIndices == null)
{
Debug.LogError("CellGetInArea: cellIndices must be initialized.");
return 0;
}
Vector2 size = new Vector2(Mathf.Abs(localEndPos.x - localStartPos.x), Mathf.Abs(localEndPos.y - localStartPos.y));
Vector2 center = (localStartPos + localEndPos) * 0.5f;
center -= size * 0.5f;
Rect rect = new Rect(center, size);
return GetCellInArea(rect, cellIndices, padding, checkAllVertices);
}
///
/// Returns the indices of the cells under a box collider
///
/// An initialized list where results will be added
/// Optional margin that's added or substracted to the resulting area.
public int CellGetInArea(BoxCollider boxCollider, List cellIndices, int resolution, float padding = 0, float offset = 0)
{
if (cellIndices == null)
{
Debug.LogError("CellGetInArea: cellIndices must be initialized.");
return 0;
}
Vector3[] points = ProjectedBasePositions(boxCollider, resolution, padding, offset);
int pointCount = points.Length;
for (int k = 0; k < pointCount; k++)
{
Vector3 point = points[k];
int cellIndex = GetCellAtPoint(point, true).index;
if (!cellIndices.Contains(cellIndex))
{
cellIndices.Add(cellIndex);
}
}
return cellIndices.Count;
}
Vector3 FindBaseCorner(BoxCollider boxCollider, float padding)
{
// Get a reference to the transform
Transform incTransform = boxCollider.transform;
// Start from the center
Vector3 corner = boxCollider.center;
// Get to the corner by substracting half lengths and taking into account scaling
corner -= incTransform.right * boxCollider.size.x / 2 * incTransform.localScale.x
+ incTransform.right * padding / 2; // Add padding
corner -= incTransform.forward * boxCollider.size.z / 2 * incTransform.localScale.z
+ incTransform.forward * padding / 2; // Add padding
corner -= incTransform.up * boxCollider.size.y / 2 * incTransform.localScale.y;
// add the transform's position to get world position
corner += incTransform.position;
// Return corner the position
return corner;
}
Vector3[] projectedPositions;
Vector3[] ProjectedBasePositions(BoxCollider boxCollider, int resolution = 10, float padding = 0.1f, float offset = 0.0f)
{
// Get a reference to the transform
Transform incTransform = boxCollider.transform;
// Make an array of positions
int positionsCount = resolution * resolution;
if (projectedPositions == null || projectedPositions.Length != positionsCount)
{
projectedPositions = new Vector3[positionsCount];
}
int j = 0; // keep track of right axis
int k = 0; // keep track of forward axis
Vector3 baseCorner = FindBaseCorner(boxCollider, padding); // Find base corner
for (int i = 0; i < positionsCount; i++)
{
Vector3 basePos = baseCorner
+ incTransform.right * ((boxCollider.size.x * incTransform.localScale.x / resolution) * j) // move to the right / resolution
+ (incTransform.right * (padding / resolution)) * j // Add padding / resolution
+ incTransform.right * offset // Add offset
+ incTransform.forward * ((boxCollider.size.z * incTransform.localScale.z / resolution) * k) // move to forward / resolution
+ (incTransform.forward * (padding / resolution)) * k // Add padding / resolution
+ incTransform.forward * offset; // Add offset
// Project base position on ground, you can change the plane of projection by changing Vector3.up
Vector3 projectedPos = Vector3.ProjectOnPlane(basePos, Vector3.up);
// Assign value to our array
projectedPositions[i] = projectedPos;
// Next point
j++;
// Next line
if (j >= resolution)
{
j = 0;
k++;
if (k >= resolution) k = 0;
}
}
return projectedPositions;
}
///
/// Returns a list of cells contained in a cone defined by a starting cell, a direction, max distance and an angle for the cone
///
/// List where results will be returned. Must be previously initialized.
/// The number of found cells (-1 if some index is out of range)
public int CellGetWithinCone(int cellIndex, Vector2 direction, float maxDistance, float angle, List cellIndices)
{
if (cellIndices == null)
{
Debug.LogError("CellGetWithinCone: cellIndices parameter must be initialized.");
return 0;
}
cellIndices.Clear();
if (!ValidCellIndex(cellIndex)) return -1;
Cell startingCell = cells[cellIndex];
Vector2 initialPos = startingCell.scaledCenter;
Vector2 targetPos = initialPos + direction * maxDistance;
int targetCellIndex = CellGetIndex(targetPos);
if (targetCellIndex < 0)
{
Vector2 step = direction * startingCell.region.rect2D.width * 0.25f;
targetPos = initialPos;
for (int k = 0; k < 2048; k++)
{
targetPos += step;
int stepCellIndex = CellGetIndex(targetPos);
if (stepCellIndex < 0) break;
targetCellIndex = stepCellIndex;
}
if (targetCellIndex < 0) return -1;
}
return CellGetWithinCone(cellIndex, targetCellIndex, angle, cellIndices);
}
///
/// Returns a list of cells contained in a cone defined by a starting cell, a target cellIndex, a max distance and an angle for the cone
///
/// List where results will be returned. Must be previously initialized.
/// The number of found cells (-1 if some index is out of range)
public int CellGetWithinCone(int cellIndex, int targetCellIndex, float angle, List cellIndices)
{
if (cellIndices == null)
{
Debug.LogError("CellGetWithinCone: cellIndices parameter must be initialized.");
return 0;
}
if (!ValidCellIndex(cellIndex)) return -1;
if (!ValidCellIndex(targetCellIndex)) return -1;
Vector2 startPos = cells[cellIndex].center;
Vector2 endPos = cells[targetCellIndex].center;
Vector2 v = endPos - startPos;
float vdot = Vector2.Dot(v, v);
Vector2 vnorm = v.normalized;
float maxCos = Mathf.Cos(angle * 0.5f * Mathf.Deg2Rad);
int distX = Mathf.Abs(cells[targetCellIndex].column - cells[cellIndex].column);
int distY = Mathf.Abs(cells[targetCellIndex].row - cells[cellIndex].row);
int dist = Mathf.CeilToInt(Mathf.Sqrt(distX * distX + distY * distY));
int rowMin = Mathf.Max(0, cells[cellIndex].row - dist);
int rowMax = Mathf.Min(_cellRowCount - 1, cells[cellIndex].row + dist);
int columnMin = Mathf.Max(0, cells[cellIndex].column - dist);
int columnMax = Mathf.Min(_cellColumnCount - 1, cells[cellIndex].column + dist);
for (int r = rowMin; r <= rowMax; r++)
{
for (int c = columnMin; c <= columnMax; c++)
{
int foundCellIndex = r * _cellColumnCount + c;
if (cells[foundCellIndex] == null) continue;
Vector2 pos = cells[foundCellIndex].center;
Vector2 w = pos - startPos;
float t = Vector2.Dot(w, v);
t /= vdot;
if (t > 1) continue;
float cos = Vector2.Dot(w.normalized, vnorm);
if (cos >= maxCos)
{
cellIndices.Add(foundCellIndex);
}
}
}
return cellIndices.Count;
}
///
/// Checks if the given cell is neighbour of the territory
///
public bool CellIsAdjacentToTerritory(int cellIndex, int territoryIndex)
{
if (!ValidCellIndex(cellIndex) || !ValidTerritoryIndex(territoryIndex)) return false;
Cell cell = cells[cellIndex];
Territory terr = territories[territoryIndex];
if (terr.cells.Contains(cell)) return false; // it's not adjacent but contained
foreach (Cell terrCell in terr.cells)
{
if (terrCell.neighbours.Contains(cell)) return true;
}
return false;
}
///
/// Checks if the given cell is neighbour of another cell
///
public bool CellIsAdjacentToCell(int cellIndex, int otherCellIndex)
{
if (!ValidCellIndex(cellIndex) || !ValidCellIndex(otherCellIndex)) return false;
Cell cell = cells[cellIndex];
Cell otherCell = cells[otherCellIndex];
return otherCell.neighbours.Contains(cell);
}
///
/// Sets the territory of a cell triggering territory boundary recalculation. Note: changes are not applied immediately, but at end of frame. To apply changes immediately, call Redraw()
///
/// true, if cell was transferred., false otherwise.
public bool CellSetTerritory(int cellIndex, int territoryIndex)
{
if (needGenerateMap) CheckGridChanges();
if (!ValidCellIndex(cellIndex)) return false;
int terrCount = territories != null ? territories.Count : 0;
Cell cell = cells[cellIndex];
if (cell.territoryIndex == territoryIndex) return true;
if (cell.territoryIndex >= 0 && cell.territoryIndex < terrCount && territories[cell.territoryIndex].cells.Contains(cell))
{
Territory territory = territories[cell.territoryIndex];
territory.isDirty = true;
territory.cells.Remove(cell);
}
cell.territoryIndex = (short)territoryIndex;
if (territoryIndex >= 0 && territoryIndex < terrCount)
{
Territory territory = territories[territoryIndex];
territory.isDirty = true;
territory.cells.Add(cell);
}
needUpdateTerritories = true;
issueRedraw = RedrawType.IncrementalTerritories;
return true;
}
///
/// Sets the territory of a cell triggering territory boundary recalculation
///
/// true, if cell was transferred., false otherwise.
public bool CellSetTerritory(List cellIndices, int territoryIndex)
{
if (needGenerateMap) CheckGridChanges();
if (!ValidTerritoryIndex(territoryIndex)) return false;
int terrCount = territories != null ? territories.Count : 0;
foreach (int cellIndex in cellIndices)
{
if (!ValidCellIndex(cellIndex)) continue;
Cell cell = cells[cellIndex];
if (cell.territoryIndex == territoryIndex) return true;
if (cell.territoryIndex >= 0 && cell.territoryIndex < terrCount && territories[cell.territoryIndex].cells.Contains(cell))
{
territories[cell.territoryIndex].isDirty = true;
territories[cell.territoryIndex].cells.Remove(cell);
}
cell.territoryIndex = (short)territoryIndex;
if (territoryIndex >= 0 && territoryIndex < terrCount)
{
territories[territoryIndex].isDirty = true;
territories[territoryIndex].cells.Add(cell);
}
}
needUpdateTerritories = true;
issueRedraw = RedrawType.IncrementalTerritories;
return true;
}
///
/// Sets the territory of a cell triggering territory boundary recalculation
///
/// true, if cell was transferred., false otherwise.
public bool CellReSetTerritory(List cellIndices, int territoryIndex)
{
if (needGenerateMap) CheckGridChanges();
if (!ValidTerritoryIndex(territoryIndex)) return false;
var t = territories[territoryIndex];
t.cells.Clear();
foreach (int cellIndex in cellIndices)
{
if (!ValidCellIndex(cellIndex)) continue;
Cell cell = cells[cellIndex];
cell.territoryIndex = (short)territoryIndex;
t.cells.Add(cell);
}
needUpdateTerritories = true;
issueRedraw = RedrawType.IncrementalTerritories;
return true;
}
///
/// Returns a string-packed representation of current cells settings.
/// Each cell separated by ;
/// Individual settings mean:
/// Position Meaning
/// 0 Visibility (0 = invisible, 1 = visible)
/// 1 Territory Index
/// 2 Color R (0..1)
/// 3 Color G (0..1)
/// 4 Color B (0..1)
/// 5 Color A (0..1)
/// 6 Texture Index
/// 7 Cell tag
/// 8 Can cross? (pathfinding)
///
/// The get configuration data.
public string CellGetConfigurationData()
{
StringBuilder sb = new StringBuilder();
int cellsCount = cells.Count;
for (int k = 0; k < cellsCount; k++)
{
if (k > 0)
sb.Append(";");
// 0
Cell cell = cells[k];
if (cell.visible)
{
sb.Append("1");
}
else
{
sb.Append("0");
}
// 1 territory index
sb.Append(",");
sb.Append(cell.territoryIndex);
// 2 color.a
sb.Append(",");
Color color = CellGetColor(k);
sb.Append(color.a.ToString("F3", CultureInfo.InvariantCulture));
// 3 color.r
sb.Append(",");
sb.Append(color.r.ToString("F3", CultureInfo.InvariantCulture));
// 4 color.g
sb.Append(",");
sb.Append(color.g.ToString("F3", CultureInfo.InvariantCulture));
// 5 color.b
sb.Append(",");
sb.Append(color.b.ToString("F3", CultureInfo.InvariantCulture));
// 6 texture index
sb.Append(",");
sb.Append(CellGetTextureIndex(k));
// 7 tag
sb.Append(",");
sb.Append(cell.tag);
// 8 can cross
sb.Append(",");
sb.Append(cell.canCross ? 1 : 0);
}
return sb.ToString();
}
///
/// Returns an array with the main settings of each cell
///
/// The get settings.
public TGSConfigEntry[] CellGetSettings()
{
if (cells == null)
return null;
int cellCount = cells.Count;
TGSConfigEntry[] cellSettings = new TGSConfigEntry[cellCount];
for (int k = 0; k < cellCount; k++)
{
if (cells[k] == null)
continue;
cellSettings[k].territoryIndex = cells[k].territoryIndex;
cellSettings[k].visible = cells[k].visibleSelf;
cellSettings[k].color = CellGetColor(k);
cellSettings[k].textureIndex = CellGetTextureIndex(k);
cellSettings[k].tag = cells[k].tag;
cellSettings[k].canCross = cells[k].canCross;
cellSettings[k].crossCost = cells[k].GetSidesCost();
cellSettings[k].crossSidesCost = cells[k].crossCost;
}
return cellSettings;
}
///
/// Sets cell configuration data from a string
///
///
///
public void CellSetConfigurationData(string cellData, int[] filterTerritories)
{
if (cells == null)
return;
string[] cellsInfo = cellData.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
char[] separators = new char[] { ',' };
if (cellsInfo.Length != cells.Count)
{
Debug.LogWarning("Grids 2D Config component has different cell count than grid itself.");
}
for (int k = 0; k < cellsInfo.Length && k < cells.Count; k++)
{
if (cells[k] == null)
continue;
string[] cellInfo = cellsInfo[k].Split(separators, StringSplitOptions.RemoveEmptyEntries);
int length = cellInfo.Length;
if (length > 1)
{
int territoryIndex = Misc.FastConvertToInt(cellInfo[1]);
if (filterTerritories != null && !filterTerritories.Contains(territoryIndex))
continue;
cells[k].territoryIndex = (short)territoryIndex;
}
if (length > 0)
{
cells[k].visible = cellInfo[0] != "0";
}
Color color = new Color(0, 0, 0, 0);
if (length > 5)
{
Single.TryParse(cellInfo[2], out color.a);
if (color.a > 0)
{
Single.TryParse(cellInfo[3], NumberStyles.Any, CultureInfo.InvariantCulture, out color.r);
Single.TryParse(cellInfo[4], NumberStyles.Any, CultureInfo.InvariantCulture, out color.g);
Single.TryParse(cellInfo[5], NumberStyles.Any, CultureInfo.InvariantCulture, out color.b);
}
}
int textureIndex = -1;
if (length > 6)
{
textureIndex = Misc.FastConvertToInt(cellInfo[6]);
}
if (color.a > 0 || textureIndex >= 1)
{
CellToggleRegionSurface(k, true, color, false, textureIndex);
}
if (length > 7)
{
CellSetTag(k, Misc.FastConvertToInt(cellInfo[7]));
}
if (length > 8)
{
CellSetCanCross(k, cellInfo[8] != "0");
}
}
needUpdateTerritories = true;
needRefreshRouteMatrix = true;
Redraw();
isDirty = true;
}
public void CellSetSettings(TGSConfigEntry[] cellSettings, int[] filterTerritories)
{
if (cellSettings == null)
return;
if (cells == null)
{
OnEnable();
if (cells == null) return;
}
if (cellSettings.Length != cells.Count)
{
Debug.LogWarning("Grids 2D Config component has different cell count than grid itself.");
}
// Get territory count
int maxTerritoryIndex = 0;
for (int k = 0; k < cellSettings.Length; k++)
{
if (cellSettings[k].territoryIndex > maxTerritoryIndex)
{
maxTerritoryIndex = cellSettings[k].territoryIndex;
}
}
_numTerritories = maxTerritoryIndex + 1;
for (int k = 0; k < cellSettings.Length && k < cells.Count; k++)
{
int territoryIndex = cellSettings[k].territoryIndex;
if (filterTerritories != null && !filterTerritories.Contains(territoryIndex))
continue;
Cell cell = cells[k];
cell.territoryIndex = (short)territoryIndex;
cell.visible = cellSettings[k].visible;
Color color = cellSettings[k].color;
int textureIndex = cellSettings[k].textureIndex;
if (color.a > 0 || textureIndex >= 1)
{
CellToggleRegionSurface(k, true, color, false, textureIndex);
}
cell.tag = cellSettings[k].tag;
cell.canCross = cellSettings[k].canCross;
if (cellSettings[k].crossSidesCost != null && cellSettings[k].crossSidesCost.Length > 0)
{
cell.crossCost = cellSettings[k].crossSidesCost;
}
else
{
cell.SetAllSidesCost(cellSettings[k].crossCost);
}
}
needUpdateTerritories = true;
needRefreshRouteMatrix = true;
Redraw();
isDirty = true;
}
///
/// Returns the cell located at given row and column
///
public Cell CellGetAtPosition(int column, int row)
{
if (_gridTopology == GridTopology.Irregular) return null;
if (column >= _cellColumnCount || column < 0 || row >= _cellRowCount || row < 0) return null;
int index = row * _cellColumnCount + column;
if (index >= 0 && index < cells.Count)
return cells[index];
return null;
}
///
/// Traces a line between two positions and check if there's no cell blocking the line
///
/// true, if there's a straight path of non-blocking cells between the two positionsfalse otherwise.
/// Start position.
/// End position.
/// Cell indices.
/// Optional cell layer mask
/// Resolution of the line. Increase to improve line accuracy.
/// If set to true, all vertices of destination cell will be considered instead of its center
/// If set to true, the LOS test ignores cells' canCross field
/// If set to true, the last cell will also be evaluated against canCross field and group mask. By default the last or target cell is not checked as long as it results in a visible cell from the starting cell, but if you want to ensure the last cell also passes the canCross or group mask criteria then pass true to this parameter.
public bool CellGetLineOfSight(Vector3 startPosition, Vector3 endPosition, ref List cellIndices, ref List worldPositions, int cellGroupMask = -1, int lineResolution = 2, bool exhaustiveCheck = false, bool ignoreCanCrossCheck = false, bool checkLastCell = false)
{
cellIndices = null;
Cell startCell = CellGetAtPosition(startPosition, true);
Cell endCell = CellGetAtPosition(endPosition, true);
if (startCell == null || endCell == null)
{
return false;
}
int cell1 = CellGetIndex(startCell);
int cell2 = CellGetIndex(endCell);
if (cell1 < 0 || cell2 < 0)
return false;
return CellGetLineOfSight(cell1, cell2, ref cellIndices, ref worldPositions, cellGroupMask, lineResolution, exhaustiveCheck, ignoreCanCrossCheck, checkLastCell);
}
///
/// Traces a line between two positions and check if there's no cell blocking the line
///
/// true, if there's a straight path of non-blocking cells between the two positionsfalse otherwise.
/// Cell indices.
/// Optional cell layer mask
/// Resolution of the line. Increase to improve line accuracy.
/// If set to true, all vertices of destination cell will be considered instead of its center
/// If set to true, the LOS test ignores cells' canCross field
/// If set to true, the last cell will also be evaluated against canCross field and group mask. By default the last or target cell is not checked as long as it results in a visible cell from the starting cell, but if you want to ensure the last cell also passes the canCross or group mask criteria then pass true to this parameter.
public bool CellGetLineOfSight(int startCellIndex, int endCellIndex, ref List cellIndices, ref List worldPositions, int cellGroupMask = -1, int lineResolution = 2, bool exhaustiveCheck = false, bool ignoreCanCrossCheck = false, bool checkLastCell = false)
{
if (cellIndices == null)
{
cellIndices = new List();
}
else
{
cellIndices.Clear();
}
if (worldPositions == null)
{
worldPositions = new List();
}
else
{
worldPositions.Clear();
}
if (startCellIndex < 0 || startCellIndex >= cells.Count || endCellIndex < 0 || endCellIndex >= cells.Count)
return false;
Vector3 startPosition = CellGetPosition(startCellIndex);
Vector3 endPosition;
int vertexCount = exhaustiveCheck ? cells[endCellIndex].region.points.Count : 0;
bool success = true;
for (int p = 0; p <= vertexCount; p++)
{
if (p == 0)
{
endPosition = CellGetPosition(endCellIndex);
}
else
{
cellIndices.Clear();
worldPositions.Clear();
endPosition = CellGetVertexPosition(endCellIndex, p - 1);
}
int numSteps;
switch (_gridTopology)
{
case GridTopology.Hexagonal:
// Hexagon distance
numSteps = CellGetHexagonDistance(startCellIndex, endCellIndex);
lineResolution = Mathf.Max(2, lineResolution);
numSteps *= lineResolution;
break;
case GridTopology.Box:
numSteps = CellGetBoxDistance(startCellIndex, endCellIndex);
lineResolution = Mathf.Max(2, lineResolution);
numSteps *= lineResolution;
if (numSteps % 2 == 0)
numSteps++;
break;
default:
float dist = Vector3.Distance(startPosition, endPosition);
numSteps = Mathf.CeilToInt(dist * lineResolution);
break;
}
Cell lastCell = cells[startCellIndex];
success = true;
for (int k = 1; k <= numSteps; k++)
{
Vector3 position = Vector3.Lerp(startPosition, endPosition, (float)k / numSteps);
Cell cell = k == numSteps ? cells[endCellIndex] : CellGetAtPosition(position, true);
if (cell != null && cell != lastCell)
{
if (checkLastCell || cell != cells[endCellIndex])
{
if (!cell.canCross && !ignoreCanCrossCheck)
{
success = false;
break;
}
if ((cell.group & cellGroupMask) == 0)
{
success = false;
break;
}
}
// Check LOD blocks
if (LOSIsBlocked(lastCell, cell) || LOSIsBlocked(cell, lastCell))
{
success = false;
break;
}
cellIndices.Add(cell.index);
lastCell = cell;
}
worldPositions.Add(position);
}
if (success)
{
if (p == 0 && _gridTopology != GridTopology.Irregular)
{
return true;
}
break;
}
}
if (success)
{
CellGetLine(startCellIndex, endCellIndex, ref cellIndices, ref worldPositions, lineResolution);
}
return success;
}
///
/// Removes any cell from a givel list of indices which are not in LOS
///
/// Start cell index.
/// Cell indices.
/// Line resolution.
/// If set to true exhaustive check.
/// If set to true, the LOS test ignores cells' canCross field
/// If set to true, the last cell will also be evaluated against canCross field and group mask. By default the last or target cell is not checked as long as it results in a visible cell from the starting cell, but if you want to ensure the last cell also passes the canCross or group mask criteria then pass true to this parameter.
public void CellTestLineOfSight(int startCellIndex, List targetCellIndices, int cellGroupMask = -1, int lineResolution = 2, bool exhaustiveCheck = false, bool ignoreCanCrossCheck = false, bool checkLastCell = false)
{
int count = targetCellIndices.Count;
List dummyPositions = null;
cellIteration++;
for (int k = 0; k < count; k++)
{
int targetCellIndex = targetCellIndices[k];
if (cells[targetCellIndex].iteration != cellIteration)
{
if (CellGetLineOfSight(startCellIndex, targetCellIndex, ref tempListCells, ref dummyPositions, cellGroupMask, lineResolution, exhaustiveCheck, ignoreCanCrossCheck, checkLastCell))
{
int lineCount = tempListCells.Count;
for (int j = 0; j < lineCount; j++)
{
int index = tempListCells[j];
cells[index].iteration = cellIteration;
}
}
else
{
targetCellIndices.RemoveAt(k);
k--;
count--;
}
}
}
}
bool LOSIsBlocked(Cell cell1, Cell cell2)
{
switch (_gridTopology)
{
case GridTopology.Box:
int row1 = cell1.row;
int column1 = cell1.column;
int row2 = cell2.row;
int column2 = cell2.column;
if (column1 == column2 && row1 == row2)
return false;
bool blocksVertically = row1 < row2 ? cell2.GetSideBlocksLOS(CELL_SIDE.Bottom) : cell2.GetSideBlocksLOS(CELL_SIDE.Top);
bool blocksHorizontally = column1 < column2 ? cell2.GetSideBlocksLOS(CELL_SIDE.Left) : cell2.GetSideBlocksLOS(CELL_SIDE.Right);
if (row1 == row2)
{
return blocksHorizontally;
}
else if (column1 == column2)
{
return blocksVertically;
}
else
{
return blocksHorizontally || blocksVertically;
}
default:
Vector2 dir = cell2.center - cell1.center;
CELL_SIDE side = GetSideByVector(dir);
return cell2.GetSideBlocksLOS(side);
}
}
///
/// Returns a line composed of cells and world positions from starting cell to ending cell
///
/// true, if there's a straight path of non-blocking cells between the two positionsfalse otherwise.
/// Cell indices.
/// Resolution of the line. Increase to improve line accuracy.
public void CellGetLine(int startCellIndex, int endCellIndex, ref List cellIndices, ref List worldPositions, int lineResolution = 2)
{
if (cellIndices == null)
cellIndices = new List();
else
cellIndices.Clear();
if (worldPositions == null)
worldPositions = new List();
else
worldPositions.Clear();
if (startCellIndex < 0 || startCellIndex >= cells.Count || endCellIndex < 0 || endCellIndex >= cells.Count)
return;
Vector3 startPosition = CellGetPosition(startCellIndex);
Vector3 endPosition = CellGetPosition(endCellIndex);
int numSteps;
switch (_gridTopology)
{
case GridTopology.Hexagonal:
// Hexagon distance
numSteps = CellGetHexagonDistance(startCellIndex, endCellIndex);
lineResolution = Mathf.Max(2, lineResolution);
numSteps *= lineResolution;
break;
case GridTopology.Box:
numSteps = CellGetBoxDistance(startCellIndex, endCellIndex);
lineResolution = Mathf.Max(2, lineResolution);
numSteps *= lineResolution;
if (numSteps % 2 == 0)
numSteps++;
break;
default:
float dist = Vector3.Distance(startPosition, endPosition);
dist *= 2f / (1f + Mathf.Sqrt(_numCells));
numSteps = Mathf.CeilToInt(dist * lineResolution);
break;
}
Cell lastCell = cells[startCellIndex];
for (int k = 1; k <= numSteps; k++)
{
Vector3 position = Vector3.Lerp(startPosition, endPosition, (float)k / numSteps);
Cell cell = CellGetAtPosition(position, true);
if (cell != null && cell != lastCell)
{
cellIndices.Add(cell.index);
}
worldPositions.Add(position);
lastCell = cell;
}
}
///
/// Returns the hexagon distance between two cells (number of steps to reach end cell from start cell).
/// This method does not take into account cell masks or blocking cells. It just returns the distance.
///
/// The get hexagon distance.
/// Start cell index.
/// End cell index.
public int CellGetHexagonDistance(int startCellIndex, int endCellIndex)
{
if (cells == null)
return -1;
int cellCount = cells.Count;
if (startCellIndex < 0 || startCellIndex >= cellCount || endCellIndex < 0 || endCellIndex >= cellCount)
return -1;
int r0 = cells[startCellIndex].row;
int c0 = cells[startCellIndex].column;
int r1 = cells[endCellIndex].row;
int c1 = cells[endCellIndex].column;
int offset = _evenLayout ? 0 : 1;
int x0, y0, x1, y1;
if (_pointyTopHexagons)
{
y0 = r0;
x0 = c0 - Mathf.FloorToInt((r0 + offset) / 2);
y1 = r1;
x1 = c1 - Mathf.FloorToInt((r1 + offset) / 2);
}
else
{
y0 = r0 - Mathf.FloorToInt((c0 + offset) / 2);
x0 = c0;
y1 = r1 - Mathf.FloorToInt((c1 + offset) / 2);
x1 = c1;
}
int dx = x1 - x0;
int dy = y1 - y0;
int numSteps = Mathf.Max(Mathf.Abs(dx), Mathf.Abs(dy));
numSteps = Mathf.Max(numSteps, Mathf.Abs(dx + dy));
return numSteps;
}
///
/// Returns the number of steps between two cells in box topology.
/// This method does not take into account cell masks or blocking cells. It just returns the distance.
///
/// Start cell index.
/// End cell index.
public int CellGetBoxDistance(int startCellIndex, int endCellIndex)
{
if (cells == null)
return -1;
int cellCount = cells.Count;
if (startCellIndex < 0 || startCellIndex >= cellCount || endCellIndex < 0 || endCellIndex >= cellCount)
return -1;
int r0 = cells[startCellIndex].row;
int c0 = cells[startCellIndex].column;
int r1 = cells[endCellIndex].row;
int c1 = cells[endCellIndex].column;
int dx = Mathf.Abs(c1 - c0);
int dy = Mathf.Abs(r1 - r0);
if (_pathFindingUseDiagonals)
{
return Mathf.Max(dx, dy);
}
return dx + dy;
}
///
/// Removes any color or texture from a cell and hides it
///
/// Cell index.
public void CellClear(int cellIndex)
{
List cellIndices = new List();
cellIndices.Add(cellIndex);
CellClear(cellIndices);
}
///
/// Removes any color or texture from a list of cells and hides them
///
/// Cell indices.
public void CellClear(List cellIndices)
{
if (cellIndices == null)
return;
int count = cellIndices.Count;
for (int k = 0; k < count; k++)
{
// Check if cell has a SurfaceFader animator
int cellIndex = cellIndices[k];
Cell cell = cells[cellIndex];
if (cell.region.surfaceGameObject != null)
{
SurfaceFader sf = cell.region.surfaceGameObject.GetComponent();
if (sf != null)
{
sf.Finish(0);
}
cell.region.surfaceGameObject.SetActive(false);
}
if (cell.region.customMaterial != null)
{
cell.region.customMaterial = null;
}
}
}
///
/// Pregenerates and caches cell geometry for faster performance during gameplay
///
public void WarmCells()
{
int cellCount = cells.Count;
Material mat = GetColoredTexturedMaterialForCell(Color.white, null, false);
for (int k = 0; k < cellCount; k++)
{
GenerateCellRegionSurface(k, mat, Misc.Vector2one, Misc.Vector2zero, 0, false);
}
}
///
/// Draws a line over a cell side.
///
/// The line.
/// Cell index.
/// Side.
/// Color.
/// Width.
public GameObject DrawLine(int cellIndex, CELL_SIDE side, Color color, float width)
{
int v1 = 0, v2 = 0;
switch (_gridTopology)
{
case GridTopology.Box:
switch (side)
{
case CELL_SIDE.Right:
v1 = 1;
v2 = 2;
break;
case CELL_SIDE.Top:
v1 = 2;
v2 = 3;
break;
case CELL_SIDE.Bottom:
v1 = 0;
v2 = 1;
break;
case CELL_SIDE.Left:
v1 = 3;
v2 = 0;
break;
}
break;
case GridTopology.Hexagonal:
if (_pointyTopHexagons)
{
switch (side)
{
case CELL_SIDE.BottomLeft:
v1 = 5;
v2 = 0;
break;
case CELL_SIDE.Left:
v1 = 4;
v2 = 5;
break;
case CELL_SIDE.TopLeft:
v1 = 3;
v2 = 4;
break;
case CELL_SIDE.TopRight:
v1 = 2;
v2 = 3;
break;
case CELL_SIDE.Right:
v1 = 1;
v2 = 2;
break;
default: // BottomRight
v1 = 0;
v2 = 1;
break;
}
}
else
{
switch (side)
{
case CELL_SIDE.BottomLeft:
v1 = 0;
v2 = 1;
break;
case CELL_SIDE.Bottom:
v1 = 1;
v2 = 2;
break;
case CELL_SIDE.BottomRight:
v1 = 2;
v2 = 3;
break;
case CELL_SIDE.TopRight:
v1 = 3;
v2 = 4;
break;
case CELL_SIDE.Top:
v1 = 4;
v2 = 5;
break;
default: // BottomLeft
v1 = 5;
v2 = 0;
break;
}
}
break;
default:
return null;
}
GameObject line = new GameObject("Line");
line.layer = gameObject.layer;
LineRenderer lr = line.AddComponent();
lr.sortingOrder = _sortingOrder;
if (cellLineMat == null)
cellLineMat = Resources.Load("Materials/CellLine") as Material;
Material mat = Instantiate(cellLineMat) as Material;
disposalManager.MarkForDisposal(mat);
mat.color = color;
lr.sharedMaterial = mat;
lr.useWorldSpace = true;
lr.positionCount = 2;
lr.startWidth = width;
lr.endWidth = width;
Vector3 offset = transform.forward * 0.05f;
lr.SetPosition(0, CellGetVertexPosition(cellIndex, v1) - offset);
lr.SetPosition(1, CellGetVertexPosition(cellIndex, v2) - offset);
return line;
}
///
/// Draws a line connecting two cells centers
///
/// The line.
/// Cell index1.
/// Cell index2.
/// Color.
/// Width.
public GameObject DrawLine(int cellIndex1, int cellIndex2, Color color, float width)
{
GameObject line = new GameObject("Line");
line.layer = gameObject.layer;
LineRenderer lr = line.AddComponent();
lr.sortingOrder = _sortingOrder;
if (cellLineMat == null)
cellLineMat = Resources.Load("Materials/CellLine") as Material;
Material mat = Instantiate(cellLineMat) as Material;
disposalManager.MarkForDisposal(mat);
mat.color = color;
lr.sharedMaterial = mat;
lr.useWorldSpace = true;
lr.positionCount = 2;
lr.startWidth = width;
lr.endWidth = width;
Vector3 offset = transform.forward * 0.05f;
lr.SetPosition(0, CellGetPosition(cellIndex1) - offset);
lr.SetPosition(1, CellGetPosition(cellIndex2) - offset);
return line;
}
///
/// Escales the gameobject of a colored/textured surface
///
/// Scale.
public void CellScaleSurface(int cellIndex, float scale)
{
if (cellIndex < 0)
return;
Cell cell = cells[cellIndex];
GameObject surf = cell.region.surfaceGameObject;
ScaleSurface(surf, cell.center, scale);
}
///
/// Updates grid mesh to reflect only specific cells
///
public void RedrawCells(List| cells)
{
GenerateCellsMeshData(cells);
DrawCellBorders();
}
///
/// Creates a gameobject with SpriteRenderer and places it on top of the cell
///
public GameObject CellAddSprite(int cellIndex, Sprite sprite, bool adjustScale = true)
{
if (cellIndex < 0 || cellIndex >= cells.Count) return null;
GameObject go = new GameObject("Tile", typeof(SpriteRenderer));
go.layer = gameObject.layer;
SpriteRenderer r = go.GetComponent();
r.sprite = sprite;
Cell cell = cells[cellIndex];
go.transform.position = CellGetPosition(cell);
go.transform.forward = transform.forward;
if (adjustScale)
{
float tileWidth;
if (_gridTopology == GridTopology.Hexagonal)
{
tileWidth = Vector3.Distance(CellGetVertexPosition(0, 0), CellGetVertexPosition(0, 4));
}
else
{
tileWidth = Vector3.Distance(CellGetVertexPosition(0, 0), CellGetVertexPosition(0, 3));
}
float spriteWidth = Mathf.Abs(sprite.bounds.max.x - sprite.bounds.min.x);
float scale = tileWidth / spriteWidth;
go.transform.localScale = new Vector3(scale, scale, 1f);
}
return go;
}
#endregion
}
}
| | | | | | | |