[SOLVED] ChaseCamera Horizontal rotation problem

First Thanks to the people in the forum who gave me tips and helped me
Translation software always produces wrong translations
I need more information to understand
As I get deeper into JME,More and more problems were encountered

I can’t solve a camera problem these days

Look video
This video shows the offset and rotation of the shooting state camera
The question now video
The second video shows my problem, ChaseCamera doesn’t have the same perspective as the first video

package net.jmecn;

import com.jme3.scene.debug.Arrow;
import com.jme3.input.ChaseCamera;
import com.jme3.anim.AnimComposer;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.collision.shapes.HullCollisionShape;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.KeyInput;
import com.jme3.input.CameraInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.scene.CameraNode;
import com.jme3.ui.Picture;
/**
 * 按键发射小球轰击砖墙。
 * 
 * @author yanmaoyuan
 *
 */
public class HelloPhysics extends SimpleApplication implements ActionListener {

    /**
     * 开火,发射小球。鼠标左键触发。
     */
    public final static String FIRE = "fire";
    public final static String FORWARD = "forward";
    public final static String BACKWARD = "backward";
    public final static String LEFT = "left";
    public final static String RIGHT = "right";
    public final static String V = "v";
    public final static String JUMP = "jump";
    public int v1= 0;
    //方向控制器
    private CharacterControl player;
    private Vector3f walkDirection = new Vector3f();
    private Vector3f walkDirectioni = new Vector3f();
    private boolean left = false, right = false, up = false, down = false, v=false;
    /**
     * 显示或隐藏BulletAppState的debug形状。按空格键触发。
     */
    public final static String DEBUG = "debug";
 // 临时变量,用于保存摄像机的方向。避免在simpleUpdate中重复创建对象。
    private Vector3f camDir = new Vector3f();
    private Vector3f camLeft = new Vector3f();
    /** 砖块的尺寸 */
    private static final float brickLength = 0.48f;
    private static final float brickWidth = 0.24f;
    private static final float brickHeight = 0.12f;
    private AnimComposer control;
    private BulletAppState bulletAppState;
    private Node character;
    private ChaseCamera chaseCam;
    private CameraInput cameraInput;
 
    @Override
    public void simpleInitApp() {

        createArrow(new Vector3f(5, 0, 0), ColorRGBA.Green);
        createArrow(new Vector3f(0, 5, 0), ColorRGBA.Red);
        createArrow(new Vector3f(0, 0, 5), ColorRGBA.Blue);
    
//        cam.setLocation(new Vector3f(0, 4f, 6f));
//        cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y);

        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        bulletAppState.setDebugEnabled(true);
        // 初始化按键
        initKeys();

        // 初始化光照
        initLight();

        // 初始化场景
        initScene();
        // 添加图片
        addPicture();
    }
 /**
     * 创建一个箭头
     * 
     * @param vec3  箭头向量
     * @param color 箭头颜色
     */
    private void createArrow(Vector3f vec3, ColorRGBA color) {
        // 创建材质,设定箭头的颜色
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", color);

        // 创建几何物体,应用箭头网格。
        Geometry geom = new Geometry("arrow", new Arrow(vec3));
        geom.setMaterial(mat);

        // 添加到场景中
        rootNode.attachChild(geom);
    }
    @Override
    public void onAction(String name, boolean isPressed, float tpf) {
        
//        if (isPressed) {
//            if (FIRE.equals(name)) {
//                shootBall();
//            } else if (DEBUG.equals(name)) {
//                boolean debugEnabled = bulletAppState.isDebugEnabled();
//                bulletAppState.setDebugEnabled(!debugEnabled);
//            }
//        }
 if (DEBUG.equals(name) && isPressed) {
     
            boolean debugEnabled = bulletAppState.isDebugEnabled();
            bulletAppState.setDebugEnabled(!debugEnabled);
        } else if (LEFT.equals(name)) {
            left = isPressed;
        } else if (RIGHT.equals(name)) {
            right = isPressed;
        } else if (FORWARD.equals(name)) {
            up = isPressed;
        } else if (BACKWARD.equals(name)) {
            down = isPressed;
        }else if(V.equals(name)) {
            if(!isPressed){
          switch(v1){
        case 0 :
       chaseCam.setLookAtOffset(camDir.add(0f, 1.5f, -2.5f).add(camLeft)); v1++;
   
       break; //可选
          case 1 :
        chaseCam.setLookAtOffset(camDir.add(0f, 1.5f, 0f)); v1=0;
     
       break; //可选
    //你可以有任意数量的case语句
    default : //可选
       //语句
          }
                
//            if(v1==0){
//          chaseCam.setLookAtOffset(camDir.add(0f, 1.5f, -2.5f).add(camLeft)); 
//           v1++;
//          }
//          
//            if(v1>1){
//                System.err.println(v1+"按下0");
//              chaseCam.setLookAtOffset(camDir.add(0f, 1.5f, 0f)); 
//              v1=0;
//            }
              

          }
             
            v = isPressed;
        }else if (JUMP.equals(name) && isPressed) {
            if(player.onGround()==true){
                player.jump();
                player.warp(walkDirection);
            }
            
            	System.err.println(player.onGround());
            //	player.warp(walkDirection);
        }
    }

    /**
     * 初始化按键输入
     */
    private void initKeys() {
        inputManager.addMapping(FIRE, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addMapping(DEBUG, new KeyTrigger(KeyInput.KEY_F1));
        inputManager.addMapping(LEFT, new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping(RIGHT, new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping(FORWARD, new KeyTrigger(KeyInput.KEY_W));
        inputManager.addMapping(BACKWARD, new KeyTrigger(KeyInput.KEY_S));
        inputManager.addMapping(JUMP, new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping(V, new KeyTrigger(KeyInput.KEY_V));
        inputManager.addListener(this, FIRE, DEBUG,LEFT,RIGHT,FORWARD,BACKWARD,JUMP,V);
        
        
    }

    /**
     * 初始化光照
     */
    private void initLight() {
        // 环境光
        AmbientLight ambient = new AmbientLight();
        ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1f));

        // 阳光
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-1, -2, -3).normalizeLocal());

        rootNode.addLight(ambient);
        rootNode.addLight(sun);
    }

    /**
     * 初始化场景
     */
    private void initScene() {
        model();
        makeFloor();
       // makeWall();
   
        
    }

    /**
     * 制作地板
     */
    private void makeFloor() {
        // 网格
        float radius = 100f;// 胶囊半径0.3米
        float height = 0.1f;// 胶囊身高1.8米
        float stepHeight = 100f;// 角色步高0.5米
        Box floor = new Box(radius, height, stepHeight);
        floor.scaleTextureCoordinates(new Vector2f(3, 6));

        // 材质
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        Texture tex = assetManager.loadTexture("Interface/pic.png");
        tex.setWrap(WrapMode.Repeat);
        mat.setTexture("ColorMap", tex);

        // 几何体
        Geometry geom = new Geometry("floor", floor);
        geom.setMaterial(mat);
        geom.setLocalTranslation(0, -1.8f, 0);// 将地板下移一定距离,让表面和xoz平面重合。

        // 刚体
        RigidBodyControl rigidBody = new RigidBodyControl(0);
        geom.addControl(rigidBody);
        rigidBody.setCollisionShape(new BoxCollisionShape(new Vector3f(radius, height, stepHeight)));
            rigidBody.setFriction(10f);
        rootNode.attachChild(geom);
        bulletAppState.getPhysicsSpace().add(rigidBody);
    }

    /**
     * 建造一堵墙
     */
    private void makeWall() {
        // 利用for循环生成一堵由众多砖块组成的墙体。
        float startpt = brickLength / 4;
        float height = 0;
        for (int j = 0; j < 15; j++) {
            for (int i = 0; i < 6; i++) {
                Vector3f vt = new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0);
                makeBrick(vt);
            }
            startpt = -startpt;
            height += 2 * brickHeight;
        }
    }
    
        private void model() {
        float radius = 0.5f;// 胶囊半径0.3米
        float height = 3.3f;// 胶囊身高1.8米
        float stepHeight = 0.5f;// 角色步高0.5米
            //模型
       character = new Node("Character");
            Node  model =  (Node)assetManager.loadModel("Textures/jisi/Test/hecheng.j3o");
            model.move(0, -(height/2+radius), 0);
            //model.scale(1.8f);
    
            character.attachChild(model);// 挂到角色根节点下
            rootNode.attachChild(character);
            control = model.getControl(AnimComposer.class);
            control.setCurrentAction("walk");
            control.setGlobalSpeed(2f);
            
//          Geometry submesh = (Geometry) model.getChild("ç½‘æ ¼.003_4");
//          HullCollisionShape hullCollisionShape = new HullCollisionShape(submesh.getMesh());
//          //刚体
//          RigidBodyControl rigidBody = new RigidBodyControl(hullCollisionShape,0f);
//          rigidBody.setCollisionShape(new BoxCollisionShape(new Vector3f(10f, 0.1f, 5f)));
//          bulletAppState.getPhysicsSpace().add(rigidBody);
        // 使用胶囊体作为玩家的碰撞形状
        CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(radius, height, 1);
        // 使用CharacterControl来控制玩家物体
        player = new CharacterControl(capsuleShape, stepHeight);
        player.setJumpSpeed(10f);// 起跳速度
        player.setFallSpeed(20f);// 坠落速度
        player.setGravity(9.8f * 3);// 重力加速度
      //  player.setPhysicsLocation(new Vector3f(-999, 90, -99));// 直接改变这个角色的位置。
        character.addControl(player);
       	
        bulletAppState.getPhysicsSpace().add(player);
        
                // Disable the default flyby cam
        // Disable the default flyby cam
      // cam.setLocation(new Vector3f(10f, 0.1f, 5f));
     
        // Enable a chase cam for this target (typically the player).
        chaseCam = new ChaseCamera(cam, character, inputManager);
//       inputManager.deleteMapping("ChaseCamZoomIn");
//       inputManager.deleteMapping("ChaseCamZoomOut");
//            //to disable rotation
//           inputManager.deleteMapping(CameraInput.CHASECAM_TOGGLEROTATE);
//           //to disable zoom out
           inputManager.deleteMapping(CameraInput.CHASECAM_ZOOMOUT);
           //to disable zoom in
           inputManager.deleteMapping(CameraInput.CHASECAM_ZOOMIN);
           flyCam.setEnabled(false);
          chaseCam.setLookAtOffset(new Vector3f(0, 1.5f, 0));
          
//        
//        chaseCam.setSmoothMotion(true);//启用此追逐相机的平滑运动
//        chaseCam.setTrailingEnabled(true);//启用相机尾随:相机在移动时平滑地进入目标轨迹。
        chaseCam.setDragToRotate(false); //把鼠标锁定在窗口里
         
//        chaseCam.setMinDistance(15f);
//        chaseCam.setMaxDistance(15f);
    
//        chaseCam.setMinVerticalRotation(0.01f);//设置摄像机围绕目标的最小垂直旋转角度,默认为 0; (说人话就是鼠标向下的最小角度)
//        chaseCam.setMaxVerticalRotation(1f);//设置摄像机围绕目标的最大垂直旋转角度
//        chaseCam.setZoomOutTrigger(new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
//	chaseCam.setZoomInTrigger(new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
        //System.err.println(chaseCam.getZoomSensitivity());        
        //chaseCam.setZoomSensitivity(10f);
        
//        flyCam.setEnabled(true);
//        flyCam.setDragToRotate(true);
//        // Disable the default flyby cam
//        flyCam.setEnabled(true);
//        //create the camera Node
//        CameraNode camNode = new CameraNode("CamNode", cam);  
//        //This mode means that camera copies the movements of the target:
//        camNode.setControlDir(ControlDirection.SpatialToCamera);
//        //Attach the camNode to the target:
//        character.attachChild(camNode);
//        //Move camNode, e.g. behind and above the target:
//        camNode.setLocalTranslation(walkDirection);
//        //Rotate the camNode to look at the target:
//        camNode.lookAt(model.getLocalTranslation(), Vector3f.UNIT_Y);
        }
        
        /**
         主循环
         **/
        
    @Override
    public void simpleUpdate(float tpf) {
        inputManager.setCursorVisible(false);
        camDir.set(cam.getDirection()).multLocal(0.1f);
        camLeft.set(cam.getLeft()).multLocal(0.1f);
        camDir.y = 0;
        camLeft.y = 0;
        walkDirection.set(0, 0, 0);
        
        // 计算运动方向
        boolean changed = false;
        if (left) {
            walkDirectioni.addLocal(camLeft);
            walkDirection.addLocal(camLeft);
            
            changed = true;
        }
        if (right) {
            walkDirectioni.addLocal(camLeft.negate());
           
            walkDirection.addLocal(camLeft.negate());
           
            changed = true;
        }
        if (up) {
            walkDirectioni.addLocal(camDir);
        
            walkDirection.addLocal(camDir);
      
            changed = true;
        }
        if (down) {
            walkDirectioni.addLocal(camDir.negate());
        
            walkDirection.addLocal(camDir.negate());
            
            changed = true;
        }
        if (v) {
           // player.setViewDirection(walkDirectioni);
            if(v){
                	  
            }else{
                   
            }
          // System.err.println(); 
            changed = true;
        }
        if (changed) {
             
            walkDirectioni.y = 0.0f;// 将行走速度的方向限制在水平面上。
            walkDirectioni.normalizeLocal();// 单位化
            walkDirectioni.multLocal(3f);// 改变速率
            walkDirection.y = 0;// 将行走速度的方向限制在水平面上。
            walkDirection.normalizeLocal();// 单位化
            walkDirection.multLocal(0.3f);// 改变速率
            
        }
        if (walkDirection.length() != 0f) {
             player.setViewDirection(walkDirection);
        }
        
        
     
       player.setWalkDirection(walkDirection);
       
       // player.setAngularDamping(5f);
      // player.setViewDirection(walkDirectioni);
       // cam.setLocation(player.getPhysicsLocation());
    }
    
    
    /**
     * 在指定位置放置一个物理砖块
     * 
     * @param loc
     *            砖块的位置
     */
    private void makeBrick(Vector3f loc) {
        // 网格
        Box box = new Box(brickLength, brickHeight, brickWidth);
        box.scaleTextureCoordinates(new Vector2f(1f, .5f));

        // 材质
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        Texture tex = assetManager.loadTexture("Interface/pic.png");
        mat.setTexture("ColorMap", tex);

        // 几何体
        Geometry geom = new Geometry("brick", box);
        geom.setMaterial(mat);
        geom.setLocalTranslation(loc);// 把砖块放在指定位置

        // 刚体
        
        RigidBodyControl rigidBody = new RigidBodyControl(2f);
        geom.addControl(rigidBody);
        rigidBody.setCollisionShape(new BoxCollisionShape(new Vector3f(brickLength, brickHeight, brickWidth)));

        rootNode.attachChild(geom);
        bulletAppState.getPhysicsSpace().add(rigidBody);
    }

    /**
     * 从摄像机所在位置发射一个小球,初速度方向与摄像机方向一致。
     */
    private void shootBall() {
        // 网格
        Sphere sphere = new Sphere(32, 32, 0.4f, true, false);
        sphere.setTextureMode(TextureMode.Projected);

        // 材质
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        Texture tex = assetManager.loadTexture("Interface/pic.png");
        mat.setTexture("ColorMap", tex);

        // 几何体
        Geometry geom = new Geometry("cannon ball", sphere);
        geom.setMaterial(mat);

        // 刚体
        RigidBodyControl rigidBody = new RigidBodyControl(1f);
        geom.addControl(rigidBody);
        rigidBody.setCollisionShape(new SphereCollisionShape(0.5f));
        rigidBody.setPhysicsLocation(cam.getLocation());// 位置
        rigidBody.setLinearVelocity(cam.getDirection().mult(50));// 初速度
      
        rootNode.attachChild(geom);
        bulletAppState.getPhysicsSpace().add(rigidBody);
    }
    /**
     * 加载“图片”
     * 
     * @return
     */
    private void addPicture() {
        Picture pic = new Picture("picture");

        // 设置图片
        pic.setImage(assetManager, "Interface/pic_a.png", true);

        // 设置图片全屏显示
        pic.setWidth(cam.getWidth());
        pic.setHeight(cam.getHeight());

        // 将图片后移一个单位,避免遮住状态界面。
        pic.setLocalTranslation(0, 0, -1);

        guiNode.attachChild(pic);
    }
    public static void main(String[] args) {
        HelloPhysics app = new HelloPhysics();
        app.start();
    }
}

chaseCam.setLookAtOffset(camDir.add(0f, 1.5f, -2.5f).add(camLeft))
I use this method to control camera offset
What misunderstandings might I have had about this approach
Is there any method or literature that can tell me how to achieve the visual Angle in the first video
If you are confused by my question, please tell me which sentence it is
I’ve learned a lot during this time thank you

I think you better look at Garret by @sgold and if you don’t want to use third party lib or don’t like the lib pattern, you could pick the vector maths part from it.

A simple App for the Garrett orbital camera:

1 Like

Thanks for your help I have a new reference

1 Like
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package net.jmecn;

import com.jme3.collision.CollisionResults;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.input.ChaseCamera;
import com.jme3.input.InputManager;
import com.jme3.math.Quaternion;
import com.jme3.renderer.Camera;
import com.jme3.scene.Spatial;
import java.util.Objects;
/**
 *
 * @author Administrator
 */
public class TerrainChaseCamera extends ChaseCamera {
private Quaternion quaternion= new Quaternion();
    public TerrainChaseCamera(Camera cam, Spatial target, InputManager inputManager) {
        super(cam, target, inputManager);
    }
public void setRotateAround(Vector3f center, Vector3f axis, float angle)
    {
        //绕axis轴旋转angle角度
        Quaternion rotation = quaternion.fromAngleAxis(angle, axis);
        //旋转之前,以center为起点,transform.position当前物体位置为终点的向量.
       
        Vector3f beforeVector = target.getLocalTranslation().subtract(center) ;//target.getLocalTranslation().subtract(center)
        //四元数 * 向量(不能调换位置, 否则发生编译错误)
        Vector3f afterVector = rotation.multLocal(beforeVector) ;//旋转后的向量

        this.lookAtOffset = afterVector.add(center);
    }
   
}

What are the better suggestions from the new developments?

Guess you might want to use english in your annotations so we monkeys can read and understand :wink:
Cam control is all about the Up-Vector and don`t forget about quaternion normalization.
core>input>FlyByCam

    /**
     * Rotate the camera by the specified amount around the specified axis.
     *
     * @param value rotation amount
     * @param axis direction of rotation (a unit vector)
     */
    protected void rotateCamera(float value, Vector3f axis) {
        if (dragToRotate) {
            if (canRotate) {
//                value = -value;
            } else {
                return;
            }
        }

        Matrix3f mat = new Matrix3f();
        mat.fromAngleNormalAxis(rotationSpeed * value, axis);

        Vector3f up = cam.getUp();
        Vector3f left = cam.getLeft();
        Vector3f dir = cam.getDirection();

        mat.mult(up, up);
        mat.mult(left, left);
        mat.mult(dir, dir);

        Quaternion q = new Quaternion();
        q.fromAxes(left, up, dir);
        q.normalizeLocal();

        cam.setAxes(q);
    }

And for " visual Angle" I assume you mean FOV, also can be found in the same class:

    /**
     * Zoom the camera by the specified amount.
     *
     * @param value zoom amount
     */
    protected void zoomCamera(float value) {
        float newFov = cam.getFov() + value * 0.1F * zoomSpeed;
        if (newFov > 0) {
            cam.setFov(newFov);
        }
    }
1 Like

I think your life will be easier if you turn this into a “scene graph” problem so that you don’t have to mess with the math for offsetting position, etc…

Put a node at the head of the avatar. That is what you rotate for ‘camera direction’.

Put a child node spaced backwards along Z some distance. child.setLocalTranslation(0, 0, -distance);

Put the camera the world location and world rotation of the child.

To rotate the camera, rotate the node.

…and for 99% of camera control code, it makes more sense to keep yaw and pitch separate and recompose them into rotation as needed.

2 Likes

Is there a similar example

The code rotation will jitter

For the Cam handling part:
As pspeed said, the best way is to use “scene graph” , nodes will do the “chasing” tricks.

the “jitter” problem:
The frames per second in your game video changed in a wide range 300- 700. So it might be other part of your program cause it. When certain scene object appears the FPS drops to 300 and will return to 700 if cam look at the other side.

Problem solved

package net.jmecn;

import com.jme3.scene.debug.Arrow;
import com.jme3.input.ChaseCamera;
import com.jme3.anim.AnimComposer;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.SphereCollisionShape;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.control.CharacterControl;
import com.jme3.bullet.control.BetterCharacterControl;
import com.jme3.bullet.collision.shapes.HullCollisionShape;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.KeyInput;
import com.jme3.input.CameraInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion; //四元数
import com.jme3.renderer.Camera;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.scene.shape.Sphere;
import com.jme3.scene.shape.Sphere.TextureMode;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.WrapMode;
import com.jme3.scene.control.CameraControl.ControlDirection;
import com.jme3.scene.CameraNode;
import com.jme3.scene.control.Control;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.ui.Picture;
import net.jmecn.TerrainChaseCamera;
import com.jme3.scene.CameraNode;
/**
 * 按键发射小球轰击砖墙。
 * 
 * @author yanmaoyuan
 *
 */
public class TestHelloCamera extends SimpleApplication implements ActionListener {
private Quaternion quaternion =new Quaternion();
    /**
     * 开火,发射小球。鼠标左键触发。
     */
    public final static String FIRE = "fire";
    public final static String FORWARD = "forward";
    public final static String BACKWARD = "backward";
    public final static String LEFT = "left";
    public final static String RIGHT = "right";
    public final static String V = "v";
    public final static String JUMP = "jump";
    public int v1= 0;
    //方向控制器
    private CharacterControl player;
    private Vector3f walkDirection = new Vector3f();
    private Vector3f walkDirectioni = new Vector3f();
    private boolean left = false, right = false, up = false, down = false, v=false;
    /**
     * 显示或隐藏BulletAppState的debug形状。按空格键触发。
     */
    public final static String DEBUG = "debug";
 // 临时变量,用于保存摄像机的方向。避免在simpleUpdate中重复创建对象。
    private Vector3f camDir = new Vector3f();
    private Vector3f camLeft = new Vector3f();
    /** 砖块的尺寸 */
    private static final float brickLength = 0.48f;
    private static final float brickWidth = 0.24f;
    private static final float brickHeight = 0.12f;
    private AnimComposer control;
    private BulletAppState bulletAppState;
    private Node character;
    private Node model;
    private TerrainChaseCamera chaseCam;
    private Camera cam1;
    private CameraInput cameraInput;

    @Override
    public void simpleInitApp() {

        createArrow(new Vector3f(5, 0, 0), ColorRGBA.Green);
        createArrow(new Vector3f(0, 5, 0), ColorRGBA.Red);
        createArrow(new Vector3f(0, 0, 5), ColorRGBA.Blue);
    
//        cam.setLocation(new Vector3f(0, 4f, 6f));
//        cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y);

        bulletAppState = new BulletAppState();
        stateManager.attach(bulletAppState);
        bulletAppState.setDebugEnabled(true);
        // 初始化按键
        initKeys();

        // 初始化光照
        initLight();

        // 初始化场景
        initScene();
        // 添加图片
        addPicture();
    }
 /**
     * 创建一个箭头
     * 
     * @param vec3  箭头向量
     * @param color 箭头颜色
     */
    private void createArrow(Vector3f vec3, ColorRGBA color) {
        // 创建材质,设定箭头的颜色
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat.setColor("Color", color);

        // 创建几何物体,应用箭头网格。
        Geometry geom = new Geometry("arrow", new Arrow(vec3));
        geom.setMaterial(mat);

        // 添加到场景中
        rootNode.attachChild(geom);
    }
    @Override
    public void onAction(String name, boolean isPressed, float tpf) {
        
//        if (isPressed) {
//            if (FIRE.equals(name)) {
//                shootBall();
//            } else if (DEBUG.equals(name)) {
//                boolean debugEnabled = bulletAppState.isDebugEnabled();
//                bulletAppState.setDebugEnabled(!debugEnabled);
//            }
//        }
 if (DEBUG.equals(name) && isPressed) {
     
            boolean debugEnabled = bulletAppState.isDebugEnabled();
            bulletAppState.setDebugEnabled(!debugEnabled);
        } else if (LEFT.equals(name)) {
            left = isPressed;
        } else if (RIGHT.equals(name)) {
            right = isPressed;
        } else if (FORWARD.equals(name)) {
            up = isPressed;
        } else if (BACKWARD.equals(name)) {
            down = isPressed;
        }else if(V.equals(name)) {
            if(!isPressed){
          switch(v1){
        case 0 :
       chaseCam.setLookAtOffset(walkDirection.add(0f, 1.5f, -1.5f)); 
      
            //player.getPhysicsLocation().subtract(cam.getLocation())
            
//           System.err.println(player.getPhysicsLocation()); 
//           System.err.println(player.getPhysicsLocation()); 
//           System.err.println(player.getPhysicsLocation()); 
        chaseCam.setMinDistance(10f);
        chaseCam.setMaxDistance(10f);
         v1++;
       break; //可选
          case 1 :
        chaseCam.setLookAtOffset(walkDirection.add(0f, 0f, 0f)); v1=0;
        chaseCam.setMinDistance(20f);
        chaseCam.setMaxDistance(20f);
       break; //可选
    //你可以有任意数量的case语句
    default : //可选
       //语句
          }
                
//            if(v1==0){
//          chaseCam.setLookAtOffset(camDir.add(0f, 1.5f, -2.5f).add(camLeft)); 
//           v1++;
//          }
//          
//            if(v1>1){
//                System.err.println(v1+"按下0");
//              chaseCam.setLookAtOffset(camDir.add(0f, 1.5f, 0f)); 
//              v1=0;
//            }
              

          }
             
            v = isPressed;
        }else if (JUMP.equals(name) && isPressed) {
            if(player.onGround()==true){
                player.jump();
                player.warp(walkDirection);
            }
            
            	System.err.println(player.onGround());
            //	player.warp(walkDirection);
        }
 
    }

    /**
     * 初始化按键输入
     */
    private void initKeys() {
        inputManager.addMapping(FIRE, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
        inputManager.addMapping(DEBUG, new KeyTrigger(KeyInput.KEY_F1));
        inputManager.addMapping(LEFT, new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping(RIGHT, new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping(FORWARD, new KeyTrigger(KeyInput.KEY_W));
        inputManager.addMapping(BACKWARD, new KeyTrigger(KeyInput.KEY_S));
        inputManager.addMapping(JUMP, new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping(V, new KeyTrigger(KeyInput.KEY_V));
        inputManager.addListener(this, FIRE, DEBUG,LEFT,RIGHT,FORWARD,BACKWARD,JUMP,V);
        
        
    }

    /**
     * 初始化光照
     */
    private void initLight() {
        // 环境光
        AmbientLight ambient = new AmbientLight();
        ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1f));

        // 阳光
        DirectionalLight sun = new DirectionalLight();
        sun.setDirection(new Vector3f(-1, -2, -3).normalizeLocal());

        rootNode.addLight(ambient);
        rootNode.addLight(sun);
    }

    /**
     * 初始化场景
     */
    private void initScene() {
        model();
        makeFloor();
       // makeWall();
   
        
    }

    /**
     * 制作地板
     */
    private void makeFloor() {
        // 网格
//        float radius = 100f;// 胶囊半径0.3米
//        float height = 0.1f;// 胶囊身高1.8米
//        float stepHeight = 100f;// 角色步高0.5米
//        Node  modelTest =  (Node)assetManager.loadModel("Textures/dixing/zokiew-w-18-wieku-zhovkva-in-18th-century.gltf");
//        RigidBodyControl rigidBody = new RigidBodyControl(0);
//       // rigidBody.setFriction(10f);
//        modelTest.addControl(rigidBody);
//        rootNode.attachChild(modelTest);
//        bulletAppState.getPhysicsSpace().add(rigidBody);
  // 网格
        float radius = 100f;// 胶囊半径0.3米
        float height = 0.1f;// 胶囊身高1.8米
        float stepHeight = 100f;// 角色步高0.5米
        Box floor = new Box(radius, height, stepHeight);
        floor.scaleTextureCoordinates(new Vector2f(3, 6));

        // 材质
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        Texture tex = assetManager.loadTexture("Interface/pic.png");
        tex.setWrap(WrapMode.Repeat);
        mat.setTexture("ColorMap", tex);

        // 几何体
        Geometry geom = new Geometry("floor", floor);
        geom.setMaterial(mat);
        geom.setLocalTranslation(0, -1.8f, 0);// 将地板下移一定距离,让表面和xoz平面重合。

        // 刚体
        RigidBodyControl rigidBody = new RigidBodyControl(0);
        geom.addControl(rigidBody);
        rigidBody.setCollisionShape(new BoxCollisionShape(new Vector3f(radius, height, stepHeight)));
            rigidBody.setFriction(10f);
        rootNode.attachChild(geom);
        bulletAppState.getPhysicsSpace().add(rigidBody);
    }

    /**
     * 建造一堵墙
     */
    private void makeWall() {
        // 利用for循环生成一堵由众多砖块组成的墙体。
        float startpt = brickLength / 4;
        float height = 0;
        for (int j = 0; j < 15; j++) {
            for (int i = 0; i < 6; i++) {
                Vector3f vt = new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0);
                makeBrick(vt);
            }
            startpt = -startpt;
            height += 2 * brickHeight;
        }
    }
    
        private void model() {
            
        float radius = 0.5f;// 胶囊半径0.3米
        float height = 3.3f;// 胶囊身高1.8米
        float stepHeight = 0.5f;// 角色步高0.5米
            //模型
       character = new Node("Character");
            Node  model =  (Node)assetManager.loadModel("Textures/jisi/Test/hecheng.j3o");
            model.move(0, -(height/2+radius), 0);
            	
            //model.scale(1.8f);
            character.attachChild(model);// 挂到角色根节点下
            rootNode.attachChild(character);
            control = model.getControl(AnimComposer.class);
            control.setCurrentAction("walk");
            control.setGlobalSpeed(2f);
            

        // 使用胶囊体作为玩家的碰撞形状
        CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(radius, height, 1);
        // 使用CharacterControl来控制玩家物体
        player = new CharacterControl(capsuleShape, stepHeight);
        player.setJumpSpeed(10f);// 起跳速度
        player.setFallSpeed(20f);// 坠落速度
        player.setGravity(9.8f * 3);// 重力加速度
      //  player.setPhysicsLocation(new Vector3f(-999, 90, -99));// 直接改变这个角色的位置。
        character.addControl(player);
      
        bulletAppState.getPhysicsSpace().add(player);
       
                // Disable the default flyby cam
        // Disable the default flyby cam
      // cam.setLocation(new Vector3f(10f, 0.1f, 5f));
      
        // Enable a chase cam for this target (typically the player).
        chaseCam = new TerrainChaseCamera(cam, character.getChild("Bip002"), inputManager);
       

        chaseCam.setDragToRotate(false); //把鼠标锁定在窗口里
        chaseCam.setInvertVerticalAxis(true);//反转鼠标的垂直轴移动
        chaseCam.setMinDistance(20f);
        chaseCam.setMaxDistance(20f);
        

        }
        
        /**
         主循环
         **/
        
    @Override
    public void simpleUpdate(float tpf) {

        inputManager.setCursorVisible(false);
        camDir.set(cam.getDirection()).multLocal(0.1f);
        camLeft.set(cam.getLeft()).multLocal(0.1f);
        camDir.y = 0;
        camLeft.y = 0;
        walkDirection.set(0, 0, 0);
        
        // 计算运动方向
        boolean changed = false;
        if (left) {
            walkDirectioni.addLocal(camLeft);
            walkDirection.addLocal(camLeft);
            
            changed = true;
        }
        if (right) {
            walkDirectioni.addLocal(camLeft.negate());
           
            walkDirection.addLocal(camLeft.negate());
           
            changed = true;
        }
        if (up) {
            walkDirectioni.addLocal(camDir);
        
            walkDirection.addLocal(camDir);
      
            changed = true;
        }
        if (down) {
            walkDirectioni.addLocal(camDir.negate());
        
            walkDirection.addLocal(camDir.negate());
            
            changed = true;
        }
        if (v) {
           // player.setViewDirection(walkDirectioni);
            if(v){
                	
            }else{
                   
            }
          // System.err.println(); 
            changed = true;
        }
        if (changed) {
             
            walkDirectioni.y = 0.0f;// 将行走速度的方向限制在水平面上。
            walkDirectioni.normalizeLocal();// 单位化
            walkDirectioni.multLocal(3f);// 改变速率
            walkDirection.y = 0;// 将行走速度的方向限制在水平面上。
            walkDirection.normalizeLocal();// 单位化
            walkDirection.multLocal(0.3f);// 改变速率
            
        }
        if (walkDirection.length() != 0f) {
             
        }
             

      System.err.println(character.getLocalTranslation());
       player.setWalkDirection(walkDirection);
     
       player.setViewDirection(chaseCam.setRotateAround( walkDirection,new Vector3f(0,1,0),-(chaseCam.getHorizontalRotation())).setY(0));  
       
    
   
    }

    
    /**
     * 在指定位置放置一个物理砖块
     * 
     * @param loc
     *            砖块的位置
     */
    private void makeBrick(Vector3f loc) {
        // 网格
        Box box = new Box(brickLength, brickHeight, brickWidth);
        box.scaleTextureCoordinates(new Vector2f(1f, .5f));

        // 材质
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        Texture tex = assetManager.loadTexture("Interface/pic.png");
        mat.setTexture("ColorMap", tex);

        // 几何体
        Geometry geom = new Geometry("brick", box);
        geom.setMaterial(mat);
        geom.setLocalTranslation(loc);// 把砖块放在指定位置

        // 刚体
        
        RigidBodyControl rigidBody = new RigidBodyControl(2f);
        geom.addControl(rigidBody);
        rigidBody.setCollisionShape(new BoxCollisionShape(new Vector3f(brickLength, brickHeight, brickWidth)));

        rootNode.attachChild(geom);
        bulletAppState.getPhysicsSpace().add(rigidBody);
    }

    /**
     * 从摄像机所在位置发射一个小球,初速度方向与摄像机方向一致。
     */
    private void shootBall() {
        // 网格
        Sphere sphere = new Sphere(32, 32, 0.4f, true, false);
        sphere.setTextureMode(TextureMode.Projected);

        // 材质
        Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        Texture tex = assetManager.loadTexture("Interface/pic.png");
        mat.setTexture("ColorMap", tex);

        // 几何体
        Geometry geom = new Geometry("cannon ball", sphere);
        geom.setMaterial(mat);

        // 刚体
        RigidBodyControl rigidBody = new RigidBodyControl(1f);
        geom.addControl(rigidBody);
        rigidBody.setCollisionShape(new SphereCollisionShape(0.5f));
        rigidBody.setPhysicsLocation(cam.getLocation());// 位置
        rigidBody.setLinearVelocity(cam.getDirection().mult(50));// 初速度
      
        rootNode.attachChild(geom);
        bulletAppState.getPhysicsSpace().add(rigidBody);
    }
    /**
     * 加载“图片”
     * 
     * @return
     */
    private void addPicture() {
        Picture pic = new Picture("picture");

        // 设置图片
        pic.setImage(assetManager, "Interface/pic_a.png", true);

        // 设置图片全屏显示
        pic.setWidth(cam.getWidth());
        pic.setHeight(cam.getHeight());

        // 将图片后移一个单位,避免遮住状态界面。
        pic.setLocalTranslation(0, 0, -1);

        guiNode.attachChild(pic);
    }
    public static void main(String[] args) {
        TestHelloCamera app = new TestHelloCamera();
        app.start();
    }

}
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package net.jmecn;

import com.jme3.collision.CollisionResults;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.terrain.geomipmap.TerrainQuad;
import com.jme3.input.ChaseCamera;
import com.jme3.input.InputManager;
import com.jme3.math.Quaternion;
import com.jme3.renderer.Camera;
import com.jme3.scene.Spatial;
import java.util.Objects;
/**
 *
 * @author Administrator
 */
public class TerrainChaseCamera extends ChaseCamera {
private Quaternion quaternion= new Quaternion();
private Vector3f afterVector= new Vector3f();
    public TerrainChaseCamera(Camera cam, Spatial target, InputManager inputManager) {
        super(cam, target, inputManager);
    }

 
public Vector3f setRotateAround(Vector3f center, Vector3f axis, float angle)
    {
        //绕axis轴旋转angle角度
        Quaternion rotation = quaternion.fromAngleAxis(angle, axis);
        //旋转之前,以center为起点,transform.position当前物体位置为终点的向量.
       
        Vector3f beforeVector = target.getLocalTranslation().subtract(center) ;//target.getLocalTranslation().subtract(center)
        //四元数 * 向量(不能调换位置, 否则发生编译错误)
        Vector3f afterVector = rotation.multLocal(beforeVector) ;//旋转后的向量
       // System.err.println(afterVector);
        afterVector = center.add(afterVector);
    return afterVector;
        
    }

}

I added a Node to the top right of the model
The camera is bound to the node and then setViewDirection is called and it works and I still don’t know why but it’s better

1 Like

I made a little change here

Glad you got it working.

But in the interest of trying to be as clear as possible. Here is what I meant.

yaw = looking left/right
pitch = looking up/down

Node avatar = ....your model
Node pivot = new Node("pivot");
avatar.attachChild(pivot);
Node camNode = new Node("camera");
pivot.attachChild(camNode);

camNode.setLocalTranslation(0, 0, -distance);
camNode.addControl(new CameraControl(cam);

// For camera rotation always relative to the avatar
Quaternion q = new Quaternion().fromAngles(pitch, yaw, 0);
pivot.setLocalRotation(q);

// For camera rotation always in world space:
Quaternion q = new Quaternion().fromAngles(pitch, yaw, 0);
q = avatar.getWorldRotation().inverse().mult(q);
pivot.setLocalRotation(q);

I have not tested it so there may be small issues but that it the idea.

3 Likes