Character-Bow-Template

I’m improving the archer demo by adding physical arrows. At the moment they are just spheres, but I will replace them with the model of an arrow in the final version.

To test the collisions I built a scenario that includes:

  • A city scene with static RigidBodyControl and mass 0
  • Simple cubes with mass greater than 0
  • Sinbad to which I added the ‘DynamicAnimControl’ controller to identify the part of the body hit by the arrow.

The goal I would like to achieve is to attach the arrow to the hit surface or body parts of animated characters.

The algorithm I wrote seems to work correctly for cubes, while with Sinbad the arrows (balls) sometimes correctly follow the position of the moving bone, other times they don’t.
(see method ArrowControl.stick(PhysicsCollisionObject other, Vector3f hitPoint) )

Could you give me some suggestions on how to improve the solution?

Here is an example video.

Here is the code.

public class SinbadRagdollPrefab extends PrefabComponent {

	public SinbadRagdollPrefab(Application app) {
		super(app);
	}

	@Override
	public Spatial getAssetModel() {
		return app.getAssetManager().loadModel("Models/Sinbad/Sinbad.mesh.xml");
	}

	@Override
	public Spatial instantiate(Vector3f position, Quaternion rotation, Node parent) {
		Spatial model = getAssetModel();
		model.setLocalTranslation(position);
		model.setLocalRotation(rotation);
		parent.attachChild(model);
		
		AnimComposer composer = model.getControl(AnimComposer.class);
		composer.setCurrentAction("Dance");
		
		DynamicAnimControl ragdoll = new DynamicAnimControl();
		setupSinbad(ragdoll);
		model.addControl(ragdoll);
		PhysicsSpace.getPhysicsSpace().add(ragdoll);
		
		return model;
	}
	
	private void setupSinbad(DynamicAnimControl ragdoll) {
		ragdoll.link("Waist", 1f, new RangeOfMotion(1f, -0.4f, 0.8f, -0.8f, 0.4f, -0.4f));
		ragdoll.link("Chest", 1f, new RangeOfMotion(0.4f, 0f, 0.4f));
		ragdoll.link("Neck", 1f, new RangeOfMotion(0.5f, 1f, 0.7f));

		ragdoll.link("Clavicle.R", 1f, new RangeOfMotion(0.3f, -0.6f, 0f, 0f, 0.4f, -0.4f));
		ragdoll.link("Humerus.R", 1f, new RangeOfMotion(1.6f, -0.8f, 1f, -1f, 1.6f, -1f));
		ragdoll.link("Ulna.R", 1f, new RangeOfMotion(0f, 0f, 1f, -1f, 0f, -2f));
		ragdoll.link("Hand.R", 1f, new RangeOfMotion(0.8f, 0f, 0.2f));

		ragdoll.link("Clavicle.L", 1f, new RangeOfMotion(0.6f, -0.3f, 0f, 0f, 0.4f, -0.4f));
		ragdoll.link("Humerus.L", 1f, new RangeOfMotion(0.8f, -1.6f, 1f, -1f, 1f, -1.6f));
		ragdoll.link("Ulna.L", 1f, new RangeOfMotion(0f, 0f, 1f, -1f, 2f, 0f));
		ragdoll.link("Hand.L", 1f, new RangeOfMotion(0.8f, 0f, 0.2f));

		ragdoll.link("Thigh.R", 1f, new RangeOfMotion(0.4f, -1f, 0.4f, -0.4f, 1f, -0.5f));
		ragdoll.link("Calf.R", 1f, new RangeOfMotion(2f, 0f, 0f, 0f, 0f, 0f));
		ragdoll.link("Foot.R", 1f, new RangeOfMotion(0.3f, 0.5f, 0f));

		ragdoll.link("Thigh.L", 1f, new RangeOfMotion(0.4f, -1f, 0.4f, -0.4f, 0.5f, -1f));
		ragdoll.link("Calf.L", 1f, new RangeOfMotion(2f, 0f, 0f, 0f, 0f, 0f));
		ragdoll.link("Foot.L", 1f, new RangeOfMotion(0.3f, 0.5f, 0f));
	}

}
public class ArrowPrefab extends RangedBullet {
	
	public ArrowPrefab(Application app, String name) {
		super(app);
		mass = 6f;
		this.name = name;
	}

	@Override
	public Spatial getAssetModel() {
		// TODO Auto-generated method stub
		Mesh mesh = new Sphere(16, 16, 0.05f);
		Geometry geo = new Geometry("Arrow", mesh);
		Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
		mat.setColor("Color", ColorRGBA.Green.clone());
		geo.setMaterial(mat);
		return geo;
	}
	
	@Override
	public Spatial instantiate(Vector3f position, Quaternion rotation, Node parent) {
    	Spatial model = getAssetModel();
    	model.setName(name + "-" + nextSeqId());
    	model.setLocalTranslation(position);
    	model.setLocalRotation(rotation);
    	parent.attachChild(model);
    	
    	// Add Physics.
        RigidBodyControl rgb = new RigidBodyControl(mass);
        model.addControl(rgb);
        PhysicsSpace.getPhysicsSpace().add(rgb);
        rgb.setCollisionGroup(PhysicsCollisionObject.COLLISION_GROUP_02);
        rgb.setCollideWithGroups(PhysicsCollisionObject.COLLISION_GROUP_01);
        rgb.setCcdMotionThreshold(0.001f);
        
        ArrowControl arrow = new ArrowControl();
        model.addControl(arrow);
    	
    	return model;
    }

}
public class ArrowControl extends AdapterControl implements PhysicsCollisionListener {
	
	private static final Logger logger = Logger.getLogger(ArrowControl.class.getName());
	
	private RigidBodyControl rigidBody;
	private PhysicsSpace m_PhysicsSpace;
	private boolean hasCollided;
	private float maxFlyingTime = 10f;
	private float timer = 0;

	@Override
	public void setSpatial(Spatial sp) {
		super.setSpatial(sp);
		if (spatial != null) {
			rigidBody = getComponent(RigidBodyControl.class);
			m_PhysicsSpace = rigidBody.getPhysicsSpace();
			m_PhysicsSpace.addCollisionListener(this);
		}
	}
	
    @Override
    protected void controlUpdate(float tpf) {
        // TODO Auto-generated method stub
    	timer += tpf;
    	
    	if (!hasCollided) {
    		// this is to cleanup old bullets that hasn't collided yet (lived more than maxTime)
    		if (timer > maxFlyingTime) {
    			destroy();
    		}
    	}
    }
    
	@Override
	public void collision(PhysicsCollisionEvent event) {
		if (hasCollided) {
			return;
		}
		
		if ( event.getObjectA() == rigidBody || event.getObjectB() == rigidBody ) {
			
			hasCollided = true;

			// Stop the rigidBody in position
			rigidBody.setEnabled(false);

			PhysicsCollisionObject other;
			Vector3f hitPoint;
			
			if (event.getObjectA() == rigidBody) {
                other = event.getObjectB();
                hitPoint = event.getPositionWorldOnB();
            } else {
                other = event.getObjectA();
                hitPoint = event.getPositionWorldOnA();
            }
			
			logger.log(Level.INFO, "Collided with: {0}", other.getUserObject().toString());
			stick(other, hitPoint);
			
			spatial.addControl(new TimerControl(15f) {
				@Override
				public void onTrigger() {
					destroy();
				}
			});
		}
	}
	
	/**
	 * 
	 * @param other
	 * @param hitPoint
	 */
	private void stick(PhysicsCollisionObject other, Vector3f hitPoint) {
		
		if (other.getUserObject() instanceof Node) {
			Node gameObject = (Node) other.getUserObject();
			gameObject.worldToLocal(hitPoint, hitPoint);
			gameObject.attachChild(spatial);
        	spatial.setLocalTranslation(hitPoint);
        	
		} else if (other.getUserObject() instanceof BoneLink) {
			BoneLink bone = (BoneLink) other.getUserObject();
        	Spatial animRoot = bone.getControl().getSpatial();
        	Node attachNode = animRoot.getControl(SkinningControl.class).getAttachmentsNode(bone.boneName());
        	System.out.println(bone.boneName() + " " + animRoot + "; " + attachNode);
        	
        	attachNode.worldToLocal(hitPoint, hitPoint);
        	attachNode.attachChild(spatial);
        	spatial.setLocalTranslation(hitPoint);
        	
		} else {
			logger.log(Level.WARNING, "Unable to attach the arrow to the hit object: " + other.getUserObject());
		}
	}
	
	//  Destroy everything
	private void destroy() {
		m_PhysicsSpace.removeCollisionListener(this);
		spatial.removeFromParent();
		logger.log(Level.INFO, "Object Destroyed: {0}", spatial);
	}

}

Let me know if you need more information on the code I wrote.
I’ll post the demo on github with the final code.

@sgold @oxplay2 @Ali_RS

3 Likes