This -

This tutorial is about

morphing. Given a model coming from, for instance, Blend Swap, any geometry in this model is rarely equipped withmorphing.Morphingconsists in computing a distorded geometry. Animations are then the possible (discrete) motions from the original geometry to the distorded one.Motions are controlled through the

`morphTargetInfluences`

attribute (an array in fact, each element being devoted to a computed morphing). Values of`morphTargetInfluences[0]`

for thefirstmorphing may follow a mathematical function mapping to, for example, a spring motion. On purpose, the TWEENJS library is here reused because it provides such animation functions. However,Three.jspossesses its own animation system.

## Installation and use

For ease of installation and use, you may download the

TypeScriptapplication with all the necessary settings: .Once unzipped, you can execute the following statements:

`cd Man_head.ts`

`npm i`

`npm run Man_head`

Be careful about running things locally, you need a local Web server to load

`Man_head.html`

(further explanation here…). Typically, if you useVisual Studio Codethen you benefit from installingLive Serverand running`Man_head.html`

within it.## Theme

A man bust in GLTF format (license “CC0 Attribution”, credit to ArtOfLight) downloaded from Blend Swap includes several geometries. While eyes, eye corneas... are excluded, the main geometry (man bust essentially being a head) is kept. A half sphere geometry named

`"FRONT"`

is created from scratch. The morphing is computedfor/fromthis geometry. The distortion is such that each vertex in this half sphere geometry is projected onto the man bust front using the`intersectObject`

function within the`THREE.Raycaster`

class. Distortion is then the carved face.Picture shows the (white) half sphere in motion (intermediate step): each half sphere vertex moves to the face's details playing the role of distortion in this app.

`class Man_head { static readonly Man_head_name = "2_Head_sculpt_retopo_mesh"; … constructor() { … new Promise(ready => (new GLTFLoader()).load('./models/Man_head.gltf', this._process_GLTF.bind(this, ready))).then(value => { window.console.assert(value === Man_head.Man_head_name); this._animate(); }); } … // Called within '_process_GLTF': private _morphing(man_head: THREE.Mesh) { // https://stackoverflow.com/questions/16361327/check-if-point-is-inside-a-custom-mesh-geometry const surrounding_bubble = new THREE.Group(); this._scene.add(surrounding_bubble); surrounding_bubble.name = "Surrounding Bubble"; man_head.geometry.computeBoundingSphere(); const sphere_geometry = new THREE.SphereGeometry(man_head.geometry.boundingSphere!.radius, 68, 40, 0, Math.PI); // 'raycaster' requires tiny offset so that intersections are *ALL* not empty: sphere_geometry.rotateY(Number.EPSILON); // const back = new THREE.Mesh(sphere_geometry, new THREE.MeshBasicMaterial({ // side: THREE.DoubleSide, // wireframe: true // })); // back.rotateY(Math.PI); // back.name = "BACK"; // surrounding_bubble.add(back); const front = new THREE.Mesh(sphere_geometry, new THREE.MeshBasicMaterial({ alphaTest: 0.5, // morphNormals: true, // No longer needed with ver. 144... // morphTargets: true, // No longer needed with ver. 144... opacity: 0.5, side: THREE.DoubleSide, transparent: true })); front.name = "FRONT"; surrounding_bubble.add(front); // Note that 'front' has been designed such that vertex number is close to half of 'man_head', i.e., 5784: window.console.assert(front.geometry.attributes.position.count === 2829); // Set up 'front' morphing (morphing vertex number is the same that 'front' geometry vertex number): const morphing = new THREE.BufferAttribute(Float32Array.from(front.geometry.attributes.position.array), front.geometry.attributes.position.itemSize); front.geometry.morphAttributes.position = []; // Array of each computed morphing... front.geometry.morphAttributes.position.push(morphing); // First one whose motion is ruled by 'front.morphTargetInfluences[0]' // Compute morphing (i.e., geometry of 'front' half sphere is distorted with regard to man head's face): const direction = new THREE.Vector3(0, 0, 0); const origin = new THREE.Vector3(0, 0, 0); const positions = front.geometry.getAttribute('position').array; const intersection = new Array(); const raycaster = new THREE.Raycaster(); for (let i = 0; i < front.geometry.attributes.position.count; i++) { direction.set(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]); direction.negate().normalize(); // To scene center... origin.set(positions[i * 3], positions[i * 3 + 1], positions[i * 3 + 2]); raycaster.set(origin, direction); // From point of 'front' geometry towards scene center as direction... /** DEBUG */ // this._scene.add(new THREE.ArrowHelper(direction, origin)); /** End of DEBUG */ intersection.length = 0; // Clear... raycaster.intersectObject(man_head, false, intersection); /* Returned by 'intersectObject': [ { distance, point, face, faceIndex, object }, ... ] distance – distance between the origin of the ray and the intersection point – point of intersection, in world coordinates face – intersected face faceIndex – index of the intersected face object – the intersected object */ if (intersection.length > 0) // 'intersection[0].point' is the closest point: // Effective distortion: morphing.setXYZ(i, intersection[0].point.x, intersection[0].point.y, intersection[0].point.z); } front.updateMorphTargets(); // 'front.morphTargetInfluences' is reset to blank array: window.console.assert(front.morphTargetInfluences!.length === 1 && front.morphTargetInfluences![0] === 0); createjs.Tween.get(this, { // TWEENJS animation from offered functions... onChange: () => { front.morphTargetInfluences![0] = this._tween; // Morphing motion here... }, loop: true, paused: false }) .to({_tween: 1}, 1500, createjs.Ease.linear) // Other mathematical functions are usable... .wait(1500) .to({_tween: 0}, 500, createjs.Ease.linear); // Other mathematical functions are usable... } private _process_GLTF(ready: Function, gltf: GLTF) { … } }`

## Exercise

Compute some morphing for

`"Eyes"`

mesh, which is currently set to “invisble”.