RTS Camera control

<cite>@asser.fahrenholz said:</cite> Sorry I never got back to you. I'll see if I can find the code - otherwise i'll have to re-create it and then i'll share it.

Thanks, that would be great! I actually never finished this camera and I quite forgot, but I will try it again if you’ll show me the way!

My current version of RtsCam is at
https://www.assembla.com/code/vmat/subversion/nodes/84/trunk/src/main/java/net/virtualmat/jme3/RtsCam.java

It might not be directly usable for most people, because it is z-up (instead of being y-up like most jme3 projects), but you can probably copy/paste wheel support from it. You might want to scale the wheel input a bit if you have different scale than 5-40 units being expected distance.

Seems that quite a few people are using it, I might look into making it more generic - I suppose that configurable up-axis (Y or Z) and some interface to provide terrain heights (for both camera and center) should be doable.

[java]
/*

  • To change this template, choose Tools | Templates
  • and open the template in the editor.
    */
    package dabble.player;

import com.jme3.export.JmeExporter;
import com.jme3.export.JmeImporter;
import com.jme3.math.Vector2f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Spatial;
import com.jme3.scene.control.Control;
import com.jme3.terrain.Terrain;
import java.io.IOException;

/**
*

  • @author Asser
    */
    public class PlayerCamControl implements Control {
    //Any local variables should be encapsulated by getters/setters so they
    //appear in the SDK properties window and can be edited.
    //Right-click a local variable to encapsulate it with getters and setters.

    private float z;
    private float x;
    private final Camera cam;
    private final Terrain terr;

    public PlayerCamControl(Camera cam, Spatial target, Terrain terr) {
    this.cam = cam;
    this.terr = terr;
    target.addControl(this);
    }

    public void setSpatial(Spatial spatial) {

    }

    public void update(float tpf) {

     x = cam.getLocation().x;
     //float y = app.getCamera().getLocation().y;
     z = cam.getLocation().z;
     
     cam.setLocation(cam.getLocation().add(0, terr.getHeight(new Vector2f(x, z)) - 100, 0));                
    

    }

    public void render(RenderManager rm, ViewPort vp) {

    }

    public Control cloneForSpatial(Spatial spatial) {
    PlayerCamControl other = new PlayerCamControl(cam, spatial, terr);
    return other;
    }

    public void write(JmeExporter ex) throws IOException {

    }

    public void read(JmeImporter im) throws IOException {

    }
    }
    [/java]

Not very complicated, but I use it in addition to abies RtsCam and it provides the effect I was looking for.

4 Likes

Here comes updated version of RtsCam. It is now covering both Y-up and Z-up worlds in same package, plus adds terrain-height tracking and rotation+drag with mouse. It has also changed from being rootNode control to app state. You can use it with TerrainTest class from jme3 by adding following code in setupKeys

[java]
getStateManager().detach(getStateManager().getState(FlyCamAppState.class));
RtsCam rtsCam = new RtsCam(UpVector.Y_UP);

rtsCam.setCenter(new Vector3f(0, 0, 0));
rtsCam.setDistance(200);
rtsCam.setMaxSpeed(DoF.FWD, 100, 0.5f);
rtsCam.setMaxSpeed(DoF.SIDE, 100, 0.5f);
rtsCam.setMaxSpeed(DoF.DISTANCE, 100, 0.5f);

rtsCam.setHeightProvider(new HeightProvider() {
@Override
public float getHeight(Vector2f coord) {
return terrain.getHeight(coord)+10;
}
});

getStateManager().attach(rtsCam);
[/java]

And the RtsCam class itself.

[java]

import com.jme3.app.Application;
import com.jme3.app.state.AbstractAppState;
import com.jme3.app.state.AppStateManager;
import com.jme3.bounding.BoundingVolume;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseAxisTrigger;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.math.FastMath;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
/**
*
*
*/
public class RtsCam extends AbstractAppState {

/**
 * Degree of Freedom
 * 
 */
public enum DoF {
    SIDE,
    FWD,
    ROTATE,
    TILT,
    DISTANCE
}

public enum UpVector {

    Y_UP(Vector3f.UNIT_Y),
    Z_UP(Vector3f.UNIT_Z);

    final Vector3f upVector;

    UpVector(Vector3f upVector) {
        this.upVector = upVector;
    }

}

interface HeightProvider {
    public float getHeight(Vector2f coord);
}

private InputManager inputManager;
private Camera cam;
private BoundingVolume centerBounds;
private BoundingVolume cameraBounds;

private final int[] direction = new int[5];
private final float[] accelTime = new float[5];
private final float[] offsetMoves = new float[5];

private final float[] maxSpeedPerSecondOfAccell = new float[5];
private final float[] maxAccellPeriod = new float[5];
private final float[] minValue = new float[5];
private final float[] maxValue = new float[5];

private final Vector3f position = new Vector3f();
private final Vector3f center = new Vector3f();

private final InternalListener listener = new InternalListener();

private final UpVector up;

private final Vector3f oldPosition = new Vector3f();
private final Vector3f oldCenter = new Vector3f();
private float tilt = FastMath.PI / 4;
private float rot = -FastMath.PI;
private float distance = 10;

private HeightProvider heightProvider;


private boolean wheelEnabled = true;
private String mouseRotationButton = "BUTTON2";
private String mouseDragButton = "BUTTON3";

private boolean mouseRotation;
private boolean mouseDrag;

private static final int SIDE = DoF.SIDE.ordinal();
private static final int FWD = DoF.FWD.ordinal();
private static final int ROTATE = DoF.ROTATE.ordinal();
private static final int TILT = DoF.TILT.ordinal();
private static final int DISTANCE = DoF.DISTANCE.ordinal();

private static final float WHEEL_SPEED = 1f / 15;

private static String[] mappings = new String[] {
        "+SIDE", "+FWD", "+ROTATE", "+TILT", "+DISTANCE", "-SIDE", "-FWD", "-ROTATE", "-TILT", "-DISTANCE", "+WHEEL", "-WHEEL", "-MOUSEX", "+MOUSEX", "-MOUSEY", "+MOUSEY",
        "BUTTON1", "BUTTON2", "BUTTON3" };

public RtsCam(UpVector up) {
    this.up = up;

    setMinMaxValues(DoF.SIDE, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY);
    setMinMaxValues(DoF.FWD, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY);
    setMinMaxValues(DoF.ROTATE, Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY);
    setMinMaxValues(DoF.TILT, 0.2f, (float) (Math.PI / 2) - 0.001f);
    setMinMaxValues(DoF.DISTANCE, 2, Float.POSITIVE_INFINITY);

    setMaxSpeed(DoF.SIDE, 10f, 0.4f);
    setMaxSpeed(DoF.FWD, 10f, 0.4f);
    setMaxSpeed(DoF.ROTATE, 2f, 0.4f);
    setMaxSpeed(DoF.TILT, 1f, 0.4f);
    setMaxSpeed(DoF.DISTANCE, 15f, 0.4f);
}

@Override
public void initialize(AppStateManager stateManager, Application app) {
    super.initialize(stateManager, app);
    this.cam = app.getCamera();
    this.inputManager = app.getInputManager();
    registerWithInput(inputManager);
}

/**
 * Set the maximum speed for given direction of movement. For SIDE/FWD/DISTANCE it is in units/second, for ROTATE/TILT it is in radians/second
 * 
 * @param deg
 *            degree of freedom for which to set the maximum speed
 * @param maxSpd
 *            maximum speed of movement in that direction
 * @param accelTime
 *            amount of time which is need to accelerate to full speed in seconds (has to be bigger than zero, values over half second feel very sluggish). Defaults are 0.4
 *            seconds
 */
public void setMaxSpeed(DoF deg, float maxSpd, float accelTime) {
    maxSpeedPerSecondOfAccell[deg.ordinal()] = maxSpd / accelTime;
    maxAccellPeriod[deg.ordinal()] = accelTime;
}

/**
 * Set the terrain following logic for camera. Camera position will not get under the value returned by the heightProvider. Please add some extra buffering here,
 * so camera will not clip the actual terrain - for example
 * 
 * new HeightProvider() {
 *     &#064;Override
 *     public float getHeight(Vector2f coord) {
 *         return terrain.getHeight(coord) + 10;
 *     }
 * }
 * 
 * @param heightProvider
 */
public void setHeightProvider(HeightProvider heightProvider) {
    this.heightProvider = heightProvider;
}

/**
 * Enables/disabled wheel-zoom behaviour
 * Default is enabled
 */
public void setWheelEnabled(boolean wheelEnabled) {
    this.wheelEnabled = wheelEnabled;
}

private String mouseButtonName(int button) {
    switch (button) {
        case MouseInput.BUTTON_LEFT:
            return "BUTTON1";

        case MouseInput.BUTTON_MIDDLE:
            return "BUTTON2";

        case MouseInput.BUTTON_RIGHT:
            return "BUTTON3";
        default:
            return null;
    }
}

/**
 * Use MouseInput.BUTTON_ constants to indicate which buttons should be used for rotation and dragging with mouse
 * Defaults are BUTTON_MIDDLE for rotation and BUTTON_RIGHT for dragging
 * Use -1 to disable given functionality
 * 
 * @param rotationButton
 *            button to hold to control TILT/ROTATION with mouse movements
 * @param dragButton
 *            button to hold to drag camera position around
 */
public void setMouseDragging(int rotationButton, int dragButton) {
    mouseDragButton = mouseButtonName(dragButton);
    mouseRotationButton = mouseButtonName(rotationButton);
}

public void update(final float tpf) {

    for (int i = 0; i &lt; direction.length; i++) {
        int dir = direction[i];
        switch (dir) {
            case -1:
                accelTime[i] = clamp(-maxAccellPeriod[i], accelTime[i] - tpf, accelTime[i]);
                break;
            case 0:
                if (accelTime[i] != 0) {
                    double oldSpeed = accelTime[i];
                    if (accelTime[i] &gt; 0) {
                        accelTime[i] -= tpf;
                    } else {
                        accelTime[i] += tpf;
                    }
                    if (oldSpeed * accelTime[i] &lt; 0) {
                        accelTime[i] = 0;
                    }
                }
                break;
            case 1:
                accelTime[i] = clamp(accelTime[i], accelTime[i] + tpf, maxAccellPeriod[i]);
                break;
        }

    }

    float distanceChange = maxSpeedPerSecondOfAccell[DISTANCE] * accelTime[DISTANCE] * tpf;
    distance += distanceChange;
    distance += offsetMoves[DISTANCE];

    tilt += maxSpeedPerSecondOfAccell[TILT] * accelTime[TILT] * tpf + offsetMoves[TILT];
    rot += maxSpeedPerSecondOfAccell[ROTATE] * accelTime[ROTATE] * tpf + offsetMoves[ROTATE];

    distance = clamp(minValue[DISTANCE], distance, maxValue[DISTANCE]);
    rot = clamp(minValue[ROTATE], rot, maxValue[ROTATE]);
    tilt = clamp(minValue[TILT], tilt, maxValue[TILT]);

    double offX = maxSpeedPerSecondOfAccell[SIDE] * accelTime[SIDE] * tpf + offsetMoves[SIDE];
    double offY = maxSpeedPerSecondOfAccell[FWD] * accelTime[FWD] * tpf + offsetMoves[FWD];

    if (up == UpVector.Y_UP) {
        center.x += offX * Math.cos(-rot) + offY * Math.sin(rot);
        center.z += offX * Math.sin(-rot) + offY * Math.cos(rot);
    } else {
        center.x += offX * Math.cos(-rot) + offY * Math.sin(rot);
        center.y += offX * Math.sin(-rot) + offY * Math.cos(rot);
    }

    if (centerBounds != null) {
        //TODO: clamp center to bounds
    }

    if (up == UpVector.Y_UP) {
        position.x = center.x + (float) (distance * Math.cos(tilt) * Math.sin(rot));
        position.y = center.y + (float) (distance * Math.sin(tilt));
        position.z = center.z + (float) (distance * Math.cos(tilt) * Math.cos(rot));
        if (heightProvider != null) {
            float h = heightProvider.getHeight(new Vector2f(position.x, position.z));
            if (position.y &lt; h) {
                position.y = h;
            }
        }
    } else {
        position.x = center.x + (float) (distance * Math.cos(tilt) * Math.sin(rot));
        position.y = center.y + (float) (distance * Math.cos(tilt) * Math.cos(rot));
        position.z = center.z + (float) (distance * Math.sin(tilt));
        if (heightProvider != null) {
            float h = heightProvider.getHeight(new Vector2f(position.x, position.y));
            if (position.z &lt; h) {
                position.z = h;
            }
        }
    }

    for (int i = 0; i &lt; offsetMoves.length; i++) {
        offsetMoves[i] = 0;
    }

    if (oldPosition.equals(position) &amp;&amp; oldCenter.equals(center)) {
        return;
    }
    
    if (cameraBounds != null) {
        //TODO: clamp position to bounds
    }

    cam.setLocation(position);
    cam.lookAt(center, up.upVector);

    oldPosition.set(position);
    oldCenter.set(center);

}

private static float clamp(float min, float value, float max) {
    if (value &lt; min) {
        return min;
    } else if (value &gt; max) {
        return max;
    } else {
        return value;
    }
}

public float getMaxSpeed(DoF dg) {
    return maxSpeedPerSecondOfAccell[dg.ordinal()];
}

public float getMinValue(DoF dg) {
    return minValue[dg.ordinal()];
}

public float getMaxValue(DoF dg) {
    return maxValue[dg.ordinal()];
}

/**
 * SIDE and FWD min/max values are ignored
 * 
 * @param dg
 * @param min
 * @param max
 */
public void setMinMaxValues(DoF dg, float min, float max) {
    minValue[dg.ordinal()] = min;
    maxValue[dg.ordinal()] = max;
}

public Vector3f getPosition() {
    return position;
}

public void setCenter(Vector3f center) {
    this.center.set(center);
}

public Vector3f getCenter() {
    return center;
}

public float getDistance() {
    return distance;
}

public float getRot() {
    return rot;
}

public float getTilt() {
    return tilt;
}

public void setDistance(float distance) {
    this.distance = distance;
}

public void setRot(float rot) {
    this.rot = rot;
}

public void setTilt(float tilt) {
    this.tilt = tilt;
}

public Camera getCamera() {
    return cam;
}

private void registerWithInput(InputManager inputManager) {
    this.inputManager = inputManager;

    if (up == UpVector.Y_UP) {
        inputManager.addMapping("-SIDE", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("+SIDE", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("+ROTATE", new KeyTrigger(KeyInput.KEY_Q));
        inputManager.addMapping("-ROTATE", new KeyTrigger(KeyInput.KEY_E));
    } else {
        inputManager.addMapping("+SIDE", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("-SIDE", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("-ROTATE", new KeyTrigger(KeyInput.KEY_Q));
        inputManager.addMapping("+ROTATE", new KeyTrigger(KeyInput.KEY_E));
    }

    inputManager.addMapping("+FWD", new KeyTrigger(KeyInput.KEY_S));
    inputManager.addMapping("-FWD", new KeyTrigger(KeyInput.KEY_W));

    inputManager.addMapping("+TILT", new KeyTrigger(KeyInput.KEY_R));
    inputManager.addMapping("-TILT", new KeyTrigger(KeyInput.KEY_F));
    inputManager.addMapping("-DISTANCE", new KeyTrigger(KeyInput.KEY_Z));
    inputManager.addMapping("+DISTANCE", new KeyTrigger(KeyInput.KEY_X));

    inputManager.addMapping("-WHEEL", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
    inputManager.addMapping("+WHEEL", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));

    inputManager.addMapping("-MOUSEX", new MouseAxisTrigger(MouseInput.AXIS_X, false));
    inputManager.addMapping("+MOUSEX", new MouseAxisTrigger(MouseInput.AXIS_X, true));
    inputManager.addMapping("-MOUSEY", new MouseAxisTrigger(MouseInput.AXIS_Y, false));
    inputManager.addMapping("+MOUSEY", new MouseAxisTrigger(MouseInput.AXIS_Y, true));

    inputManager.addMapping("BUTTON1", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    inputManager.addMapping("BUTTON2", new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE));
    inputManager.addMapping("BUTTON3", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));

    inputManager.addListener(listener, mappings);
}

@Override
public void cleanup() {
    super.cleanup();

    for (String mapping : mappings) {
        if (inputManager.hasMapping(mapping)) {
            inputManager.deleteMapping(mapping);
        }
    }
    inputManager.removeListener(listener);
}

private class InternalListener implements ActionListener, AnalogListener {

    public void onAction(String name, boolean isPressed, float tpf) {
        if (!isEnabled()) {
            return;
        }

        int press = isPressed ? 1 : 0;

        if (name.contains("WHEEL") || name.contains("MOUSE")) {
            return;
        }

        if (name.equals(mouseRotationButton)) {
            mouseRotation = isPressed;
            inputManager.setCursorVisible(!mouseDrag &amp;&amp; !mouseRotation);
            return;
        }

        if (name.equals(mouseDragButton)) {
            mouseDrag = isPressed;
            inputManager.setCursorVisible(!mouseDrag &amp;&amp; !mouseRotation);
            return;
        }

        char sign = name.charAt(0);
        if (sign == '-') {
            press = -press;
        } else if (sign != '+') {
            return;
        }

        DoF deg = DoF.valueOf(name.substring(1));
        direction[deg.ordinal()] = press;
    }

    @Override
    public void onAnalog(String name, float value, float tpf) {
        if (!isEnabled()) {
            return;
        }

        if (!name.contains("WHEEL") &amp;&amp; !name.contains("MOUSE")) {
            return;
        }

        char sign = name.charAt(0);
        if (sign == '-') {
            value = -value;
        } else if (sign != '+') {
            return;
        }

        if (name.contains("WHEEL")) {
            if (!wheelEnabled) {
                return;
            }
            float speed = maxSpeedPerSecondOfAccell[DISTANCE] * maxAccellPeriod[DISTANCE] * WHEEL_SPEED;
            offsetMoves[DISTANCE] += value * speed;
        } else if (name.contains("MOUSE")) {
            if (mouseRotation) {
                int direction;
                if (name.endsWith("X")) {
                    direction = ROTATE;
                    if ( up == UpVector.Z_UP ) {
                        value = -value;
                    }
                } else {
                    direction = TILT;
                }
                offsetMoves[direction] += value;
            } else if (mouseDrag) {
                int direction;
                if (name.endsWith("X")) {
                    direction = SIDE;
                    if ( up == UpVector.Z_UP ) {
                        value = -value;
                    }
                } else {
                    direction = FWD;
                    value = -value;
                }
                offsetMoves[direction] += value * maxSpeedPerSecondOfAccell[direction] * maxAccellPeriod[direction];
            }
        }

    }
}

}
[/java]

Bounds are not working yet, as there seems to be no easy way of clamping a point to a bounding volume - will implement it if there is a requirement from anybody.

5 Likes

Thanks for sharing :slight_smile:

Hi and first of all i want to thank you so much for providing this great work !

I’m using this camera in my first game and i changed some parts and wanted to provide you guys those.


so first thing

cos(-rot) = cos(rot)
sin(-rot) = - sin(rot)

this saves some calculations on update:

[java]
double sinRot = Math.sin(rot);
double cosRot = Math.cos(rot);
double cosTilt = Math.cos(tilt);
double sinTilt = Math.sin(tilt);

    center.x += offX * cosRot + offY * sinRot;
    if (up == UpVector.Y_UP) {
        center.z = (float) (center.z - offX * sinRot + offY * cosRot);
    } else {
        center.y = (float) (center.y - offX * sinRot + offY * cosRot);
    }

if (centerBounds != null) {
//TODO: clamp center to bounds
}

    position.x = center.x + (float) (distance * cosTilt * sinRot);
    if (up == UpVector.Y_UP) {
        position.y = center.y + (float) (distance * sinTilt);
        position.z = center.z + (float) (distance * cosTilt * cosRot);
        if (heightProvider != null) {
            float h = heightProvider.getHeight(coordCheck.set(position.x, position.z));
            if (position.y &lt; h) {
                position.y = h;
            }
        }
    } else {
        position.y = center.y + (float) (distance * cosTilt * cosRot);
        position.z = center.z + (float) (distance * sinTilt);
        if (heightProvider != null) {
            float h = heightProvider.getHeight(coordCheck.set(position.x, position.y));
            if (position.y &lt; h) {
                position.y = h;
            }
        }
    }

[/java]

aswell i would change creating new Vector2f() on height check to:

[java]

if (heightProvider != null) {
coordCheck.set(position.x, position.y);
float h = heightProvider.getHeight(coordCheck);
if (position.y < h) {
position.y = h;
}
}
[/java]

to use this you need to change another part aswell:

[java]
public void setHeightProvider(HeightProvider heightProvider) {
this.heightProvider = heightProvider;
if (coordCheck == null) { //If we haven’t used a height Provider before, than we have to set a Vector for coords
coordCheck = new Vector2f();
}
}
[/java]

and maybe another change in update method. But this works only if u want to allow unlimited rotation. So if someone allows 360° Degree Rotation and wants to prevent from running to +/- infinity

change

[java]
rot += maxSpeedPerSecondOfAccell[ROTATE] * accelTime[ROTATE] * tpf + offsetMoves[ROTATE];
rot = clamp(minValue[ROTATE],rot, maxValue[ROTATE]);
[/java]

to
[java]
rot = (rot + maxSpeedPerSecondOfAccell[ROTATE] * accelTime[ROTATE] * tpf + offsetMoves[ROTATE]) % FastMath.TWO_PI;
[/java]

Replace the first two lines with the third line and u have a rotation between [0 , TWO_PI] plus u save clamp call

3 Likes

Thanks @b00n - I have applied these changes to repository - with exception of rotation clamp change. While most probably nobody will clamp rotation in real life, I kind of like the symmetry between degrees of freedom and I don’t think that clamp will be a killer here.
I hope I have not confused any of the sin/cos changes - I’m testing this class only in Z_UP configuration right now.

1 Like

I’m using it with Y-UP and it works perfect =).

About the rotation:

The modulo (%) FastMath.TWO_PI produces the same result as [Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY)]

so if you turn right the rotation output would be

30°
60°
90°
120° (…150°,180°,210°,240°,270°,300°)
330°
360° which is the same as 0°,
so if you continue turning
next will be 30° … 60° , … 180°

instead of 390° which would be the same as 30°

and for left rotations it would be (Step=-30°)
0
330°
300°
.
.
.
180°
.
.
.
30°

I did this change, to prevent from getting strange behavior if someone just keeps turning aroung for some fun ^^ and reaches infinity

I think rotating 200 times is enough to start losing precision in the decimal part if you don’t roll the angle over. Probably not noticeable as jitter until around 2000 or 20000 times around. But still, a mod rollover is easy as suggested.

…just pointing out that you will see problems long before infinity.

After giving some people the camera to test, they all said that the acceleration and deceleration on FWD and SIDE is feeling not good… Trying to fix that problem with setting the maxspeed to different values is irrelvant, because setting them to lower or higher values results in “The camera is moving to slow” or “The camera moves to fast”/“The camera feel very sluggish” ) .So after some brain fights ^^. I would suggest using this code for acceleration
"
[java] public final void setMaxSpeed(DegreeOfFreedom deg, float maxSpd, float accelTime) {
maxSpeedPerSecondOfAccell[deg.ordinal()] = maxSpd / accelTime;
maxAccellPeriod[deg.ordinal()] = accelTime;
}[/java]

and a similar thing for deceleration. I just did a little try with

[java] public void update(final float tpf) {
for (int i = 0; i < direction.length; i++) {
int dir = direction[i];
switch (dir) {
case -1:
accelTime[i] = clamp(accelTime[i] - tpf, -maxAccellPeriod[i], accelTime[i]);
break;
case 0:
if (accelTime[i] != 0) {
if (i == FWD || i == SIDE) {//This is added
accelTime[i] = 0;
} else {// Do the old stuff with rotate/tile, …
double oldSpeed = accelTime[i];
if (accelTime[i] > 0) {
accelTime[i] -= tpf;
} else {
accelTime[i] += tpf;
}
if (oldSpeed * accelTime[i] < 0) {
accelTime[i] = 0;
}
}
}
break;
case 1:
accelTime[i] = clamp(accelTime[i] + tpf, accelTime[i], maxAccellPeriod[i]);
break;
}

    }[/java] 

now after stop pressing FWD or SIDE the camera stops immediately. this is the part where deceleration should come in.

So what do you think ?

P.s. this forums needs a post-preview ^^

Another thing :D.

I added a jump/goto command to my game that allows me to move the center of the camera. Using this command to goto Vector3f(512000 , 0, 512000) works, but the terrain and water are moving really strange… They start to flicker and it seems that the update loop is triggered more than once per frame. Does anyone think this is a camera problem or does anyone know the problem causing this?

greetz

here a video of this problem
[video]https://vidd.me/Ve7[/video]

Isn’t putting acceltime to very small value (like 0.001) giving the same effect you are trying to achieve? It should reach max speed in one frame and stop also in one frame.

Regarding going to 50000 coordinates - floats are starting to break up around that. You generally cannot use opengl easily with such coordinates, unless your objects are thousand units big as well. I don’t think it is particularly related to camera - you will start having problems with a lot of other functionalities there. Certainly, trig functions on floats here are not helping the situation - but think about recentering the world on your avatar once per some time.

1 Like

Thank you for your fast response =).

@abies said: Isn't putting acceltime to very small value (like 0.001) giving the same effect you are trying to achieve? It should reach max speed in one frame and stop also in one frame..

That’s the problem, what do i do if i want to move just a little piece ? For example doing
[java]setMaxSpeed(DegreeOfFreedom.FWD, 2048f, 5.f);[/java] this is pretty good to move along the world, but stopping will make you crazy ^^
and doing this
[java]setMaxSpeed(DegreeOfFreedom.FWD, 2048f, .01f);[/java] will stop you from looking at pretty flowers, because you flew away.

Please check out latest version from https://subversion.assembla.com/svn/vmat/vmat/trunk/src/main/java/net/virtualmat/jme3/RtsCam.java.

I have added extra parameter to setMaxSpeed, where you can set explicit deceleration time. You can do thing like setMaxSpeed(FWD,2048,5,0.1f).

Said that, do not try to use it with worlds where center is going outside of 10000.0 coords or so.You will get hurt by precision of float.

3 Likes

Hi folks,
I made an additional tilt clamp based up on distance which should be between minValue[DISTANCE] and maxValue[DISTANCE].

The additonal code goes into the update method:

private static final float MIN_TILT_AT_MIN_DISTANCE = 0.66f; //Minimum Tilt if minValue[DISTANCE] is reached.

public void update(final float tpf) {

float distancedBasedMinTilt = FastMath.interpolateLinear(step(minValue[DISTANCE], maxValue[DISTANCE], distance), MIN_TILT_AT_MIN_DISTANCE, MIN_TILT);
    
 tilt = clamp(tilt + (maxSpeedPerSecondOfAccell[TILT] * accelTime[TILT] * tpf + offsetMoves[TILT]), distancedBasedMinTilt, maxValue[TILT]);

here the step functions

public float step(float start, float end, float valueBetweenStartAndEnd) {
            return step((valueBetweenStartAndEnd - start) / (end - start));
        }

 public float step(float step) {
            step = FastMath.clamp(step, 0.0f, 1.0f);
            return step * step * step * (step * (step * 6 - 15) + 10);
        }

Benefits of this change:
The camera will behave more flexible or more smoothly between distance scrolling. This works only if a the angle for MIN_TILT_AT_MIN_DISTANCE is set wider than minValue[TILT]. If you do this, you achieve that the camera is able to allow a look around near ground and be more strict on high distance.

That dam’n float precision.
As others said: “90 percent of people out there aren’t aware of float”.
The “float weirdness” as I call it, adds more confusion to the low precision (even with double).
Still, I would like to have double or long double in 3D graphics, but only few gfx cards make it.
So you always have to do all sorts of nasty tricks -
or the “fraction of an inch” precision means that you can walk a mile or so without problems -
ridiculous (in my eyes)… :chimpanzee_closedlaugh: