Files
2024-11-20 15:21:28 +01:00

800 lines
32 KiB
C#

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System;
using static Gaia.GaiaConstants;
using System.Security.AccessControl;
namespace Gaia
{
/// <summary>
/// Terrain utility functions
/// </summary>
public class TerrainHelper : MonoBehaviour
{
[Range(1, 5), Tooltip("Number of smoothing interations to run. Can be run multiple times.")]
public int m_smoothIterations = 1;
//Knock ourselves out if we happen to be left there in play mode
void Awake()
{
gameObject.SetActive(false);
}
/// <summary>
/// Flatten all the active terrains
/// </summary>
public static void Flatten()
{
FlattenTerrain(Terrain.activeTerrains);
}
/// <summary>
/// Flatten the terrain passed in
/// </summary>
/// <param name="terrain">Terrain to be flattened</param>
public static void FlattenTerrain(Terrain terrain)
{
float[,] heights = new float[terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution];
terrain.terrainData.SetHeights(0, 0, heights);
}
/// <summary>
/// Flatten all the terrains passed in
/// </summary>
/// <param name="terrains">Terrains to be flattened</param>
public static void FlattenTerrain(Terrain[] terrains)
{
foreach (Terrain terrain in terrains)
{
float[,] heights = new float[terrain.terrainData.heightmapResolution, terrain.terrainData.heightmapResolution];
terrain.terrainData.SetHeights(0, 0, heights);
}
}
/// <summary>
/// Stitch the terrains together with unity set neighbors calls
/// </summary>
public static void Stitch()
{
StitchTerrains(Terrain.activeTerrains);
}
/// <summary>
/// Stitch the terrains together - wont align them although should update this to support that as well.
/// </summary>
/// <param name="terrains">Array of terrains to organise as neighbors</param>
public static void StitchTerrains(Terrain[] terrains)
{
Terrain right = null;
Terrain left = null;
Terrain bottom = null;
Terrain top = null;
foreach (Terrain terrain in terrains)
{
right = null;
left = null;
bottom = null;
top = null;
foreach (Terrain neighbor in terrains)
{
//Check to see if neighbor is above or below
if (neighbor.transform.position.x == terrain.transform.position.x)
{
if ((neighbor.transform.position.z + neighbor.terrainData.size.z) == terrain.transform.position.z)
{
top = neighbor;
}
else if ((terrain.transform.position.z + terrain.terrainData.size.z) == neighbor.transform.position.z)
{
bottom = neighbor;
}
}
else if (neighbor.transform.position.z == terrain.transform.position.z)
{
if ((neighbor.transform.position.x + neighbor.terrainData.size.z) == terrain.transform.position.z)
{
left = neighbor;
}
else if ((terrain.transform.position.x + terrain.terrainData.size.x) == neighbor.transform.position.x)
{
right = neighbor;
}
}
}
terrain.SetNeighbors(left, top, right, bottom);
}
}
/// <summary>
/// Smooth the active terrain - needs to be extended to all and to handle edges
/// </summary>
/// <param name="iterations">Number of smoothing iterations</param>
public void Smooth()
{
Smooth(m_smoothIterations);
}
/// <summary>
/// Smooth the active terrain - needs to be extended to all and to handle edges
/// </summary>
/// <param name="iterations">Number of smoothing iterations</param>
public static void Smooth(int iterations)
{
UnityHeightMap hm = new UnityHeightMap(Terrain.activeTerrain);
hm.Smooth(iterations);
hm.SaveToTerrain(Terrain.activeTerrain);
}
/// <summary>
/// Get the vector of the centre of the active terrain, and flush to ground level if asked to
/// </summary>
/// <param name="flushToGround">If true set it flush to the ground</param>
/// <returns>Vector3.zero if no terrain, otherwise the centre of it</returns>
public static Vector3 GetActiveTerrainCenter(bool flushToGround = true)
{
Bounds b = new Bounds();
Terrain t = GetActiveTerrain();
if (GetTerrainBounds(t, ref b))
{
if (flushToGround == true)
{
return new Vector3(b.center.x, t.SampleHeight(b.center), b.center.z);
}
else
{
return b.center;
}
}
return Vector3.zero;
}
/// <summary>
/// Gets the world map terrain from the scene
/// </summary>
/// <returns>The world map terrain</returns>
public static Terrain GetWorldMapTerrain()
{
foreach (Terrain t in Terrain.activeTerrains)
{
if (TerrainHelper.IsWorldMapTerrain(t))
{
return t;
}
}
//still no world map terrain? might be a deactivated GameObject, check those as well
GameObject worldMapGO = GaiaUtils.FindObjectDeactivated(GaiaConstants.worldMapTerrainPrefix + "_", false);
if (worldMapGO != null)
{
Terrain t = worldMapGO.GetComponent<Terrain>();
if (t != null)
{
return t;
}
}
return null;
}
/// <summary>
/// Get any active terrain - pref active terrain
/// </summary>
/// <returns>Any active terrain or null</returns>
public static Terrain GetActiveTerrain()
{
//Grab active terrain if we can
Terrain terrain = Terrain.activeTerrain;
if (terrain != null && terrain.isActiveAndEnabled)
{
return terrain;
}
//Then check rest of terrains
for (int idx = 0; idx < Terrain.activeTerrains.Length; idx++)
{
terrain = Terrain.activeTerrains[idx];
if (terrain != null && terrain.isActiveAndEnabled)
{
return terrain;
}
}
return null;
}
/// <summary>
/// Get the layer mask of the active terrain, or default if there isnt one
/// </summary>
/// <returns>Layermask of activer terrain or default if there isnt one</returns>
public static LayerMask GetActiveTerrainLayer()
{
LayerMask layer = new LayerMask();
Terrain terrain = GetActiveTerrain();
if (terrain != null)
{
layer.value = 1 << terrain.gameObject.layer;
return layer;
}
layer.value = 1 << LayerMask.NameToLayer("Default");
return layer;
}
/// <summary>
/// Get the layer mask of the active terrain, or default if there isnt one
/// </summary>
/// <returns>Layermask of activer terrain or default if there isnt one</returns>
public static LayerMask GetActiveTerrainLayerAsInt()
{
LayerMask layerValue = GetActiveTerrainLayer().value;
for (int layerIdx = 0; layerIdx < 32; layerIdx++)
{
if (layerValue == (1 << layerIdx))
{
return layerIdx;
}
}
return LayerMask.NameToLayer("Default");
}
/// <summary>
/// Get the number of active terrain tiles in this scene
/// </summary>
/// <returns>Number of terrains in the scene</returns>
public static int GetActiveTerrainCount()
{
Terrain terrain;
int terrainCount = 0;
for (int idx = 0; idx < Terrain.activeTerrains.Length; idx++)
{
terrain = Terrain.activeTerrains[idx];
if (terrain != null && terrain.isActiveAndEnabled)
{
terrainCount++;
}
}
return terrainCount;
}
#if GAIA_PRO_PRESENT
/// <summary>
/// Get the terrain scene that matches this location, otherwise return null
/// </summary>
/// <param name="locationWU">Location to check in world units</param>
/// <returns>Terrain here or null</returns>
public static TerrainScene GetDynamicLoadedTerrain(Vector3 locationWU, GaiaSessionManager gsm = null)
{
if (gsm == null)
{
gsm = GaiaSessionManager.GetSessionManager(false);
}
foreach (TerrainScene terrainScene in TerrainLoaderManager.TerrainScenes)
{
if (terrainScene.m_bounds.min.x <= locationWU.x && terrainScene.m_bounds.min.z <= locationWU.z && terrainScene.m_bounds.max.x >= locationWU.x && terrainScene.m_bounds.max.z >= locationWU.z)
{
return terrainScene;
}
}
return null;
}
#endif
/// <summary>
/// Get the terrain that matches this location, otherwise return null
/// </summary>
/// <param name="locationWU">Location to check in world units</param>
/// <returns>Terrain here or null</returns>
public static Terrain GetTerrain(Vector3 locationWU, bool selectWorldMapTerrains = false)
{
Terrain terrain;
Vector3 terrainMin = new Vector3();
Vector3 terrainMax = new Vector3();
//First check active terrain - most likely already selected
terrain = Terrain.activeTerrain;
if (terrain != null && terrain.terrainData !=null &&(selectWorldMapTerrains == TerrainHelper.IsWorldMapTerrain(terrain)))
{
terrainMin = terrain.GetPosition();
terrainMax = terrainMin + terrain.terrainData.size;
if (locationWU.x >= terrainMin.x && locationWU.x <= terrainMax.x)
{
if (locationWU.z >= terrainMin.z && locationWU.z <= terrainMax.z)
{
return terrain;
}
}
}
//Then check rest of terrains
Terrain closestTerrain = null;
float closestDistance = float.MaxValue;
for (int idx = 0; idx < Terrain.activeTerrains.Length; idx++)
{
terrain = Terrain.activeTerrains[idx];
if (terrain.terrainData==null || (selectWorldMapTerrains != TerrainHelper.IsWorldMapTerrain(terrain)))
{
continue;
}
terrainMin = terrain.GetPosition();
terrainMax = terrainMin + terrain.terrainData.size;
if (locationWU.x >= terrainMin.x && locationWU.x <= terrainMax.x)
{
if (locationWU.z >= terrainMin.z && locationWU.z <= terrainMax.z)
{
return terrain;
}
}
if (closestTerrain == null || Vector3.Distance(terrain.transform.position, locationWU) < closestDistance)
{
closestTerrain = terrain;
}
}
return closestTerrain;
}
/// <summary>
/// Get the bounds of the space encapsulated by the supplied terrain
/// </summary>
/// <param name="terrain">Terrain to get bounds for</param>
/// <param name="bounds">Bounds to update</param>
/// <returns>True if we got some terrain bounds</returns>
public static bool GetTerrainBounds(Terrain terrain, ref Bounds bounds)
{
if (terrain == null)
{
return false;
}
bounds.center = terrain.transform.position;
bounds.size = terrain.terrainData.size;
bounds.center += bounds.extents;
return true;
}
/// <summary>
/// Get the bounds of the terrain at this location or fail with a null
/// </summary>
/// <param name="locationWU">Location to check and get terrain for</param>
/// <returns>Bounds of selected terrain or null if invalid for some reason</returns>
public static bool GetTerrainBounds(ref BoundsDouble bounds, bool activeTerrainsOnly = false)
{
//Terrain terrain = GetTerrain(locationWU);
//if (terrain == null)
//{
// return false;
//}
//bounds.center = terrain.transform.position;
//bounds.size = terrain.terrainData.size;
//bounds.center += bounds.extents;
Vector3Double accumulatedCenter = new Vector3Double();
//Do we use dynamic loaded terrains in the scene?
if (GaiaUtils.HasDynamicLoadedTerrains() && !activeTerrainsOnly)
{
#if GAIA_PRO_PRESENT
//we do have dynamic terrains -> calculate the bounds according to the terrain scene data in the session
GaiaSessionManager gsm = GaiaSessionManager.GetSessionManager(false);
foreach (TerrainScene t in TerrainLoaderManager.TerrainScenes)
{
accumulatedCenter += t.m_bounds.center;
}
bounds.center = accumulatedCenter / TerrainLoaderManager.TerrainScenes.Count;
foreach (TerrainScene t in TerrainLoaderManager.TerrainScenes)
{
bounds.Encapsulate(t.m_bounds);
}
#endif
}
else
{
//no placeholder -> calculate bounds according to the active terrains in the scene
foreach (Terrain t in Terrain.activeTerrains)
{
if (!TerrainHelper.IsWorldMapTerrain(t))
{
if (t.terrainData != null)
{
accumulatedCenter += new Vector3Double(t.transform.position) + new Vector3Double(t.terrainData.bounds.extents);
}
else
{
Debug.LogWarning("Terrain " + t.name + " in the scene is missing the terrain data object!");
}
}
}
if (Terrain.activeTerrains.Length > 0)
{
bounds.center = accumulatedCenter / Terrain.activeTerrains.Length;
}
else
{
bounds.center = Vector3.zero;
}
foreach (Terrain t in Terrain.activeTerrains)
{
if (!TerrainHelper.IsWorldMapTerrain(t))
{
if (t.terrainData != null)
{
Bounds newBounds = new Bounds();
newBounds.center = t.transform.position;
newBounds.size = t.terrainData.size;
newBounds.center += t.terrainData.bounds.extents;
bounds.Encapsulate(newBounds);
}
}
}
}
return true;
}
/// <summary>
/// Get a random location on the terrain supplied
/// </summary>
/// <param name="terrain">Terrain to check</param>
/// <param name="start">Start locaton</param>
/// <param name="radius">Radius to hunt in</param>
/// <returns></returns>
public static Vector3 GetRandomPositionOnTerrain(Terrain terrain, Vector3 start, float radius)
{
Vector3 newLocation;
Vector3 terrainMin = terrain.GetPosition();
Vector3 terrainMax = terrainMin + terrain.terrainData.size;
while (true)
{
//Get a new location
newLocation = UnityEngine.Random.insideUnitSphere * radius;
newLocation = start + newLocation;
//Make sure the new location is within the terrain bounds
if (newLocation.x >= terrainMin.x && newLocation.x <= terrainMax.x)
{
if (newLocation.z >= terrainMin.z && newLocation.z <= terrainMax.z)
{
//Update it to be on the terrain surface
newLocation.y = terrain.SampleHeight(newLocation);
return newLocation;
}
}
}
}
/// <summary>
/// Returns the bounds of a terrain in world space (The bounds in the terrainData object is in local space of the terrain itself)
/// </summary>
/// <param name="t">The terrain to get the bounds in world space for.</param>
/// <returns></returns>
public static Bounds GetWorldSpaceBounds(Terrain t)
{
Bounds worldSpaceBounds = t.terrainData.bounds;
worldSpaceBounds.center = new Vector3(worldSpaceBounds.center.x + t.transform.position.x, worldSpaceBounds.center.y + t.transform.position.y, worldSpaceBounds.center.z + t.transform.position.z);
return worldSpaceBounds;
}
/// <summary>
/// Clear all the trees on all the terrains
/// </summary>
public static void ClearSpawns(SpawnerResourceType resourceType, ClearSpawnFor clearSpawnFor, ClearSpawnFrom clearSpawnFrom, List<string> terrainNames = null, Spawner spawner = null)
{
if (terrainNames == null)
{
if (clearSpawnFor == ClearSpawnFor.AllTerrains)
{
if (GaiaUtils.HasDynamicLoadedTerrains())
{
GaiaSessionManager sessionManager = GaiaSessionManager.GetSessionManager();
terrainNames = TerrainLoaderManager.TerrainScenes.Select(x => x.GetTerrainName()).ToList();
}
else
{
terrainNames = Terrain.activeTerrains.Select(x => x.name).ToList();
}
}
else
{
terrainNames = new List<string> { spawner.GetCurrentTerrain().name };
}
}
string progressBarTitle = "Clearing...";
Action<Terrain> act = null;
switch (resourceType)
{
case SpawnerResourceType.TerrainTexture:
progressBarTitle = "Clearing Textures";
//Not supported, should not be required
throw new NotSupportedException("Clearing of Textures is currently not supported via the terrain helper");
case SpawnerResourceType.TerrainDetail:
progressBarTitle = "Clearing Terrain Details";
act = (t) => ClearDetailsOnSingleTerrain(t, spawner, clearSpawnFrom);
break;
case SpawnerResourceType.TerrainTree:
progressBarTitle = "Clearing Trees";
act = (t) => ClearTreesOnSingleTerrain(t, spawner, clearSpawnFrom);
break;
case SpawnerResourceType.GameObject:
progressBarTitle = "Clearing Game Objects";
act = (t) => ClearGameObjectsOnSingleTerrain(t, spawner, clearSpawnFrom);
break;
case SpawnerResourceType.Probe:
progressBarTitle = "Clearing Probes";
act = (t) => ClearGameObjectsOnSingleTerrain(t, spawner, clearSpawnFrom);
break;
case SpawnerResourceType.SpawnExtension:
progressBarTitle = "Clearing Spawn Extensions";
act = (t) => ClearSpawnExtensionsOnSingleTerrain(t, spawner, clearSpawnFrom);
break;
case SpawnerResourceType.StampDistribution:
//Not supported, should not be required
throw new NotSupportedException("Clearing of Stamps is currently not supported via the terrain helper");
}
if (GaiaUtils.HasDynamicLoadedTerrains())
{
GaiaUtils.CallFunctionOnDynamicLoadedTerrains(act, true, terrainNames);
}
else
{
for (int idx = 0; idx < terrainNames.Count; idx++)
{
ProgressBar.Show(ProgressBarPriority.Spawning ,progressBarTitle, progressBarTitle, idx + 1, terrainNames.Count(), true);
GameObject go = GameObject.Find(terrainNames[idx]);
if (go != null)
{
Terrain terrain = go.GetComponent<Terrain>();
act(terrain);
}
ProgressBar.Clear(ProgressBarPriority.Spawning);
}
}
}
private static void ClearTreesOnSingleTerrain(Terrain terrain, Spawner spawner, ClearSpawnFrom clearSpawnFrom)
{
if (spawner == null || clearSpawnFrom == ClearSpawnFrom.AnySource)
{
//No tree prototypes passed in => we delete any tree from any source
terrain.terrainData.treeInstances = new TreeInstance[0];
}
else
{
//We need to get the correct prototype Ids for this terrain only
//Prototype Ids might be different from terrain to terrain, depending on when / how the prototype was added
List<int> treePrototypeIds = new List<int>();
foreach (SpawnRule sr in spawner.m_settings.m_spawnerRules)
{
if (sr.m_resourceType == GaiaConstants.SpawnerResourceType.TerrainTree)
{
int treePrototypeIndex = spawner.m_settings.m_resources.PrototypeIdxInTerrain(sr.m_resourceType, sr.m_resourceIdx, terrain);
if (treePrototypeIndex != -1)
{
treePrototypeIds.Add(treePrototypeIndex);
}
}
}
//Reapply the tree instances on this terrain, but leave all "to be deleted" ids out via the where clause
terrain.terrainData.SetTreeInstances(terrain.terrainData.treeInstances.Where(x => !treePrototypeIds.Contains(x.prototypeIndex)).ToArray(), true);
}
terrain.Flush();
}
/// <summary>
/// Clear all the details (grass) on all the terrains
/// </summary>
private static void ClearDetailsOnSingleTerrain(Terrain terrain, Spawner spawner, ClearSpawnFrom clearSpawnFrom)
{
int[,] details = new int[terrain.terrainData.detailWidth, terrain.terrainData.detailHeight];
if (spawner == null || clearSpawnFrom == ClearSpawnFrom.AnySource)
{
for (int dtlIdx = 0; dtlIdx < terrain.terrainData.detailPrototypes.Length; dtlIdx++)
{
terrain.terrainData.SetDetailLayer(0, 0, dtlIdx, details);
}
}
else
{
//We need to get the correct prototype Ids for this terrain only
//Prototype Ids might be different from terrain to terrain, depending on when / how the prototype was added
List<int> detailPrototypeIds = new List<int>();
foreach (SpawnRule sr in spawner.m_settings.m_spawnerRules)
{
if (sr.m_resourceType == GaiaConstants.SpawnerResourceType.TerrainDetail)
{
int detailPrototypeIndex = spawner.m_settings.m_resources.PrototypeIdxInTerrain(sr.m_resourceType, sr.m_resourceIdx, terrain);
if (detailPrototypeIndex != -1)
{
detailPrototypeIds.Add(detailPrototypeIndex);
}
}
}
for (int dtlIdx = 0; dtlIdx < detailPrototypeIds.Count; dtlIdx++)
{
terrain.terrainData.SetDetailLayer(0, 0, detailPrototypeIds[dtlIdx], details);
}
}
terrain.Flush();
}
private static void ClearGameObjectsOnSingleTerrain(Terrain terrain, Spawner spawner, ClearSpawnFrom clearSpawnFrom)
{
Spawner[] allAffectedSpawners;
if (clearSpawnFrom == ClearSpawnFrom.OnlyThisSpawner)
{
allAffectedSpawners = new Spawner[1] { spawner };
}
else
{
allAffectedSpawners = Resources.FindObjectsOfTypeAll<Spawner>();
}
foreach (Spawner sp in allAffectedSpawners)
{
foreach (SpawnRule sr in sp.m_settings.m_spawnerRules.Where(x=>x.m_resourceType == SpawnerResourceType.GameObject || x.m_resourceType == SpawnerResourceType.SpawnExtension || x.m_resourceType == SpawnerResourceType.Probe))
sp.ClearGameObjectsForRule(sr, false, terrain);
}
}
private static void ClearSpawnExtensionsOnSingleTerrain(Terrain terrain, Spawner spawner, ClearSpawnFrom clearSpawnFrom)
{
Spawner[] allAffectedSpawners;
if (clearSpawnFrom == ClearSpawnFrom.OnlyThisSpawner)
{
allAffectedSpawners = new Spawner[1] { spawner };
}
else
{
allAffectedSpawners = Resources.FindObjectsOfTypeAll<Spawner>();
}
foreach (Spawner sp in allAffectedSpawners)
{
foreach (SpawnRule sr in sp.m_settings.m_spawnerRules)
{
sp.ClearSpawnExtensionsForRule(sr);
sp.ClearGameObjectsForRule(sr, false, terrain);
}
}
}
/// <summary>
/// Get the range from the terrain
/// </summary>
/// <returns></returns>
public static float GetRangeFromTerrain()
{
Terrain t = Gaia.TerrainHelper.GetActiveTerrain();
if (t != null)
{
return Mathf.Max(t.terrainData.size.x, t.terrainData.size.z) / 2f;
}
return 0f;
}
/// <summary>
/// Returns true if this terrain is a world map terrain.
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public static bool IsWorldMapTerrain(Terrain t)
{
return t.name.StartsWith(GaiaConstants.worldMapTerrainPrefix);
}
/// <summary>
/// Returns terrain names of terrains that intersect with the given bounds object
/// </summary>
/// <param name="bounds">A bounds object to check against the terrains. Needs to be in absolute world space position, mind the current origin offset!</param>
/// <returns></returns>
public static string[] GetTerrainsIntersectingBounds(BoundsDouble bounds)
{
if (GaiaUtils.HasDynamicLoadedTerrains())
{
GaiaSessionManager sessionManager = GaiaSessionManager.GetSessionManager();
if (sessionManager == null)
{
Debug.LogError("Trying to get terrains that intersect with bounds, but there is no session manager in scene.");
return null;
}
return TerrainLoaderManager.TerrainScenes.Where(x => x.m_bounds.Intersects(bounds)).Select(x => x.GetTerrainName()).ToArray();
}
else
{
List<string> affectedTerrainNames = new List<string>();
foreach (Terrain t in Terrain.activeTerrains)
{
if (bounds.Intersects(TerrainHelper.GetWorldSpaceBounds(t)))
{
affectedTerrainNames.Add(t.name);
}
}
return affectedTerrainNames.ToArray();
}
}
public static TerrainLayer GetLayerFromPrototype(ResourceProtoTexture proto)
{
foreach (Terrain t in Terrain.activeTerrains)
{
foreach (TerrainLayer layer in t.terrainData.terrainLayers)
{
if (proto != null && layer != null)
{
if (proto.m_texture != null && layer.diffuseTexture != null)
{
if (PWCommon3.Utils.IsSameTexture(proto.m_texture, layer.diffuseTexture, false) == true)
{
return layer;
}
}
}
}
}
return null;
}
public static int GetTreePrototypeIDFromSpawnRule(SpawnRule sr, Terrain terrain)
{
Spawner spawner = CollisionMask.m_allTreeSpawners.FirstOrDefault(x => x.m_settings.m_spawnerRules.Contains(sr));
if (spawner != null)
{
GameObject treePrefabInRule = spawner.m_settings.m_resources.m_treePrototypes[sr.m_resourceIdx].m_desktopPrefab;
for (int i = 0; i < terrain.terrainData.treePrototypes.Length; i++)
{
TreePrototype tp = terrain.terrainData.treePrototypes[i];
if (tp.prefab == treePrefabInRule)
{
return i;
}
}
}
return -1;
}
public static Vector3 GetWorldCenter(bool sampleHeight = false)
{
BoundsDouble bounds = new BoundsDouble();
GetTerrainBounds(ref bounds);
if (sampleHeight)
{
Terrain t = GetTerrain(bounds.center);
if (t != null)
{
Vector3 centerOnTerrain = t.transform.position + new Vector3(t.terrainData.size.x / 2f, 0f, t.terrainData.size.z / 2f);
float height = t.SampleHeight(centerOnTerrain);
return new Vector3(centerOnTerrain.x, height, centerOnTerrain.z);
}
else
{
return Vector3.zero;
}
}
else
{
return new Vector3((float)bounds.center.x, (float)bounds.center.y, (float)bounds.center.z);
}
}
}
}