/// <summary> /// The connections in the set. /// </summary> /// <param name="index"> /// The index of the connection. [Limits: 0 <= value < <see cref="Count"/>]. /// </param> /// <returns>The connection.</returns> public OffMeshConnection this[int index] { get { // Not using parameterized constructor. Don't need validation cost. OffMeshConnection result = new OffMeshConnection(); result.start = mVerts[index * 2 + 0]; result.end = mVerts[index * 2 + 1]; result.radius = mRadii[index]; result.direction = mDirs[index]; result.flags = mFlags[index]; result.area = mAreas[index]; result.userId = mUserIds[index]; return(result); } set { // Must be validated. // Let exceptions be thrown. mRadii[index] = MathUtil.ClampToPositiveNonZero(value.radius); mDirs[index] = (byte)(value.IsBiDirectional ? 1 : 0); mAreas[index] = NMGen.ClampArea(value.area); mFlags[index] = value.flags; mUserIds[index] = value.userId; // Keep verts last. mVerts[index * 2 + 0] = value.start; mVerts[index * 2 + 1] = value.end; } }
public static void CombineMeshes(Queue <CombineInstance> items , byte area , InputGeometryCompiler compiler) { const int MaxTris = 65000; List <CombineInstance> combineInstancesPart = new List <CombineInstance>(); byte[] areas = NMGen.CreateAreaBuffer(MaxTris, area); while (items.Count != 0) { int vertCount = 0; while (items.Count > 0 && (vertCount + items.Peek().mesh.vertexCount < MaxTris)) { vertCount += items.Peek().mesh.vertexCount; combineInstancesPart.Add(items.Dequeue()); } Mesh meshPart = new Mesh(); meshPart.CombineMeshes(combineInstancesPart.ToArray(), true, true); UnityEngine.Vector3[] vs = meshPart.vertices; Vector3[] vvs = VectorHelper.ToVector3Array(ref vs); compiler.AddTriangles(vvs, meshPart.vertexCount , meshPart.triangles, areas, meshPart.triangles.Length / 3); Object.DestroyImmediate(meshPart); combineInstancesPart.Clear(); } }
/// <summary> /// Creates a new tile set. /// </summary> /// <remarks> /// <para> /// The bounds is normally based on the desired origin of the navigation mesh /// and the maximum bounds of the input geometry. /// </para> /// </remarks> /// <param name="boundsMin">The minimum AABB bounds of the set.</param> /// <param name="boundsMax">The maximum AABB counds of the set.</param> /// <param name="config">The shared NMGen configuration.</param> /// <param name="geom">The input geometry.</param> /// <returns>A new tile set, or null on error.</returns> public static TileSetDefinition Create(Vector3 boundsMin, Vector3 boundsMax , NMGenParams config , InputGeometry geom) { if (config == null || !config.IsValid() || !TriangleMesh.IsBoundsValid(boundsMin, boundsMax) || geom == null || config.tileSize <= 0) { return(null); } int w; int d; NMGen.DeriveSizeOfTileGrid(boundsMin, boundsMax , config.XZCellSize , config.tileSize , out w, out d); if (w < 1 || d < 1) { return(null); } return(new TileSetDefinition(w, d, boundsMin, boundsMax, config, geom)); }
/// <summary> /// Creates a builder. /// </summary> /// <remarks> /// <para> /// No validation is performed and the builder will use the parameters directly /// during the build. /// </para> /// <para> /// Builders created using this method are not guarenteed to produce a usable result. /// </para> /// <para> /// It is the responsibility of the caller to ensure thread safely if /// <paramref name="isThreadSafe"/> is set to true. /// </para> /// <para> /// <b>Warning:</b> If walkable slope if greather than zero then the builder will /// apply <see cref="NMGen.ClearUnwalkableTriangles"/> directly to the areas parameter. /// </para> /// </remarks> /// <param name="mesh">The triangle mesh to use for the build.</param> /// <param name="areas">The triangle areas. (Null not permitted.)</param> /// <param name="walkableSlope">The walkable slope. /// (See <see cref="NMGenParams.WalkableSlope"/>)</param> /// <param name="isThreadSafe">True if the builder can run safely on its own thread.</param> /// <returns>A builder, or null on error.</returns> public static InputGeometryBuilder UnsafeCreate(TriangleMesh mesh , byte[] areas , float walkableSlope , bool isThreadSafe) { if (mesh == null || areas == null || mesh.triCount < 0) { return(null); } walkableSlope = System.Math.Min(NMGen.MaxAllowedSlope, walkableSlope); if (walkableSlope > 0) { BuildContext context = new BuildContext(); if (!NMGen.ClearUnwalkableTriangles(context, mesh, walkableSlope, areas)) { return(null); } } ChunkyTriMeshBuilder builder = ChunkyTriMeshBuilder.Create(mesh, areas, 32768); if (builder == null) { return(null); } Vector3 bmin; Vector3 bmax; mesh.GetBounds(out bmin, out bmax); return(new InputGeometryBuilder(builder, bmin, bmax, isThreadSafe)); }
/// <summary> /// Creates a thread-safe, fully validated builder. /// </summary> /// <remarks> /// <para> /// The input mesh and area parameters are fully validated. /// </para> /// <para> /// Will return null if there are zero triangles. /// </para> /// <para> /// All triangleswill default to <see cref="NMGen.MaxArea"/> if the /// <paramref name="areas"/> parameter is null. /// </para> /// <para> /// If walkable slope if greather than zero then the builder will apply /// <see cref="NMGen.ClearUnwalkableTriangles"/> to the areas. /// </para> /// </remarks> /// <param name="mesh">The triangle mesh to use for the build.</param> /// <param name="areas">The triangle areas. (Null permitted.)</param> /// <param name="walkableSlope">The walkable slope. /// (See <see cref="NMGenParams.WalkableSlope"/>)</param> /// <returns>A thread-safe, fully validated builder. Or null on error.</returns> public static InputGeometryBuilder Create(TriangleMesh mesh , byte[] areas , float walkableSlope) { if (!IsValid(mesh, areas)) { return(null); } TriangleMesh lmesh = new TriangleMesh(mesh.vertCount, mesh.triCount); lmesh.triCount = mesh.triCount; lmesh.vertCount = mesh.vertCount; System.Array.Copy(mesh.verts, 0, lmesh.verts, 0, lmesh.verts.Length); System.Array.Copy(mesh.tris, 0, lmesh.tris, 0, lmesh.tris.Length); byte[] lareas; if (areas == null) { lareas = NMGen.CreateDefaultAreaBuffer(mesh.triCount); } else { lareas = new byte[mesh.triCount]; System.Array.Copy(areas, 0, lareas, 0, lareas.Length); } return(UnsafeCreate(lmesh, lareas, walkableSlope, true)); }
/// <summary> /// Adds an arbitrary group of triangles. /// </summary> /// <remarks> /// <para> /// All triangles will default to <see cref="NMGen.MaxArea"/> if the /// <paramref name="areas"/> parameter is null. /// </para> /// </remarks> /// <param name="verts"> /// The triangle vertices. [Length: >= <paramref name="vertCount"/>] /// </param> /// <param name="vertCount">The number of vertices. [Length: >= 3]</param> /// <param name="tris"> /// The triangles. [(vertAIndex, vertBIndex, vertCIndex) * triCount] /// [Length: >= 3 * <paramref name="triCount"/>] /// </param> /// <param name="areas"> /// The triangle areas. (Optional) [Length: >= <paramref name="triCount"/>] /// </param> /// <param name="triCount">The number of triangles. [Limit: > 0]</param> /// <returns>True if the triangles were successfully added.</returns> public bool AddTriangles(Vector3[] verts, int vertCount , int[] tris, byte[] areas, int triCount) { if (triCount < 1 || vertCount < 3 || verts == null || verts.Length < vertCount || tris == null || tris.Length < triCount * 3 || areas != null && areas.Length < triCount) { return(false); } if (areas == null) { areas = NMGen.CreateDefaultAreaBuffer(triCount); } int iVertOffset = mVerts.Count; if (vertCount == verts.Length) { mVerts.AddRange(verts); } else { mVerts.Capacity += vertCount; for (int p = 0; p < vertCount; p++) { mVerts.Add(verts[p]); } } int length = triCount * 3; mTris.Capacity += length; for (int p = 0; p < length; p++) { mTris.Add(tris[p] + iVertOffset); } if (areas.Length == triCount) { mAreas.AddRange(areas); } else { mAreas.Capacity += triCount; for (int i = 0; i < triCount; i++) { mAreas.Add(areas[i]); } } return(true); }
private void Compile(InputBuildContext context) { context.info.compilerCount++; InputGeometryCompiler compiler = context.geomCompiler; List <Component> items = context.components; List <byte> areas = context.areas; for (int i = 0; i < items.Count; i++) { Component item = items[i]; if (item is Terrain) { Terrain terrain = (Terrain)item; if (terrain.terrainData != terrainData) { continue; } TriangleMesh mesh = TerrainUtil.TriangulateSurface(terrain, mResolution); byte[] lareas = NMGen.CreateAreaBuffer(mesh.triCount, areas[i]); if (compiler.AddTriangles(mesh, lareas)) { string msg = string.Format("Compiled the {0} terrain surface. Triangles: {1}" , terrain.name, mesh.triCount); context.Log(msg, this); } else { string msg = string.Format("Compiler rejected mesh for the {0} terrain.", terrain.name); context.LogError(msg, this); return; } if (includeTrees) { int before = compiler.TriCount; TerrainUtil.TriangluateTrees(terrain, areas[i], compiler); string msg = string.Format("Compiled the {0} terrain trees. Triangles: {1}" , terrain.name, compiler.TriCount - before); context.Log(msg, this); } break; } } }
/// <summary> /// Add a connection. /// </summary> /// <remarks> /// <para> /// The connection fields are auto-clamped to valid values. /// </para> /// </remarks> /// <param name="connection">The connection to add.</param> public void Add(OffMeshConnection connection) { // Must be validated. mVerts.Add(connection.start); mVerts.Add(connection.end); mRadii.Add(MathUtil.ClampToPositiveNonZero(connection.radius)); mDirs.Add((byte)(connection.IsBiDirectional ? 1 : 0)); mAreas.Add(NMGen.ClampArea(connection.area)); mFlags.Add(connection.flags); mUserIds.Add(connection.userId); }
/// <summary> /// Add a connection. /// </summary> /// <remarks> /// <para> /// All values are auto-clamped to valid values. /// </para> /// </remarks> /// <param name="start">The connection start point.</param> /// <param name="end">The connection end point.</param> /// <param name="radius">The radius of the connection vertices.</param> /// <param name="isBidirectional">True if the connection can be traversed in both /// directions. (Start to end, end to start.)</param> /// <param name="area">The connection area id.</param> /// <param name="flags">The connection flags.</param> /// <param name="userId">The connection user id.</param> public void Add(Vector3 start, Vector3 end, float radius , bool isBidirectional, byte area, ushort flags, uint userId) { mVerts.Add(start); mVerts.Add(end); mRadii.Add(System.Math.Max(MathUtil.Epsilon, radius)); mDirs.Add((byte)(isBidirectional ? 1 : 0)); mAreas.Add(NMGen.ClampArea(area)); mFlags.Add(flags); mUserIds.Add(userId); }
/// <summary> /// Creates a new mapper. /// </summary> /// <remarks> /// <para> /// Will return null if parameters are invalid. Must have at least one valid area/flag map. /// </para> /// </remarks> /// <param name="name">The processor name.</param> /// <param name="priority">The processor priority.</param> /// <param name="areas">The areas check for.</param> /// <param name="flags">The flags to apply.</param> /// <returns>A new marker, or null on error.</returns> public static AreaFlagMapper Create(string name, int priority, byte[] areas, ushort[] flags) { if (areas == null || flags == null || areas.Length != flags.Length || !NMGen.IsValidAreaBuffer(areas, areas.Length)) { return(null); } return(new AreaFlagMapper(name, priority , (byte[])areas.Clone(), (ushort[])flags.Clone())); }
/// <summary> /// Validates the mesh and areas. /// </summary> /// <remarks> /// <para> /// The <paramref name="areas"/> parameter is validated only if it is non-null. /// </para> /// </remarks> /// <param name="mesh">The mesh to validate.</param> /// <param name="areas">The area to validate. (If non-null.)</param> /// <returns>True if the structure and content of the parameters are considered valid. /// </returns> public static bool IsValid(TriangleMesh mesh, byte[] areas) { if (mesh == null || mesh.triCount == 0 || !TriangleMesh.IsValid(mesh, true)) { return(false); } if (areas != null && (areas.Length != mesh.triCount || !NMGen.IsValidAreaBuffer(areas, mesh.triCount))) { return(false); } return(true); }
private void BuildHeightfield() { int width; int depth; NMGen.DeriveSizeOfCellGrid(mTileConfig.BoundsMin , mTileConfig.BoundsMax , mConfig.XZCellSize , out width , out depth); Heightfield hf = Heightfield.Create(width, depth , mTileConfig.BoundsMin, mTileConfig.BoundsMax , mConfig.XZCellSize, mConfig.YCellSize); hf.AddTriangles(mBuildContext , mGeometry.Mesh , mTileConfig.boundsMin , mTileConfig.boundsMax , mConfig.WalkableStep); // Merge for any spans less than step. if (hf.GetSpanCount() < 1) { FinalizeNoResult("Complete at heightfield build. No spans."); return; } mBuildContext.Heightfield = hf; if (PostProcess() && PostHeightfieldCheck()) { mBuildContext.Log("Voxelized triangles. Span count: " + hf.GetSpanCount(), this); mState = NMGenState.CompactFieldBuild; } }
internal bool InitializeBuild(BuildContext context, bool fromTarget) { Navmesh navmesh = null; if (fromTarget) { if (!CanLoadFromTarget(context, true)) { return(false); } navmesh = BuildTarget.GetNavmesh(); SetConfigFromTargetIntern(navmesh); } mIsDirty = true; // Note: If loading from the target, the tile size was already validated. // So it won't trigger this adjustment. if (mConfig.TileSize != 0 && mConfig.TileSize < MinAllowedTileSize) { string msg = string.Format("Tile size too small. Reverting tile size from" + " {0} to 0 (non-tiled). Minimum tile size is {1}" , mConfig.TileSize, MinAllowedTileSize); context.LogWarning(msg, this); mConfig.TileSize = 0; } if (mConfig.TileSize == 0) { mBuildData.Resize(1, 1); } else { // Need to check to see if the the build is truly tiled. int w; int d = 0; if (navmesh == null) { NMGen.DeriveSizeOfTileGrid(mBoundsMin, mBoundsMax , mConfig.XZCellSize, mConfig.TileSize , out w, out d); } else { // Existing navmesh will always be tiled. w = 2; } if (w > 1 || d > 1) { mTileSet = TileSetDefinition.Create(mBoundsMin, mBoundsMax , mConfig.GetConfig() , mInputGeom); if (mTileSet == null) { context.LogError("Create tile build definition: Unexpected error." + " Invalid input data or configuration." , this); return(false); } mBuildData.Resize(mTileSet.Width, mTileSet.Depth); } else { // Not really tiled. mBuildData.Resize(1, 1); } } if (navmesh != null) { // Need to load the tiles from existing navmesh. NavmeshTileExtract[] tiles; NavmeshParams meshConfig; NavStatus status = Navmesh.ExtractTileData(navmesh.GetSerializedMesh() , out tiles , out meshConfig); if ((status & NavStatus.Failure) != 0) { context.LogError("Could not extract the tile data from the target's" + " navigation mesh. (Can't initialize from build target.)" , this); return(false); } foreach (NavmeshTileExtract tile in tiles) { int polyCount = tile.header.polyCount; if (polyCount == 0) { continue; } int tx = tile.header.tileX; int tz = tile.header.tileZ; if (tx >= mBuildData.Width || tz >= mBuildData.Depth) { // Shouldn't happen. Probably indicates in internal error // of some type. string msg = string.Format("The existing navigation mesh" + " contains a tile outside the expected range. Ignoring" + " the tile. (Tile: [{0},{1}])" , tx, tz); context.LogWarning(msg, this); continue; } mBuildData.SetAsBaked(tx, tz, tile.data, polyCount); } } // Note: Dirty state was set earlier in the code. return(true); }
/// <summary> /// Constructor /// </summary> /// <param name="name">The processor name.</param> /// <param name="priority">The processor priority.</param> /// <param name="area">The area to apply.</param> public AreaMarker(string name, int priority, byte area) : base(name, priority) { mArea = NMGen.ClampArea(area); }