Hello dear community,
for my game I want to rotate the players head so that the head always faces the view direction. For that I wrote a HeadRotatingControl
which extends AbstractControl
and is added to the player model. This control rotates the head bone of the player to the according viewDirection
(which is a Vector3f
) . Another system sets the current viewDirection
for that player.
I use an EntitySystem (Zay-Es) for my game. There is a component which stores the current viewDirection for a player. When it is changed another system (app state) sets this new viewDirection
for the corresponding HeadRotatingControl
.
For Singleplayer this works fine because the updates are made immediately.
Heres the problem: In Multiplayer updates are sent only 10 times a second which doesn’t really lead to fluent movements of the head bone BUT the head rotation was still correct.
Due to the fact that I wanted fluent movements for the head I decided to interpolate (slerp) between the view direction updates. But “slerping” between the updates causes very weird artifacts… The head starts to “jump” away from his body…
For better understanding I made a short video: First you see the Singleplayer mode which works fine and after that you see two connected clients where you can see the issue I described above. (Sorry for laggy video)
Here is the code of the HeadRotatingControl
class
import com.jme3.animation.Bone;
import com.jme3.animation.SkeletonControl;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.AbstractControl;
/**
* A control which sets the head bone rotation according to the set view direction.
*
* Created by Domenic on 09.06.2017.
*/
public class HeadRotatingControl extends AbstractControl {
private Vector3f oldViewDir = new Vector3f();
private Vector3f viewDirection = new Vector3f(Vector3f.UNIT_X);
private Bone headBone;
private Quaternion initRotation = new Quaternion();
private float[] initAngles = new float[3];
private float angle;
private Quaternion headRotation = new Quaternion();
private Quaternion finalHeadRotation = new Quaternion();
public void setViewDirection(Vector3f viewDirection) {
this.viewDirection.set(viewDirection).normalizeLocal();
}
@Override
public void setSpatial(Spatial spatial) {
super.setSpatial(spatial);
if (spatial != null) {
// setup
SkeletonControl skeletonControl = spatial.getControl(SkeletonControl.class);
this.headBone = skeletonControl.getSkeleton().getBone("Neck1");
this.headBone.setUserControl(true);
this.initRotation = headBone.getLocalRotation().clone();
this.initRotation.toAngles(initAngles); // save init angles
System.out.println(initAngles[0]*FastMath.RAD_TO_DEG);
} else {
// cleanup
if (headBone != null) {
headBone.setUserControl(false);
}
}
}
@Override
protected void controlUpdate(float tpf) {
// if true --> viewDirection has been updated, so we need to set a new rotation for the head
if (!oldViewDir.equals(viewDirection)) {
// get angle between character rotation and view direction
angle = spatial.getLocalRotation().getRotationColumn(2).normalize().angleBetween(viewDirection);
Vector3f vec = viewDirection.subtract(spatial.getLocalRotation().getRotationColumn(2));
if (vec.getY() > 0) {
angle *= -1; // if the character is looking up we negate the angle
}
headRotation.set(headBone.getLocalRotation());
finalHeadRotation.fromAngles(initAngles[0] + angle, 0, 0);
oldViewDir.set(viewDirection);
}
// interpolate to new head rotation
headRotation.slerp(finalHeadRotation, tpf/0.05f);
// apply new user transforms
headBone.setUserTransforms(Vector3f.ZERO, headRotation, Vector3f.UNIT_XYZ);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
}
Again: For Singleplayer this works, even with interpolation. But on Multiplayer it doesn’t anymore.
I really hope somebody can help me.
Best regards
Domenic