[SOLVED] Attaching spatial to bone through controls

Ups, I have overlooked that. No, the weapon wasn’t animated.
Is it possible to create a control which refreshes translation, rotation, scale of the gun by getting the transformation via “AttachmentBone”. The gun would not be attached to “AttachmentNode” then. It should be attached to root node or so.

Yes, the gun is animated.

Hi, no offense… but have you even read the thread?

The whole thread is talking exactly about a control to do this. Literally, 95% of the text here is talking about exactly that.

Sorry guys, I read the thread too quickly. Will never happen again, I promise :grinning:

@domas could you post the whole control class here? I have to agree with @pspeed, some lines are very strange.

Another question: Why is the gun animated? Does it have a reload, shoot animation etc.? What does the hand model do then?

I’ll post it when I come home.

The gun has got its own animations and so the character model to which I attach the gun does.

Hey @domas, I quickly wrote a test app for you. It just shall show you how to place your gun to the players hand. It might be that you have to adjust your gun with an extra node, so that the gun fits perfectly into the hands.

Here the TestApp:

public class Main extends SimpleApplication {

	public static void main(String[] args) throws IOException {
		Main app = new Main();
		app.start();
	}

	@Override
	public void simpleInitApp() {
		cam.setLocation(Vector3f.UNIT_Z);

		Spatial player = assetManager.loadModel("Models/Player/Player.j3o");
		rootNode.attachChild(player);

		AnimControl animControl = player.getControl(AnimControl.class);
		AnimChannel channel = animControl.createChannel();
		
		Collection<String> anims = animControl.getAnimationNames();
		Iterator i = anims.iterator();
		channel.setAnim((String) i.next());
		
		animControl.addListener(new AnimEventListener() {
			@Override
			public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
				if (i.hasNext()) {
					channel.setAnim((String) i.next());
				}
			}

			@Override
			public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {}
		});

		SkeletonControl skeletonControl = player.getControl(SkeletonControl.class);
		Bone attachmentBone = skeletonControl.getSkeleton().getBone("YOUR_ATTACHMENT_BONE");

		WeaponAttachmentControl control = new WeaponAttachmentControl(skeletonControl, attachmentBone.getName());

		Spatial gun = assetManager.loadModel("Models/cube.j3o");
		gun.scale(0.1f);
		gun.addControl(control);
		rootNode.attachChild(gun);

	   
		AmbientLight ambient = new AmbientLight();
		ambient.setColor(ColorRGBA.White);
		rootNode.addLight(ambient);
	}

	@Override
	public void simpleUpdate(float tpf) {
	}

	@Override
	public void simpleRender(RenderManager rm) {
	}

}

Here the simple control:

public class WeaponAttachmentControl extends AbstractControl {
	
	private final Node attachmentNode;

	public WeaponAttachmentControl(Node attachmentNode) {
		this.attachmentNode = attachmentNode;
	}
	
	public WeaponAttachmentControl(SkeletonControl skeletonControl, String bone) {
		if (skeletonControl != null && bone != null) {
			attachmentNode = skeletonControl.getAttachmentsNode(bone);
		} else {
			attachmentNode = null;
		}
	}

	@Override
	protected void controlUpdate(float tpf) {
		if (spatial == null || attachmentNode == null) {
			return;
		}
		spatial.setLocalTranslation(attachmentNode.getWorldTranslation());
		spatial.setLocalRotation(attachmentNode.getWorldRotation());
	}
	
	@Override
	protected void controlRender(RenderManager rm, ViewPort vp) {
	}
	
}

If I were you, I would create several controls, one for keeping the gun at the correct position, one to take care of animations, and so on …

It should be no problem to execute animations of the gun anymore.

If you are missing something, feel free to tell us.

Greetings, Domenic

It works perfectly now!

Many thanks! :slight_smile:

1 Like

Glad it works :blush:

Ok, but how do I apply an offset to the attached spatial’s translation using the attachment node’s local axis? Do I need to use attachmentNode.localToWorld()?

You can attach an “offsetNode” to the attachment node and the gun in turn should be attached to the offsetNode. With the OffsetNode you can determine perfect translation, rotation etc. and it is easy to use in SceneComposer.

Attaching the gun to the offset node which is attached to an attachment node triggers the “Unsupported Operation Exception”.

Oh yeah forgot about those 2 animation controls. Then you have to create the offset node in the control I posted above for example. You could give translation, rotation etc. as parameters in the constructor. It’s just a thought of me :slight_smile: . Try it and tell me whether it works or not.

I did create the offset node in the control class but the app crashed because an animated spatial (gun) has got an animated (grand)parent (character).

What? You attached the gun to the character somewhere?

I meant to say “to an attachment node” but the end result is the same: the app crashes.

Could you please post the complete strack trace with the line(s) causing the error?

    java.lang.UnsupportedOperationException: Material instances cannot be shared when hardware skinning is used. Ensure all models use unique material instances.
at com.jme3.animation.SkeletonControl.controlRenderHardware(SkeletonControl.java:258)
at com.jme3.animation.SkeletonControl.controlRender(SkeletonControl.java:299)
at com.jme3.scene.control.AbstractControl.render(AbstractControl.java:135)
at com.jme3.scene.Spatial.runControlRender(Spatial.java:756)
at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:723)
at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:733)
at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:733)
at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:733)
at com.jme3.renderer.RenderManager.renderSubScene(RenderManager.java:733)
at com.jme3.renderer.RenderManager.renderScene(RenderManager.java:712)
at com.jme3.renderer.RenderManager.renderViewPort(RenderManager.java:1086)
at com.jme3.renderer.RenderManager.render(RenderManager.java:1145)
at com.jme3.app.SimpleApplication.update(SimpleApplication.java:253)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.runLoop(LwjglAbstractDisplay.java:151)
at com.jme3.system.lwjgl.LwjglDisplay.runLoop(LwjglDisplay.java:193)
at com.jme3.system.lwjgl.LwjglAbstractDisplay.run(LwjglAbstractDisplay.java:232)
at java.lang.Thread.run(Unknown Source)`

package utilities;
import com.jme3.animation.SkeletonControl;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;

public class WeaponAttachmentControl extends AbstractControl {
	
	private final Node attachmentNode;
	private Node offsetNode;
	private Vector3f gunPos = new Vector3f();
	private Quaternion gunRot = new Quaternion();
	
	public WeaponAttachmentControl(Node attachmentNode) {
		this.attachmentNode = attachmentNode;
	}
	
	public WeaponAttachmentControl(SkeletonControl skeletonControl, String bone,Vector3f pos, Quaternion rot,Node rootNode) {
		if (skeletonControl != null && bone != null) {
			attachmentNode = skeletonControl.getAttachmentsNode(bone);
		} else {
			attachmentNode = null;
		}
		offsetNode = new Node("");
		offsetNode.setLocalTranslation(gunPos);
		attachmentNode.attachChild(offsetNode);
		
		gunRot = rot;
	}

	@Override
	protected void controlUpdate(float tpf) {
		if (spatial == null || attachmentNode == null) {
			return;
		}offsetNode.attachChild(spatial);
		Vector3f v = new Vector3f(0,0,0);
		//attachmentNode.localToWorld(gunPos, v);
		spatial.setLocalTranslation(attachmentNode.getWorldTranslation().add(0,0,0));
		spatial.setLocalRotation(attachmentNode.getWorldRotation().mult(gunRot));
	}
	
	@Override
	protected void controlRender(RenderManager rm, ViewPort vp) {
	}
	
}`

Hmm… Can you remove some lines to see which line of code causes the issue.
By the way, you are doing some strange stuff in update I noticed: offsetNode.attachChild(spatial); whould be one.

This was written by @pspeed: two SkeletonControls are modifying the SAME Material instance as one control detects that another control has modified the same material parameter.

Now you are turn. Look what you have done wrong. If you say that the code I provided worked for you, you have to check where you did something wrong afterwards.

attachmentNode.attachChild(offsetNode);

Without this line the app runs ok. I just didn’t exactly understand how I’m supposed to utilize the offset node. Could you perhaps demonstrate to me what you meant with some pseudo-code?

You had a gun node. You were automatically adjusting the position/rotation of the gun node.

When you want an offset node… you stop doing that.

You have an offset node. Your gun node is attached to that. You automatically adjust the position/rotation of the OFFSET node. Then you can adjust the gun’s position relative to that to give it an offset.