Skip to content
animorio
IT
All 3D objects

The cloud with a cane

NewGLBThree.js1 October 2026

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_sinistra looks left
  • occhi_destra looks right
  • occhi_su looks up
  • occhi_giu looks down
  • chiudi closes the eyes
  • sorriso happy, arched eyes
  • stupore wider eyes
  • respiro_alto the top puff swells
  • respiro_spalle the shoulders swell
  • respiro_basso the 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.

three.js
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.

LibraryThe animations, with the code