Building a mesh in code
Open the live demo · Read the source · View on GitHub
A shape generator is a MeshBuilder used the same way a hand-written mesh
would be. This page skips the generator and writes the pyramid by hand, one
vertex and one triangle at a time.
Step 1: Start from a layout #
MeshBuilder is handed the layout it should write. VertexLayout.standard is
what the lit shaders read: position, normal, texture coordinates, tangent and
colour. An attribute the layout does not declare is simply ignored if it is
passed in.
final MeshBuilder builder = MeshBuilder(VertexLayout.standard);
const double half = 0.9;
const double apexHeight = 1.3;
final Vector3 apex = Vector3(0.0, apexHeight, 0.0);
final List<Vector3> base = <Vector3>[
Vector3(-half, 0.0, -half),
Vector3(half, 0.0, -half),
Vector3(half, 0.0, half),
Vector3(-half, 0.0, half),
];
Step 2: Add vertices and triangles #
addVertex returns the index it was stored at, which is exactly what
addTriangle and addQuad need. The four side faces share the apex and each
computes its own flat normal from the two edges that meet there, so the
pyramid reads as faceted rather than smoothed.
final int apexIndex = builder.addVertex(
position: apex,
normal: Vector3(0.0, 1.0, 0.0),
texcoord: Vector2(0.5, 0.0),
color: Vector4(1.0, 0.92, 0.6, 1.0),
);
final List<int> baseIndices = <int>[];
for (var i = 0; i < base.length; i++) {
final Vector3 edge = base[(i + 1) % base.length] - base[i];
final Vector3 toApex = apex - base[i];
final Vector3 normal = edge.cross(toApex)..normalize();
baseIndices.add(
builder.addVertex(
position: base[i],
normal: normal,
texcoord: Vector2(i / base.length, 1.0),
color: Vector4(0.65, 0.7, 0.8, 1.0),
),
);
}
for (var i = 0; i < base.length; i++) {
builder.addTriangle(baseIndices[i], baseIndices[(i + 1) % 4], apexIndex);
}
builder.addQuad(
baseIndices[3],
baseIndices[2],
baseIndices[1],
baseIndices[0],
);
_pyramid = builder.build();
Step 3: Draw it #
The finished MeshData uploads and draws exactly like one that came from a
shape.
final Scene scene = Scene()
..ambientColor = Vector3(0.42, 0.5, 0.66)
..ambientIntensity = 0.16
..add(
MeshNode(
DeviceMesh.upload(context.device, _pyramid),
Material(name: 'pyramid', roughness: 0.6),
name: 'pyramid',
),
)
..add(
LightNode(name: 'key', intensity: 3.2)
..setLocalForward(Vector3(-0.42, -0.78, -0.4)),
);
Step 4: Check the counts #
Five vertices went in: one apex and four base corners, none of them repeated. Eighteen indices came out: four side triangles and a base split into two.
if (_pyramid.vertexCount != 5 ||
_pyramid.indexCount != 18 ||
frame.drawCalls < 1) {
throw StateError('the hand-built pyramid did not reach the frame');
}