# VesselMars 2020 Rover and Mars Helicopter Scout

#### kuddel

##### Donator
Donator
if you mean by applying [Numpad+] rsp. [Numpad+], I can only see a difference in acceleration, not in speed.

But based on your code, that's to be expected.
You are assuming that the number of calls (per second) to clbkPostStep is equal for both situations....It is not!
If you want a predictable acceleration for your wheelspin member you have to take the sim-time to calculate that (either simt, simdt or mjd).

As a demonstration, just try to gain speed [Num+] at accelerated time (x10) and you'll see a huge difference!

#### gattispilot

So I have a tragetspeed that incrteases whenyou press +
then
if (FORWARDgear == 1) {
wheelspin = wheelspin + (targetSpeed / 400);
if (wheelspin > 1)wheelspin = 0;
}

double dv = 0.001;
LASTGEAR = 1;
targetSpeed = (targetSpeed + dv);
if (targetSpeed > MAXSPEED) targetSpeed = MAXSPEED;

So what do I need to change?

wheelspin is the animation rate for the wheels

#### kuddel

##### Donator
Donator
• increase speed as long as [+] is pressed (until MAXSPEED),
• decrease speed as long as [-] is pressed (until MAXSPEED in backwards direction)
• and slowly decrease speed towards zero if no key is pressed.
Then you should set a member in clbkConsumeDirectKey(...), that indicates the current "direction of acceleration".
Something like
C++:
// enum { NEUTRAL, FORWARD, REVERSE } eAccDir;

if (KEYDOWN(kstate, OAPI_KEY_SUBTRACT)) { eAccDir = REVERSE; }
if (KEYDOWN(kstate, OAPI_KEY_ADD)     ) { eAccDir = FORWARD; }
else                                    { eAccDir = NEUTRAL; }
and handle the current speed in clbkPostStep(...) by calculating it with respect to the time passed (simdt):
C++:
const double MAXSPEED = 2.78;                  // m/s
const double ROVER_ACCELERATION_FWD_REV = 0.5; // m/s^2
const double ROVER_ACCELERATION_NEUTRAL = 0.1; // m/s^2
double direction;

switch(eAccDir) {
case NEUTRAL:
if (abs(currentSpeed) > 0.001) { // deadband (don't know if really needed)
direction = (currentSpeed < 0) ? +1.0 : -1.0; // "accelerate" towards zero (decelerate ;) )
} else {
direction = 0;
}
break;
case FORWARD:
direction = +1.0;
break;
case REVERSE:
direction = -1.0;
break;
}
// Calculate new speed
currentSpeed += direction * simdt * ROVER_ACCELERATION_FWD_REV;
// limit speed to +- max
currentSpeed = max(-MAXSPEED, min(currentSpeed, MAXSPEED));

if (eAccDir == NEUTRAL && abs(currentSpeed) < 0.001) {
currentSpeed = 0;
}
// ...
// apply wheelspin, set animations, ...
Let's check the units:
currentSpeed += direction * simdt * ROVER_ACCELERATION_FWD_REV;
is:
currentSpeed [m/s] = currentSpeed [m/s] + direction x simdt [m/s] x ROVER_ACCELERATION_FWD_REV [m/s]
[m/s] = [m/s] + ( [dimensionless] x x [m/s^2] )
[m/s] = [m/s]

This is a "witeboard code", so it might not compile without any warnings
It might even be wrong, as I haven't tested it.

Last edited:

#### gattispilot

Thanks. So that seems to work. So on the movement. I had if case forward then move forward. but if you are in neutral the movement needs to be slowing down and in the reverse of the current motion.

So if moving forward and you release he key the rover should slowly come to a stop

I only have it moving when forward or reverse.

So
in the hud
Code:
sprintf(abuf, "Current Ground Speed %0.2f", currentSpeed);

on forward it goes up. maybe too fast? but in reverse no speed is indicated.
so I did a video to see the difference
normal graphics:

d3d9

So I this for the wheel spin.

i3 = GetControlSurfaceLevel(AIRCTRL_RUDDER);
if (eAccDir == FORWARD) {
wheelspin = wheelspin + (currentSpeed / 100);
if (wheelspin > 1)wheelspin = 0;
}

if (eAccDir == REVERSE) {
wheelspin = wheelspin - (currentSpeed /100);
if (wheelspin < 0)wheelspin = 1;

}
if (eAccDir == NEUTRAL){
wheelspin = wheelspin - (currentSpeed / 100);
if (wheelspin < 0)wheelspin = 1;
}

in normal graphics it is good but in d3d9 it is much faster.

Last edited:

#### kuddel

##### Donator
Donator
in normal graphics it is good but in d3d9 it is much faster.

Should not be the case if you only calculate your currentSpeed in clbkPostStep(...) with respect to the time passed (simdt)!

As the ROVER_ACCELERATION_FWD_REV is in m/s^2, it can only increase/decrease by 0.5 m/s every (sim-)second[1].

Therefore it takes 5.56 seconds (sim-seconds that is) to go from 0 m/s to 2.78 m/s; independent of the number of times clbkPostStep or clbkConsumeDirectKey is called during that period[2].

That is only true, if it is really calculated that way of course!

---

[1] The actual value (0.5 m/s) of ROVER_ACCELERATION_FWD_REV is of course just a "random" value taken by me to have a value. The real Mars Rover 2020 acceleration might be different.
[2] the difference in "how fast it increases" you experience has only to do with the fact that D3D9Client is faster (produces more frames per second) and therefore lbkPostStep rsp. clbkConsumeDirectKey are called much more frequent per (realtime) second.

Last edited:

#### kuddel

##### Donator
Donator
So if the frame rate is faster in d3d9 wouldn't the animation wheel spin also?
Not if you program the animations correct (also based on simdt).
You cannot know the frame rate at any computer nowadays[*], usually people try to buy the fastest possible hardware, so what runs at 100 FPS on my machine will run at 400 FPS on someone else's machine or at 30 FPS on another one.

[*] On the C64 for example this was possible as every machine was identical. They all ran at either 0.9852486 MHz (PAL) or 1.0227273 MHz (NTSC).

#### kuddel

##### Donator
Donator
Hello again,

I've took a look at the (new) code and had to do some modifications to make the wheel animations work as they should.
See the comments in the attached code (Marked with "kuddel").
The actual movement of the rover currently only works in forward direction, but as I don't really understand how you planned to accomplish that, I left that up to you.
I assume that the MoveAround() method has something to be looked at

The main issue was that your wheelspin member is used as an animation-state. Therefore it has to be limited between 0.0 and 1.0!
So a little "modulo" operation for that was needed, that creates the wrap-around at 1 (in up-counting mode) and 0 (in down-counting mode)

C++:
    // current speed to wheelspin (can become negative or greater than 1)
wheelspin = wheelspin + (currentSpeed / 400);

// kuddel: 'wheelspin' can only be positive in the range [0-1]!
//         As it's an animation-state!
//

// Floating point "modulo" (0...1)
if (wheelspin > 1.0) { wheelspin = 0.0; } // flip over at top range
else if (wheelspin < 0.0) { wheelspin = 1.0; } // flip over at bottom range

Some code was already there to account for under-run at zero or over-run at 1, but it made the resulting value for SetAnimation(anim, state) stick at 1 rsp. 0.
My code provides the needed "sawtooth" form.

The wrap around should ideally be more like:
C++:
    // Floating point "modulo" (0...1)
while (wheelspin > 1.0) { wheelspin -= 1.0; } // flip over at top range
while (wheelspin < 0.0) { wheelspin += 1.0; } // flip over at bottom range
Again, this does not have to be your final code! It's just my way to help you get on the right track

#### Attachments

• marsrovercode1.1(kuddel changes).zip
13.6 KB · Views: 3

#### gattispilot

Thanks. So in normal graphics it seems good. But id d3d9 the wheel spin rate is fast. When you go neutral they really spin. So Move around. Yes basically it moves the vessel so far past on speed and heading.

#### kuddel

##### Donator
Donator
Oh, I think I did not take my own advice :doh:
The wheelspin has to be time dependent too!
So it's something lke this
C++:
    // ...

// current speed to wheelspin (can become negative or greater than 1)
wheelspin += simdt * currentSpeed / 3.5; // The "3.5" has unit [m] and is wheel-circumference dependent (and how much MoveAround() moves around ;) )

// 'wheelspin' can only be positive in the range [0-1]!
// As it's an animation-state!
while (wheelspin > 1.0) { wheelspin -= 1.0; } // flip over at op range
while (wheelspin < 0.0) { wheelspin += 1.0; } // flip over at bottom range

// ....

Ah... and while I was on it, attached my changes to MoveAround (wich can be simplified a lot as the calculations are always "+=", independent of eAccDir ...
I've also took the liberty to derive MSL_ROVER from VESSEL4 (to be more modern )

#### Attachments

• marsrovercode1.2(kuddel changes).zip
13.4 KB · Views: 4
Last edited:

#### gattispilot

Thanks. So now weird stuff. I press + and the wheel spin and just spin around. I need to look at the move around. So as + is applied the
(eAccDir == FORWARD) and the vessel moves forward. but when the + isd not applied. (eAccDir == NEUTRAL)) speed slows down but the vessel is stoped it should slow down also

#### kuddel

##### Donator
Donator
A quick change in MoveAround makes the thing move
Forward, backwards and a little siteways (when wheels were turned by 5 and 6 key), but the details work are up to you.

#### Attachments

• marsrovercode1.3(kuddel changes).zip
11.6 KB · Views: 4

#### gattispilot

Thanks. Weird. so it moves great except for to move forward the wheels need to be at 90 degrees

Oh. So the rover can steer 2 ways. 1/3 like a rudder and then 5/6 rotates the 4 wheels for sidewise movement

Code:
if (hChildVessel == GetHandle())
{
VECTOR3 pves_x;
VECTOR3 glob_x;
VECTOR3 pves_pos;
v->GetGlobalPos(pves_pos);
GlobalRot(_V(1, 0, 0), glob_x);
glob_x = glob_x + pves_pos;
v->Global2Local(glob_x, pves_x);

v->GetAttachmentParams(hParentAttach, att_pos, att_dir, att_rot);
att_pos.x = att_pos.x + pves_x.x*delta_pos;
att_pos.y = att_pos.y + pves_x.y*delta_pos;
att_pos.z = att_pos.z + pves_x.z*delta_pos;
v->SetAttachmentParams(hParentAttach, att_pos, att_dir, att_rot);
return;
}

This is code to move the eva guy. I noticed in normal graphics the speed is good. But in D3d9 the speed is slow like you need to do x10 time

Last edited:

#### gattispilot

Thanks. So a couple of things. So the rover had 2 steering systems. 1 where the front wheels would turn like a car and the the heading and vessel would turn.

second 4 wheel turning.

So now by pressing 1/3 it turns the wheels so the rover goes in the direction of the wheels. BUT the heading doesn't turn.

So maybe go back to 5/6 for wheels rotate. 1/3 rotates surface heading?

#### gattispilot

So how to get the keys to work at the same time? I mean press + and 4 to turn the wheels left while you are moving forward?
Code:
int MSL_ROVER::clbkConsumeDirectKey(char* kstate) {
//new
if (KEYDOWN(kstate, OAPI_KEY_SUBTRACT)) {
eAccDir = REVERSE;
return 0;
}
eAccDir = FORWARD;
return 0;
}

if (KEYDOWN(kstate, OAPI_KEY_NUMPAD4)) { // turns wheels left
ROTATELEFT();
return 0;
}
if (KEYDOWN(kstate, OAPI_KEY_NUMPAD6)) { // turns wheels left
ROTATERIGHT();
return 0;
}

else {
eAccDir = NEUTRAL;
return 0;

}
if (KEYDOWN(kstate, OAPI_KEY_NUMPAD1)) { // turns wheels left
TURNDir = LEFT;
return 0;
}
if (KEYDOWN(kstate, OAPI_KEY_NUMPAD3)) { // turns wheels left
TURNDir = RIGHT;;
return 0;
}
else {
TURNDir = STRAIGHT;
return 0;

}
}

#### kuddel

##### Donator
Donator
Look at your code! Or single-step through it
Hint: look for "return" statements

That should give you an idea of the program flow.

Something like
C++:
{
// ...
int keyConsumed = 0;
eAccDir = FORWARD;
keyConsumed = 1;
}
else if (KEYDOWN(kstate, OAPI_KEY_SUBTRACT)) { // note the "else", 'cause these two are exclusive
eAccDir = REVERSE;
keyConsumed = 1;
)

ROTATELEFT();
// ...
keyConsumed = 1;
)
// ... etc. pp.

return keyConsumed; // one single return "path"
}

#### asbjos

##### tuanibrO
Instead of using ConsumeKey on the numpad keys, I recommend using dummy RCS thrusters for the same effect, for the reason specified here: https://www.orbiter-forum.com/threads/solarsailex.16301/post-252495
TL;DR: many (some / just me?) use computers without numeric keys, and will therefore have no possibility of controlling the rover.

#### gattispilot

Instead of using ConsumeKey on the numpad keys, I recommend using dummy RCS thrusters for the same effect, for the reason specified here: https://www.orbiter-forum.com/threads/solarsailex.16301/post-252495
TL;DR: many (some / just me?) use computers without numeric keys, and will therefore have no possibility of controlling the rover.
I don't understand. Are you saying to use thruster or set up like dummy thrusters.

Code:
int keyConsumed = 0;
eAccDir = FORWARD;
keyConsumed = 1;
}
else if (KEYDOWN(kstate, OAPI_KEY_SUBTRACT)) { // note the "else", 'cause these two are exclusive
eAccDir = REVERSE;
keyConsumed = 1;
}
else eAccDir = NEUTRAL;

if (KEYDOWN(kstate, OAPI_KEY_NUMPAD1)) { // turns wheels left
TURNDir = LEFT;
keyConsumed = 1;
}
if (KEYDOWN(kstate, OAPI_KEY_NUMPAD3)) { // turns wheels left
TURNDir = RIGHT;;
keyConsumed = 1;
}
else TURNDir = STRAIGHT;

//rotate 4 wheels
ROTATELEFT();
keyConsumed = 1;
}
ROTATERIGHT();
keyConsumed = 1;
}
return keyConsumed; // one single return "path"

}

So this works except when I press 1 on the keypad nothing. 3 works.
Ran in debug mode and set breaks but they didn't hit

Might need to adjust the heading turn. In d3d9 good but normal graphics slow.

#### asbjos

##### tuanibrO
I don't understand. Are you saying to use thruster or set up like dummy thrusters.

If you already have defined other thrusters (using CreateThrusterGroup(yourThrusterHandle, THGROUP_ATT_...)), then you can ignore the rest.

If not:
When you use ConsumeKey, you look specifically for a numpad press. I don't have a numpad, so I've remapped the attitude keys to WASD+QE, using keymap.cfg in ORBITER_ROOT. Therefore, the ConsumeKey will not notice my key presses, as it only looks for the numpad actions.

A (in my view) better solution, is then to create attitude thrusters with no thrust. With your ConsumeKey numpad detection, you are looking for such a keypress anyway.

The way to do this is to create the attitude thrusters, which then have to be connected to a propellant source, and then mapped to the default thrustergroups (THGROUP_ATT_PITCHUP, _PITCHDOWN, _YAWLEFT, ...).

If you have a constant vessel for the rover (i.e. it's always in the rover state), you declare the thrusters in clbkSetClassCaps. If you have a vessel that is first the rocket crane, then switches state to the rover, then you'll have to declare the thrusters in clbkPreStep/clbkPostStep when you do the state switch (but only do this once, i.e. not every frame afterwards).

C++:
THRUSTER_HANDLE attitudeDummy[6]; // 6, as we have pitch, yaw, bank, in both directions
PROPELLANT_HANDLE smallDummyPropellant = CreatePropellantResource(0.01); // small, non-zero propellant mass

// the position and direction vectors don't matter, as we have no thrust anyway.
attitudeDummy[0] = CreateThruster(_V(0, 0, 0), _V(0, 1, 0), 0,    smallDummyPropellant, 0, 0);
attitudeDummy[1] = CreateThruster(_V(0, 0, 0), _V(0, -1, 0), 0,    smallDummyPropellant, 0, 0);
attitudeDummy[2] = CreateThruster(_V(0, 0, 0), _V(1, 0, 0), 0,    smallDummyPropellant, 0, 0);
attitudeDummy[3] = CreateThruster(_V(0, 0, 0), _V(-1, 0, 0), 0,    smallDummyPropellant, 0, 0);
attitudeDummy[4] = CreateThruster(_V(0, 0, 0), _V(0, 1, 0), 0,    smallDummyPropellant, 0, 0);
attitudeDummy[5] = CreateThruster(_V(0, 0, 0), _V(0, -1, 0), 0,    smallDummyPropellant, 0, 0);

// assign to the default thruster groups. Note that you could also instead assign to the translational groups (THGROUP_ATT_LEFT, THGROUP_ATT_RIGHT, ...). You decide
CreateThrusterGroup(&attitudeDummy[0], 1, THGROUP_ATT_PITCHUP);
CreateThrusterGroup(&attitudeDummy[1], 1, THGROUP_ATT_PITCHDOWN);
CreateThrusterGroup(&attitudeDummy[2], 1, THGROUP_ATT_YAWLEFT);
CreateThrusterGroup(&attitudeDummy[3], 1, THGROUP_ATT_YAWRIGHT);
CreateThrusterGroup(&attitudeDummy[4], 1, THGROUP_ATT_BANKLEFT);
CreateThrusterGroup(&attitudeDummy[5], 1, THGROUP_ATT_BANKRIGHT);

Then, in clbkPreStep or clbkPostStep, you must every frame check the thrust level set by the user. This can have been set by the regular numpad keys, other keys set by the keymap file, the Remote Vessel Control dialog box, a joystick. Doesn't matter! You will anyway always get the input directly through the thruster level readout by Orbiter.

C++:
if (GetThrusterGroupLevel(THGROUP_ATT_PITCHUP) != 0.0) eAccDir = FORWARD;
else if (GetThrusterGroupLevel(THGROUP_ATT_PITCHDOWN) != 0.0) eAccDir = REVERSE;

if (GetThrusterGroupLevel(THGROUP_ATT_YAWLEFT) != 0.0) TURNDir = LEFT;
else if (GetThrusterGroupLevel(THGROUP_ATT_YAWRIGHT) != 0.0) TURNDir = RIGHT;
else TURNDir = STRAIGHT;

if (GetThrusterGroupLevel(THGROUP_ATT_BANKLEFT) != 0.0) ROTATELEFT();
else if (GetThrusterGroupLevel(THGROUP_ATT_BANKRIGHT) != 0.0) ROTATERIGHT();

But finally: DO WHAT YOU WANT! I don't want you to do something you don't understand or see the need for just for me. Develop something for yourself, don't worry too much about others. So if you enjoy your own solution, then keep doing that.

_________________________________________

The reason for your current lack of control with NUMPAD1, is that you're now using

if (1) then turn left if (2) then turn right, else go straight
You can see that you first check 1, but then says either 2 or 3, which will always do either 2 or 3. So the 1 will always be overridden.
The correct way is to check
if (1) then turn left, but if (2) turn right, and if none of them then go straight

Explicitly:
C++:
if (KEYDOWN(kstate, OAPI_KEY_NUMPAD1)) { // turns wheels left
TURNDir = LEFT;
keyConsumed = 1;
}
else if (KEYDOWN(kstate, OAPI_KEY_NUMPAD3)) { // turns wheels left
TURNDir = RIGHT;;
keyConsumed = 1;
}
else TURNDir = STRAIGHT;