Surfaces of revolution
Open the live demo · Read the source · View on GitHub
A lathe mesh starts with a line in the radius-height plane. Sweeping that line around the Y axis makes a vessel, column, wheel, or any other rotationally symmetric surface.
Step 1: Draw the side profile #
Each Vector2 stores radius in x and height in y. The profile runs from
bottom to top, which gives the finished surface outward-facing normals. Points
on the axis close the base without a separate mesh.
final List<Vector2> profile = <Vector2>[
Vector2(0.0, -1.25),
Vector2(0.72, -1.25),
Vector2(0.78, -1.1),
Vector2(0.82, -0.55),
Vector2(0.62, 0.05),
Vector2(0.46, 0.7),
Vector2(0.46, 1.05),
Vector2(0.62, 1.18),
Vector2(0.62, 1.25),
Vector2(0.5, 1.25),
];
Step 2: Sweep it around the axis #
segments controls how many angular slices are built. The first vessel keeps
the slices visible, the second uses enough for a smooth outline, and the third
stops after three quarters of a turn so the profile can be seen from inside.
final List<(String, double, int)> variants = <(String, double, int)>[
('faceted', math.pi * 2.0, 12),
('smooth', math.pi * 2.0, 48),
('open sweep', math.pi * 1.5, 36),
];
final Scene scene = Scene()
..ambientColor = Vector3(0.48, 0.56, 0.72)
..ambientIntensity = 0.14;
for (var i = 0; i < variants.length; i++) {
final (String name, double sweep, int segments) = variants[i];
final MeshData data = LatheShape(
profile: profile,
segments: segments,
sweepAngle: sweep,
name: name,
).build();
final Material clay = Material(
name: '$name clay',
baseColor: Vector4(0.78, 0.35 + i * 0.12, 0.2 + i * 0.16, 1.0),
roughness: 0.48,
doubleSided: sweep < math.pi * 2.0,
);
final MeshNode node = MeshNode(
DeviceMesh.upload(context.device, data),
clay,
name: name,
)..setPosition((i - 1) * 2.6, 1.25, 0.0);
_vessels.add(node);
scene.add(node);
}
Step 3: Light the profile #
A directional light shows the broad change in normals around each vessel. A point light behind them catches the rim and makes the open sweep easier to read.
scene
..add(
LightNode(name: 'key', intensity: 3.2)
..setLocalForward(Vector3(-0.45, -0.82, -0.34)),
)
..add(
LightNode(
type: LightType.point,
intensity: 8.0,
range: 10.0,
name: 'rim',
)..setPosition(2.5, 3.8, -2.5),
);
Step 4: Check every variant #
The page records the three nodes, checks their names, and confirms that each variant contributed a draw to the frame.
const Set<String> expected = <String>{'faceted', 'smooth', 'open sweep'};
final Set<String?> actual = <String?>{
for (final MeshNode node in _vessels) node.name,
};
if (_vessels.length != 3 ||
!actual.containsAll(expected) ||
frame.drawCalls < 3) {
throw StateError('the three lathe variants were not drawn');
}