Detect certain gltf model part with Raycaster

I am trying to catch the click event on exact parts of gltf model using raycaster. I am able to catch a click on the whole model but cannot get its part. Here is a working example:
https://codepen.io/yaroslavnikiforov/project/editor/DaVpyx
It runs random animation of the model by clicking on it. The goal is to run it only by click on the leg, for example.

Hi!
Your model has no children, it’s a single object itself:

But what about the first element in children’s array on the same level as SkinnedMesh with Bone type?

I can get all these objects using traverse:

Can you describe in more detail what you’re trying to do? Bones do not support raycasting because they’re effectively just considered empty frames that are used for animation.

If you want to get the bone that’s most closely associated with the point on the SkinnedMesh you clicked it’s a bit of work but you can extract the vertex bone weights using the skinWeight and skinIndex buffer attributes for the triangle vertices, interpolate them using barycentric interpolation, and select the bone that’s associated with the highest weighted bone for that point.

1 Like

Hi @gkjohnson, thanks for the answer. That is exactly what I am trying to achieve - get the bone that’s most closely associated with the point on the SkinnedMesh you clicked but a bit unclear with the solution you suggested. Do I need to use face property somehow from intersect?

Here’s a snippet from one of my projects that lets you select a bone based on where you clicked a skinned mesh. I’ve added some comments so it’s hopefully more clear.

// extract information from the raycast hit
const { object, geometry, face, point } = hit;

// temp variables
const triangle = new Triangle();
const baryCoord = new Vector3();
const getFunctions = [ 'getX', 'getY', 'getZ', 'getW' ];

const skinWeightAttr = geometry.getAttribute( 'skinWeight' );
const skinIndexAttr = geometry.getAttribute( 'skinIndex' );
const weightTotals = {};

const aIndex = face.a;
const bIndex = face.b;
const cIndex = face.c;

// Get the position of the triangle vertices after being transformed by bones
// and into world space
object.boneTransform( aIndex, triangle.a );
object.boneTransform( bIndex, triangle.b );
object.boneTransform( cIndex, triangle.c );

triangle.a.applyMatrix4( object.matrixWorld );
triangle.b.applyMatrix4( object.matrixWorld );
triangle.c.applyMatrix4( object.matrixWorld );

// Get the Barycoordinate interpolation parameters
triangle.getBarycoord( point, baryCoord );
for ( let i = 0; i < skinIndexAttr.itemSize; i ++ ) {

    // get the first set of skin weight info for the vertices
    const func = getFunctions[ i ];
    const aWeightIndex = skinIndexAttr[ func ]( aIndex );
    const bWeightIndex = skinIndexAttr[ func ]( bIndex );
    const cWeightIndex = skinIndexAttr[ func ]( cIndex );
    const aWeight = skinWeightAttr[ func ]( aIndex );
    const bWeight = skinWeightAttr[ func ]( bIndex );
    const cWeight = skinWeightAttr[ func ]( cIndex );

    // initialize the weights to 0 if they haven't been set yet
    weightTotals[ aWeightIndex ] = weightTotals[ aWeightIndex ] || 0;
    weightTotals[ bWeightIndex ] = weightTotals[ bWeightIndex ] || 0;
    weightTotals[ cWeightIndex ] = weightTotals[ cWeightIndex ] || 0;

    // add the interpolated weight for each vertex to the stored total
    weightTotals[ aWeightIndex ] += aWeight * baryCoord.x;
    weightTotals[ bWeightIndex ] += bWeight * baryCoord.y;
    weightTotals[ cWeightIndex ] += cWeight * baryCoord.z;

}

// Sort the weight info by total weight so we can find the highest weighted index
const sorted =
    Object
        .entries( weightTotals )
        .map( ( [ key, value ] ) => ( { weight: value, index: key } ) )
        .sort( ( a, b ) => b.weight - a.weight );

// Get the bone and bone index from the click
const boneIndex = sorted[ 0 ].index;
const bone = skeleton.bones[ boneIndex ];
1 Like

@gkjohnson, great thanks, I will try that.