Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion editor/js/Loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -470,10 +470,24 @@ var Loader = function ( editor ) {

var mesh;

if ( geometry.animation && geometry.animation.hierarchy ) {
if ( geometry.bones !== undefined ) {

mesh = new THREE.SkinnedMesh( geometry, material );

if ( mesh.material instanceof THREE.MultiMaterial ) {

for ( var i = 0; i < mesh.material.materials.length; i++ ) {

mesh.material.materials[ i ].skinning = true;

}

} else {

mesh.material.skinning = true;

}

} else {

mesh = new THREE.Mesh( geometry, material );
Expand Down
11 changes: 9 additions & 2 deletions editor/js/Viewport.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,19 @@ var Viewport = function ( editor ) {

selectionBox.update( object );

if ( editor.helpers[ object.id ] !== undefined ) {
function updateHelper ( object ) {

editor.helpers[ object.id ].update();
if ( editor.helpers[ object.id ] !== undefined ) {

editor.helpers[ object.id ].update();

}

}

updateHelper( object );
object.traverseAncestors( updateHelper );

signals.refreshSidebarObject3D.dispatch( object );

}
Expand Down
49 changes: 49 additions & 0 deletions src/core/Geometry.js
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,51 @@ THREE.Geometry.prototype = {

}

var bones = [];
var skinIndices = [];
var skinWeights = [];
var influencesPerVertex = 0;

if ( this.bones !== undefined ) {

for ( var i = 0; i < this.bones.length; i ++ ) {

bones.push( this.bones[ i ] );

}

}

for ( var i = 0; i < this.skinIndices.length; i ++ ) {

var array = this.skinIndices[ i ].toArray();

for ( var j = 0; j < array.length; j ++ ) {

skinIndices.push( array[ j ] );

}

}

for ( var i = 0; i < this.skinWeights.length; i ++ ) {

var array = this.skinWeights[ i ].toArray();

for ( var j = 0; j < array.length; j ++ ) {

skinWeights.push( array[ j ] );

}

}

if ( this.skinIndices.length > 0 ) {

influencesPerVertex = this.skinIndices[ 0 ].toArray().length;

}

function setBit( value, position, enabled ) {

return enabled ? value | ( 1 << position ) : value & ( ~ ( 1 << position ) );
Expand Down Expand Up @@ -1107,6 +1152,10 @@ THREE.Geometry.prototype = {
if ( colors.length > 0 ) data.data.colors = colors;
if ( uvs.length > 0 ) data.data.uvs = [ uvs ]; // temporal backward compatibility
data.data.faces = faces;
if ( bones.length > 0 ) data.data.bones = bones;
if ( skinIndices.length > 0 ) data.data.skinIndices = skinIndices;
if ( skinWeights.length > 0 ) data.data.skinWeights = skinWeights;
if ( influencesPerVertex > 0 ) data.data.influencesPerVertex = influencesPerVertex;

return data;

Expand Down
1 change: 1 addition & 0 deletions src/loaders/MaterialLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ THREE.MaterialLoader.prototype = {
if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite;
if ( json.wireframe !== undefined ) material.wireframe = json.wireframe;
if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth;
if ( json.skinning !== undefined ) material.skinning = json.skinning;

// for PointsMaterial
if ( json.size !== undefined ) material.size = json.size;
Expand Down
95 changes: 95 additions & 0 deletions src/loaders/ObjectLoader.js
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,7 @@ THREE.ObjectLoader.prototype = {
break;

case 'Mesh':
case 'SkinnedMesh':

var geometry = getGeometry( data.geometry );
var material = getMaterial( data.material );
Expand Down Expand Up @@ -553,6 +554,12 @@ THREE.ObjectLoader.prototype = {

break;

case 'Bone':

object = new THREE.Bone();

break;

default:

object = new THREE.Object3D();
Expand Down Expand Up @@ -610,6 +617,94 @@ THREE.ObjectLoader.prototype = {

}

/*
* Now SkinnedMesh could have two bone sets in its children,
* one is made from geometry.bones in SkinnedMesh constroctor
* and another one is made from data.children in this method.
* These two bone sets correspond to the same bones.
* The former would represent a default stand pose while
* the latter would represent a pose updated by skinning.
* To let SkinnedMesh be updated pose, here this logic copies
* the latter one to the former one then removes the latter one.
* If in the latter one there're objects which don't correspond to
* the ones in the former one, they'll be moved to the former one.
*/
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrdoob
What do you think of this idea?
I suppose this could be a bit too complex and there would be any better/simpler ways...

(Maybe the root issue is that SkinnedMesh here has two bones sets in its children and
this idea could be kinda just workaround)

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds a bit confusing...

Copy link
Collaborator Author

@takahirox takahirox Mar 25, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So let me make the issue more specific with an example.

Imagine that there is a geometry with bones which
hold default pose bone parameters as just Objects.

// Bone B is a child of Bone A
geometry
    - bones[0] (Object A)
    - bones[1] (Object B)
    - bones[2] (Object C)

You create SkinnedMesh instance from that geometry.
Bone instances are generated in SkinnedMesh constructor
and they'll be children of SkinnedMesh with forming of parent-child structure
and also will be bones of SkinnesMesh.skeleton.

SkinnedMesh
    - geometry
        - bones[0] (Object A)
        - bones[1] (Object B)
        - bones[2] (Object C)
    - skeleton
        - bones[0] (Bone A)
        - bones[1] (Bone B)
        - bones[2] (Bone C)
    - children[0] (Bone A)
        - children[0] (Bone B)
    - children[1] (Bone C)

You add Mesh to Bone B.

SkinnedMesh
    - geometry
        - bones[0] (Object A)
        - bones[1] (Object B)
        - bones[2] (Object C)
    - skeleton
        - bones[0] (Bone A)
        - bones[1] (Bone B)
        - bones[2] (Bone C)
    - children[0] (Bone A)
        - children[0] (Bone B)
            - Mesh
    - children[1] (Bone C)

You let SkinnedMesh do skinning.

// ' indicates updated instance by skinning
SkinnedMesh
    - geometry
        - bones[0] (Object A)
        - bones[1] (Object B)
        - bones[2] (Object C)
    - skeleton
        - bones[0] (Bone A')
        - bones[1] (Bone B')
        - bones[2] (Bone C')
    - children[0] (Bone A')
        - children[0] (Bone B')
            - Mesh
    - children[1] (Bone C')

After that you create json with toJSON() and then
re-create SkinnedMesh with ObjectLoader.parse(json).
(This represents auto-save and auto-load of Editor)

The issue that SkinnedMesh which you get has two bone sets.
One is made from json.geometry.bones and represents default pose (Bone A, B, C).
Another one is made from json.object(SkinnedMesh).children and represents updated bones (Bone A', B', C')

SkinnedMesh
    - geometry
        - bones[0] (Object A)
        - bones[1] (Object B)
        - bones[2] (Object C)
    - skeleton
        - bones[0] (Bone A)
        - bones[1] (Bone B)
        - bones[2] (Bone C)
    - children[0] (Bone A)
        - children[0] (Bone B)
    - children[1] (Bone C)
    - children[2] (Object3D A')
        - children[0] (Object3D B')
            - Mesh
    - children[3] (Object3D C')

What we want is the updated one.
So my idea is that

// SkinnedMesh.children[0] <- copy - SkinnedMesh.children[2]
// SkinnedMesh.children[0][0] <- copy - SkinnedMesh.children[2][0]
// SkinnedMesh.children[1] <- copy - SkinnedMesh.children[3]
// move Mesh from a child of children[2][0] to a child of children[0][0]
// remove SkinnedMesh.children[2], [2][0], [3]
SkinnedMesh
    - geometry
        - bones[0] (Object A)
        - bones[1] (Object B)
        - bones[2] (Object C)
    - skeleton
        - bones[0] (Bone A')
        - bones[1] (Bone B')
        - bones[2] (Bone C')
    - children[0] (Bone A')
        - children[0] (Bone B')
            - Mesh
    - children[1] (Bone C')

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mrdoob
So, what do you think of this.
Should I write comment in more detail?
Or should I consider any simpler ways?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/ping @mrdoob
Is my idea too complicated to be merged?

Copy link
Contributor

@bhouston bhouston May 2, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that we still convert node-based bones into skeleton bones during load time if I am not mistaken. If we were to only use skeleton-bones by default, we could likely speed up load time.

I could see it useful to have a function to convert skeleton-bones to nodes and vice versa, but I would only keep one representation at a time to avoid issues -- although as I said modifying the bones in a game engine will likely invalidate all saved animations for that character -- which is likely a bad thing. Character's bones should be modified in the content creation tool that is also creating all your animations (e.g. Maya, Blender, etc.) so that they are properly synchronized.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it comes down to what the intended scope of Three.js and the editor is supposed to be.

Authoring software will most probably do a better job when it comes to editing, OTOH I guess it could be very useful (e.g. for debugging) to have a GUI-way to explore an exported animation, maybe even to change it in some ways.

Looking at all the trouble with the exporters lately, going "baked only" may make it more difficult to get right even if it means fewer code, but I could be wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it could be very useful (e.g. for debugging) to have a GUI-way to explore an exported animation

This is a necessity. There is the Skeleton helper for this.

Looking at all the trouble with the exporters lately, going "baked only" may make it more difficult to get right even if it means fewer code, but I could be wrong.

We need to get the exporters/workflow fixed or ThreeJS will be considered to be unserious/unstable/unusable compared to its competitors. To avoid that issue is to put one's head in the sand -- that is a huge issue that if it isn't fixed will hurt ThreeJS tremendously. The most important is Blender, because it is a free tool, the second most important should be the FBX importer (for the commercial tools 3DS Max, Maya, etc.).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are three markets that ThreeJS target in my opinion: those learning to code 3D (where ThreeJS is very dominant) and those creating 3D projects (there is a lot of competition here) and those using ThreeJS as the renderer for other tools (such as our usage.) The exporters do not need to be fixed to target those learning to code 3D, they are happy with OBJ/MTL imports for the most part. For most third party tools using ThreeJS as the renderer, they also do not need to be fixed, these other tools have their own loaders -- these users of ThreeJS Iview as low value to the project as they tend not to be contributors back to the project though because they view the other users of ThreeJS as potential competitors. But those creating serious 3D projects need to be able to rely on their artists (who want to work in their content creation tool of choice since that is where they are productive) creating content that just works in ThreeJS. If there are non-trivial post export steps that need to be done to get content to work, when it will just work in Unity3D or other tools, then you are putting ThreeJS at a disadvantage, and a serious one at that. A great model to follow is Blend4Web - it is nearly perfect WYSIWYG when moving from Blender to WebGL both in terms of materials, lights, animations, meshes, bones, skinning, etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bhouston

There is the Skeleton helper for this.

Yes, that one lets you see whether it worked, but not necessarily what went wrong. An online tool (like the Three editor) that lets you load a model from a support request in order to see names & hierarchy might come useful, I figure.

We need to get the exporters/workflow fixed... The most important is Blender...

Absolutely. If all others fail can still use Blender as a converter and at least won't have to pay an extra fee on top of the mess. I'm following that topic closely - ready to step in when required. The code quality is very workable - well-structured and not bad at all.

if ( object.type === 'SkinnedMesh' ) {

function copyBone ( bone, bone2 ) {

bone.position.copy( bone2.position );
bone.rotation.copy( bone2.rotation );
bone.scale.copy( bone2.scale );
bone.updateMatrixWorld();

}

function traverseBones ( object, object2 ) {

for ( var i = 0; i < object2.children.length; i ++ ) {

var child2 = object2.children[ i ];

var found = false;

if ( child2.type === 'Bone' ) {

for ( var j = 0; j < object.children.length; j ++ ) {

var child = object.children[ j ];

if ( child.type === 'Bone' && child.name === child2.name ) {

copyBone( child, child2 );
traverseBones( child, child2 );
found = true;

break;

}

}

}

if ( ! found ) {

object.add( child2 );

}

}

}

for ( var i = 0; i < object.children.length; i ++ ) {

var child = object.children[ i ];

if ( child.type === 'Bone' ) {

for ( var j = i + 1; j < object.children.length; j ++ ) {

var child2 = object.children[ j ];

if ( child2.type === 'Bone' && child.name === child2.name ) {

copyBone( child, child2 );
traverseBones( child, child2 );
object.remove( child2 );
break;

}

}

}

}

}

return object;

};
Expand Down
2 changes: 2 additions & 0 deletions src/materials/Material.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ THREE.Material.prototype = {
if ( this.wireframe === true ) data.wireframe = this.wireframe;
if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth;

if ( this.skinning !== undefined ) data.skinning = this.skinning;

// TODO: Copied from Object3D.toJSON

function extractFromCache ( cache ) {
Expand Down