Skip to content

Commit

Permalink
FBXLoader: Fixed morph attributes to match base geometry length (#28397)
Browse files Browse the repository at this point in the history
* 28378 fixed morph attributes to match base geometry length

* added fbx morph example

* Update webgl_loader_fbx.html

Remove nurbs asset since it already has its own example.

* Update webgl_loader_fbx.html

The Stanford bunny is already used in three other examples. Let's only add assets in `webgl_loader_fbx` which are not used elsewhere.

* resized morph_test and cleaned up adjustments

---------

Co-authored-by: Michael Herzog <michael.herzog@human-interactive.org>
  • Loading branch information
catalin-enache and Mugen87 authored May 20, 2024
1 parent 114f0ad commit ad0e26e
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 38 deletions.
37 changes: 24 additions & 13 deletions examples/jsm/loaders/FBXLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -2051,14 +2051,18 @@ class GeometryParser {
// Triangulate n-gon using earcut

const vertices = [];

// in morphing scenario vertexPositions represent morphPositions
// while baseVertexPositions represent the original geometry's positions
const positions = geoInfo.baseVertexPositions || geoInfo.vertexPositions;
for ( let i = 0; i < facePositionIndexes.length; i += 3 ) {

vertices.push( new Vector3(
geoInfo.vertexPositions[ facePositionIndexes[ i ] ],
geoInfo.vertexPositions[ facePositionIndexes[ i + 1 ] ],
geoInfo.vertexPositions[ facePositionIndexes[ i + 2 ] ]
) );
vertices.push(
new Vector3(
positions[ facePositionIndexes[ i ] ],
positions[ facePositionIndexes[ i + 1 ] ],
positions[ facePositionIndexes[ i + 2 ] ]
)
);

}

Expand All @@ -2071,6 +2075,12 @@ class GeometryParser {

}

// When vertices is an array of [0,0,0] elements (which is the case for vertices not participating in morph)
// the triangulationInput will be an array of [0,0] elements
// resulting in an array of 0 triangles being returned from ShapeUtils.triangulateShape
// leading to not pushing into buffers.vertex the redundant vertices (the vertices that are not morphed).
// That's why, in order to support morphing scenario, "positions" is looking first for baseVertexPositions,
// so that we don't end up with an array of 0 triangles for the faces not participating in morph.
triangles = ShapeUtils.triangulateShape( triangulationInput, [] );

} else {
Expand Down Expand Up @@ -2225,17 +2235,18 @@ class GeometryParser {
// Normal and position attributes only have data for the vertices that are affected by the morph
genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, name ) {

const vertexIndices = ( parentGeoNode.PolygonVertexIndex !== undefined ) ? parentGeoNode.PolygonVertexIndex.a : [];
const basePositions = parentGeoNode.Vertices !== undefined ? parentGeoNode.Vertices.a : [];
const baseIndices = parentGeoNode.PolygonVertexIndex !== undefined ? parentGeoNode.PolygonVertexIndex.a : [];

const morphPositionsSparse = ( morphGeoNode.Vertices !== undefined ) ? morphGeoNode.Vertices.a : [];
const indices = ( morphGeoNode.Indexes !== undefined ) ? morphGeoNode.Indexes.a : [];
const morphPositionsSparse = morphGeoNode.Vertices !== undefined ? morphGeoNode.Vertices.a : [];
const morphIndices = morphGeoNode.Indexes !== undefined ? morphGeoNode.Indexes.a : [];

const length = parentGeo.attributes.position.count * 3;
const morphPositions = new Float32Array( length );

for ( let i = 0; i < indices.length; i ++ ) {
for ( let i = 0; i < morphIndices.length; i ++ ) {

const morphIndex = indices[ i ] * 3;
const morphIndex = morphIndices[ i ] * 3;

morphPositions[ morphIndex ] = morphPositionsSparse[ i * 3 ];
morphPositions[ morphIndex + 1 ] = morphPositionsSparse[ i * 3 + 1 ];
Expand All @@ -2245,9 +2256,9 @@ class GeometryParser {

// TODO: add morph normal support
const morphGeoInfo = {
vertexIndices: vertexIndices,
vertexIndices: baseIndices,
vertexPositions: morphPositions,

baseVertexPositions: basePositions
};

const morphBuffers = this.genBuffers( morphGeoInfo );
Expand Down
Binary file added examples/models/fbx/morph_test.fbx
Binary file not shown.
115 changes: 90 additions & 25 deletions examples/webgl_loader_fbx.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,23 @@

import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';

let camera, scene, renderer, stats;

let camera, scene, renderer, stats, object, loader, guiMorphsFolder;
const clock = new THREE.Clock();

let mixer;

const params = {
asset: 'Samba Dancing'
};

const assets = [
'Samba Dancing',
'morph_test'
];


init();

function init() {
Expand Down Expand Up @@ -76,15 +86,75 @@
grid.material.opacity = 0.2;
grid.material.transparent = true;
scene.add( grid );

loader = new FBXLoader( );
loadAsset( params.asset );

renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.shadowMap.enabled = true;
container.appendChild( renderer.domElement );

const controls = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 100, 0 );
controls.update();

window.addEventListener( 'resize', onWindowResize );

// stats
stats = new Stats();
container.appendChild( stats.dom );

const gui = new GUI();
gui.add( params, 'asset', assets ).onChange( function ( value ) {

loadAsset( value );

} );

guiMorphsFolder = gui.addFolder( 'Morphs' ).hide();

}

function loadAsset( asset ) {

loader.load( 'models/fbx/' + asset + '.fbx', function ( group ) {

if ( object ) {

// model
const loader = new FBXLoader();
loader.load( 'models/fbx/Samba Dancing.fbx', function ( object ) {
object.traverse( function ( child ) {

mixer = new THREE.AnimationMixer( object );
if ( child.material ) child.material.dispose();
if ( child.material && child.material.map ) child.material.map.dispose();
if ( child.geometry ) child.geometry.dispose();

const action = mixer.clipAction( object.animations[ 0 ] );
action.play();
} );

scene.remove( object );

}

//

object = group;

if ( object.animations && object.animations.length ) {

mixer = new THREE.AnimationMixer( object );

const action = mixer.clipAction( object.animations[ 0 ] );
action.play();

} else {

mixer = null;

}

guiMorphsFolder.children.forEach( ( child ) => child.destroy() );
guiMorphsFolder.hide();

object.traverse( function ( child ) {

Expand All @@ -93,6 +163,18 @@
child.castShadow = true;
child.receiveShadow = true;

if ( child.morphTargetDictionary ) {

guiMorphsFolder.show();
const meshFolder = guiMorphsFolder.addFolder( child.name || child.uuid );
Object.keys( child.morphTargetDictionary ).forEach( ( key ) => {

meshFolder.add( child.morphTargetInfluences, child.morphTargetDictionary[ key ], 0, 1, 0.01 );

} );

}

}

} );
Expand All @@ -101,23 +183,6 @@

} );

renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.shadowMap.enabled = true;
container.appendChild( renderer.domElement );

const controls = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 100, 0 );
controls.update();

window.addEventListener( 'resize', onWindowResize );

// stats
stats = new Stats();
container.appendChild( stats.dom );

}

function onWindowResize() {
Expand Down

0 comments on commit ad0e26e

Please sign in to comment.