This page has been translated automatically.
Video Tutorials
Interface
Essentials
Advanced
How To
Basics
Rendering
Professional (SIM)
UnigineEditor
Interface Overview
Assets Workflow
Version Control
Settings and Preferences
Working With Projects
Adjusting Node Parameters
Setting Up Materials
Setting Up Properties
Lighting
Sandworm
Using Editor Tools for Specific Tasks
Extending Editor Functionality
Built-in Node Types
Nodes
Objects
Effects
Decals
Light Sources
Geodetics
World Nodes
Sound Objects
Pathfinding Objects
Players
Programming
Fundamentals
Setting Up Development Environment
C++
C#
UnigineScript
UUSL (Unified UNIGINE Shader Language)
Plugins
File Formats
Materials and Shaders
Rebuilding the Engine Tools
GUI
Double Precision Coordinates
API
Animations-Related Classes
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
VR-Related Classes
Content Creation
Content Optimization
Materials
Material Nodes Library
Miscellaneous
Input
Math
Matrix
Textures
Art Samples
Tutorials

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:

  • 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#

Accessing a Mesh of a Mesh-Based Object#

You can access a Mesh of a mesh-based object (e.g. a static mesh, a skinned mesh and so on) via the getMesh() method of the corresponding object class.

For example, to access the Mesh of the ObjectMeshStatic, you can do the following:

MeshClass.cs

Source code (C#)
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class MeshClass : Component
{
	private void Init()
	{

	// a mesh-based object from which geometry will be obtained
	ObjectMeshStatic box = new ObjectMeshStatic("core/meshes/box.mesh");
	// create a mesh
	Mesh boxMesh = new Mesh();
	// copy the mesh of the ObjectMeshStatic into the created mesh// copy the mesh of the ObjectMeshStatic into the created mesh
	box.GetMeshForce().GetMesh(boxMesh);

	}
}

The accessed mesh can be used at your discretion.

Copying a Mesh#

You may need to copy a Mesh in the following cases:

  • When you have a mesh-based object that should be copied into another mesh-based object (new or existing one).
  • When you have a Mesh that should be copied into another Mesh.

From One Mesh-Based Object to Another#

You can copy a mesh of a mesh-based object into another object in one of the following ways:

  • By passing a Mesh to a new mesh-based object:
    1. Get a source mesh-based object.
    2. Create a Mesh class instance as a container.
    3. Copy the mesh of the source mesh-based object by using the getMesh() function.
    4. Create a new mesh-based object from the Mesh instance.
    For example, you can copy the ObjectMeshDynamic mesh to the new ObjectMeshDynamic as follows:

    MeshClass.cs

    Source code (C#)
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    
    [Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
    public class MeshClass : Component
    {
    	private void Init()
    	{
    
    	// create an instance of the ObjectMeshDynamic class
    	ObjectMeshDynamic dynamic_0 = new ObjectMeshDynamic();
    	// create a Mesh class instance
    	Mesh mesh = new Mesh();
    	// copy geometry of the dynamic mesh to the Mesh class instance
    	dynamic_0.GetMesh(mesh);
    	// create a new ObjectMeshDynamic instance from the obtained mesh
    	ObjectMeshDynamic dynamic_1 = new ObjectMeshDynamic(mesh);
    
    	}
    }
  • By passing a Mesh to an existing mesh-based object:
    1. Get two mesh-based objects.
    2. Create a Mesh class instance as a container.
    3. Copy the mesh of the source mesh-based object by using the getMesh() function.
    4. Set the copied mesh to the second mesh-based object by using the setMesh() function.
    For example, you can copy the ObjectMeshDynamic mesh to the existing ObjectMeshDynamic as follows:

    MeshClass.cs

    Source code (C#)
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using Unigine;
    
    [Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
    public class MeshClass : Component
    {
    	private void Init()
    	{
    
    	// create ObjectMeshDynamic instances 
    	ObjectMeshDynamic dynamic_0 = new ObjectMeshDynamic();
    	ObjectMeshDynamic dynamic_1 = new ObjectMeshDynamic();
    	// create a Mesh class instance
    	Mesh mesh = new Mesh();
    	// copy geometry of the dynamic mesh to the Mesh class instance
    	dynamic_0.GetMesh(mesh);
    	// put the obtained mesh to the second dynamic mesh
    	dynamic_1.SetMesh(mesh);
    
    	}
    }
Notice
You can return the transformed mesh from the Mesh class to the same ObjectMeshDynamic instance without creating a new one, if necessary.

From One Mesh to Another#

You can easily copy the existing mesh instance to a new one: pass the mesh instance as an argument to the constructor as follows:

MeshClass.cs

Source code (C#)
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class MeshClass : Component
{
	private void Init()
	{

	// create a source Mesh instance
	Mesh mesh_0 = new Mesh("core/meshes/box.mesh");
	// pass the source mesh to the constructor of the second mesh 
	Mesh mesh_1 = new Mesh(mesh_0);

	}
}

Accessing Mesh Surfaces#

By using the Mesh class, you can add new surfaces to a mesh or copy surfaces from one mesh to another.

Adding a New Surface to a Mesh#

You can add a new surface to a mesh in one of the following ways:

Creating a Surface from Scratch#

The Mesh class allows creating a surface and adding vertices to it.

In the following example, we create a plane by adding vertices to the mesh:

  1. Create a Mesh class instance, add a surface and 4 vertices to it.
  2. Add 6 indices to create a plane.
    Notice
    You can add indices explicitly as in the example below, or you can create indices for recently added vertices by using the createIndices() function.
  3. Create tangents and normals by using the createTangents() and createNormals() functions.
  4. Update bounds of the mesh to include all new vertices via the createBounds() function.
  5. Create an ObjectMeshDynamic instance using the mesh to check the result.

MeshClass.cs

Source code (C#)
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class MeshClass : Component
{
	private void 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(0);
	// create mesh bounds
	mesh.CreateBounds(0);
	// create normals
	mesh.CreateNormals(0);

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

	}
}

Adding a Predefined Surface#

To add a predefined surface to a Mesh instance, use the required function. For example, to add a new capsule surface to a mesh, do the following:

  1. Create a Mesh class instance.
  2. Add a capsule surface via the addCapsuleSurface() function.
  3. Create an ObjectMeshDynamic instance using the mesh to check the result.

MeshClass.cs

Source code (C#)
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class MeshClass : Component
{
	private void Init()
	{

	// create a mesh instance
	Mesh mesh_0 = new Mesh();
	// add a capsule surface to the mesh
	mesh_0.AddCapsuleSurface("capsule_surface", 1.0f, 2.0f, 200, 100);
	// create an ObjectMeshDynamic from the Mesh instance
	ObjectMeshDynamic dynamicMesh = new ObjectMeshDynamic(mesh_0);

	}
}

Copying 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 execution sequence is:

  • Create 2 instances of the Mesh class and add the capsule and box surfaces to them.
  • Add the box surface from the second mesh (mesh_1) to the first mesh (mesh_0) by using the addMeshSurface() function.
  • Create a new ObjectMeshDynamic mesh from the mesh_0 instance.

MeshClass.cs

Source code (C#)
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class MeshClass : Component
{
	private void Init()
	{

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

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

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

	// create an ObjectMeshDynamic from the Mesh instance
	ObjectMeshDynamic dynamicMesh = new ObjectMeshDynamic(mesh_0);

	// set the position of the dynamic mesh
	dynamicMesh.WorldTransform = MathLib.Translate(new vec3(10.0f, 10.0f, 10.0f));

	}
}

In the result, the Mesh will have 2 surfaces.

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 surface by using the overloaded addMeshSurface() function.

The ObjectMeshDynamic mesh will appear in the editor:

ObjectMeshDynamic with 2 surfaces

Adding 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 bounds of the mesh to include the created surface by using the createBounds() function.
  6. Change the surface color.

MeshClass.cs

Source code (C#)
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class MeshClass : Component
{
	private void Init()
	{

	// load a mesh from the file
	Mesh source_mesh = new Mesh("core/meshes/box.mesh");

	// add a new surface
	int surface_index = source_mesh.AddSurface("surface_triangle");

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

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

	// create an ObjectMeshStatic, switch it to procedural mode and set its mesh via the source_mesh instance
	ObjectMeshStatic staticMesh = new ObjectMeshStatic();
	staticMesh.MeshProceduralMode = true;
	staticMesh.ApplyMeshProcedural(source_mesh);

	// change the material color
	staticMesh.SetMaterialParameterFloat4("albedo_color", new vec4(255, 255, 0, 255), 1);

	}
}

The triangle surface added to the mesh:

Changing Vertex Attributes#

The Mesh class allows changing vertex attributes.

In the following example, we take a plane and change attributes of its vertices:

  1. Create a Mesh and add a new surface to it.
  2. Print the current normal values of 2 vertices to the console.
  3. Set new values for the normals and print the updated values to the console.
  4. Print the current tangent values of 2 vertices to the console.
  5. Set new values for the tangents and print the updated values to the console.
  6. Create a new ObjectMeshDynamic from the Mesh instance to check the result.

MeshClass.cs

Source code (C#)
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class MeshClass : Component
{
	private void 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 normals
	mesh.CreateNormals();
	// create tangents
	mesh.CreateTangents();
	// create mesh bounds
	mesh.CreateBounds();
    
	// 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);
	// print values of the normals to the console
	Log.Message("the 1st normal: {0}\n", normal_0.ToString());
	Log.Message("the 2nd normal: {0}\n", normal_1.ToString());

	// set new values for the normals
	mesh.SetNormal(0, new vec3(1.0f, 1.0f, 0.0f), 0, 0);
	mesh.SetNormal(1, new vec3(1.0f, 1.0f, 1.0f), 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);
	// print values of the normals to the console
	Log.Message("the 1st normal: {0}\n", new_normal_0.ToString());
	Log.Message("the 2nd normal: {0}\n", new_normal_1.ToString());
	// get the tangent value
	quat tangent = mesh.GetTangent(0, 0, 0);
	// show the tangent value in the console
	Log.Message("tangent: {0}\n", tangent.ToString());

	// set a new value of the tangent
	mesh.SetTangent(0, new quat(new vec3(1.0f, 0.0f, 0.0f), 20), 0, 0);

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

	// set the dynamic mesh position
	newDynamicMesh.WorldTransform = MathLib.Translate(new vec3(1.0f, 1.0f, 1.0f)) * (mat4)MathLib.RotateZ(90.0f);

	}
}

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

Output
the first normal is: -1 0 0 and the second is: -1 0 0
the first normal is: 1 1 0 and the second is: 1 1 1
tangent is: : 0,5 -0,5 -0,5 0,5
tangent is: : 0,1736 0 0 0,9848

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

Let's comment this part of the code:

MeshClass.cs

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

// set a new value of the tangent
mesh.SetTangent(0, new quat(new vec3(1.0f, 0.0f, 0.0f), 20), 0, 0);

// get the new tangent value
quat new_tangent = mesh.GetTangent(0, 0, 0);
// show the tangent value after changing
Log.Message("tangent: {0}\n", new_tangent.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#

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

MeshClass.cs

Source code (C#)
using System;
using System.Collections;
using System.Collections.Generic;
using Unigine;

[Component(PropertyGuid = "AUTOGENERATED_GUID")] // <-- this line is generated automatically for a new component
public class MeshClass : Component
{
	private void Init()
	{

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

	// add a manually created box surface 
	AddManualBoxSurface(mesh, new vec3(2.0f, 2.0f, 2.0f), "box");

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

	// clear the mesh pointer
	mesh.Clear();

	// set the position of the dynamic mesh
	dynamicMesh.WorldTransform = MathLib.Translate(vec3.ZERO);
	// set the texture
	dynamicMesh.SetMaterialTexture("albedo", "core/textures/common/checker_d.texture", 0);

	}
	private void AddManualBoxSurface(Mesh mesh, vec3 size, string name) {
	// add a new surface
	int surface_index = mesh.AddSurface(name);

	// 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 };

	// 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, surface_index);
			mesh.AddNormal(normals[i], surface_index);
			if (i == 4)
			{
				mesh.AddTexCoord0((vec2.ONE - texcoords[index]), surface_index);

			}
			else
			{
				mesh.AddTexCoord0(texcoords[index], surface_index);
			}
		}
	}

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

	// create tangents
	mesh.CreateTangents(surface_index);

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

	}
}

Last update: 2024-09-21
Build: ()