
A white cloud with inset eyes, thin legs and a wooden cane. It breathes, looks around, smiles and waves, taps its cane; and you drive the face yourself, with ten poses.
What’s inside
- Mesh
- 80,027 vertices, 159,988 triangles
- Skeleton
- 16 bones
- Animation
vita, 10 seconds, looping. It breathes (the cloud’s puffs swell in a wave), blinks, looks at its cane and then the other way, smiles and waves with a little hop, taps its cane twice, looks up at the sky. It loops seamlessly.- Facial poses
occhi_sinistralooks leftocchi_destralooks rightocchi_sulooks upocchi_giulooks downchiudicloses the eyessorrisohappy, arched eyesstuporewider eyesrespiro_altothe top puff swellsrespiro_spallethe shoulders swellrespiro_bassothe lower puffs swell
- Materials
- Nuvola (matte white, slightly warm), Occhi (glossy dark brown), Riflesso (self-lit white), Arti (satin chocolate), Legno (light walnut)
- Weight
- 1.4 MB
How to use it
Load the GLB, start the animation and, if you like, drive the facial poses yourself: each shape key has a name and a weight from 0 to 1.
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { MeshoptDecoder } from 'three/addons/libs/meshopt_decoder.module.js';
import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
// the model: meshopt-compressed, it needs its decoder
const loader = new GLTFLoader().setMeshoptDecoder(MeshoptDecoder);
const gltf = await loader.loadAsync('nuvola-col-bastone.glb');
scene.add(gltf.scene);
// the "vita" animation: 10 seconds, looping
const mixer = new THREE.AnimationMixer(gltf.scene);
mixer.clipAction(gltf.animations[0]).play();
// the facial poses: a weight from 0 to 1 for each shape key
const faccia = [];
gltf.scene.traverse((m) => {
if (m.morphTargetDictionary && 'sorriso' in m.morphTargetDictionary) faccia.push(m);
});
const posa = (nome, peso) => faccia.forEach((m) => {
m.morphTargetInfluences[m.morphTargetDictionary[nome]] = peso;
});
// the light: a white studio, and white stays white
renderer.toneMapping = THREE.NoToneMapping;
scene.environment = new THREE.PMREMGenerator(renderer)
.fromScene(new RoomEnvironment(), 0.04).texture;
const clock = new THREE.Clock();
renderer.setAnimationLoop(() => {
mixer.update(clock.getDelta());
posa('occhi_destra', 0.6); // after the mixer: it wins over the clip
renderer.render(scene, camera);
});How it’s made
From drawing to shape
The illustration’s outline is made of seven arcs: each one was measured to a tenth of a pixel and becomes a sphere. The spheres join with a fillet that changes with depth: sharp at the rim, so the points between puffs stay crisp, and softer and softer towards the face, where the creases fade out as in the drawing.
Truly smooth
Every part is a mathematical surface: the mesh is built on a dense grid and every vertex is then moved exactly onto the surface, with the normal taken from the surface itself. For the web the mesh was reduced where it is flat and kept dense where it curves, then moved back onto the surface: 80 thousand vertices, and no facets.
The face
The eyes sit in a socket with a flared rim, which catches the light below and the shadow above. For the poses the socket moves with the eye keeping its depth under the skin, so the cloud stays exact while it looks around, closes its eyes or smiles.
The skeleton
Sixteen bones: the body, the legs with their feet planted on the ground (inverse kinematics, baked into the animation), the waving arm, the cane with the hand hooked on it. The choreography is written as functions of time and sampled at 24 frames per second.