/// <summary>
        /// Creates a Wavefront format string from a triangle mesh.
        /// </summary>
        /// <param name="mesh">A valid triangle mesh.</param>
        /// <param name="reverseWrap">Reverse the wrap direction of the triangles.</param>
        /// <param name="invertXAxis">Invert the x-axis values.</param>
        /// <returns>A string representing the mesh in Wavefront format.</returns>
        public static string TranslateTo(TriangleMesh mesh , bool reverseWrap , bool invertXAxis)
        {
            StringBuilder sb = new StringBuilder();

            float xFactor = (invertXAxis ? -1 : 1);

            for (int i = 0; i < mesh.vertCount; i += 1)
            {
                sb.Append("v "
                    + (mesh.verts[i].x * xFactor).ToString(CultureInfo.InvariantCulture) + " "
                    + mesh.verts[i].y.ToString(CultureInfo.InvariantCulture) + " "
                    + mesh.verts[i].z.ToString(CultureInfo.InvariantCulture) + "\n");
            }

            for (int p = 0; p < mesh.triCount * 3; p += 3)
            {
                // The +1 converts to a 1-based index.
                if (reverseWrap)
                {
                    sb.Append("f "
                        + (mesh.tris[p + 0] + 1).ToString(CultureInfo.InvariantCulture) + " "
                        + (mesh.tris[p + 2] + 1).ToString(CultureInfo.InvariantCulture) + " "
                        + (mesh.tris[p + 1] + 1).ToString(CultureInfo.InvariantCulture) + "\n");
                }
                else
                {
                    sb.Append("f "
                        + (mesh.tris[p + 0] + 1).ToString(CultureInfo.InvariantCulture) + " "
                        + (mesh.tris[p + 1] + 1).ToString(CultureInfo.InvariantCulture) + " "
                        + (mesh.tris[p + 2] + 1).ToString(CultureInfo.InvariantCulture) + "\n");
                }
            }

            return sb.ToString();
        }
Beispiel #2
0
        public static TriangleMesh TriangulateSurface(Terrain terrain, float resolution)
        {
            if (terrain == null || terrain.terrainData == null || resolution <= 0)
                return null;

            Vector3 origin = terrain.transform.position;

            int xCount;
            int zCount;
            Vector3 scale = DeriveScale(terrain, resolution, out xCount, out zCount);

            TriangleMesh m = new TriangleMesh(xCount * zCount, (xCount - 1) * (zCount - 1) * 2);

            TriangulateSurface(terrain, origin, scale, xCount, zCount, 0, m);

            return m;
        }
Beispiel #3
0
        /// <summary>
        /// Validates the structure and, optionally, the content of the mesh.
        /// </summary>
        /// <remarks>
        /// <para>
        /// The basic structural validation includes null checks, array size checks, etc.
        /// </para>
        /// <para>
        /// The optional content validation checks that the indices refer to valid vertices
        /// and that triangles do not contain duplicate vertices.
        /// </para>
        /// </remarks>
        /// <param name="mesh">The mesh to check.</param>
        /// <param name="includeContent">
        /// If true, the content will be checked.  Otherwise only the structure will be checked.
        /// </param>
        /// <returns>True if the validation tests pass.</returns>
        public static bool IsValid(TriangleMesh mesh, bool includeContent)
        {
            if (mesh == null)
                return false;

            return IsValid(mesh.verts, mesh.vertCount
                , mesh.tris, mesh.triCount
                , includeContent);
        }
        /// <summary>
        /// Extracts all input geometry for a particular bounds.
        /// </summary>
        /// <remarks>
        /// <para>
        /// This method exists to permit debugging.
        /// </para>
        /// <para>
        /// The returned result is only guarenteed to be the result 'seen' by the NMGen build
        /// process.
        /// </para>
        /// </remarks>
        /// <param name="xmin">The minimum x-axis bounds.</param>
        /// <param name="zmin">The minimum z-axis bounds.</param>
        /// <param name="xmax">The maximum x-axis bounds.</param>
        /// <param name="zmax">The maximum z-axis bounds.</param>
        /// <param name="areas">The triangle areas.</param>
        /// <returns>The triangle mesh.</returns>
        public TriangleMesh ExtractMesh(float xmin, float zmin, float xmax, float zmax
            , out byte[] areas)
        {
            byte[] lareas;

            TriangleMesh lmesh = mMesh.ExtractMesh(out lareas);

            List<ChunkyTriMeshNode> nodes = new List<ChunkyTriMeshNode>();

            int triCount = mMesh.GetChunks(xmin, zmin, xmax, zmax, nodes);

            if (triCount == 0)
            {
                areas = new byte[0];
                return new TriangleMesh();
            }

            TriangleMesh result = new TriangleMesh();
            result.verts = lmesh.verts;
            result.vertCount = lmesh.vertCount;

            result.tris = new int[triCount * 3];
            result.triCount = triCount;
            areas = new byte[triCount];

            int i = 0;
            foreach (ChunkyTriMeshNode node in nodes)
            {
                for (int j = 0; j < node.count; j++, i++)
                {
                    result.tris[i * 3 + 0] = lmesh.tris[(node.i + j) * 3 + 0];
                    result.tris[i * 3 + 1] = lmesh.tris[(node.i + j) * 3 + 1];
                    result.tris[i * 3 + 2] = lmesh.tris[(node.i + j) * 3 + 2];
                    areas[i] = lareas[node.i + j];
                }
            }

            return result;
        }
        /// <summary>
        /// Adds a triangle mesh.  
        /// </summary>
        /// <remarks>
        /// <para>
        /// All triangles will default to <see cref="NMGen.MaxArea"/> if the 
        /// <paramref name="areas"/> parameter is null.
        /// </para>
        /// <para>
        /// Will return false if the mesh triangle count is zero.
        /// </para>
        /// </remarks>
        /// <param name="mesh">The triangle mesh.</param>
        /// <param name="areas">
        /// The triangle areas. (Optional)[Length: >= mesh.triCount]
        /// </param>
        /// <returns>True if the triangles were successfully added.</returns>
        public bool AddTriangles(TriangleMesh mesh, byte[] areas)
        {
            if (mesh == null || mesh.triCount == 0)
                return false;

            return AddTriangles(mesh.verts, mesh.vertCount, mesh.tris, areas, mesh.triCount);
        }
Beispiel #6
0
        public static TriangleMesh TriangulateSurface(Terrain terrain
            , float xmin, float zmin, float xmax, float zmax
            , float resolution
            , float yOffset)
        {
            if (terrain == null || terrain.terrainData == null 
                || resolution <= 0
                || xmin > xmax || zmin > zmax)
            {
                return null;
            }

            int xCount;
            int zCount;
            Vector3 scale = DeriveScale(terrain, resolution, out xCount, out zCount);

            Vector3 origin = terrain.transform.position;

            /*
             * We are generating part of a larger mesh.  The vertices must match that of the larger
             * mesh.
             * 
             * Convert input values to local grid space.
             * Clamp to the heightfield bounds.
             * Convert back to worldspace.
             */
            xmin = origin.x + Mathf.Max(0, Mathf.Floor((xmin - origin.x) / scale.x)) * scale.x;
            zmin = origin.z + Mathf.Max(0, Mathf.Floor((zmin - origin.z) / scale.z)) * scale.z;
            xmax = origin.x + Mathf.Min(xCount, Mathf.Ceil((xmax - origin.x) / scale.x)) * scale.x;
            zmax = origin.z + Mathf.Min(xCount, Mathf.Ceil((zmax - origin.z) / scale.z)) * scale.z;

            if (xmin + scale.x > xmax || zmin + scale.z > zmax)
                // Triangulation zone is too small.
                return null;

            // Everyting is already snapped to the grid.  But there may be floating point errors.
            // So round it.
            xCount = Mathf.RoundToInt((xmax - xmin) / scale.x);
            zCount = Mathf.RoundToInt((zmax - zmin) / scale.z);

            TriangleMesh m = new TriangleMesh(xCount * zCount, (xCount - 1) * (zCount - 1) * 2);

            TriangulateSurface(terrain
                , new Vector3(xmin, origin.y, zmin)
                , scale
                , xCount, zCount
                , yOffset
                , m);

            return m;
        }
Beispiel #7
0
        private static void TriangulateSurface(Terrain terrain
            , Vector3 origin
            , Vector3 scale
            , int xCount
            , int zCount
            , float yOffset
            , TriangleMesh buffer)
        {

            // Create the vertices by sampling the terrain.
            for (int ix = 0; ix < xCount; ix++)
            {
                float x = origin.x + ix * scale.x;
                for (int iz = 0; iz < zCount; iz++)
                {
                    float z = origin.z + iz * scale.z;
                    Vector3 pos = new Vector3(x, origin.y, z);
                    pos.y += terrain.SampleHeight(pos) + yOffset;
                    buffer.verts[buffer.vertCount] = pos;
                    buffer.vertCount++;
                }
            }

            // Triangulate surface sample points.
            for (int ix = 0; ix < xCount - 1; ix++)
            {
                for (int iz = 0; iz < zCount - 1; iz++)
                {
                    int i = iz + (ix * zCount);
                    int irow = i + zCount;

                    buffer.tris[buffer.triCount * 3 + 0] = i;
                    buffer.tris[buffer.triCount * 3 + 1] = irow + 1;
                    buffer.tris[buffer.triCount * 3 + 2] = irow;
                    buffer.triCount++;

                    buffer.tris[buffer.triCount * 3 + 0] = i;
                    buffer.tris[buffer.triCount * 3 + 1] = i + 1;
                    buffer.tris[buffer.triCount * 3 + 2] = irow + 1;
                    buffer.triCount++;
                }
            }
        }
        /// <summary>
        /// Set the area of all triangles with a slope below the specified value to 
        /// <see cref="MaxArea"/>.
        /// </summary>
        /// <param name="context">The context to use duing the operation.</param>
        /// <param name="mesh">The source mesh.</param>
        /// <param name="walkableSlope">The maximum walkable slope.</param>
        /// <param name="areas">
        /// The areas associated with each triangle. [Length: >= mesh.triCount] (In/Out)
        /// </param>
        /// <returns>True if the operation was successful.</returns>
        public static bool MarkWalkableTriangles(BuildContext context, TriangleMesh mesh
            , float walkableSlope
            , byte[] areas)
        {
            if (mesh == null
                || context == null
                || areas == null || areas.Length < mesh.triCount)
            {
                return false;
            }

            NMGenEx.nmgMarkWalkableTriangles(context.root
                , walkableSlope
                , mesh.verts
                , mesh.vertCount
                , mesh.tris
                , mesh.triCount
                , areas);

            return true;
        }
        /// <summary>
        /// Extracts all triangles from the mesh. (Not efficient.)
        /// </summary>
        /// <param name="areas">The areas for each triangle.</param>
        /// <returns>The triangle mesh data.</returns>
        public TriangleMesh ExtractMesh(out byte[] areas)
        {
            if (this.tris == IntPtr.Zero)
            {
                areas = null;
                return null;
            }

            TriangleMesh result = new TriangleMesh();
            result.triCount = mTriCount;
            result.vertCount = mVertCount;

            float[] lverts = new float[mVertCount * 3];
            Marshal.Copy(this.verts, lverts, 0, mVertCount * 3);
            result.verts = Vector3Util.GetVectors(lverts);

            result.tris = new int[mTriCount * 3];
            Marshal.Copy(this.tris, result.tris, 0, mTriCount * 3);

            areas = new byte[mTriCount];
            Marshal.Copy(this.areas, areas, 0, mTriCount);

            return result;
        }
Beispiel #10
0
        /// <summary>
        /// Voxelizes the triangles in the provided mesh into the heightfield.
        /// </summary>
        /// <param name="context">The context to use for the operation</param>
        /// <param name="mesh">The triangle mesh.</param>
        /// <param name="areas">
        /// The ids of the areas the triangles belong to. 
        /// [Limit: &lt;= <see cref="NMGen.MaxArea"/>] [Size: >= mesh.triCount]
        /// </param>
        /// <param name="flagMergeThreshold">
        /// The distance where the walkable flag is favored over the non-walkable flag. 
        /// [Limit: >= 0] [Normal: 1]
        /// </param>
        /// <returns>True if the operation was successful.</returns>
        public bool AddTriangles(BuildContext context, TriangleMesh mesh, byte[] areas
            , int flagMergeThreshold)
        {
            if (IsDisposed)
                return false;

            return HeightfieldEx.nmhfRasterizeTriMesh(context.root
                , mesh.verts
                , mesh.vertCount
                , mesh.tris
                , areas
                , mesh.triCount
                , root
                , flagMergeThreshold);
        }
        /// <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>
        /// 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;
        }
        /// <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);
        }