Usando o ArrayMesh
Este tutorial apresentará o básico de uso de um ArrayMesh.
Para isso, usaremos a função add_surface_from_arrays(), que aceita até cinco parâmetros. Os dois primeiros são obrigatórios, enquanto os três últimos são opcionais.
O primeiro parâmetro é o PrimitiveType, um conceito do OpenGL que instrui a GPU sobre como organizar a primitiva com base nos vértices fornecidos, ou seja, se eles representam triângulos, linhas, pontos etc. Veja Mesh.PrimitiveType para as opções disponíveis.
O segundo parâmetro, arrays, é o Array propriamente dito que armazena as informações da malha. O array é um array normal do Godot, construído com colchetes vazios []. Ele armazena um Packed**Array (por exemplo, PackedVector3Array, PackedInt32Array etc.) para cada tipo de informação que será usada para construir a superfície.
Elementos comuns de arrays estão listados abaixo, junto com a posição que devem ocupar dentro de arrays. Veja Mesh.ArrayType para a lista completa.
Índice |
Mesh.ArrayType Enum |
Tipo de matriz |
|---|---|---|
0 |
|
|
1 |
|
|
2 |
|
PackedFloat32Array ou PackedFloat64Array de grupos de 4 números float. Os primeiros 3 floats determinam a tangente, e o último float determina a direção da binormal como -1 ou 1. |
3 |
|
|
4 |
|
|
5 |
|
|
10 |
|
PackedFloat32Array de grupos de 4 floats ou PackedInt32Array de grupos de 4 inteiros. Cada grupo lista os índices de 4 ossos que afetam um determinado vértice. |
11 |
|
PackedFloat32Array ou PackedFloat64Array de grupos de 4 floats. Cada float indica a quantidade de influência que o osso correspondente em |
12 |
|
Geralmente ao criar uma malha, ela é definida pelas posições dos vértices. Portanto, geralmente o array de vértices (no índice 0) é obrigatório, enquanto o array de índices (no índice 12) é opcional e só será usado se for incluído. Também é possível criar uma malha apenas com o array de índices e sem o array de vértices, mas isso está além do escopo deste tutorial.
Todos os outros arrays carregam informações sobre os vértices. Eles são opcionais e só serão usados se incluídos. Alguns desses arrays (por exemplo, ARRAY_COLOR) usam uma entrada por vértice para fornecer informações extras sobre os vértices. Eles devem ter o mesmo tamanho do array de vértices. Outros arrays (por exemplo, ARRAY_TANGENT) usam quatro entradas para descrever um único vértice. Esses devem ter exatamente quatro vezes o tamanho do array de vértices.
Para uso comum, os três últimos parâmetros de add_surface_from_arrays() são geralmente deixados em branco.
Setting up the ArrayMesh
No editor, crie um MeshInstance3D e adicione um ArrayMesh a ele no Inspetor. Normalmente, adicionar um ArrayMesh no editor não é útil, mas neste caso isso nos permite acessar o ArrayMesh pelo código sem precisar criar um.
Next, add a script to the MeshInstance3D.
Em ''_ready()'', crie uma nova Array.
var surface_array = []
Godot.Collections.Array surfaceArray = [];
Esta será a matriz na qual manteremos nossas informações de superfície - ela conterá todas as matrizes de dados que a superfície necessita. Godot espera que ela seja do tamanho Mesh.ARRAY_MAX, portanto, redimensione-a de acordo.
var surface_array = []
surface_array.resize(Mesh.ARRAY_MAX)
Godot.Collections.Array surfaceArray = [];
surfaceArray.Resize((int)Mesh.ArrayType.Max);
Em seguida, crie as matrizes para cada tipo de dado que você usará.
var verts = PackedVector3Array()
var uvs = PackedVector2Array()
var normals = PackedVector3Array()
var indices = PackedInt32Array()
List<Vector3> verts = [];
List<Vector2> uvs = [];
List<Vector3> normals = [];
List<int> indices = [];
Uma vez que você tenha preenchido suas matrizes de dados com sua geometria, você pode criar uma malha adicionando cada matriz a surface_array e, em seguida, comitando para a malha.
surface_array[Mesh.ARRAY_VERTEX] = verts
surface_array[Mesh.ARRAY_TEX_UV] = uvs
surface_array[Mesh.ARRAY_NORMAL] = normals
surface_array[Mesh.ARRAY_INDEX] = indices
# No blendshapes, lods, or compression used.
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array)
surfaceArray[(int)Mesh.ArrayType.Vertex] = verts.ToArray();
surfaceArray[(int)Mesh.ArrayType.TexUV] = uvs.ToArray();
surfaceArray[(int)Mesh.ArrayType.Normal] = normals.ToArray();
surfaceArray[(int)Mesh.ArrayType.Index] = indices.ToArray();
var arrMesh = Mesh as ArrayMesh;
if (arrMesh != null)
{
// No blendshapes, lods, or compression used.
arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, surfaceArray);
}
Nota
Neste exemplo, utilizamos Mesh.PRIMITIVE_TRIANGLES, mas você pode utilizar qualquer tipo primitivo disponível na malha.
Juntos, o código completo se parece com:
extends MeshInstance3D
func _ready():
var surface_array = []
surface_array.resize(Mesh.ARRAY_MAX)
# PackedVector**Arrays for mesh construction.
var verts = PackedVector3Array()
var uvs = PackedVector2Array()
var normals = PackedVector3Array()
var indices = PackedInt32Array()
#######################################
## Insert code here to generate mesh ##
#######################################
# Assign arrays to surface array.
surface_array[Mesh.ARRAY_VERTEX] = verts
surface_array[Mesh.ARRAY_TEX_UV] = uvs
surface_array[Mesh.ARRAY_NORMAL] = normals
surface_array[Mesh.ARRAY_INDEX] = indices
# Create mesh surface from mesh array.
# No blendshapes, lods, or compression used.
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, surface_array)
public partial class MyMeshInstance3D : MeshInstance3D
{
public override void _Ready()
{
Godot.Collections.Array surfaceArray = [];
surfaceArray.Resize((int)Mesh.ArrayType.Max);
// C# arrays cannot be resized or expanded, so use Lists to create geometry.
List<Vector3> verts = [];
List<Vector2> uvs = [];
List<Vector3> normals = [];
List<int> indices = [];
/***********************************
* Insert code here to generate mesh.
* *********************************/
// Convert Lists to arrays and assign to surface array
surfaceArray[(int)Mesh.ArrayType.Vertex] = verts.ToArray();
surfaceArray[(int)Mesh.ArrayType.TexUV] = uvs.ToArray();
surfaceArray[(int)Mesh.ArrayType.Normal] = normals.ToArray();
surfaceArray[(int)Mesh.ArrayType.Index] = indices.ToArray();
var arrMesh = Mesh as ArrayMesh;
if (arrMesh != null)
{
// Create mesh surface from mesh array
// No blendshapes, lods, or compression used.
arrMesh.AddSurfaceFromArrays(Mesh.PrimitiveType.Triangles, surfaceArray);
}
}
}
O código que vai no meio pode ser o que você quiser. A seguir apresentaremos um exemplo de código para gerar uma esfera.
Gerando geometria
Aqui está uma amostra de código para gerar uma esfera. Embora o código seja apresentado no GDScript, não há nada de específico do Godot sobre a abordagem para gerá-lo. Esta implementação não tem nada em particular a ver com ArrayMeshes e é apenas uma abordagem genérica para a geração de uma esfera. Se você estiver tendo problemas para compreendê-la ou quiser aprender mais sobre a geometria de procedimentos em geral, você pode usar qualquer tutorial que encontrar on-line.
extends MeshInstance3D
var rings = 50
var radial_segments = 50
var radius = 1
func _ready():
# Insert setting up the PackedVector**Arrays here.
# Vertex indices.
var thisrow = 0
var prevrow = 0
var point = 0
# Loop over rings.
for i in range(rings + 1):
var v = float(i) / rings
var w = sin(PI * v)
var y = cos(PI * v)
# Loop over segments in ring.
for j in range(radial_segments + 1):
var u = float(j) / radial_segments
var x = sin(u * PI * 2.0)
var z = cos(u * PI * 2.0)
var vert = Vector3(x * radius * w, y * radius, z * radius * w)
verts.append(vert)
normals.append(vert.normalized())
uvs.append(Vector2(u, v))
point += 1
# Create triangles in ring using indices.
if i > 0 and j > 0:
indices.append(prevrow + j - 1)
indices.append(prevrow + j)
indices.append(thisrow + j - 1)
indices.append(prevrow + j)
indices.append(thisrow + j)
indices.append(thisrow + j - 1)
prevrow = thisrow
thisrow = point
# Insert committing to the ArrayMesh here.
public partial class MyMeshInstance3D : MeshInstance3D
{
private int _rings = 50;
private int _radialSegments = 50;
private float _radius = 1;
public override void _Ready()
{
// Insert setting up the surface array and lists here.
// Vertex indices.
var thisRow = 0;
var prevRow = 0;
var point = 0;
// Loop over rings.
for (var i = 0; i < _rings + 1; i++)
{
var v = ((float)i) / _rings;
var w = Mathf.Sin(Mathf.Pi * v);
var y = Mathf.Cos(Mathf.Pi * v);
// Loop over segments in ring.
for (var j = 0; j < _radialSegments + 1; j++)
{
var u = ((float)j) / _radialSegments;
var x = Mathf.Sin(u * Mathf.Pi * 2);
var z = Mathf.Cos(u * Mathf.Pi * 2);
var vert = new Vector3(x * _radius * w, y * _radius, z * _radius * w);
verts.Add(vert);
normals.Add(vert.Normalized());
uvs.Add(new Vector2(u, v));
point += 1;
// Create triangles in ring using indices.
if (i > 0 && j > 0)
{
indices.Add(prevRow + j - 1);
indices.Add(prevRow + j);
indices.Add(thisRow + j - 1);
indices.Add(prevRow + j);
indices.Add(thisRow + j);
indices.Add(thisRow + j - 1);
}
}
prevRow = thisRow;
thisRow = point;
}
// Insert committing to the ArrayMesh here.
}
}
Salvando
Finally, we can use the ResourceSaver class to save the ArrayMesh. This is useful when you want to generate a mesh and then use it later without having to re-generate it.
# Saves mesh to a .tres file with compression enabled.
ResourceSaver.save(mesh, "res://sphere.tres", ResourceSaver.FLAG_COMPRESS)
// Saves mesh to a .tres file with compression enabled.
ResourceSaver.Save(Mesh, "res://sphere.tres", ResourceSaver.SaverFlags.Compress);