One Asteroid Recipe for Blender and the Browser


Shape an asteroid in your browser with the Rust recipe that also builds it in Blender, through blr and pyo3_bindgen. The asteroid below is built in your browser. Every slider reruns a Rust recipe, compiled to WebAssembly, that rebuilds the mesh (10,242 vertices at the default detail) in a few milliseconds; WebGL 2 draws it. The same recipe builds the asteroid in Blender through blr, whose bindings to Blender’s Python API come from pyo3_bindgen. Press Load demo to start it. Drag to orbit, and scroll or pinch to zoom. Download GLB saves the current asteroid, which Blender opens with File › Import › glTF 2.0. An asteroid starts as an icosphere with Blender’s vertex layout: like Blender cannot run on this page, so a separate Rust program builds the three presets in Blender 5.2 through the upcoming version of blr. It asks Blender for its own icosphere, moves Blender’s vertices with the recipe as a shape key, and renders the result with Cycles: The three presets, rendered by Cycles in Blender 5.2 with one camera and a sun light. The program then compares Blender’s evaluated mesh, with the shape key applied, to the mesh that the browser builds; it runs the recipe natively, which gives the same numbers as WebAssembly. For each preset, both meshes have 10,242 vertices and the same 20,480 triangles, and every Blender vertex lies within 2.1 × 10⁻⁵ of its own browser vertex, on an asteroid whose radius is about 1. The difference comes from Blender’s own icosahedron, whose stored vertices are rounded. The calls above are blr’s hand-written layer. Beneath it, blr’s typed bindings are generated: a dump of the types in Blender 5.2’s Python API fills 13 MB of JSON, blr keeps the types that it uses, and pyo3_bindgen turns them into 156,436 lines of Rust.

The recipe
bpy.ops.mesh.primitive_ico_sphere_add, the recipe splits each edge of an icosahedron into evenly spaced points, fills each face with a triangular grid and moves every point onto the unit sphere. It then pushes each vertex in or out along its direction, by seeded noise for the lumps and the fine grain and by a bowl with a raised rim for each crater, and stretches the long axis. The displacement depends only on the vertex position and uses only arithmetic, square roots, rounding down, minimum and maximum, which IEEE 754 defines exactly, so the browser and a native program compute the same numbers. In Blender
let asteroid = Object::new_mesh_primitive_ico_sphere(
access, params., 1.0, false, [0.0; 3], [0.0; 3], [1.0; 3],
)?;
let mesh: Mesh = asteroid.bind(py).getattr("data")?.extract()?;
let shape = Shape::new(params);
let displaced: Vec<[f32; 3]> =
mesh.positions(access)?.iter().map(|&p| shape.displace(p)).collect();
asteroid.add_shape_key(access, "Basis", false)?;
let key = asteroid.add_shape_key(access, "Recipe", false)?;
key.set_positions(access, &displaced)?;
key.set_value(py, 1.0)?;