[solved]ChaseCamera does not work correctly

Hello, everyone :
I come across a strange problem when I use ChaseCamera in my jme application.

At first, the scene looks like this,

When i rotate it vertically with a small angle, suddenly, the red,green, blue axes are changed from the left to right. It look like this:

Why does this happen? I am really confused.
My code:

ChaseCamera   chaseCam = new ChaseCamera(cam, rootNode, inputManager); 
    chaseCam.setRotationSpeed(7);
    chaseCam.setMinDistance(1f);  
    chaseCam.setMaxDistance(1000f);   
    chaseCam.setMinVerticalRotation(-FastMath.PI*2);
    chaseCam.setMaxVerticalRotation(FastMath.PI*2); 

Thanks for your help.

It’s hard to help debug code that we can’t see.

I have two java files:
when i rotate the sphere horizontally, the chasecamera works correctly.
But when i rotate the sphere vertically, the chasecamera works wrongly sometimes.
The three axes from one side to another side suddenly.

The first one is :

  public class CustomSphereTest extends SimpleApplication {

 public boolean updateState = false;  //用户点击按钮后,需要更新导航校正三维展示球
 CustomSphereStateTest state = null;
public static void main(String[] args) {
    CustomSphereTest app = new CustomSphereTest();
    app.start();
}

@Override
public void simpleInitApp() {
    flyCam.setEnabled(false);
   
   // chaseCam.setInvertVerticalAxis(true);
  //  chaseCam.setDragToRotate(true);
  //  chaseCam.setSmoothMotion(true);
     ChaseCamera   chaseCam = new ChaseCamera(cam, rootNode, inputManager); 
    chaseCam.setRotationSpeed(7); //Rubby 默认速度为1很慢,需要更快的。
    chaseCam.setMinDistance(1f);   //camera 与 the target 之间距离。(此值越小,说明越可以放大).否则使用默认值,有时候无法继续缩放,扩大
    chaseCam.setMaxDistance(1000f);   //camera 与 the target 之间距离。(此值越大,说明越可以缩小).否则使用默认值,有时候无法继续缩放,扩大
    chaseCam.setMinVerticalRotation(-FastMath.PI*2);
    chaseCam.setMaxVerticalRotation(FastMath.PI*2); //可以旋转的最大角度
   // chaseCam.setDownRotateOnCloseViewOnly(false);
    
    //light
    AmbientLight ambient = new AmbientLight();
    rootNode.addLight(ambient);
    DirectionalLight sun = new DirectionalLight();
    sun.setDirection(new Vector3f(1.4f, -1.4f, -1.4f));
    rootNode.addLight(sun);
    
    //Real...
    state = new CustomSphereStateTest(1.0f);     
    this.stateManager.attach(state);
}

@Override
public void simpleUpdate(float tpf) {
  
}

@Override
public void simpleRender(RenderManager rm) {
    //TODO: add render code
}

}

The second one is:

public class CustomSphereStateTest extends AbstractAppState{    

private SimpleApplication app;
private Camera cam;
private Node rootNode;
private AssetManager assetManager;    
BitmapFont guiFont; 

//属性值
private boolean isThreeAxes = false; //是否是三轴坐标系
private ColorRGBA  otherColor = ColorRGBA.White;
private ColorRGBA  colorX = ColorRGBA.Red;   //JME3坐标系中X轴
private ColorRGBA  colorY = ColorRGBA.Green; //JME3坐标系中Y轴 
private ColorRGBA  colorZ = ColorRGBA.Blue;  //JME3坐标系中Z轴 

public static final int Axis_X =1; //JME3坐标系中X轴
public static final int Axis_Y =2; //JME3坐标系中Y轴 
public static final int Axis_Z =3; //JME3坐标系中Z轴   

float xStep = -1.0f;    //初始为无效值
float yStep = -1.0f;
float zStep = -1.0f;
float markOffset =0.2f; //坐标轴上的标记,偏移大小 
float textOffset =0.6f; //坐标轴上文字    偏移大小    
float textSize = 0.4f;  //文字的字体大小

float radius; //用户传入的半径值,此值太大,会影响显示
float scale;  //需要将用户传入的半径值进行缩放,使之永远位于0~10之间

//TODO 记录用户传入的数据点值[],并进行缩放

public boolean isIsThreeAxes() {
    return isThreeAxes;
}

public void setIsThreeAxes(boolean isThreeAxes) {
    this.isThreeAxes = isThreeAxes;
}

public ColorRGBA getOtherColor() {
    return otherColor;
}

public void setOtherColor(ColorRGBA otherColor) {
    this.otherColor = otherColor;
}

public ColorRGBA getColorX() {
    return colorX;
}

public void setColorX(ColorRGBA colorX) {
    this.colorX = colorX;
}

public ColorRGBA getColorY() {
    return colorY;
}

public void setColorY(ColorRGBA colorY) {
    this.colorY = colorY;
}

public ColorRGBA getColorZ() {
    return colorZ;
}

public void setColorZ(ColorRGBA colorZ) {
    this.colorZ = colorZ;
}

public float getxStep() {
    return xStep;
}

public void setxStep(float xStep) {
    this.xStep = xStep;
}

public float getyStep() {
    return yStep;
}

public void setyStep(float yStep) {
    this.yStep = yStep;
}

public float getzStep() {
    return zStep;
}

public void setzStep(float zStep) {
    this.zStep = zStep;
}

public float getMarkOffset() {
    return markOffset;
}

public void setMarkOffset(float markOffset) {
    this.markOffset = markOffset;
}

public float getTextOffset() {
    return textOffset;
}

public void setTextOffset(float textOffset) {
    this.textOffset = textOffset;
}

public float getTextSize() {
    return textSize;
}

public void setTextSize(float textSize) {
    this.textSize = textSize;
}

public float getRadius() {
    return radius*scale;
}

public void setRadius(float inRadius) {
    this.radius = 8.0f; 
    this.scale = inRadius/this.radius;
}


public CustomSphereStateTest( float inRadius){
    super();
    setRadius(inRadius);
}



@Override
public void initialize(AppStateManager stateManager, Application app) {//将此加入到StateManager才会调用此函数初始化
    super.initialize(stateManager, app); //To change body of generated methods, choose Tools | Templates.
    this.app = (SimpleApplication)app;
    this.cam = this.app.getCamera();
    this.rootNode = this.app.getRootNode();
    this.assetManager = this.app.getAssetManager();        
    guiFont = this.assetManager.loadFont("Interface/Fonts/Default.fnt");
    //创建模型
   this.createColoredSphere(16,32,radius );//半径为5
    this.createAxes(2*radius); //坐标轴为2*5

}

@Override
public void update(float tpf) {
    super.update(tpf); //To change body of generated methods, choose Tools | Templates.

}

@Override
public void cleanup() {
    super.cleanup(); //To change body of generated methods, choose Tools | Templates.
    rootNode.detachAllChildren();
}
private void createColoredSphere(int zSamples,int radialSamples,float radius ){
    Sphere b = new Sphere( zSamples, radialSamples, radius);
    Geometry geom = new Geometry("Sphere", b);
    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
     mat.setColor("Color", ColorRGBA.White);
   // mat.setBoolean("VertexColor", true); //Rubby 顶点作色
    //mat.getAdditionalRenderState().setWireframe(true); //显示三角形线框

  //  setColorData(b,zSamples,radialSamples); //TODO 顶点作色的数据,新的可能并不需要
    geom.setMaterial(mat);      
    
    //TODO 用户可以控制是否开启 三角线框的展示
    Sphere b2= new Sphere( zSamples, radialSamples, radius);
    //  b2.setMode(Mesh.Mode.Triangles);
    Geometry geom2= new Geometry("Sphere2", b2);
    Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    ColorRGBA gray = new ColorRGBA(0.3f,0.3f,0.3f,0.3f);
    mat2.setColor("Color", gray);       
    mat2.getAdditionalRenderState().setWireframe(true); //显示三角形线框      
    geom2.setMaterial(mat2);    
    
   // BoundingSphere bs =  new BoundingSphere(radius, new Vector3f(0.0f,0.0f,0.0f));
    rootNode.attachChild(geom2);
    rootNode.attachChild(geom);
}



//创建坐标轴:按照jme3的坐标系
private void createAxes(float length){ //length 是2*radius的值
    Vector3f [] vertices = new Vector3f[4];
    float half = length/2.0f;
    vertices[0] = new Vector3f(-half,-half,-half);  //坐标轴原点 (-5,-5,-5)
    vertices[1] = new Vector3f(half,-half,-half);//x(JME3)
    vertices[2] = new Vector3f(-half,half,-half);//Y(JME3)
    vertices[3] = new Vector3f(-half,-half,half);//Z(JME3)
    Line x = new Line( vertices[0], vertices[1]);
    Line y = new Line( vertices[0], vertices[2]);
    Line z = new Line( vertices[0], vertices[3]);
    
   //先创建三个轴.  创建markLine,创建mark text,创建名称(X,Y,Z)
   Geometry geomX = new Geometry("LineX", x);
   Material matX = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
   matX.setColor("Color", ColorRGBA.Red);
   geomX.setMaterial(matX);
   rootNode.attachChild(geomX);
   createMarkLine(vertices[0],vertices[1],CustomSphereStateTest.Axis_X);
   
    Geometry geomY = new Geometry("LineY", y);
    Material matY = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    matY.setColor("Color", ColorRGBA.Green);
    geomY.setMaterial(matY);
    rootNode.attachChild(geomY);
    createMarkLine(vertices[0],vertices[2],CustomSphereStateTest.Axis_Y);
    
    Geometry geomZ = new Geometry("LineZ", z);
    Material matZ = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    matZ.setColor("Color", ColorRGBA.Blue);
    geomZ.setMaterial(matZ);
    rootNode.attachChild(geomZ);
    createMarkLine(vertices[0],vertices[3],CustomSphereStateTest.Axis_Z);
     
    //其它九条轴
    if(isThreeAxes == false){
        //line1
        Vector3f a = new Vector3f(-half,-half,half);
        Vector3f b = new Vector3f(half,-half,half);          
        Geometry geom = createLine(a,b,otherColor);           
        rootNode.attachChild(geom);
        
        //line2
        a = new Vector3f(half,-half,half);
        b = new Vector3f(half,-half,-half);
        geom = createLine(a,b,otherColor);           
        rootNode.attachChild(geom);
        
        //line3
        a = new Vector3f(-half,half,half);
        b = new Vector3f(-half,-half,half);
        geom = createLine(a,b,otherColor);           
        rootNode.attachChild(geom);; 
        
        //line4
        a = new Vector3f(half,-half,half);
        b = new Vector3f(half,half,half);
        geom = createLine(a,b,otherColor);           
        rootNode.attachChild(geom);
        
        //line5
        a = new Vector3f(half,-half,-half);
        b = new Vector3f(half,half,-half);      
        geom = createLine(a,b,otherColor);           
        rootNode.attachChild(geom);
        
        
         //line6
        a = new Vector3f(-half,half,half);
        b = new Vector3f(-half,half,-half);           
        geom = createLine(a,b,otherColor);           
        rootNode.attachChild(geom);
        
        //line7
        a = new Vector3f(-half,half,half);
        b = new Vector3f(half,half,half);
        geom = createLine(a,b,otherColor);           
        rootNode.attachChild(geom);
        
        
       //line8
        a = new Vector3f(half,half,half);
        b = new Vector3f(half,half,-half);            
        geom = createLine(a,b,otherColor);           
        rootNode.attachChild(geom);
        
        //line9
        a = new Vector3f(half,half,-half);
        b = new Vector3f(-half,half,-half);           
        geom = createLine(a,b,otherColor);           
        rootNode.attachChild(geom);
        
    }
   
}
private Geometry createLine(Vector3f a, Vector3f b, ColorRGBA color){
        Line line = new Line( a, b);
        Geometry geom = new Geometry("Axes-others", line);
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", color);
        geom.setMaterial(mat);
        return geom;
}

private void createMarkLine(Vector3f start,Vector3f end,int type){
    if(CustomSphereStateTest.Axis_X == type){
        float xStart =start.x;
        float xEnd = end.x;
        if(this.xStep<0){
            this.xStep =  (end.x)/2;
        }
        float index = xStart;
        float y = start.y;
        float z = start.z;
        for(;index <=0.0;index= index+this.xStep){
            Vector3f a = new Vector3f(index,y,z);
            Vector3f b = new Vector3f(index,y,z-this.markOffset);                        
            Geometry geom = this.createLine(a,b, colorX);
            this.rootNode.attachChild(geom);
            BitmapText bitmapText =this.createBitmapText(index*this.scale+"", new Vector3f(index,y,z-this.textOffset));
            this.rootNode.attachChild(bitmapText);
        }
        index =0;
           for(;index <=xEnd;index= index+this.xStep){
            Vector3f a = new Vector3f(index,y,z);
            Vector3f b = new Vector3f(index,y,z-this.markOffset);                        
            Geometry geom = this.createLine(a,b, colorX);
            this.rootNode.attachChild(geom);
            BitmapText bitmapText =this.createBitmapText(index*this.scale+"", new Vector3f(index,y,z-this.textOffset));
            this.rootNode.attachChild(bitmapText);
        }            
        
    }else if(CustomSphereStateTest.Axis_Y == type){
        float yStart =start.y;
        float yEnd = end.y;
        if(this.yStep<0){
            this.yStep =  (end.y)/2;
        }
        float index = yStart;
        float x = start.x;
        float z = start.z;
        for(;index <=0.0;index= index+this.yStep){
            Vector3f a = new Vector3f(x,index,z);
            Vector3f b = new Vector3f(x-this.markOffset,index,z);                        
            Geometry geom = this.createLine(a,b, colorY);
            this.rootNode.attachChild(geom);
            BitmapText bitmapText =this.createBitmapText(index*this.scale+"", new Vector3f(x-this.textOffset,index+0.5f,z));//由于怕坐标轴标记重叠,所以Y轴标记往上移动0.3
            this.rootNode.attachChild(bitmapText);
        }
        index =0;
           for(;index <=yEnd;index= index+this.xStep){
            Vector3f a = new Vector3f(x,index,z);
            Vector3f b = new Vector3f(x-this.markOffset,index,z);                        
            Geometry geom = this.createLine(a,b, colorY);
            this.rootNode.attachChild(geom);
            BitmapText bitmapText =this.createBitmapText(index*this.scale+"", new Vector3f(x-this.textOffset,index+0.5f,z));
            this.rootNode.attachChild(bitmapText);
        }            
    }else if(CustomSphereStateTest.Axis_Z == type){
        //由于用户坐标系问题,所以Z轴的marktext 要取反
         float zStart =start.z;
        float zEnd = end.z;
        if(this.zStep<0){
            this.zStep =  (end.z)/2;
        }
        float index = zStart;
        float x = start.x;
        float y = start.y;
        for(;index <=0.0;index= index+this.zStep){
            Vector3f a = new Vector3f(x,y,index);
            Vector3f b = new Vector3f(x-this.markOffset,y,index);                        
            Geometry geom = this.createLine(a,b, colorZ);
            this.rootNode.attachChild(geom);
            BitmapText bitmapText =this.createBitmapText(index*this.scale+"", new Vector3f(x-this.textOffset,y,index));
            this.rootNode.attachChild(bitmapText);
        }
        index =0;
           for(;index <=zEnd;index= index+this.zStep){
            Vector3f a = new Vector3f(x,y,index);
            Vector3f b = new Vector3f(x-this.markOffset,y,index);                        
            Geometry geom = this.createLine(a,b, colorZ);
            this.rootNode.attachChild(geom);
            BitmapText bitmapText =this.createBitmapText(index*this.scale+"", new Vector3f(x-this.textOffset,y,index));
            this.rootNode.attachChild(bitmapText);
        }        
    }
}

private BitmapText createBitmapText(String str, Vector3f location){
   
    BitmapText text = new BitmapText(guiFont);
    text.setText(str);
    text.setSize(textSize);//TODO 文本字体大小
    text.setLocalTranslation(location); //将其转移到指定位置  
    //让其始终面向屏幕.否则会看不到
    BillboardControl control = new BillboardControl();
    text.addControl(control);
    return text;
}

private void setColorData(Sphere sphere ,int zSamples,int radialSamples) {
   int triCount;       
    triCount = 2 * (zSamples - 2) * radialSamples+2; //这么多个三角形+2        
    int vertCount = (zSamples - 2) * (radialSamples + 1) + 2; //顶点数目
    float[] colorArray = new float[vertCount*4];
     int colorIndex =0;
    for(int i=0; i<zSamples - 2; i++){ //红色渐渐加强
        for(int j=0; j<radialSamples + 1;j++){
            colorArray[colorIndex++] = ( 1.0f/(zSamples-2) )*i;
            colorArray[colorIndex++]= 0.0f;           
            colorArray[colorIndex++]= 0.0f;           
            colorArray[colorIndex++]= 1.0f;
        }
    }
      // south pole triangles:  green  // Z为-radius
    {
     colorArray[colorIndex++] = 0.0f;
            colorArray[colorIndex++]= 1.0f;           
            colorArray[colorIndex++]= 0.0f;           
            colorArray[colorIndex++]= 1.0f;
    }
    
    // north pole triangles: blue    // Z为+radius
     {
            colorArray[colorIndex++] = 0.0f;
            colorArray[colorIndex++]= 0.0f;           
            colorArray[colorIndex++]= 1.0f;           
            colorArray[colorIndex++]= 1.0f;
    }
    
    sphere.setBuffer(VertexBuffer.Type.Color, 4, colorArray); //每个颜色有4个float值。

}
}

I have put the code here, Thanks for your help, i appreciate it so much.

Rotate how (I couldn’t see it in the code, do you mean with the mouse?)… and which “sometimes”?

You have it setup to allow +/- 180 degrees rotation vertically which seems a bit odd as that means you could go fully upside down in both directions. Depending on your “sometimes”, it may be that looking straight down on the object confuses chase cam.

Thanks. so much.
I rotate it by mouse.
In this simple application ,i want to the user can rotate the sphere vertically and horizontally arbitrarily.
Thus, I think fully upside down in both directions is needed.
If you rotate it by mouse, at certain degree, you will find the three axes change from one side to another side suddenly.

The pictures in the first poster show the chase.
Maybe you can run the application to see such case. Thanks for your time.

Even i setup to allow +/- 90 degrees rotation vertically, the problem still exists. It is so confused.

     chaseCam.setMinVerticalRotation(-FastMath.PI/2);
     chaseCam.setMaxVerticalRotation(FastMath.PI/2);

Does it happen when you are directly vertical?

If you want free rotation in x and y without roll then your best bet might be to keep track of those yaw and pitch values yourself as radians and then construct rotation from them.

When you reach the poles whit the chase camera, the up axis inverts. It’ s aside effect of how it’s done. You need values below 90°. try 89°.

yes, it happens when directly vertical rotation.

According to your advice, I change the code to

chaseCam.setMinVerticalRotation(-89);
chaseCam.setMaxVerticalRotation(89);

The problem still exists.

chaseCam.setInvertVerticalAxis(false); does not work either.

You need to set the value in radians of course.

sorry,
with the following code, the three axes are always right when mouse rotation.

   chaseCam.setMinVerticalRotation(-89*FastMath.DEG_TO_RAD);
   chaseCam.setMaxVerticalRotation(89*FastMath.DEG_TO_RAD);

But if i want rotation to go fully upside down in both directions,
do i need to write a new chasecamera by myself?

Is there any example? Thanks.

Yes. As I said, you will need to track the yaw and pitch for the mouse movement and then convert to Quaternion.

Yeah, ChaseCamera.java

Thanks, got it. :smile: