This page has been translated automatically.
Video Tutorials
Interface
Essentials
Advanced
How To
UnigineEditor
Interface Overview
Assets Workflow
Settings and Preferences
Working With Projects
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Lighting
Landscape Tool
Sandworm
Using Editor Tools for Specific Tasks
Extending Editor Functionality
Built-in Node Types
Nodes
Objects
Effects
Decals
Light Sources
Geodetics
World Objects
Sound Objects
Pathfinding Objects
Players
Programming
Fundamentals
Setting Up Development Environment
UnigineScript
C++
C#
UUSL (Unified UNIGINE Shader Language)
File Formats
Rebuilding the Engine Tools
GUI
Double Precision Coordinates
API
Containers
Common Functionality
Controls-Related Classes
Engine-Related Classes
Filesystem Functionality
GUI-Related Classes
Math Functionality
Node-Related Classes
Objects-Related Classes
Networking Functionality
Pathfinding-Related Classes
Physics-Related Classes
Plugins-Related Classes
IG Plugin
CIGIConnector Plugin
Rendering-Related Classes
Content Creation
Content Optimization
Materials
Art Samples
Tutorials
Warning! This version of documentation is OUTDATED, as it describes an older SDK version! Please switch to the documentation for the latest SDK version.
Warning! This version of documentation describes an old SDK version which is no longer supported! Please upgrade to the latest SDK version.

Mesh Class

The Mesh class provides an interface for mesh loading, manipulating and saving.

By using this class, you can create a mesh, add geometry to it (e.g. box, plane, capsule or cylinder surface), add animations and bones (if you want to create a skinned mesh) and then use it to create the following objects:

Also you can get geometry of all of these objects via the Mesh class.

Features of the mesh are listed below:

  • Each surface of the mesh supports several morph targets.
  • Each surface has a quaternion-based tangent basis for the better loading speed.
  • 8-bit vertex colors are supported.
  • Each vertex of a surface has 2 sets of indices: coordinate and triangle indices. It improves the loading speed and reduces data duplication.
  • Each vertex of a surface has 2 UV sets.
  • A bind pose transformation is stored for each mesh (even if there are no animations and animation frames). See also the getBoneTransforms() function.
    Notice
    The 1st animation frame should not contain the bind pose.

Vertex Data and Indices#

Each surface of the mesh consists of triangles, and each triangle has 3 vertices. Thus, for each mesh surface we have:

Total number of vertices = 3 * number of triangles.

If a vertex belongs to several triangles, we have to store several copies of such vertex. Each of these copies would store information about position, normal, binormal, tangent and texture coordinates. In order to reduce data duplication and increase loading speed UNIGINE uses the optimization described below.

There are 2 separate vertex data buffers are used:

  • CVertices coordinate buffer, which stores only vertex coordinates.
  • TVertices triangle buffer, which stores vertex attributes such as normal, binormal, tangent, color, UV coordinates.

On the picture below, arrows are used to show normals:

Surface that contains 2 adjacent triangles. Here C0...C3 are coordinate vertices, T0...T5 are triangle vertices

The coordinate buffer is an array of coordinate vertices. In order to reduce data duplication, vertices of surface which have the same coordinates are saved only once in the buffer. For example, the coordinate buffer for the surface presented on the picture above is the following:

Output
CB = [C0,C1,C2,C3]
Each polygon of the surface has 3 coordinates. We have 2 polygons, thus, we have 6 vertices, but we save in the CVertices buffer only 4, because 2 of them have the same coordinates. Each coordinate vertex contains coordinates (float[3]) of the vertex.
Notice
The number of vertices and coordinate vertices is the same.

The triangle buffer is an array of triangle vertices. For example, the triangle buffer for the surface presented on the picture above is the following:

Output
TB = [T0,T1,T2,T3,T4,T5]
Each triangle vertex can store:
  • Normal
  • Binormal
  • Tangent
  • 1st UV map texture coordinates
  • 2nd UV map texture coordinates
  • Color
Notice
The number of vertices and triangle vertices can be different.
The number of triangle vertices depends on the number of triangles, to which the vertex belongs. For example, if 2 triangles have 1 adjacent vertex with different normals for each triangle, 2 triangle vertices will be stored (the 1st and the 3rd vertices on the picture above). However, if the components are the same for all of the adjacent triangles, only 1 triangle vertex will be stored.

Both the coordinate and triangle vertices have indices. There are 2 index buffers to get proper cvertex and tvertex data for each vertex of each triangle of the mesh:

  • CIndices buffer — coordinate indices, which are links to CVertices data.
  • TIndices buffer — triangle indices, which are links to TVertices data.
The number of elements in these index buffers is equal to the total number of vertices of a mesh surface.

Coordinate Indices#

Each vertex of a mesh surface has a coordinate index - a number of the corresponding element of the coordinate buffer, where the data is stored. For the given surface the array of the coordinate indices is as follows:

Output
CIndices = [Ci0,Ci1,Ci3,Ci1,Ci2,Ci3]
Here:
  • The first 3 elements are the coordinate indices of the first (bottom) triangle.
  • The second 3 elements are the coordinate indices of the second (top) triangle.

Triangle Indices#

Each vertex of a mesh surface also has a triangle index - a number of the corresponding element of the triangle buffer, where the data is stored. For the given surface the array of the triangle indices is as follows:

Output
TIndices = [Ti0,Ti1,Ti5,Ti2,Ti3,Ti4]
Here:
  • The first 3 elements are the triangle indices of the first (bottom) triangle.
  • The second 3 elements are the triangle indices of the second (top) triangle.

Notice
The number of the coordinate and triangle indices is the same.

See Also#

Obtaining Geometry from a Mesh-Based Object#

To get vertices from a mesh-based object (e.g. a static mesh, a skinned mesh and so on), you can do the following:

  1. Create an instance of the Mesh class by using the create() method.
  2. Copy geometry of the mesh-based object into this Mesh instance via the getMesh() method. The Mesh instance must be passed as a parameter to this method.
  3. Get vertices of the Mesh.
Source code (C#)
// a mesh-based object from which geometry will be obtained
ObjectMeshStatic staticMesh = new ObjectMeshStatic("core/meshes/material_ball.mesh");
// create a new mesh
Mesh mesh = new Mesh();
// copy geometry to the created mesh
if (staticMesh.GetMesh(mesh)) {
	// get a vertex of the first mesh surface
	vec3 vertex = mesh.GetVertex(0, 0);
} else {
	Log.Error("Failed to copy a mesh\n");
}

Copying a Mesh into Another#

You can copy a mesh into another by using the Mesh class.

In the following example, we copy the ObjectMeshDynamic mesh to the new ObjectMeshDynamic class instance by using the Mesh class as a container. The executing sequence is the following:

  • It is supposed that you already have an ObjectMeshDynamic to copy. Create a Mesh class instance as a container for the ObjectMeshDynamic mesh.
  • Copy the ObjectMeshDynamic mesh into the Mesh by using the getMesh() function.
  • Create a new ObjecteMeshDynamic instance from the Mesh instance by using overloaded constructor of the ObjectMeshDynamic class.
Source code (C#)
/* ... */
// create ObjectMeshDynamic and Mesh instances
ObjectMeshDynamic dynamicMesh = new ObjectMeshDynamic();
Mesh firstMesh = new Mesh();

// get the ObjectMeshDynamic and copy it to the Mesh class instance
dynamicMesh.GetMesh (firstMesh);

// create a new ObjectMeshDynamic instance from the firstMesh mesh
ObjectMeshDynamic dynamicMesh_2 = new ObjectMeshDynamic(firstMesh);
/* ... */

Also you can copy the mesh by using the setMesh() function. For example, in the code below we set the mesh instance to the already created ObjectMeshDynamic mesh. The executing sequence is the following:

  • It is supposed that you already have 2 ObjectMeshDynamic class instances. Create the Mesh class instance as a container.
  • Copy the ObjectMeshDynamic to the Mesh by using the getMesh() function.
  • Set the mesh to the second ObjectMeshDynamic instance by using setMesh() function.
Source code (C#)
/* ... */
// create ObjectMeshDynamic instances 
ObjectMeshDynamic dynamicMesh = new ObjectMeshDynamic();
ObjectMeshDynamic dynamicMesh_2 = new ObjectMeshDynamic();

// create a Mesh instance
Mesh firstMesh = new Mesh();

// get the mesh of the ObjectMeshDynamic and copy it to the Mesh class instance
dynamicMesh.GetMesh(firstMesh);

// put the firstMesh mesh to the dynamicMesh_2 instance
dynamicMesh_2.SetMesh(firstMesh);
/* ... */
Notice
You can return the transformed mesh from the Mesh class to the same ObjectMeshDynamic instance without creating a new one, if necessary.

You can easily copy one mesh instance to a new one by using the constructor and pass the mesh instance as an argument:

Source code (C#)
Mesh firstMesh = new Mesh();
Mesh secondMesh = new Mesh(firstMesh);

Copying Mesh Surfaces#

By using the Mesh class you can copy surfaces from one mesh to another.

In the following example we create two meshes with different surfaces. The first mesh has the capsule surface, the second has the box surface. We copy the box surface from the second mesh to the first. The executing sequence is:

  • Create 2 instances of the Mesh class and add capsule and box surfaces to them.
  • Add the box surface from the second mesh (mesh_2) and add it to the first mesh (mesh_1) by using the addMeshSurface() function.
  • Create a new ObjectMeshDynamic mesh from the mesh_1 instance.
  • Add the dynamic mesh to the editor and set the material, property and name.
Source code (C#)
// AppWorldLogic.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Unigine;

#if UNIGINE_DOUBLE
    using Vec3 = Unigine.dvec3;
    using Vec4 = Unigine.dvec4;
    using Mat4 = Unigine.dmat4;
#else
    using Vec3 = Unigine.vec3;
    using Vec4 = Unigine.vec4;
    using Mat4 = Unigine.mat4;
#endif

namespace UnigineApp
{
	class AppWorldLogic : WorldLogic
	{

		public AppWorldLogic()
		{
		}

        public override bool Init()
        {

            // create mesh instances
            Mesh mesh_1 = new Mesh();
            Mesh mesh_2 = new Mesh();

            // add surfaces for the added meshes
            mesh_1.AddCapsuleSurface("capsule_surface", 1.0f, 2.0f, 200, 100);
            mesh_2.AddBoxSurface("box_surface", new Vec3(2.2f));

            // add the surface from the mesh_2 to the mesh_1 as a new surface
            // with the name "new_box_surface"
            mesh_1.AddMeshSurface("new_box_surface", mesh_2, 0);

            // create the ObjectMeshDynamic from the mesh_1 object
            ObjectMeshDynamic dynamicMesh = new ObjectMeshDynamic(mesh_1);

            // set the position of the mesh
            dynamicMesh.WorldTransform = MathLib.Translate(new Vec3(10.0f, 10.0f, 10.0f));
            // set the material to the mesh
            dynamicMesh.SetMaterial("mesh_base", "*");
            // set the name of the mesh
            dynamicMesh.Name = "Dynamic Mesh";

            return true;
        }
	}
}
Notice
You can copy a surface from the source mesh and add it to the existing surface of the current mesh without creating a new one.

The ObjectMeshDynamic mesh will appear in the editor:

ObjectMeshDynamic with 2 surfaces

Adding Vertices and Changing Their Attributes#

The Mesh class allows adding vertices to a mesh and change vertex attributes.

In the following example, we create a plane by adding vertices to the mesh and change the attributes of some of these vertices. Let's clarify this:

  • Create the Mesh class instance and add 4 vertices to it.
  • Add 6 indices to create a plane.
  • After that tangents and normals are created by using the createTangents() and createNormals() functions.
  • The bounds of the mesh are updated to include all new vertices via the createBounds() function.
  • Current normal values of the 2 vertices are shown in console. After changing, new values are shown in console.
  • Tangent is shown in the console, and then it is changed and shown in the console again.
  • Create a new ObjectMeshDynamic mesh from the mesh instance.
  • Add the dynamic mesh to the editor and set the material, property and name.
Source code (C#)
// AppWorldLogic.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Unigine;

#if UNIGINE_DOUBLE
    using Vec3 = Unigine.dvec3;
    using Vec4 = Unigine.dvec4;
    using Mat4 = Unigine.dmat4;
#else
    using Vec3 = Unigine.vec3;
    using Vec4 = Unigine.vec4;
    using Mat4 = Unigine.mat4;
#endif

namespace UnigineApp
{
	class AppWorldLogic : WorldLogic
	{
		// World logic, it takes effect only when the world is loaded.
		// These methods are called right after corresponding world script's (UnigineScript) methods.


		public AppWorldLogic()
		{
		}

        public override bool Init()
        {

           // create a mesh instance
            Mesh mesh = new Mesh();

            // add a new surface
            mesh.AddSurface("surface_0");

            // add vertices of the plane
            mesh.AddVertex(new Vec3(0.0f, 0.0f, 0.0f), 0);
            mesh.AddVertex(new Vec3(0.0f, 0.0f, 1.0f), 0);
            mesh.AddVertex(new Vec3(0.0f, 1.0f, 0.0f), 0);
            mesh.AddVertex(new Vec3(0.0f, 1.0f, 1.0f), 0);

            // add indices 
            mesh.AddIndex(0, 0);
            mesh.AddIndex(1, 0);
            mesh.AddIndex(2, 0);

            mesh.AddIndex(3, 0);
            mesh.AddIndex(2, 0);
            mesh.AddIndex(1, 0);

            // create tangents
            mesh.CreateTangents();

			// create mesh bounds
			mesh.CreateBounds(0);

            // create normals
            mesh.CreateNormals(0);

            // get the normal vectors of the 1st and the 2nd normal before changing
            Vec3 normal_0 = mesh.GetNormal(0, 0, 0);
            Vec3 normal_1 = mesh.GetNormal(1, 0, 0);
            // show the message in console about the normals
            Log.Message("the first normal is: {0}: {1} and the second is: {2}: {3}\n", normal_0.GetType(), normal_0.ToString(), normal_1.GetType(), normal_1.ToString());

            // set new normals values of the normals
            mesh.SetNormal(0, new Vec3(1, 1, 0), 0, 0);
            mesh.SetNormal(1, new Vec3(1, 1, 1), 0, 0);

            // get the new normal vectors of the 1st and the 2nd normal
            Vec3 new_normal_0 = mesh.GetNormal(0, 0, 0);
            Vec3 new_normal_1 = mesh.GetNormal(1, 0, 0);
            // show the new normals values in the console
            Log.Message("the first normal is: {0}: {1} and the second is: {2}: {3}\n", mesh.GetNormal(0, 0, 0).GetType(), mesh.GetNormal(0, 0, 0).ToString(), mesh.GetNormal(1, 0, 0).GetType(), mesh.GetNormal(1, 0, 0).ToString());

            // get the tangent value
            quat tangent = mesh.GetTangent(0, 0, 0);
            // show the tangent value in the console
            Log.Message("tangent is: {0}: {1}\n", mesh.GetTangent(0, 0, 0).GetType(), mesh.GetTangent(0, 0, 0).ToString());

            // set the new value of the tangent
            mesh.SetTangent(0, new quat(0.0f,-0.0f,-0.0f,1), 0, 0);

            // get the new tangent value
            quat new_tangent = mesh.GetTangent(0, 0, 0);
            // show the tangent value after changing
            Log.Message("tangent is: {0}: {1}\n", mesh.GetTangent(0, 0, 0).GetType(), mesh.GetTangent(0, 0, 0).ToString());
			
            // create the ObjectMeshDynamic from the Mesh object
            ObjectMeshDynamic dynamicMesh = new ObjectMeshDynamic(mesh);

            // set the position of the mesh
            dynamicMesh.WorldTransform = MathLib.Translate(new Vec3(10.0f, 10.0f, 10.0f));
            // set the material to the mesh
            dynamicMesh.SetMaterial("mesh_base", "*");
            // set the name of the mesh
            dynamicMesh.Name = "new_mesh";

            return true;
        }
	}
}

When you launch the project, in the console you'll see the following:

Output
the first normal is: Unigine.vec3: -1 0 0 and the second is: Unigine.vec3: -1 0 0
the first normal is: Unigine.vec3: 1 1 0 and the second is: Unigine.vec3: 1 1 1
tangent is: : 0.5 -0.5 -0.5 0.5
tangent is: : 0 0 0 0.9999619

When setTangent() and setNormal() functions were called, the normal and the tangent changed their values.

Let's comment this part of the code:

Source code (C#)
// get the tangent value
quat tangent = mesh.GetTangent(0, 0, 0);
// show the tangent value in the console
Log.Message("tangent is: {0}: {1}\n", mesh.GetTangent(0, 0, 0).GetType(), mesh.GetTangent(0, 0, 0).ToString());

// set the new value of the tangent
mesh.SetTangent(0, new quat(0.0f,-0.0f,-0.0f,1), 0, 0);

// get the new tangent value
quat new_tangent = mesh.GetTangent(0, 0, 0);
// show the tangent value after changing
Log.Message("tangent is: {0}: {1}\n", mesh.GetTangent(0, 0, 0).GetType(), mesh.GetTangent(0, 0, 0).ToString());

The result will be different. You can see what influence the tangent has on the vertex:

Plane with changed tangent
Plane with unchanged tangent

Creating a Primitive#

There is no need to add indices explicitly, you can create indices for recently added vertices by using the createIndices() function.

The following example shows the manual creation of a box primitive by specifying its vertices, normals and texture coordinates:

Source code (C#)
// AppWorldLogic.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Unigine;

#if UNIGINE_DOUBLE
    using Vec2 = Unigine.dvec2;
    using Vec3 = Unigine.dvec3;
    using Vec4 = Unigine.dvec4;
    using Mat4 = Unigine.dmat4;
#else
    using Vec2 = Unigine.vec2;
    using Vec3 = Unigine.vec3;
    using Vec4 = Unigine.vec4;
    using Mat4 = Unigine.mat4;
#endif

namespace UnigineApp
{
	class AppWorldLogic : WorldLogic
	{

		public AppWorldLogic()
		{
		}

        public override bool Init()
        {
        	// the size of a box
        	Vec3 size = Vec3.ONE;

        	// array of vertices
			Vec3[] vertex = {
				new Vec3(-0.5f, -0.5f, -0.5f), new Vec3(0.5f, -0.5f, -0.5f), new Vec3(-0.5f, 0.5f, -0.5f),
				new Vec3(0.5f, 0.5f, -0.5f), new Vec3(-0.5f, -0.5f, 0.5f), new Vec3(0.5f, -0.5f, 0.5f),
				new Vec3(-0.5f, 0.5f, 0.5f), new Vec3(0.5f, 0.5f, 0.5f)
			};

			// array of face normals
			Vec3[] normals = {
				Vec3.RIGHT, Vec3.LEFT,
				Vec3.FORWARD, Vec3.BACK,
				Vec3.UP, Vec3.DOWN
			};

			// array of texture coordinates
			Vec2[] texcoords = {
				new Vec2(1.0f, 1.0f), new Vec2(0.0f, 1.0f),
				new Vec2(0.0f, 0.0f), new Vec2(1.0f, 0.0f)
			};

			// list of indices for 6 polygons
			int[,] cindices = {
				{3, 1, 5, 7}, {0, 2, 6, 4},
				{2, 3, 7, 6}, {1, 0, 4, 5},
				{6, 7, 5, 4}, {0, 1, 3, 2}
			};

			// list of indices for a single quad made of 2 triangles
			int[] indices = {
				0, 3, 2, 2, 1, 0
			};

			// create a mesh instance
			Mesh mesh = new Mesh();

			// add a new surface
			mesh.AddSurface("box");

			// cycle through all quads
			for (int i = 0; i < 6; i++)
			{
				// cycle through all indices of a single quad
				for (int j = 0; j < 6; j++)
				{
					int index = indices[j];
					mesh.AddVertex(vertex[cindices[i,index]] * size, 0);
					mesh.AddNormal(normals[i], 0);
					if (i == 4)
					{
						mesh.AddTexCoord0(Vec2.ONE - texcoords[index], 0);
					} else
					{
						mesh.AddTexCoord0(texcoords[index], 0);
					}
				}
			}

			// create indices per each 3 added vertices
			mesh.CreateIndices(0);

			// create tangents
			mesh.CreateTangents(0);

			// create mesh bounds
			mesh.CreateBounds(0);

            // create the ObjectMeshDynamic from the Mesh object
            ObjectMeshDynamic dynamicMesh = new ObjectMeshDynamic(mesh);

            // clear the mesh pointer
            mesh.Clear();
            
            // set the name of the mesh
            dynamicMesh.Name = "new_mesh";

            // set the position of the mesh
            dynamicMesh.WorldTransform = MathLib.Translate(Vec3.ZERO);
            // set the material to the mesh
            dynamicMesh.SetMaterial("mesh_base", "*");
            // set the checker albedo texture
			dynamicMesh.SetMaterialTexture("albedo", "core/textures/common/checker_d.dds", 0);

            return true;
        }
	}
}

Adding a Surface to the Existing Mesh#

This section contains information on how to add a surface to the existing mesh.

In the following example, we add a surface to the existing mesh and then add necessary vertices and indices for the surface:

  1. Create a new mesh instance from the file.
  2. Add a new surface to the mesh.
  3. Add vertices for the mesh surface.
  4. Create indices for recently created vertices by using the createIndices() function.
  5. Update the bounds of the mesh to include the created surface by using the createBounds() function.
  6. Create a material to be assigned to the added surface.
  7. Save mesh to the file and add it to the editor.

In the example, the box.mesh file has been taken from the UNIGINE SDK Samples. However, you can create a box mesh and save it to a file as follows:

Source code (C#)
// create an empty Mesh instance
Mesh box = new Mesh();
// add a box surface to the Mesh 
box.AddBoxSurface("surface_box", new vec3(1.0f));
// save the box to a file
box.Save("unigine_project_2/meshes/box.mesh");
Source code (C#)
// AppWorldLogic.cs			

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Unigine;

#if UNIGINE_DOUBLE
    using Vec3 = Unigine.dvec3;
    using Vec4 = Unigine.dvec4;
    using Mat4 = Unigine.dmat4;
#else
    using Vec3 = Unigine.vec3;
    using Vec4 = Unigine.vec4;
    using Mat4 = Unigine.mat4;
#endif

namespace UnigineApp
{
	class AppWorldLogic : WorldLogic
	{

		public AppWorldLogic()
		{
		}

        public override bool Init()
        {

            // load a mesh form the file
            Mesh mesh = new Mesh("unigine_project/meshes/box.mesh");

            // add a new surface
            mesh.AddSurface("surface_triangle");

            // add 3 vertices to the new surface
            mesh.AddVertex(new Vec3(-0.5f, 0.5f, 0.5f), 1);
            mesh.AddVertex(new Vec3(-0.5f, -0.5f, 0.5f), 1);
            mesh.AddVertex(new Vec3(-0.5f, -0.5f, 1.5f), 1);

            // create indices for the new surface
            mesh.CreateIndices(1);
            // create a new boundbox for the mesh including new surface
            mesh.CreateBounds();

            // save mesh to file
            mesh.Save("unigine_project/meshes/box_2.mesh");

            // create ObjectMeshStatic from the Mesh object
            ObjectMeshStatic staticMesh = new ObjectMeshStatic("unigine_project/meshes/box_2.mesh");

            // prepare a material for the added surface
			Material mesh_base = Materials.FindMaterial("mesh_base");
			Material material = mesh_base.Inherit("mesh_base_yellow", "unigine_project/materials/mesh_base_yellow.mat");
			material.SetParameter(Material.PARAMETER_COLOR, new Vec4(255, 255, 0, 255));
			 
            // set the position of the mesh
            staticMesh.WorldTransform = MathLib.Translate(new Vec3(10.0f, 0.0f, 2.0f));
            // set the material to the mesh surfaces
            staticMesh.SetMaterial("mesh_base", 0);
            staticMesh.SetMaterial("mesh_base_yellow", 1);

            return true;
        }
	}
}

The triangle surface added to the mesh:

Last update: 2021-04-29
Build: ()