Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attach objects to GTLF model bones component #294

Open
rexraptor08 opened this issue Apr 1, 2019 · 4 comments
Open

Attach objects to GTLF model bones component #294

rexraptor08 opened this issue Apr 1, 2019 · 4 comments

Comments

@rexraptor08
Copy link

I wrote a component that lets you attach objects to GLTF model bones. It’s working great so far, but I was wondering if you wouldn’t mind taking a look at my code to see if there are any use cases I’m not considering or if it could be written more efficiently. Right now the component let’s you attach either another gltf model to a bone or an a-frame entity (like a-box for example). There’s even an option to trigger an animation clip to the attached gltf model.

It’s used like this:

<a-scene>

 <a-assets>

  <a-asset-item id="skeleton" src="models/glb/skeleton.glb"></a-asset-item>

<a-asset-item id="heart" src="models/glb/heart.glb"></a-asset-item>

  </a-assets>

 <a-entity                             

  gltf-model="#skeleton"

  animation-mixer="clip: idle"

  attach-to-bone="boneName: Armature_mixamorig_Spine2_2; isGLTF: true; addObj: #heart; objPos: 5 -6 2; objScale: 100 100 100; objRot: 0 0 0; hasAnimation: true; clip: heartbeat"

attach-to-bone__box="boneName: Armature_mixamorig_Head_2; isGLTF: false; addObj: #myBox; objPos: 0 0 0; objScale: 20 20 20; objRot: 0 45 0"

           > </a-entity>



<a-box id="myBox" position="0 0 0" rotation="0 0 0" color="#4CC3D9"></a-box>

</a-scene>

The way I get the bone name is by looking at the bone names in blender. The gltf that gets exported from blender adds the prefix “Armature_” to the bone name. So for example, even though it might say “mixamorig_Head_2” in blender, the real name is “Armature_mixamorig_Head_2”.

Let me know what you think. I’d love to get your feedback.

AFRAME.registerComponent("attach-to-bone", {
multiple: true,
  schema: {
boneName: {
  type: 'string'
},
mainModelLoaded: {
  type: 'boolean',
  default: false
},	  
addObj: {
  type: 'string'
},
objPos: {
  type: 'vec3',
  default: { x: 0, y: 0, z: 0 }
},
objScale: {
  type: 'vec3',
  default: { x: 2, y: 2, z: 2 }
},	  
	objRot: {
  type: 'vec3',
  default: { x: 0, y: 0, z: 0 }
},
isGLTF: {
  type: 'boolean',
  default: false
},	  
hasAnimation: {
  type: 'boolean',
  default: false
},	  
clip: {
  type: 'string',
  
},	  

},

	init: function () {
// Boolean to tell component if main model has already been loaded or not
    //Used to set the attribute later if you want to attach to model that already exists
		if(this.data.mainModelLoaded == false){
		this.el.addEventListener('model-loaded', () => {
			// Grab the mesh / scene.
			const obj = this.el.getObject3D('mesh');
			const newElement = document.createElement('a-entity');
			var entityEl;
			this.el.sceneEl.appendChild(newElement);
			if(this.data.isGLTF == true){
				
			newElement.setAttribute('gltf-model', this.data.addObj);	
				
				
			}else{
				
				entityEl = this.el.sceneEl.querySelector(this.data.addObj);
				newElement.appendChild(entityEl);
				
			}
			
			newElement.setAttribute('scale', this.data.objScale);
			newElement.setAttribute('position', this.data.objPos);
			newElement.setAttribute('rotation', this.data.objRot);
			newElement.setAttribute('id',this.id);
			
			if(this.data.hasAnimation == true){
				
				newElement.setAttribute("animation-mixer","clip:"+this.data.clip);
				
				
			}
			
			
			var boneObj = this.el.sceneEl.querySelector('#'+this.id).object3D;
			
			if(this.data.isGLTF == true){
			newElement.addEventListener('model-loaded', () => { //console.log("did I get the object mesh? "+boneObj);
	      										   
			// Go over the submeshes 
			obj.traverse(node => {
				if (node.name.indexOf(this.data.boneName) !== -1) {
					
					node.add(boneObj)
				
				
				}
			});
			
			
			});
			
		}else{
			
			newElement.addEventListener('loaded', () => { //console.log("did I get the object mesh? "+boneObj);
	      										   
			// Go over the submeshes 
			obj.traverse(node => {
				if (node.name.indexOf(this.data.boneName) !== -1) {
					
					node.add(boneObj)
				
				
				}
			});
			
			
			});
			
			
		}
			
		}); 
		
		} else{
			
					
			// Grab the mesh / scene.
			const obj = this.el.getObject3D('mesh');
			const newElement = document.createElement('a-entity');
			var entityEl;
			this.el.sceneEl.appendChild(newElement);
			if(this.data.isGLTF == true){
				
			newElement.setAttribute('gltf-model', this.data.addObj);	
				
				
			}else{
				
				entityEl = this.el.sceneEl.querySelector(this.data.addObj);
				newElement.appendChild(entityEl);
				
			}
			
			newElement.setAttribute('scale', this.data.objScale);
			newElement.setAttribute('position', this.data.objPos);
			newElement.setAttribute('rotation', this.data.objRot);
			newElement.setAttribute('id',this.id);
			
			if(this.data.hasAnimation == true){
				
				newElement.setAttribute("animation-mixer","clip:"+this.data.clip);
				
				
			}
			
			
			var boneObj = this.el.sceneEl.querySelector('#'+this.id).object3D;
			
			if(this.data.isGLTF == true){
			newElement.addEventListener('model-loaded', () => { //console.log("did I get the object mesh? "+boneObj);
	      										   
			// Go over the submeshes 
			obj.traverse(node => {
				if (node.name.indexOf(this.data.boneName) !== -1) {
					
					node.add(boneObj)
				
				
				}
			});
			
			
			});
			
		}else{
			
			newElement.addEventListener('loaded', () => { //console.log("did I get the object mesh? "+boneObj);
	      										   
			// Go over the submeshes 
			obj.traverse(node => {
				if (node.name.indexOf(this.data.boneName) !== -1) {
					
					node.add(boneObj)
				
				
				}
			});
			
			
			});
			
			
		}
						
			
			
		}
		
	}

    });
@donmccurdy
Copy link
Collaborator

@wmurphyrd any thoughts on this? Do you know of a component that already does it, or think there should be one?

My first reaction would be that it's possibly better to have a component update the object's position on every frame without reparenting it, to match the bone's position. Similar to constraints in the physics system, but without the need for physics. Reparenting things dynamically has seemed more error prone and complex than needed in a lot of cases.

@rexraptor08
Copy link
Author

Hi @donmccurdy. After attempting to use the above component in an actual project, it turns out that you were right that it was error prone and needlessly complicated. I took your advice and made it so that the objects I want to attach follow the position and rotation of the bones. It's a lot easier to work with. Here's the new one. Let me know if there's anything you think I can do to improve it. Thanks.

AFRAME.registerComponent('attach-to-bone', {
multiple: true,
schema: {

	addObj: {
		type: 'string'
	},
	target: {
		type: 'selector'
	},
	boneName: {
		type: 'string'
	},
	matchPosition: {
		type: 'boolean',
		default: true
	},
	positionObj: {
		type: 'boolean',
		default: true
	},
	matchRotation: {
		type: 'boolean',
		default: true
	},
	rotatateObj: {
		type: 'boolean',
		default: true
	},
}, //end schema
init: function () {
	this.el.addEventListener('object3dset', () => {
		const mesh = this.el.getObject3D('mesh');
		//this.data.matchRotation = this.data.matchRotation;

		mesh.traverse(node => {
			if (node.name.indexOf(this.data.boneName) !== -1) {
				var self = this;
				var el = this.el;

				this.data.target = node;
				self.data.rotateObj = self.data.matchRotation;
				self.data.positionObj = self.data.matchPosition;

				//  console.log("match rotation =="+this.data.matchRotation);
			}
		});
	});

},


tick: function (time, timeDelta) {
	var self = this;
	var el = this.el;
	var position = new THREE.Vector3();
	var rotation = new THREE.Euler();
	var targetPosition = self.data.target;
	var targetPos = targetPosition.getWorldPosition(position);
	var targetRot = targetPosition.getWorldQuaternion(rotation);
	var movePart = el.sceneEl.querySelector('#' + this.data.addObj).object3D;

	if (this.data.positionObj == true) {
		movePart.position.set(targetPos.x, targetPos.y, targetPos.z);
	}
	if (this.data.rotateObj == true) {

		movePart.rotation.set(targetRot.x, targetRot.y, targetRot.z);
	}
}

});

@planetvoodoo
Copy link

@rexraptor08 do you have a working example anywhere?

@MELT9000
Copy link

MELT9000 commented Jul 6, 2021

Hey! Can't get it to work. Can you explain how to use it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants