What is the meaning of skin indices and skin weights? - three.js

This is undocumented, so I'm asking here. I'm trying to animate a mesh in JavaScript. I'm using Blender->Three.js exporter because it is convenient. I can't use Three.js itself because I was not able to figure out how to solve certain problems on it (rendering the normals and depth informations of a scene with animated meshes to a buffer). So, how do you read the "skinIndices" and "skinWeights" properties that get exported from Blender to Three.js? What do they mean, and what are they roles when calculating the position of the vertices on the animations?
"bones" : [
{"parent":-1,"name":"pelvis","pos":[-3.52132e-08,0.0410043,0.880063],"rotq":[0,0,0,1]},
{"parent":0,"name":"thigh.L","pos":[0.0878887,0.00522349,0.102822],"rotq":[0,0,0,1]},
{"parent":1,"name":"shin.L","pos":[0.103679,0.00638392,-0.445744],"rotq":[0,0,0,1]},
{"parent":2,"name":"foot.L","pos":[0.0655578,0.0194668,-0.418675],"rotq":[0,0,0,1]},
{"parent":3,"name":"toe.L","pos":[0.0280578,-0.107185,-0.0704246],"rotq":[0,0,0,1]},
{"parent":3,"name":"heel.L","pos":[3.58224e-05,0.036576,-0.0885088],"rotq":[0,0,0,1]},
{"parent":0,"name":"thigh.R","pos":[-0.0878888,0.00522352,0.102822],"rotq":[0,0,0,1]},
{"parent":6,"name":"shin.R","pos":[-0.103679,0.00638412,-0.445745],"rotq":[0,0,0,1]},
{"parent":7,"name":"foot.R","pos":[-0.0655576,0.0194677,-0.418675],"rotq":[0,0,0,1]},
{"parent":8,"name":"toe.R","pos":[-0.0280577,-0.107185,-0.0704248],"rotq":[0,0,0,1]},
{"parent":8,"name":"heel.R","pos":[-3.57926e-05,0.036576,-0.0885083],"rotq":[0,0,0,1]},
{"parent":0,"name":"stomach","pos":[5.37268e-09,-0.008465,0.121596],"rotq":[0,0,0,1]},
{"parent":11,"name":"chest","pos":[1.94616e-08,0.0538289,0.269019],"rotq":[0,0,0,1]},
{"parent":12,"name":"upper_arm.L","pos":[0.160045,-0.010388,0.159844],"rotq":[0,0,0,1]},
{"parent":13,"name":"forearm.L","pos":[0.165089,0.0102809,-0.232678],"rotq":[0,0,0,1]},
{"parent":14,"name":"hand.L","pos":[0.0980782,-0.0148839,-0.245313],"rotq":[0,0,0,1]},
{"parent":15,"name":"index.L.001","pos":[0.019191,-0.040475,-0.0743723],"rotq":[0,0,0,1]},
{"parent":16,"name":"index.L.002","pos":[-0.00562334,-0.00824448,-0.0310695],"rotq":[0,0,0,1]},
{"parent":17,"name":"index.L.003","pos":[-0.00953785,-0.00126594,-0.0192741],"rotq":[0,0,0,1]},
{"parent":15,"name":"middle.L.001","pos":[0.0191911,-0.0188201,-0.0769786],"rotq":[0,0,0,1]},
{"parent":19,"name":"middle.L.002","pos":[0.00288424,-0.00695575,-0.0326532],"rotq":[0,0,0,1]},
{"parent":20,"name":"middle.L.003","pos":[-0.0111618,-0.00550338,-0.0242877],"rotq":[0,0,0,1]},
{"parent":15,"name":"ring.L.001","pos":[0.0186397,0.00194495,-0.0777299],"rotq":[0,0,0,1]},
{"parent":22,"name":"ring.L.002","pos":[0.00393239,-0.00062982,-0.0309386],"rotq":[0,0,0,1]},
{"parent":23,"name":"ring.L.003","pos":[-0.00873661,-0.00165674,-0.024165],"rotq":[0,0,0,1]},
{"parent":15,"name":"pinky.L.001","pos":[0.0191911,0.02271,-0.0758559],"rotq":[0,0,0,1]},
{"parent":25,"name":"pinky.L.002","pos":[-0.0057596,0.0014303,-0.0236881],"rotq":[0,0,0,1]},
{"parent":26,"name":"pinky.L.003","pos":[-0.00877053,-0.0020119,-0.0195478],"rotq":[0,0,0,1]},
{"parent":15,"name":"thumb.L.001","pos":[-0.0073517,-0.0318671,-0.0156776],"rotq":[0,0,0,1]},
{"parent":28,"name":"thumb.L.002","pos":[-0.00941652,-0.0166059,-0.0179188],"rotq":[0,0,0,1]},
{"parent":29,"name":"thumb.L.003","pos":[-0.0081799,-0.0129757,-0.0276645],"rotq":[0,0,0,1]},
{"parent":12,"name":"upper_arm.R","pos":[-0.160044,-0.010388,0.159844],"rotq":[0,0,0,1]},
{"parent":31,"name":"forearm.R","pos":[-0.165089,0.0102809,-0.232679],"rotq":[0,0,0,1]},
{"parent":32,"name":"hand.R","pos":[-0.0980774,-0.0148839,-0.245313],"rotq":[0,0,0,1]},
{"parent":33,"name":"index.R.001","pos":[-0.0185038,-0.0404748,-0.0743726],"rotq":[0,0,0,1]},
{"parent":34,"name":"index.R.002","pos":[0.00562337,-0.00824449,-0.0310695],"rotq":[0,0,0,1]},
{"parent":35,"name":"index.R.003","pos":[0.00953785,-0.00126596,-0.0192741],"rotq":[0,0,0,1]},
{"parent":33,"name":"middle.R.001","pos":[-0.0185038,-0.0188199,-0.0769789],"rotq":[0,0,0,1]},
{"parent":37,"name":"middle.R.002","pos":[-0.00288421,-0.00695577,-0.0326532],"rotq":[0,0,0,1]},
{"parent":38,"name":"middle.R.003","pos":[0.0111619,-0.00550339,-0.0242877],"rotq":[0,0,0,1]},
{"parent":33,"name":"ring.R.001","pos":[-0.0179525,0.00194514,-0.0777302],"rotq":[0,0,0,1]},
{"parent":40,"name":"ring.R.002","pos":[-0.00393245,-0.000629827,-0.0309386],"rotq":[0,0,0,1]},
{"parent":41,"name":"ring.R.003","pos":[0.00873658,-0.00165676,-0.024165],"rotq":[0,0,0,1]},
{"parent":33,"name":"pinky.R.001","pos":[-0.0185039,0.0227101,-0.0758562],"rotq":[0,0,0,1]},
{"parent":43,"name":"pinky.R.002","pos":[0.0057596,0.00143027,-0.0236881],"rotq":[0,0,0,1]},
{"parent":44,"name":"pinky.R.003","pos":[0.00877053,-0.00201192,-0.0195478],"rotq":[0,0,0,1]},
{"parent":33,"name":"thumb.R.001","pos":[0.00803882,-0.0318669,-0.0156779],"rotq":[0,0,0,1]},
{"parent":46,"name":"thumb.R.002","pos":[0.00941664,-0.0166059,-0.0179188],"rotq":[0,0,0,1]},
{"parent":47,"name":"thumb.R.003","pos":[0.00817987,-0.0129757,-0.0276645],"rotq":[0,0,0,1]},
{"parent":12,"name":"neck","pos":[1.6885e-08,-0.0164749,0.225555],"rotq":[0,0,0,1]},
{"parent":49,"name":"head","pos":[0.000806741,-0.0273245,0.0637051],"rotq":[0,0,0,1]}],
"skinIndices" : [
11,0,11,0,1,11,11,0,0,11,0,11,1,11,1,11,0,11,11,0,11,0,11,0,1,11,11,0,11,0,0,11,1,11,1,11,0,11,0,11,0,11,12,0,0,11,0,11,
0,11,12,0,12,0,11,0,11,0,11,0,12,0,11,0,11,0,11,0,12,0,12,11,11,0,11,0,12,13,11,0,0,11,11,0,12,13,12,13,0,11,12,13,12,0,
12,0,13,12,13,12,12,13,12,0,12,13,13,12,12,13,12,13,13,12,13,0,12,13,12,0,12,13,13,0,12,0,13,0,0,13,13,0,13,0,0,13,0,13,
13,0,13,0,0,13,13,12,13,12,13,12,13,0,13,0,13,0,13,12,13,0,13,0,13,0,13,0,13,0,13,0,13,0,13,0,13,0,13,0,13,0,13,0,13,0,13,
(... too big)]
"skinWeights" : [
0.454566,0.443267,0.456435,0.4405,0.568642,0.331477,0.452697,0.446034,0.600277,0.577654,0.603738,0.578153,0.557686,0.334716,
0.579597,0.328238,0.596817,0.577156,0.481496,0.447683,0.604872,0.59171,0.466162,0.448242,0.567426,0.35812,0.49683,0.447124,
0.618979,0.590887,0.592533,0.590764,0.578989,0.341559,0.555862,0.37468,0.477411,0.438341,0.617349,0.569542,0.454728,0.432345,
0.401061,0.337472,0.500093,0.444338,0.633534,0.572105,0.601164,0.56698,0.388198,0.308292,0.413925,0.366652,0.449179,0.424051,
0.618298,0.58735,0.458406,0.430254,0.473939,0,0.439952,0.417849,0.605333,0.579977,0.631263,0.594722,0.517687,0,0.430191,0.274572,
(... too big)]
"animations" : [
{"name":"ArmatureAction",
"fps":24,
"length":0.625,
"hierarchy":
[{"parent":-1,"keys":[
{"time":0,"pos":[-3.52132e-08,0.0410043,0.880063],"rot":[0,0,0,1],"scl":[1,1,1]},
{"time":0.291667,"pos":[-3.52132e-08,0.0410043,0.880063]},
{"time":0.625,"pos":[-3.52132e-08,0.0410043,0.880063],"rot":[0,0,0,1],"scl":[1,1,1]}]
},
{"parent":0,"keys":[
{"time":0,"pos":[0.0878887,0.00522349,0.102822],"rot":[0,0,0,1],"scl":[1,1,1]},
{"time":0.291667,"pos":[0.0878887,0.00522349,0.102822],"rot":[-0.36166,-1.53668e-08,-7.05768e-10,0.93231]},
{"time":0.625,"pos":[0.0878887,0.00522349,0.102822],"rot":[0,0,0,1],"scl":[1,1,1]}
]},
{"parent":1,"keys":[
{"time":0,"pos":[0.103679,0.00638392,-0.445744],"rot":[0,0,0,1],"scl":[1,1,1]},
{"time":0.291667,"pos":[0.103679,0.00638392,-0.445744]},
{"time":0.625,"pos":[0.103679,0.00638392,-0.445744],"rot":[0,0,0,1],"scl":[1,1,1]}
]},
{"parent":2,"keys":[
{"time":0,"pos":[0.0655578,0.0194668,-0.418675],"rot":[0,0,0,1],"scl":[1,1,1]},
{"time":0.291667,"pos":[0.0655578,0.0194668,-0.418675]},
{"time":0.625,"pos":[0.0655578,0.0194668,-0.418675],"rot":[0,0,0,1],"scl":[1,1,1]}
]},
{"parent":3,"keys":[
{"time":0,"pos":[0.0280578,-0.107185,-0.0704246],"rot":[0,0,0,1],"scl":[1,1,1]},
{"time":0.291667,"pos":[0.0280578,-0.107185,-0.0704246]},
{"time":0.625,"pos":[0.0280578,-0.107185,-0.0704246],"rot":[0,0,0,1],"scl":[1,1,1]}
]},
{"parent":4,"keys":[
{"time":0,"pos":[3.58149e-05,0.036576,-0.0885088],"rot":[0,0,0,1],"scl":[1,1,1]},
{"time":0.291667,"pos":[3.58149e-05,0.036576,-0.0885088]},
{"time":0.625,"pos":[3.58149e-05,0.036576,-0.0885088],"rot":[0,0,0,1],"scl":[1,1,1]}
]},

Each vertex corresponds to one skin index which corresponds to one skin weight. The skin index is the index of the bone that the particular vertex is influenced by (each vertex can only belong to one bone). The skin weight is the amount of influence that bone has over that vertex.

The skinIndices and skinWeights properties are arrays of arrays (technically the inner arrays are three.js Vector4 objects). Each item in the outer array of either corresponds, one-to-one, based on the indexed position, with each vertex in the mesh.
Here's the relevant JSONLoader code that creates the values for these properties from a three.js JSON model file:
if ( json.skinWeights ) {
for ( var i = 0, l = json.skinWeights.length; i < l; i += influencesPerVertex ) {
var x = json.skinWeights[ i ];
var y = ( influencesPerVertex > 1 ) ? json.skinWeights[ i + 1 ] : 0;
var z = ( influencesPerVertex > 2 ) ? json.skinWeights[ i + 2 ] : 0;
var w = ( influencesPerVertex > 3 ) ? json.skinWeights[ i + 3 ] : 0;
geometry.skinWeights.push( new Vector4( x, y, z, w ) );
}
}
if ( json.skinIndices ) {
for ( var i = 0, l = json.skinIndices.length; i < l; i += influencesPerVertex ) {
var a = json.skinIndices[ i ];
var b = ( influencesPerVertex > 1 ) ? json.skinIndices[ i + 1 ] : 0;
var c = ( influencesPerVertex > 2 ) ? json.skinIndices[ i + 2 ] : 0;
var d = ( influencesPerVertex > 3 ) ? json.skinIndices[ i + 3 ] : 0;
geometry.skinIndices.push( new Vector4( a, b, c, d ) );
}
}
The docs for the three.js Geometry object were updated to include info about this [some formatting and two inconsistency corrections mine]:
Just like the skinWeights property, the skinIndices' values correspond to the geometry's vertices. Each vertex can have up to 4 bones associated with it. So if you look at the first vertex, and the first skin index, this will tell you the bones associated with that vertex. For example the first vertex could have a value of ( 10.05, 30.10, 12.12 ). Then the first skin index could have the value of ( 10, 2, 0, 0 ). The first skin weight could have the value of ( 0.8, 0.2, 0, 0 ). In affect this would take the first vertex, and then the bone mesh.bones[10] and apply it 80% of the way. Then it would take the bone mesh.bones[2] and apply it 20% of the way. The next two values have a weight of 0, so they would have no affect.
In code another example could look like this:
// e.g.
geometry.skinIndices[15] = new THREE.Vector4( 0, 5, 9, 0 );
geometry.skinWeights[15] = new THREE.Vector4( 0.2, 0.5, 0.3, 0 );
// corresponds with the following vertex
geometry.vertices[15];
// these bones will be used like so:
skeleton.bones[0]; // weight of 0.2
skeleton.bones[5]; // weight of 0.5
skeleton.bones[9]; // weight of 0.3
skeleton.bones[10]; // weight of 0

Related

How to morphTarget of an .obj file (BufferGeometry)

I'm trying to morph the vertices of a loaded .obj file like in this example: https://threejs.org/docs/#api/materials/MeshDepthMaterial - when 'wireframe' and 'morphTargets' are activated in THREE.MeshDepthMaterial.
But I can't reach the desired effect. From the above example the geometry can be morphed via geometry.morphTargets.push( { name: 'target1', vertices: vertices } ); however it seems that morphTargets is not available for my loaded 3D object as it is a BufferGeometry.
Instead I tried to change independently each vertices point from myMesh.child.child.geometry.attributes.position.array[i], it kind of works (the vertices of my mesh are moving) but not as good as the above example.
Here is a Codepen of what I could do.
How can I reach the desired effect on my loaded .obj file?
Adding morph targets to THREE.BufferGeometry is a bit different than THREE.Geometry. Example:
// after loading the mesh:
var morphAttributes = mesh.geometry.morphAttributes;
morphAttributes.position = [];
mesh.material.morphTargets = true;
var position = mesh.geometry.attributes.position.clone();
for ( var j = 0, jl = position.count; j < jl; j ++ ) {
position.setXYZ(
j,
position.getX( j ) * 2 * Math.random(),
position.getY( j ) * 2 * Math.random(),
position.getZ( j ) * 2 * Math.random()
);
}
morphAttributes.position.push(position); // I forgot this earlier.
mesh.updateMorphTargets();
mesh.morphTargetInfluences[ 0 ] = 0;
// later, in your render() loop:
mesh.morphTargetInfluences[ 0 ] += 0.001;
three.js r90

Morphing vertices' positions between multiple 3d models with three.js

I am trying to transition between 3+ 3D models with some nice perlin noise based on user input, just like this:
http://experience.mausoleodiaugusto.it/en/
http://na.leagueoflegends.com/en/featured/skins/project-2016
I can easily transition between two models in my vertex shader, passing down a u_morphFactor uniform variable, which I tween between 0 and 1 (0 = first model, 1 = second model). My question is how should I do it with 3 or more models.
Here is how I handle my geometry:
class CustomGeometry extends THREE.BufferGeometry {
// pass down the two models' reference geometries
constructor (geo1, geo2) {
super()
let { count } = geo1.attributes.position
let timeArray = new Float32Array(count)
let targetArray = new Float32Array(count)
for (let i = 0; i < count; i += 3) {
// assign next model's vertex position to the current one.
// if there are is no corresponding vertex, simply move to vec3(0, 0, 0)
targetArray[i + 0] = geo2.attributes.position.array[i + 0] || 0
targetArray[i + 1] = geo2.attributes.position.array[i + 1] || 0
targetArray[i + 2] = geo2.attributes.position.array[i + 2] || 0
}
// assign position AND targetPosition as attributes, so we can transition between them
this.addAttribute('a_targetPosition', new THREE.BufferAttribute(targetArray, 3))
this.addAttribute('position', geo1.attributes.position)
}
}
Now with the two models' vertices uploaded to the GPU, I can pass down the uniform and make my transition:
let uniforms = {
u_time: { value: 0 },
u_morphFactor: { value: 0 } // show first model by default
}
And the GLSL is:
vec3 new_position = mix(position, a_targetPosition, u_morphFactor);
However, I still can't wrap my head around how should I approach this same technique with 3 or more models. I guess I have to mess with the shader math that handles u_morphFactor..
TL;DR: I know how to map vertices from one 3D model to the next, simply going from 0 to 1 in my shaders. How do I do this with 3 or more models?

Custom UVGenerator - howto?

I have a use case in which users can specify texture that then is to be mapped onto an object across multiple faces. So unfortunately I cannot use standard UV mapping like with Blender and thus want to write my own custom UVGenerator to create my own projection.
So I am looking for detailed info how to do it.
I know that THREE.ExtrudeGeometry.WorldUVGenerator can serve as an example that is located in src/extras/geometries/ExtrudeGeometry.js
In there I found two methods where I am not sure how the work together. The first is generateTopUV which basically takes 3 vertices as parameters and expects that I return three pairs of u/v values. So that's pretty straight forward what the method does.
generateTopUV: function ( geometry, indexA, indexB, indexC )
The second is a odd to me since it takes four vertices and I wonder why and also the name doesn't really help me. Hope sb can shed some light on this.
It's code for WorldUVGenerator is this:
generateSideWallUV: function ( geometry, indexA, indexB, indexC, indexD ) {
var vertices = geometry.vertices;
var a = vertices[ indexA ];
var b = vertices[ indexB ];
var c = vertices[ indexC ];
var d = vertices[ indexD ];
if ( Math.abs( a.y - b.y ) < 0.01 ) {
return [
new THREE.Vector2( a.x, 1 - a.z ),
new THREE.Vector2( b.x, 1 - b.z ),
new THREE.Vector2( c.x, 1 - c.z ),
new THREE.Vector2( d.x, 1 - d.z )
];
} else {
return [
new THREE.Vector2( a.y, 1 - a.z ),
new THREE.Vector2( b.y, 1 - b.z ),
new THREE.Vector2( c.y, 1 - c.z ),
new THREE.Vector2( d.y, 1 - d.z )
];
}
}
Cheers Tom
In the end I solved this by just setting geometry.faceVertexUvs with an array of arrays where the inner array contained 3 vector2's that give the uv values.
Works nicely :-) and no need to handle 4 vertices
The three vertices in the Top and Bottom UV generation process each define one triangle.
The side wall is (separately) created as a string of rectangles, each of which is subsequently split into two coplanar triangles. Those 4 vertices define one rectangle.
If a bevel is requested, it is part of this rectangle process.

Rotation after a Rotation not acting how I Expect

I have a rubiks-cube-like puzzle I am trying to model in webgl using Three.js:
Each of the two-color centers can rotate. When they rotate, they bring all of the pieces around them along on the rotation. For example, if we rotate the orange-blue center, this is what happens:
When you complete a rotation, everything lines up:
However, now when I try to rotate the Orange-White center, I get strange behavior for the two pieces it inherited from the Orange-Blue center after the first rotation:
I expect them to rotate like the other pieces, but instead they rotate differently.
I am using Three.js's Object3D.rotateOnAxis() function to do my rotations:
function rotate ( center, distance ) {
var axis = center.axis;
center.model.rotateOnAxis ( axis, distance );
for ( var i in center.stickers ) {
center.stickers[i].rotateOnAxis ( axis, distance );
}
for ( var i in center.children ) {
center.children[i].model.rotateOnAxis ( axis, distance );
//Note: the stickers are just the colored faces
for ( var s in center.children[i].stickers ) {
center.children[i].stickers[s].rotateOnAxis ( axis, distance );
}
}
}
Here is what my key-press code looks like:
function showCube() {
//set stuff up, then...
count = 0;
parent.onkeypress = function(event) {
if ( count < 6 ) {
rotate( blocks.centers [ "BO" ], Math.PI / 6 );
count++;
if ( count == 6 ) {
moveFace ( blocks, "O1", "OY", "BW" );
moveFace ( blocks, "O2", "OW", "BY" );
moveFace ( blocks, "B1", "BW", "OY" );
moveFace ( blocks, "B2", "BY", "OW" );
moveCorner ( blocks, "BOW", "OW", "OY" );
moveCorner ( blocks, "BOW", "BW", "BY" );
moveCorner ( blocks, "BOY", "OY", "OW" );
moveCorner ( blocks, "BOY", "BY", "BW" );
}
} else {
rotate( blocks.centers [ "OW" ], Math.PI / 6 );
}
}
}
function moveFace ( blocks, child, oldParent, newParent ) {
var index = blocks.centers [ oldParent ].children.indexOf ( blocks.faces [ child ] );
blocks.centers [ oldParent ].children.splice ( index, 1 );
blocks.centers [ newParent ].children.push ( blocks.faces [ child ] );
}
function moveCorner ( blocks, child, oldParent, newParent ) {
var index = blocks.centers [ oldParent ].children.indexOf ( blocks.corners [ child ] );
blocks.centers [ oldParent ].children.splice ( index, 1 );
blocks.centers [ newParent ].children.push ( blocks.corners [ child ] );
}
Interestingly, to get the Blue-Orange-Yellow corner to rotate correctly, I need to do the rotation around the difference between the BO vector and the OW vector.
That is to say:
Blue-Orange axis is the normalized vector of: (0, 1, 1)
Orange-White axis is the normalized vector of: (1, 0, 1)
After the first rotation is complete, if I try to rotate the BOY
corner around the normal of (1, 0, 1), I get the behavior shown in
the pictures.
However, if I try to rotate it around (0, 1, 1) - (1,
0, 1), i.e. (-1, 1, 0), I get the desired behavior.
I don't understand what's happening. Can you help me update my rotate function so I get the behavior I want, without having to keep a long list of past rotations that a piece has experienced?
I suspect that after I do my first rotation, I need to tell the piece to "zero" its rotation state without causing any motion, but I'm not quite sure how to do that, or if it's the right approach.
You can play with the thing here: http://joshuad.net/clover-cube/so-question/
Thanks!
I fixed it by changing my rotate method to this:
function rotate ( center, distance ) {
var axis = center.axis;
center.model.rotateOnAxis ( axis, distance );
applyStatesToMatrixDirectly ( center.model );
for ( var stickerIndex in center.stickers ) {
center.stickers[stickerIndex].rotateOnAxis ( axis, distance );
applyStatesToMatrixDirectly ( center.stickers[stickerIndex] );
}
for ( var childIndex in center.children ) {
center.children[childIndex].model.rotateOnAxis ( axis, distance );
applyStatesToMatrixDirectly ( center.children[childIndex].model );
for ( var childStickerIndex in center.children[childIndex].stickers ) {
center.children[childIndex].stickers[childStickerIndex].rotateOnAxis ( axis, distance );
applyStatesToMatrixDirectly ( center.children[childIndex].stickers[childStickerIndex] );
}
}
}
function applyStatesToMatrixDirectly ( model ) {
model.updateMatrix();
model.geometry.applyMatrix( model.matrix );
model.position.set( 0, 0, 0 );
model.rotation.set( 0, 0, 0 );
model.scale.set( 1, 1, 1 )
model.updateMatrix();
}
The idea is that the function applyStatesToMatrixDirectly() applies the rotation directly to the model matrix and then reset all of the rotation data (as well as everything else). This allows the model to "forget" that it has been rotated, allowing the new rotateOnAxis to work.

three.js maintaining creases when smooth shading custom geometry

I created a custom mesh by adding vertices and faces to a new THREE.Geometry(), then running computeFaceNormals() and computeVertexNormals() on it to smooth out the rendering (I'm using the MeshPhongMaterial). Without computevertexnormals, parts of my mesh appear striped. The problem is that the stock computeVertexNormals() included in r69 ignores sharp edges. It's an elegant function that builds each vertex's normal by averaging the surrounding faces. However it averages the normals at edges that I need to remain sharp in appearance. There were some promising comments on another question with the same topic However no code was posted to solve the issue of keeping edges sharp.
I have attempted to modify computeVertexNormals() to add edge detection but with no luck. My attempt is based on detecting the angle between neighboring faces and only adding their normal to the average if it's within a given threshold. Here's my code:
function computeVertexNormals( object, angle_threshold, areaWeighted ) { //will compute normals if faces diverge less than given angle (in degrees)
var v, vl, f, fl, face, vertices;
angle = angle_threshold * 0.0174532925; //degrees to radians
vertices = new Array( object.vertices.length );
for ( v = 0, vl = object.vertices.length; v < vl; v ++ ) {
vertices[ v ] = new THREE.Vector3();
}
if ( areaWeighted && areaWeighted == true) {
// vertex normals weighted by triangle areas
// http://www.iquilezles.org/www/articles/normals/normals.htm
var vA, vB, vC, vD;
var cb = new THREE.Vector3(), ab = new THREE.Vector3(),
db = new THREE.Vector3(), dc = new THREE.Vector3(), bc = new THREE.Vector3();
for ( f = 0, fl = object.faces.length; f < fl; f ++ ) {
face = object.faces[ f ];
vA = object.vertices[ face.a ];
vB = object.vertices[ face.b ];
vC = object.vertices[ face.c ];
cb.subVectors( vC, vB );
ab.subVectors( vA, vB );
cb.cross( ab );
vertices[ face.a ].add( cb );
vertices[ face.b ].add( cb );
vertices[ face.c ].add( cb );
}
} else {
for ( f = 0, fl = object.faces.length; f < fl; f ++ ) {
face = object.faces[ f ];
vertices[ face.a ].add(face.normal);
vertices[ face.b ].add( face.normal );
vertices[ face.c ].add( face.normal );
}
}
for ( v = 0, vl = object.vertices.length; v < vl; v ++ ) {
vertices[ v ].normalize();
}
for ( f = 0, fl = object.faces.length; f < fl; f ++ ) {
face = object.faces[ f ];
//**********my modifications are all in this last section*************
if(face.normal && face.normal != undefined){
if(vertices[ face.a ].angleTo(face.normal) < angle_threshold){
face.vertexNormals[ 0 ] = vertices[ face.a ].clone();
}else{
face.vertexNormals[ 0 ] = face.normal.clone();
}
if(vertices[ face.b ].angleTo(face.normal) < angle_threshold){
face.vertexNormals[ 1 ] = vertices[ face.b ].clone();
}else{
face.vertexNormals[ 1 ] = face.normal.clone();
}
if(vertices[ face.c ].angleTo(face.normal) < angle_threshold){
face.vertexNormals[ 2 ] = vertices[ face.c ].clone();
}else{
face.vertexNormals[ 2 ] = face.normal.clone();
}
}
}
}
Can anybody please offer a strategy for crease detection so I can have smooth shapes with some sharp edges?
Thanks in advance!
WestLangley is correct in the comment above. To get the sharp edges I wanted, I simply duplicated vertices that were on "creases" while constructing my geometry. Then I used the standard computeVertexNormals() function included in the THREE.Geometry() prototype.
I was constructing my geometry with a home-made 'loft' function: basically iterating through an array of shapes (using i) and creating B-splines between their vertices (using j), then constructing a mesh from the B-Splines. The fix was to test the angle at each vertex of each shape. If its angle was larger than a given threshold (I used 70 degrees), I added the B-Spline a second time, effectively duplicating the vertices. Sorry if the code below is a little cryptic taken out of context.
//test if vertex is on a crease
if (j == 0) {
before = arrCurves[i].vertices[j].clone().sub(arrCurves[i].vertices[arrCurves[i].vertices.length-1]);
}else{
before = arrCurves[i].vertices[j].clone().sub(arrCurves[i].vertices[j-1]);
}
if (j == arrCurves[i].vertices.length-1) {
after = arrCurves[i].vertices[0].clone().sub(arrCurves[i].vertices[j]);
}else{
after = arrCurves[i].vertices[j+1].clone().sub(arrCurves[i].vertices[j]);
}
if( before.angleTo(after) > crease_threshold ){
//here's where I'm adding the curve for a second time to make the 'crease'
arrSplines.push(new THREE.SplineCurve3(nurbsCurve.getPoints(resolution)));
}
Works like a charm, Thanks WestLangley!

Resources