Класс для навигации ботов способность обходить препятствия.
RecastNavigationSystem.cs
Код:// Copyright (C) NeoAxis Group Ltd. This is part of NeoAxis 3D Engine SDK. using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Runtime.InteropServices; using System.IO; using System.Drawing.Design; using Engine.FileSystem; using Engine.Utils; using Engine.EntitySystem; using Engine.MapSystem; using Engine.MathEx; using Engine.Renderer; namespace Engine { public class RecastNavigationSystemType : MapGeneralObjectType { public RecastNavigationSystemType() { AllowEmptyName = true; } } ////////////////////////////////////////////////////////////////////////////////////////////////////////// struct Wrapper { public const string library = "Recast"; public const CallingConvention convention = CallingConvention.Cdecl; [DllImport( Wrapper.library, EntryPoint = "Recast_Initialize", CallingConvention = Wrapper.convention )] public unsafe static extern IntPtr Initialize( ref Vec3 bmin, ref Vec3 bmax, float tileSize, float cellSize, float cellHeight, int minRegionSize, int mergeRegionSize, [MarshalAs( UnmanagedType.U1 )] bool monotonePartitioning, float maxEgdeLength, float maxEdgeError, int vertsPerPoly, float detailSampleDistance, float detailMaxSampleError, float agentHeight, float agentRadius, float agentMaxClimb, float agentMaxSlope ); [DllImport( Wrapper.library, EntryPoint = "Recast_NavQueryInit", CallingConvention = Wrapper.convention )] [return: MarshalAs( UnmanagedType.U1 )] public unsafe static extern bool NavQueryInit( IntPtr world, int maxNodes ); [DllImport( Wrapper.library, EntryPoint = "Recast_GetSizes", CallingConvention = Wrapper.convention )] public unsafe static extern void GetSizes( IntPtr world, out int maxTiles, out int maxPolysPerTile ); [DllImport( Wrapper.library, EntryPoint = "Recast_BuildAllTiles", CallingConvention = Wrapper.convention )] public unsafe static extern void BuildAllTiles( IntPtr world ); [DllImport( Wrapper.library, EntryPoint = "Recast_DestroyAllTiles", CallingConvention = Wrapper.convention )] public unsafe static extern void DestroyAllTiles( IntPtr world ); [DllImport( Wrapper.library, EntryPoint = "Recast_SetGeometry", CallingConvention = Wrapper.convention )] public unsafe static extern void SetGeometry( IntPtr world, IntPtr vertices, int vertexCount, IntPtr indices, int indexCount, int trianglesPerChunk ); [DllImport( Wrapper.library, EntryPoint = "Recast_Destroy", CallingConvention = Wrapper.convention )] public unsafe static extern void Destroy( IntPtr world ); [DllImport( Wrapper.library, EntryPoint = "Recast_GetNavigationMesh", CallingConvention = Wrapper.convention )] [return: MarshalAs( UnmanagedType.U1 )] public unsafe static extern bool GetNavigationMesh( IntPtr world, out Vec3* vertices, out int vertexCount ); [DllImport( Wrapper.library, EntryPoint = "Recast_FindPath", CallingConvention = Wrapper.convention )] [return: MarshalAs( UnmanagedType.U1 )] public unsafe static extern bool FindPath( IntPtr world, ref Vec3 start, ref Vec3 end, float stepSize, ref Vec3 polygonPickExtents, int maxPolygonPath, int maxSmoothPath, int maxSteerPoints, out Vec3* outPath, out int outPathCount ); [DllImport( Wrapper.library, EntryPoint = "Recast_FreeMemory", CallingConvention = Wrapper.convention )] public unsafe static extern void FreeMemory( IntPtr pointer ); [DllImport( Wrapper.library, EntryPoint = "Recast_LoadNavMesh", CallingConvention = Wrapper.convention )] [return: MarshalAs( UnmanagedType.U1 )] public unsafe static extern bool LoadNavMesh( IntPtr world, IntPtr data, int dataSize ); [DllImport( Wrapper.library, EntryPoint = "Recast_SaveNavMesh", CallingConvention = Wrapper.convention )] public unsafe static extern void SaveNavMesh( IntPtr world, out IntPtr data, out int dataSize ); [DllImport( Wrapper.library, EntryPoint = "Recast_BuildTile", CallingConvention = Wrapper.convention )] public unsafe static extern void BuildTile( IntPtr world, ref Vec3 position ); [DllImport( Wrapper.library, EntryPoint = "Recast_RemoveTile", CallingConvention = Wrapper.convention )] public unsafe static extern void RemoveTile( IntPtr world, ref Vec3 position ); } ////////////////////////////////////////////////////////////////////////////////////////////////////////// [ExtendedFunctionalityDescriptor( "Engine.Editor.RecastNavigationSystemExtendedFunctionalityDescriptor, RecastNavigationSystem.Editor" )] public class RecastNavigationSystem : MapGeneralObject { static List<RecastNavigationSystem> instances = new List<RecastNavigationSystem>(); static ReadOnlyCollection<RecastNavigationSystem> instancesReadOnly; [FieldSerialize( "boundsMin" )] Vec3 boundsMin = new Vec3( -100, -100, -100 ); [FieldSerialize( "boundsMax" )] Vec3 boundsMax = new Vec3( 100, 100, 100 ); [FieldSerialize( "tileSize" )] int tileSize = 32; [FieldSerialize( "cellSize" )] float cellSize = .3f; [FieldSerialize( "cellHeight" )] float cellHeight = .3f; [FieldSerialize( "trianglesPerChunk" )] int trianglesPerChunk = 512; [FieldSerialize( "minRegionSize" )] int minRegionSize = 8; [FieldSerialize( "mergeRegionSize" )] int mergeRegionSize = 20; [FieldSerialize( "monotonePartitioning" )] bool monotonePartitioning = false; [FieldSerialize( "maxEdgeLength" )] float maxEdgeLength = 12; [FieldSerialize( "maxEdgeError" )] float maxEdgeError = 1.3f; [FieldSerialize( "maxVerticesPerPolygon" )] int maxVerticesPerPolygon = 6; [FieldSerialize( "detailSampleDistance" )] float detailSampleDistance = 6; [FieldSerialize( "detailMaxSampleError" )] float detailMaxSampleError = 1; [FieldSerialize( "agentHeight" )] float agentHeight = 2.0f; [FieldSerialize( "agentRadius" )] float agentRadius = .6f; [FieldSerialize( "agentMaxClimb" )] float agentMaxClimb = .9f; [FieldSerialize( "agentMaxSlope" )] Degree agentMaxSlope = 45; [FieldSerialize( "geometries" )] List<Entity> geometries = new List<Entity>(); ReadOnlyCollection<Entity> geometriesReadOnly; [FieldSerialize( "alwaysDrawNavMesh" )] bool alwaysDrawNavMesh; [FieldSerialize( "pathfindingMaxNodes" )] int pathfindingMaxNodes = 8192; [FieldSerialize( "dataDirectory" )] string dataDirectory = "RecastNavigationSystem"; //[FieldSerialize( "gridHeight" )] //float gridHeight = .5f; IntPtr recastWorld; Vec3[] debugNavigationMeshVertices; int[] debugNavigationMeshIndices; //Vec3[] tileGridMeshVertices; //int[] tileGridMeshIndices; //Vec3[] cellGridMeshVertices; //int[] cellGridMeshIndices; //bool drawTileGrid; /////////////////////////////////////////// [TypeField] RecastNavigationSystemType __type = null; /// <summary> /// Gets the entity type. /// </summary> public new RecastNavigationSystemType Type { get { return __type; } } /////////////////////////////////////////// class IndexVertexBufferCollector { //!!!!!!need instancing public Vec3[] resultVertices = new Vec3[ 4096 ]; public int[] resultIndices = new int[ 4096 ]; public int resultVertexCount; public int resultIndexCount; public void Add( Vec3[] vertices, int vertexCount, int[] indices, int indexCount ) { int newVertexCount = resultVertexCount + vertexCount; int newIndexCount = resultIndexCount + indexCount; if( newVertexCount > resultVertices.Length ) { int s = resultVertices.Length; while( newVertexCount > s ) s *= 2; Vec3[] old = resultVertices; resultVertices = new Vec3[ s ]; Array.Copy( old, resultVertices, old.Length ); } if( newIndexCount > resultIndices.Length ) { int s = resultIndices.Length; while( newIndexCount > s ) s *= 2; int[] old = resultIndices; resultIndices = new int[ s ]; Array.Copy( old, resultIndices, old.Length ); } Array.Copy( vertices, 0, resultVertices, resultVertexCount, vertexCount ); for( int n = 0; n < indexCount; n++ ) resultIndices[ resultIndexCount + n ] = resultVertexCount + indices[ n ]; resultVertexCount = newVertexCount; resultIndexCount = newIndexCount; } } /////////////////////////////////////////// public RecastNavigationSystem() { instances.Add( this ); geometriesReadOnly = new ReadOnlyCollection<Entity>( geometries ); NativeLibraryManager.PreLoadLibrary( "Recast" ); } public static IList<RecastNavigationSystem> Instances { get { if( instancesReadOnly == null ) instancesReadOnly = new ReadOnlyCollection<RecastNavigationSystem>( instances ); return instancesReadOnly; } } [Category( "Debug" )] [DefaultValue( false )] public bool AlwaysDrawNavMesh { get { return alwaysDrawNavMesh; } set { alwaysDrawNavMesh = value; } } //[Browsable( false )] //SodanKerjuu: controlled by the initialize toolbox form //public bool DrawTileGrid //{ // get { return drawTileGrid; } // set { drawTileGrid = value; } //} //[DefaultValue( .5f )] //public float GridHeight //{ // get { return gridHeight; } // set // { // gridHeight = value; // MathFunctions.Saturate( ref gridHeight ); // ClearDebugGrids(); // } //} [Category( "Grid" )] public Vec3 BoundsMin { get { return boundsMin; } set { if( boundsMin == value ) return; boundsMin = value; //ClearDebugGrids(); } } [Category( "Grid" )] public Vec3 BoundsMax { get { return boundsMax; } set { if( boundsMax == value ) return; boundsMax = value; //ClearDebugGrids(); } } [Category( "Grid" )] [DefaultValue( 32 )] [LocalizedDescription( "Размер плитки.", "RecastNavigationSystem" )] public int TileSize { get { return tileSize; } set { if( value < 16 ) value = 16; tileSize = value; } } [Category( "Grid" )] [DefaultValue( .3f )] [LocalizedDescription( "Разрешение по ширине и глубине, используемое при выборке исходной геометрии. Ширина и глубина вокселей в полях вокселей. Ширина и глубина столбцов ячеек, составляющих воксельные поля. Более низкое значение позволяет сгенерированным сеткам более точно соответствовать исходной геометрии, но при более высокой стоимости обработки и памяти.", "RecastNavigationSystem" )] public float CellSize { get { return cellSize; } set { if( value < .01f ) value = .01f; cellSize = value; } } [Category( "Grid" )] [DefaultValue( .3f )] [LocalizedDescription( "Разрешение по высоте, используемое при выборке исходной геометрии. Высота вокселей в полях вокселей.", "RecastNavigationSystem" )] public float CellHeight { get { return cellHeight; } set { if( value < .01f ) value = .01f; cellHeight = value; } } [Category( "Grid" )] [DefaultValue( 512 )] [LocalizedDescription( "Максимальное количество треугольников для каждого чанка во внутреннем дереве AABB.", "RecastNavigationSystem" )] public int TrianglesPerChunk { get { return trianglesPerChunk; } set { if( value < 128 ) value = 128; trianglesPerChunk = value; } } [Category( "Regions" )] [DefaultValue( 8 )] [LocalizedDescription( "Минимальный размер региона для не связанных (островных) регионов. Значение в вокселях. Области, которые не связаны ни с одним другим регионом и имеют размер меньше этого размера, будут отбракованы до создания сетки. То есть Они больше не будут считаться проходимыми.", "RecastNavigationSystem" )] public int MinRegionSize { get { return minRegionSize; } set { if( value < 1 ) value = 1; minRegionSize = value; } } [Category( "Regions" )] [DefaultValue( 20 )] [LocalizedDescription( "Любые регионы меньше этого размера будут, если возможно, объединены с более крупными регионами. Значение в вокселях. Помогает уменьшить количество небольших регионов. Это особенно проблема в областях диагонального пути, где присущие ошибки в алгоритме генерации области могут привести к излишне маленьким областям.", "RecastNavigationSystem" )] public int MergeRegionSize { get { return mergeRegionSize; } set { if( value < 0 ) value = 0; mergeRegionSize = value; } } [Category( "Regions" )] [DefaultValue( false )] [LocalizedDescription( "Разделите пройденную поверхность на простые области без отверстий.", "RecastNavigationSystem" )] public bool MonotonePartitioning { get { return monotonePartitioning; } set { monotonePartitioning = value; } } [Category( "Polygonization" )] [DefaultValue( 12.0f )] [LocalizedDescription( "Максимальная длина ребер многоугольника, представляющих границу ячеек. Больше вершин будет добавлено к граничным ребрам, если это значение будет превышено для определенного ребра. Нулевое значение отключит эту функцию.", "RecastNavigationSystem" )] public float MaxEdgeLength { get { return maxEdgeLength; } set { if( value < 0 ) value = 0; maxEdgeLength = value; } } [Category( "Polygonization" )] [DefaultValue( 1.3f )] [LocalizedDescription( "Максимальное расстояние, на которое края сетки могут отклоняться от исходной геометрии. Более низкое значение приведет к тому, что ребра сетки будут более точно следовать контуру геометрии плоскости xz за счет увеличения числа треугольников.", "RecastNavigationSystem" )] public float MaxEdgeError { get { return maxEdgeError; } set { if( value < .1f ) value = .1f; maxEdgeError = value; } } [Category( "Polygonization" )] [DefaultValue( 6 )] public int MaxVerticesPerPolygon { get { return maxVerticesPerPolygon; } set { if( value < 3 ) value = 3; maxVerticesPerPolygon = value; //!!!!!! //need change debug drawing NavMesh. at this time navmesh draws as triangle list. } } [Category( "Detail Mesh" )] [DefaultValue( 6.0f )] [LocalizedDescription( "Устанавливает расстояние выборки, используемое при сопоставлении сетки детали с поверхностью исходной геометрии. Влияет на то, насколько хорошо конечная сетка детали соответствует контуру поверхности исходной геометрии. Более высокие значения приводят к созданию сетки деталей, которая более точно соответствует поверхности исходной геометрии за счет более высокого конечного числа треугольников и более высоких затрат на обработку.", "RecastNavigationSystem" )] public float DetailSampleDistance { get { return detailSampleDistance; } set { if( value < 0 ) value = 0; detailSampleDistance = value; } } [Category( "Detail Mesh" )] [DefaultValue( 1.0f )] [LocalizedDescription( "Максимальное расстояние, на котором поверхность сетки деталей может отклоняться от поверхности исходной геометрии.", "RecastNavigationSystem" )] public float DetailMaxSampleError { get { return detailMaxSampleError; } set { if( value < 0 ) value = 0; detailMaxSampleError = value; } } [Category( "Agent" )] [DefaultValue( 2.0f )] [LocalizedDescription( "Минимальная высота, на которой агент еще может ходить.", "RecastNavigationSystem" )] public float AgentHeight { get { return agentHeight; } set { if( value < .1f ) value = .1f; agentHeight = value; } } [Category( "Agent" )] [DefaultValue( .6f )] [LocalizedDescription( "Радиус агента.", "RecastNavigationSystem" )] public float AgentRadius { get { return agentRadius; } set { if( value < 0 ) value = 0; agentRadius = value; } } [Category( "Agent" )] [DefaultValue( .9f )] [LocalizedDescription( "Максимальная высота между ячейками сетки, на которую может подняться агент.", "RecastNavigationSystem" )] public float AgentMaxClimb { get { return agentMaxClimb; } set { if( value < .001f ) value = .001f; agentMaxClimb = value; } } [Category( "Agent" )] [DefaultValue( typeof( Degree ), "45" )] [Editor( typeof( SingleValueEditor ), typeof( UITypeEditor ) )] [EditorLimitsRange( 1, 89 )] [LocalizedDescription( "Максимальный ходовой угол наклона в градусах.", "RecastNavigationSystem" )] public Degree AgentMaxSlope { get { return agentMaxSlope; } set { if( value < 1 ) value = 1; if( value > 89 ) value = 89; agentMaxSlope = value; } } [Browsable( false )] public IList<Entity> Geometries { get { return geometriesReadOnly; } } [Category( "Pathfinding" )] [DefaultValue( 8192 )] [LocalizedDescription( "Максимальное количество используемых поисковых узлов (не более 65536).", "RecastNavigationSystem" )] public int PathfindingMaxNodes { get { return pathfindingMaxNodes; } set { if( value < 4 ) value = 4; if( value > 65536 ) value = 65536; pathfindingMaxNodes = value; if( recastWorld != IntPtr.Zero ) Wrapper.NavQueryInit( recastWorld, pathfindingMaxNodes ); } } //void ClearDebugGrids() //{ // tileGridMeshVertices = null; // tileGridMeshIndices = null; // cellGridMeshVertices = null; // cellGridMeshIndices = null; //} static Vec3 ToRecastVec3( Vec3 v ) { return new Vec3( v.X, v.Z, -v.Y ); } static Vec3 ToEngineVec3( Vec3 v ) { return new Vec3( v.X, -v.Z, v.Y ); } protected override bool OnLoad( TextBlock block ) { if( !base.OnLoad( block ) ) return false; InitRecastWorld(); string virtualDataDirectory = Path.Combine( Map.Instance.GetVirtualFileDirectory(), dataDirectory ); string virtualFilePath = Path.Combine( virtualDataDirectory, "NavMesh.dat" ); if( VirtualFile.Exists( virtualFilePath ) ) { byte[] data = VirtualFile.ReadAllBytes( virtualFilePath ); unsafe { fixed( byte* pData = data ) { if( Wrapper.LoadNavMesh( recastWorld, (IntPtr)pData, data.Length ) ) { Wrapper.NavQueryInit( recastWorld, pathfindingMaxNodes ); } } } } return true; } protected override void OnSave( TextBlock block ) { base.OnSave( block ); if( EntitySystemWorld.Instance.SerializationMode == SerializationModes.MapSceneFile ) { Log.Warning( "Scene export: RecastNavigationSystem is not supported." ); return; } string mapRealFileDirectory = VirtualFileSystem.GetRealPathByVirtual( Map.Instance.GetVirtualFileDirectory() ); string realFullDataDirectory = Path.Combine( mapRealFileDirectory, dataDirectory ); string realFilePath = Path.Combine( realFullDataDirectory, "NavMesh.dat" ); if( recastWorld != IntPtr.Zero ) { if( !Directory.Exists( realFullDataDirectory ) ) Directory.CreateDirectory( realFullDataDirectory ); IntPtr data; int size; Wrapper.SaveNavMesh( recastWorld, out data, out size ); if( data != IntPtr.Zero ) { byte[] buffer = new byte[ size ]; Marshal.Copy( data, buffer, 0, size ); Wrapper.FreeMemory( data ); File.WriteAllBytes( realFilePath, buffer ); } } else { if( File.Exists( realFilePath ) ) File.Delete( realFilePath ); } } protected override void OnCreate() { base.OnCreate(); string mapRealFileDirectory = VirtualFileSystem.GetRealPathByVirtual( Map.Instance.GetVirtualFileDirectory() ); //dataDirectory for( int counter = 1; ; counter++ ) { dataDirectory = "RecastNavigationSystem"; if( counter != 1 ) dataDirectory += counter.ToString(); bool busy = false; foreach( RecastNavigationSystem system in Instances ) { if( system != this && dataDirectory == system.dataDirectory ) busy = true; } if( !busy ) { string realPath = Path.Combine( mapRealFileDirectory, dataDirectory ); if( !Directory.Exists( realPath ) ) break; } } } /// <summary>Overridden from <see cref="Engine.EntitySystem.Entity"/>.</summary> protected override void OnPostCreate( bool loaded ) { if( !instances.Contains( this ) ) instances.Add( this ); base.OnPostCreate( loaded ); //remove null geometry entries if( loaded ) { again: for( int n = 0; n < geometries.Count; n++ ) { if( geometries[ n ] == null ) { geometries.RemoveAt( n ); goto again; } } } } /// <summary>Overridden from <see cref="Engine.EntitySystem.Entity"/>.</summary> protected override void OnDestroy() { DestroyRecastWorld(); base.OnDestroy(); instances.Remove( this ); } public void InitRecastWorld() { unsafe { DestroyRecastWorld(); Vec3 min = boundsMin; Vec3 max = boundsMax; for( int n = 0; n < 3; n++ ) if( max[ n ] - min[ n ] < 1 ) max[ n ] = min[ n ] + 1; Vec3 recastBoundsMin = ToRecastVec3( min ); Vec3 recastBoundsMax = ToRecastVec3( max ); recastWorld = Wrapper.Initialize( ref recastBoundsMin, ref recastBoundsMax, tileSize, cellSize, cellHeight, minRegionSize, mergeRegionSize, monotonePartitioning, maxEdgeLength, maxEdgeError, maxVerticesPerPolygon, detailSampleDistance, detailMaxSampleError, agentHeight, agentRadius, agentMaxClimb, (float)agentMaxSlope ); if( recastWorld != IntPtr.Zero ) { Wrapper.NavQueryInit( recastWorld, pathfindingMaxNodes ); } //if( recastWorld != IntPtr.Zero ) //{ //Log.Info( "RecastWorld Initialized." ); //int mT = 0, mPPT = 0; //Wrapper.GetSizes( recastWorld, out mT, out mPPT ); //Log.Info( "MaxTiles: " + mT.ToString() + " MaxPolygonsPerTile: " + mPPT.ToString() ); //} } } [Browsable( false )] public bool IsInitialized { get { return recastWorld != IntPtr.Zero; } } public void DestroyRecastWorld() { debugNavigationMeshVertices = null; debugNavigationMeshIndices = null; if( recastWorld != IntPtr.Zero ) { Wrapper.Destroy( recastWorld ); recastWorld = IntPtr.Zero; } } public void DestroyAllTiles() { if( recastWorld == IntPtr.Zero ) return; Wrapper.DestroyAllTiles( recastWorld ); //refresh debug mesh debugNavigationMeshVertices = null; debugNavigationMeshIndices = null; } public bool BuildAllTiles( out string error ) { error = null; if( recastWorld == IntPtr.Zero ) { error = "Need to initialize the Recast World."; return false; } if( geometries.Count == 0 ) { error = "No collision objects are selected."; return false; } IndexVertexBufferCollector collector = GetAllGeometriesForNavigationMesh(); { Vec3[] vertices = collector.resultVertices; int[] indices = collector.resultIndices; int vertexCount = collector.resultVertexCount; int indexCount = collector.resultIndexCount; if( vertexCount == 0 ) { error = "No vertices were gathered from collision objects."; return false; } //convert to Recast space for( int n = 0; n < vertexCount; n++ ) vertices[ n ] = ToRecastVec3( vertices[ n ] ); unsafe { fixed( Vec3* pVertices = vertices ) { fixed( int* pIndices = indices ) { Wrapper.SetGeometry( recastWorld, (IntPtr)pVertices, vertexCount, (IntPtr)pIndices, indexCount, trianglesPerChunk ); Wrapper.BuildAllTiles( recastWorld ); } } } } //refresh debug mesh debugNavigationMeshVertices = null; debugNavigationMeshIndices = null; return true; } static bool IsAllowSaveRecursive( Entity entity ) { while( entity != null ) { if( !entity.AllowSave ) return false; entity = entity.Parent; } return true; } public bool IsSupportedGeometry( Entity entity ) { if( !IsAllowSaveRecursive( entity ) ) return false; HeightmapTerrain terrain = entity as HeightmapTerrain; if( terrain != null ) return true; StaticMesh staticMesh = entity as StaticMesh; if( staticMesh != null ) { if( staticMesh.Collision ) return true; return false; } MapObject mapObject = entity as MapObject; if( mapObject != null ) { foreach( MapObjectAttachedObject attachedObject in mapObject.AttachedObjects ) { MapObjectAttachedMesh attachedMesh = attachedObject as MapObjectAttachedMesh; if( attachedMesh != null && attachedMesh.Collision ) return true; } } return false; } //IndexVertexBufferCollector GetGeometriesForNavigationMesh( Bounds bounds ) //{ // IndexVertexBufferCollector collector = new IndexVertexBufferCollector(); // странный способ // Map.Instance.GetObjects( new Box( bounds ), delegate( MapObject obj ) // { // if( geometries.Contains( obj ) ) // AddEntityToCollector( collector, obj ); // } ); // //heightmapterrain не добавляется в выборку объема, это на самом деле хорошо, потому что теперь мы можем добавить только необходимую часть // { // //raycast does not hit terrain?!? // /* // Vec3 top = bounds.GetCenter(); // top.Z = boundsMax.Z; // Vec3 bottom = bounds.GetCenter(); // bottom.Z = boundsMin.Z; // RayCastResult[] results = PhysicsWorld.Instance.RayCastPiercing(new Ray(top, bottom), (int)ContactGroup.CastOnlyCollision); // foreach (RayCastResult result in results) // { // HeightmapTerrain terrain = HeightmapTerrain.GetTerrainByBody( result.Shape.Body ); // if( terrain != null) // if( geometries.Contains(terrain) ) // AddHeightmapTerrainPartToCollector(collector, terrain, bounds); // } // */ // //SodanKerjuu: стиль Lo-Tek, лучше надеяться, что у вас не более одного ландшафта // foreach( HeightmapTerrain terrain in HeightmapTerrain.Instances ) // if( geometries.Contains( terrain ) ) // AddHeightmapTerrainPartToCollector( collector, terrain, bounds ); // } // return collector; //} //void AddHeightmapTerrainPartToCollector( IndexVertexBufferCollector collector, HeightmapTerrain terrain, Bounds bounds ) //{ // int size = terrain.GetHeightmapSizeAsInteger(); // Vec3[] vertices = new Vec3[ ( size + 1 ) * ( size + 1 ) ]; // int[] indices = new int[ size * size * 6 ]; // int vertexPosition = 0; // int indexPosition = 0; // for( int y = 0; y < size + 1; y++ ) // { // for( int x = 0; x < size + 1; x++ ) // { // если очень большое число дыр, то будет излишне? // //SodanKerjuu: no need, you will have unused vertices, but they won't matter for Recast if they are not indexed // //if( !terrain.GetHoleFlag( new Vec2i( x, y ) ) ) // //{ // Vec2 pos2 = terrain.GetPositionXY( new Vec2i( x, y ) ); // Vec3 pos = new Vec3( pos2.X, pos2.Y, terrain.GetHeight( new Vec2i( x, y ) ) ); // vertices[ vertexPosition ] = pos; // vertexPosition++; // //} // } // } // for( int y = 0; y < size; y++ ) // { // for( int x = 0; x < size; x++ ) // { // if( !terrain.GetHoleFlag( new Vec2i( x, y ) ) ) // { // indices[ indexPosition + 0 ] = ( size + 1 ) * y + x; // indices[ indexPosition + 1 ] = ( size + 1 ) * y + x + 1; // indices[ indexPosition + 2 ] = ( size + 1 ) * ( y + 1 ) + x + 1; // indices[ indexPosition + 3 ] = ( size + 1 ) * ( y + 1 ) + x + 1; // indices[ indexPosition + 4 ] = ( size + 1 ) * ( y + 1 ) + x; // indices[ indexPosition + 5 ] = ( size + 1 ) * y + x; // indexPosition += 6; // } // } // } // collector.Add( vertices, vertexPosition, indices, indexPosition ); //} bool AddEntityToCollector( IndexVertexBufferCollector collector, Entity entity ) { //Static meshes StaticMesh staticMesh = entity as StaticMesh; if( staticMesh != null ) { if( staticMesh.Collision == false ) return false; Mesh mesh; if( !string.IsNullOrEmpty( staticMesh.CollisionSpecialMeshName ) ) mesh = MeshManager.Instance.Load( staticMesh.CollisionSpecialMeshName ); else mesh = MeshManager.Instance.Load( staticMesh.MeshName ); if( mesh == null ) return false; Mat4 transform = staticMesh.GetTransform(); foreach( SubMesh subMesh in mesh.SubMeshes ) { if( subMesh.AllowCollision ) { Vec3[] vertices; int[] indices; subMesh.GetSomeGeometry( out vertices, out indices ); if( vertices != null ) { for( int n = 0; n < vertices.Length; n++ ) vertices[ n ] = transform * vertices[ n ]; collector.Add( vertices, vertices.Length, indices, indices.Length ); } } } return true; } //MapObjectAttachedMesh MapObject mapObject = entity as MapObject; if( mapObject != null && !( entity is StaticMesh ) ) //SodanKerjuu: static meshes qualify as both { bool added = false; foreach( MapObjectAttachedObject attachedObject in mapObject.AttachedObjects ) { MapObjectAttachedMesh attachedMesh = attachedObject as MapObjectAttachedMesh; if( attachedMesh != null && attachedMesh.Collision ) { Mesh mesh; if( !string.IsNullOrEmpty( attachedMesh.CollisionSpecialMeshName ) ) mesh = MeshManager.Instance.Load( attachedMesh.CollisionSpecialMeshName ); else mesh = MeshManager.Instance.Load( attachedMesh.MeshName ); if( mesh != null ) { Vec3 pos; Quat rot; Vec3 scl; attachedMesh.GetGlobalTransform( out pos, out rot, out scl ); Mat4 transform = new Mat4( rot.ToMat3() * Mat3.FromScale( scl ), pos ); foreach( SubMesh subMesh in mesh.SubMeshes ) { if( subMesh.AllowCollision ) { Vec3[] vertices; int[] indices; subMesh.GetSomeGeometry( out vertices, out indices ); if( vertices != null ) { for( int n = 0; n < vertices.Length; n++ ) vertices[ n ] = transform * vertices[ n ]; collector.Add( vertices, vertices.Length, indices, indices.Length ); added = true; } } } } } } return added; } //HeightmapTerrain HeightmapTerrain terrain = entity as HeightmapTerrain; if( terrain != null ) { int size = terrain.GetHeightmapSizeAsInteger(); Vec3[] vertices = new Vec3[ ( size + 1 ) * ( size + 1 ) ]; int[] indices = new int[ size * size * 6 ]; int vertexPosition = 0; int indexPosition = 0; for( int y = 0; y < size + 1; y++ ) { for( int x = 0; x < size + 1; x++ ) { //SodanKerjuu: no need, you will have unused vertices, but they won't matter for Recast if they are not indexed //if( !terrain.GetHoleFlag( new Vec2i( x, y ) ) ) //{ Vec2 pos2 = terrain.GetPositionXY( new Vec2I( x, y ) ); Vec3 pos = new Vec3( pos2.X, pos2.Y, terrain.GetHeight( new Vec2I( x, y ) ) ); vertices[ vertexPosition ] = pos; vertexPosition++; //} } } for( int y = 0; y < size; y++ ) { for( int x = 0; x < size; x++ ) { if( !terrain.GetHoleFlag( new Vec2I( x, y ) ) ) { indices[ indexPosition + 0 ] = ( size + 1 ) * y + x; indices[ indexPosition + 1 ] = ( size + 1 ) * y + x + 1; indices[ indexPosition + 2 ] = ( size + 1 ) * ( y + 1 ) + x + 1; indices[ indexPosition + 3 ] = ( size + 1 ) * ( y + 1 ) + x + 1; indices[ indexPosition + 4 ] = ( size + 1 ) * ( y + 1 ) + x; indices[ indexPosition + 5 ] = ( size + 1 ) * y + x; indexPosition += 6; } } } collector.Add( vertices, vertexPosition, indices, indexPosition ); return true; } return false; } IndexVertexBufferCollector GetAllGeometriesForNavigationMesh() { IndexVertexBufferCollector collector = new IndexVertexBufferCollector(); foreach( Entity geometry in geometries ) { if( !geometry.Editor_IsExcludedFromWorld() ) AddEntityToCollector( collector, geometry ); } return collector; } public void AddAllGeometriesOnMap() { foreach( Entity entity in Entities.Instance.EntitiesCollection ) { if( IsSupportedGeometry( entity ) ) { if( !Geometries.Contains( entity ) ) AddGeometry( entity ); } } } Bounds GetGeometryBounds( Entity entity ) { HeightmapTerrain terrain = entity as HeightmapTerrain; if( terrain != null && terrain.Enabled ) return terrain.CalculateBounds(); MapObject mapObject = entity as MapObject; if( mapObject != null ) return mapObject.MapBounds; return Engine.MathEx.Bounds.Cleared; } public void RecalculateBounds() { Bounds bounds = Bounds.Cleared; foreach( Entity entity in Geometries ) { if( entity.Editor_IsExcludedFromWorld() ) continue; bounds.Add( GetGeometryBounds( entity ) ); } if( bounds.IsCleared() ) bounds = new Bounds( -10, -10, -10, 10, 10, 10 ); //needs to be whole number because decimals are rejected on the initialize toolbox Vec3 padding = new Vec3( 1, 1, 1 ); BoundsMin = bounds.Minimum - padding; BoundsMax = bounds.Maximum + padding; } public bool FindPath( Vec3 start, Vec3 end, float stepSize, Vec3 polygonPickExtents, int maxPolygonPath, int maxSmoothPath, int maxSteerPoints, out Vec3[] outPath ) { unsafe { outPath = null; if( recastWorld == IntPtr.Zero ) return false; //convert to Recast space Vec3 recastStart = ToRecastVec3( start ); Vec3 recastEnd = ToRecastVec3( end ); Vec3 recastPolygonPickExtents = new Vec3( polygonPickExtents.X, polygonPickExtents.Z, polygonPickExtents.Y ); Vec3* pathPointer; int pathCount; bool result = Wrapper.FindPath( recastWorld, ref recastStart, ref recastEnd, stepSize, ref recastPolygonPickExtents, maxPolygonPath, maxSmoothPath, maxSteerPoints, out pathPointer, out pathCount ); if( result ) { if( pathCount > 0 ) { outPath = new Vec3[ pathCount ]; for( int n = 0; n < pathCount; n++ ) outPath[ n ] = ToEngineVec3( pathPointer[ n ] ); } else { outPath = new Vec3[ 1 ]; outPath[ 0 ] = start; } Wrapper.FreeMemory( (IntPtr)pathPointer ); } return result; } } //public Bounds GetCellBounds( Vec3 pos ) //{ // float gridSize = tileSize * cellSize; // float minX = boundsMin.X + ( (int)Math.Floor( ( pos.X - boundsMin.X ) / gridSize ) * gridSize ); // float minY = boundsMax.Y + ( (int)Math.Floor( ( pos.Y - boundsMax.Y ) / gridSize ) * gridSize ); // xx; // //SodanKerjuu: mirrored Y because of Recast coordinate system // return new Bounds( minX, minY, boundsMin.Z, minX + gridSize, minY + gridSize, boundsMax.Z ); //} ////use this to manually set the geometry, used with BuildTileCached to speed things up //public void AssignGeometry( Bounds bounds ) //{ // if( recastWorld == IntPtr.Zero ) // return; // IndexVertexBufferCollector collector = GetGeometriesForNavigationMesh( bounds ); // if( collector.resultVertexCount > 0 ) // { // Vec3[] vertices = collector.resultVertices; // int[] indices = collector.resultIndices; // int vertexCount = collector.resultVertexCount; // int indexCount = collector.resultIndexCount; // //convert to Recast space // for( int n = 0; n < vertexCount; n++ ) // vertices[ n ] = ToRecastVec3( vertices[ n ] ); // unsafe // { // fixed( Vec3* pVertices = vertices ) // fixed( int* pIndices = indices ) // Wrapper.SetGeometry( recastWorld, (IntPtr)pVertices, vertexCount, (IntPtr)pIndices, indexCount ); // } // } //} ////this will use the previously assigned geometry instead of getting new one //public void BuildTileCached( Vec3 position ) //{ // if( recastWorld == IntPtr.Zero ) // return; // Vec3 targetPosition = ToRecastVec3( position ); // Wrapper.BuildTile( recastWorld, ref targetPosition ); // //redraw debug mesh // debugNavigationMeshVertices = null; // debugNavigationMeshIndices = null; //} //public void BuildTile( Vec3 position ) //{ // if( recastWorld == IntPtr.Zero ) // return; // Bounds selectionBounds = GetCellBounds( position ); // IndexVertexBufferCollector collector = GetGeometriesForNavigationMesh( selectionBounds ); // if( collector.resultVertexCount > 0 ) // { // Vec3[] vertices = collector.resultVertices; // int[] indices = collector.resultIndices; // int vertexCount = collector.resultVertexCount; // int indexCount = collector.resultIndexCount; // //convert to Recast space // for( int n = 0; n < vertexCount; n++ ) // vertices[ n ] = ToRecastVec3( vertices[ n ] ); // unsafe // { // Log.Info( "SetGeometry: Vertices: {0}, Indices {1}", vertexCount, indexCount ); // fixed( Vec3* pVertices = vertices ) // fixed( int* pIndices = indices ) // { // Wrapper.SetGeometry( recastWorld, (IntPtr)pVertices, vertexCount, (IntPtr)pIndices, indexCount ); // Vec3 targetPosition = ToRecastVec3( position ); // Wrapper.BuildTile( recastWorld, ref targetPosition ); // //redraw debug mesh // debugNavigationMeshVertices = null; // debugNavigationMeshIndices = null; // } // } // } //} //public void RemoveTile( Vec3 position ) //{ // if( recastWorld == IntPtr.Zero ) // return; // Vec3 targetPosition = ToRecastVec3( position ); // Wrapper.RemoveTile( recastWorld, ref targetPosition ); // debugNavigationMeshVertices = null; // debugNavigationMeshIndices = null; //} protected override void OnDeleteSubscribedToDeletionEvent( Entity entity ) { base.OnDeleteSubscribedToDeletionEvent( entity ); if( Geometries.Contains( entity ) ) RemoveGeometry( entity ); } protected override void OnRender( Camera camera ) { base.OnRender( camera ); if( camera.Purpose == Camera.Purposes.MainCamera ) { //if( drawTileGrid ) // DebugRenderTileGrid( camera ); bool drawMapEditor = false; if( MapEditorInterface.Instance != null ) { //bool allow try { bool v = (bool)MapEditorInterface.Instance.SendCustomMessage( this, "IsAllowToRenderNavigationMesh", null ); if( v ) drawMapEditor = true; } catch { } } if( alwaysDrawNavMesh || drawMapEditor ) DebugDrawNavMesh( camera ); //draw global bounds bool mapEditorIsSelected = MapEditorInterface.Instance != null && MapEditorInterface.Instance.IsEntitySelected( this ); if( mapEditorIsSelected ) { camera.DebugGeometry.Color = new ColorValue( 0, 0, 1 ); Bounds bounds = new Bounds( boundsMin, boundsMax ); camera.DebugGeometry.AddBounds( bounds ); } //bool mapEditorIsSelected = false; //if( MapEditorInterface.Instance != null && MapEditorInterface.Instance.IsEntitySelected( this ) ) // mapEditorIsSelected = true; //if( alwaysDrawNavMesh || mapEditorIsSelected ) // DebugDrawNavMesh( camera ); ////draw global bounds //if( mapEditorIsSelected ) //{ // camera.DebugGeometry.Color = new ColorValue( 0, 0, 1 ); // Bounds bounds = new Bounds( boundsMin, boundsMax ); // camera.DebugGeometry.AddBounds( bounds ); //} } } public void DebugDrawNavMesh( Camera camera ) { if( recastWorld == IntPtr.Zero ) return; if( debugNavigationMeshVertices == null ) { if( !GetDebugNavigationMeshGeometry( out debugNavigationMeshVertices ) ) return; debugNavigationMeshIndices = new int[ debugNavigationMeshVertices.Length ]; for( int n = 0; n < debugNavigationMeshIndices.Length; n++ ) debugNavigationMeshIndices[ n ] = n; } //Render NavMesh { Mat4 transform = new Mat4( Mat3.Identity, new Vec3( 0, 0, .1f ) ); //draw without depth test { camera.DebugGeometry.SetSpecialDepthSettings( false, false ); camera.DebugGeometry.Color = new ColorValue( 0, 1, 0, .1f ); camera.DebugGeometry.AddVertexIndexBuffer( debugNavigationMeshVertices, debugNavigationMeshIndices, transform, false, true ); camera.DebugGeometry.Color = new ColorValue( 1, 1, 0, .1f ); camera.DebugGeometry.AddVertexIndexBuffer( debugNavigationMeshVertices, debugNavigationMeshIndices, transform, true, true ); camera.DebugGeometry.RestoreDefaultDepthSettings(); } //draw with depth test { camera.DebugGeometry.Color = new ColorValue( 0, 1, 0, .3f ); camera.DebugGeometry.AddVertexIndexBuffer( debugNavigationMeshVertices, debugNavigationMeshIndices, transform, false, true ); camera.DebugGeometry.Color = new ColorValue( 1, 1, 0, .3f ); camera.DebugGeometry.AddVertexIndexBuffer( debugNavigationMeshVertices, debugNavigationMeshIndices, transform, true, true ); } } } //void DebugRenderTileGrid( Camera camera ) //{ // //make a tile grid // { // if( tileGridMeshVertices == null ) // CreateTileGridMesh( out tileGridMeshVertices, out tileGridMeshIndices, false ); // camera.DebugGeometry.Color = new ColorValue( 0f, 1f, 0f, .3f ); // camera.DebugGeometry.AddVertexIndexBuffer( tileGridMeshVertices, tileGridMeshIndices, // Mat4.Identity, true, false ); // } // //make a cell grid // { // if( cellGridMeshVertices == null ) // CreateTileGridMesh( out cellGridMeshVertices, out cellGridMeshIndices, true ); // camera.DebugGeometry.Color = new ColorValue( 1f, 0f, 0f, .3f ); // camera.DebugGeometry.AddVertexIndexBuffer( cellGridMeshVertices, cellGridMeshIndices, // Mat4.Identity, true, false ); // } // //add the bounds // { // camera.DebugGeometry.Color = new ColorValue( 0f, 1f, 1f, .6f ); // camera.DebugGeometry.AddBounds( new Bounds( boundsMin, boundsMax ) ); // } //} //void CreateTileGridMesh( out Vec3[] vertices, out int[] indices, bool cellSplit ) //{ // int size; // if( cellSplit ) // size = 128; // else // size = 64; // vertices = new Vec3[ ( size + 1 ) * ( size + 1 ) ]; // { // int vertexPosition = 0; // for( int y = 0; y < size + 1; y++ ) // { // for( int x = 0; x < size + 1; x++ ) // { // xx; // //SodanKerjuu: mirrored Y because of Recast coordinate system // if( cellSplit ) // { // vertices[ vertexPosition ] = new Vec3( // boundsMin.X + x * tileSize * cellSize, // boundsMax.Y - y * tileSize * cellSize, // boundsMin.Z + gridHeight * ( boundsMax.Z - boundsMin.Z ) ); // } // else // { // vertices[ vertexPosition ] = new Vec3( // boundsMin.X + x * tileSize, // boundsMax.Y - y * tileSize, // boundsMin.Z + gridHeight * ( boundsMax.Z - boundsMin.Z ) ); // } // vertexPosition++; // } // } // } // indices = new int[ size * size * 6 ]; // { // int indexPosition = 0; // for( int y = 0; y < size; y++ ) // { // for( int x = 0; x < size; x++ ) // { // indices[ indexPosition + 0 ] = ( size + 1 ) * y + x; // indices[ indexPosition + 1 ] = ( size + 1 ) * y + x + 1; // indices[ indexPosition + 2 ] = ( size + 1 ) * ( y + 1 ) + x + 1; // indices[ indexPosition + 3 ] = ( size + 1 ) * ( y + 1 ) + x + 1; // indices[ indexPosition + 4 ] = ( size + 1 ) * ( y + 1 ) + x; // indices[ indexPosition + 5 ] = ( size + 1 ) * y + x; // indexPosition += 6; // } // } // } //} public bool GetDebugNavigationMeshGeometry( out Vec3[] vertices ) { vertices = null; if( recastWorld == IntPtr.Zero ) return false; unsafe { Vec3* nativeVertices; int vertexCount; if( !Wrapper.GetNavigationMesh( recastWorld, out nativeVertices, out vertexCount ) ) return false; vertices = new Vec3[ vertexCount ]; for( int n = 0; n < vertices.Length; n++ ) vertices[ n ] = ToEngineVec3( nativeVertices[ n ] ); Wrapper.FreeMemory( (IntPtr)nativeVertices ); } return true; } public void AddGeometry( Entity entity ) { if( geometries.Contains( entity ) ) Log.Fatal( "RecastNavigationSystem: AddGeometry: This entity is already added." ); geometries.Add( entity ); SubscribeToDeletionEvent( entity ); } public void RemoveGeometry( Entity entity ) { if( !geometries.Contains( entity ) ) return; UnsubscribeToDeletionEvent( entity ); geometries.Remove( entity ); } public void RemoveAllGeometries() { while( geometries.Count != 0 ) RemoveGeometry( geometries[ geometries.Count - 1 ] ); } } }