How to get a Spaceship Fly up and down!

M@gg!

20-08-2006 00:43:09

At the moment the Character Controller of NxOgre only supports:
SIDESTEP_LEFT, SIDESTEP_RIGHT, FORWARD, BACKWARD

I added two small improvements.

1. You can now set the step widh with "setSpeed(Ogre::Real _speed)"

2. Move accepts now UP and DOWN for flying up and down.


If you want to use it you'll have to replace the "nxOgre_character.cpp" and "nxOgre_character.h" and then recompile NxOgre. Don't forget to replace the dll.

M@gg!

20-08-2006 00:43:30

nxOgre_character.h:
/*

NxOgre a wrapper for the PhysX (formerly Novodex) physics library and the Ogre 3D rendering engine.
Copyright (C) 2005, 2006 Robin Southern <betajaen@ihoed.com>

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

*/

#ifndef __nxOgre_character_H__
#define __nxOgre_character_H__


#include "nxOgre_includes.h"
#include "nxOgre_controllable.h"
#include "nxOgre_blueprint.h"
#include "nxOgre_scene.h"
#include "nxOgre_converters.h"
#include "NxCharacter.h"
#include "NxBoxController.h"
#include "NxCapsuleController.h"


namespace nxOgre {

template<>
class blueprint<character> {

friend character;

public:



blueprint<character>(){
setToDefault();
}


void setToDefault() {
mShape.set(0.25,1,0);
mUp = NxVec3(0,1,0);
mGravity = true;
mMesh = "";
}

character* create(Ogre::String _name, Ogre::Vector3 _pos, scene *_scene) {
return _scene->createCharacter(new character(_name, _pos, this, _scene));
}

void setShapeAsBox(float _width, float _height, float _depth) {
mShape.set(_width, _height, _depth);
}

void setShapeAsCapsule(float _radius, float _height) {
mShape.set(_radius, _height, 0);
}

void setUpDirection(Ogre::Vector3 _direction) {
mUp = NxTools::convert(_direction);
}

void setStep(float _height) {
mStep = _height;
}

void setSlopeLimit(float _limit) {
mSlopeLimit = _limit;
}

void setGravity(bool _g) {
mGravity = _g;
}

void setMesh(Ogre::String _mesh) {
mMesh = _mesh;
}


void save(Ogre::String _fn);
void load(Ogre::String _fn);

protected:

NxVec3 mShape;
NxVec3 mUp;
Ogre::String mMesh;

float mStep;
float mStepDirection;
float mSkinWidth;
float mSlopeLimit;
Ogre::Real mSpeed;

bool mGravity;

};


class _nxOgreExport character : public controllable, public NxUserControllerHitReport {

friend scene;
friend blueprint<character>;

character(Ogre::String _name, Ogre::Vector3 _pos, blueprint<character> *_bp, scene *_scene);
void simulate(float _time);


scene *owner;
Ogre::String mName;
blueprint<character> *mBlueprint;
public:
// Basic Movements
Ogre::Vector3 mNextMovement;
Ogre::Vector3 mMovementVector;
Ogre::Quaternion mDirection;
//M@gg! added
Ogre::Real mSpeed;
NxActor *mActor;
protected:
bool mDirection_Locked;

//
NxU32 mCollisionFlags;

public:

NxController* mController;
Ogre::SceneNode *mNode;

enum Direction {
FORWARD,
BACKWARD,
SIDESTEP_LEFT,
SIDESTEP_RIGHT,
UP,
DOWN
};

enum MovementState {
WALKING,
FALLING,
JUMPING,
SWIMMING,
FLYING,
LADDER,
VEHICLE,
NONE
};


void turn(Ogre::Quaternion _dir);
void move(Direction _direction);
//M@gg! added setSpeed
void setSpeed(Ogre::Real _speed){
mSpeed = _speed;
}
Ogre::Quaternion getDirection(){
return mDirection;
}
NxControllerAction onShapeHit(const NxControllerShapeHit& hit);
NxControllerAction onControllerHit(const NxControllersHit& hit);


};


}




#endif



#if 0


namespace nxOgre {

class _nxOgreExport character : public controllable, public NxUserControllerHitReport {

public:
character(Ogre::String _name, Ogre::String _modelName, Ogre::Vector3 _bounds, Ogre::Vector3 _pos, scene *_owner, bool _alwaysIgnoreGravity = false, bool _noOtherCollisions = false);
~character();

void simulate(float _time);
void move(int _direction);
void jump();
void run();
void runAlways(bool _v = true);

Ogre::SceneNode* mNode;

Ogre::Vector3 getPosition();

// Will Position the character exactly where _pos is, meaning that it could end inside a body.
void forcePosition(Ogre::Vector3 _pos);

// Will Position the character relative to the position that is considered safe.
// false is returned if the character cannot find a correct position.
//
// Note: This method will only check if there is a floor or object underneath, and will not
// check if the character is inside one.
// For better results add 1 to the _pos.y
bool setPosition(Ogre::Vector3 _pos);

void turn(Ogre::Quaternion _dir);

enum movementTypes {
FORWARD,
BACKWARD,
SIDESTEP_LEFT,
SIDESTEP_RIGHT,
SWIM_UP,
SWIM_DOWN,
FLY_UP,
FLY_DOWN,
LADDER_UP,
LADDER_DOWN
};

enum movementStates {
WALKING,
FALLING,
JUMPING,
SWIMMING,
FLYING,
LADDER,
VEHICLE,
NONE
};

protected:

Ogre::String mName;

scene *owner;
NxActor* mActor;

Ogre::Entity* mEntity;

NxVec3 mUpDirection;
float mHeight;
NxVec3 mMoveVector;
Ogre::Vector3 mMoveDirection;
bool mMoveDirection_Locked;
Ogre::Quaternion mDirection;

NxVec3 mLastPosition;
NxVec3 mVelocity;

bool mOneWayCollisions;
bool mAlwaysNoGravity;

int mState;

int mWalking;
int mSidesteping;

bool mRun_once;
bool mRun_always;
bool mRun_running;

bool mJump_jumping;
float mJump_time;
float mJump_maxTime;
float mJump_nextTime;

float mFall_time;
int fCount;

bool mTouching;


NxBoxController* mController;
NxF32 mSharpness;
NxU32 mCollisionFlags;

virtual NxControllerAction onShapeHit(const NxControllerShapeHit& hit);
virtual NxControllerAction onControllerHit(const NxControllersHit& hit);

void simulateWalking(float _time);
void simulateFalling(float _time);
void simulateJumping(float _time);
void simulateFlying(float _time);
void simulateSwimming(float _time);
void simulateLadder(float _time);
void simulateVehicle(float _time);


};
}

#endif

M@gg!

20-08-2006 00:44:21

nxOgre_character.cpp:
/*

NxOgre a wrapper for the PhysX (formerly Novodex) physics library and the Ogre 3D rendering engine.
Copyright (C) 2005, 2006 Robin Southern <betajaen@ihoed.com>

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA

*/

#include "nxOgre_character.h"
#include "nxOgre_scene.h"
#include "nxOgre_body.h"
#include "nxOgre_shape.h"
#include "nxOgre_joint.h"
#include "nxOgre_converters.h"


namespace nxOgre {

character::character(Ogre::String _name, Ogre::Vector3 _pos, blueprint<character> *_bp, scene *_scene) {

owner = _scene;
mName = _name;
mNode = owner->mSceneMgr->getRootSceneNode()->createChildSceneNode(mName);
mBlueprint = _bp;
//M@gg!
mSpeed = 1;

if (_bp->mShape.z == 0) {

NxCapsuleControllerDesc desc;
desc.setToDefault();
desc.height = NxMath::abs(_bp->mShape.y);
desc.radius = NxMath::abs(_bp->mShape.x);
desc.position.set(_pos.x, _pos.y, _pos.z);
desc.slopeLimit = cosf(NxMath::degToRad(_bp->mSlopeLimit));
desc.skinWidth = 0.0025f;
desc.stepOffset = _bp->mStep;
desc.callback = this;
desc.upDirection = NX_Y;
mController = mControlMgr.createController(owner->mScene,desc);
}
else {
NxBoxControllerDesc desc;
desc.setToDefault();
desc.extents.set(_pos.x, _pos.y, _pos.z);
desc.position.set(_pos.x, _pos.y, _pos.z);
desc.slopeLimit = cosf(NxMath::degToRad(_bp->mSlopeLimit));
desc.skinWidth = 0.0025f;
desc.callback = this;
desc.stepOffset = _bp->mStep;
desc.upDirection = NX_Y;

mController = mControlMgr.createController(owner->mScene,desc);

}

if (_bp->mMesh != "") {
Ogre::Entity* mEntity = owner->mSceneMgr->createEntity(mName, _bp->mMesh);
mEntity->setCastShadows(true);
mNode->attachObject(mEntity);
}

mController->setCollision(true);

//M@gg! added
mActor = mController->getActor();

mActor->setGroup(owner->findGroupIndex("default"));

mNextMovement = Ogre::Vector3::ZERO;
mDirection = Ogre::Quaternion::IDENTITY;
mDirection_Locked = false;
}

void character::simulate(float _time) {

// 15 should be movementSpeed.
mMovementVector = 25 * (mDirection * mNextMovement ) * _time;

if (mBlueprint->mGravity) {
// TODO Check which is up, and get gravity constant.
mMovementVector.y -= (9.80665 * 9.80665 *_time);
}

mController->move(NxTools::convert(mMovementVector), owner->findGroupIndex("default"), 0.001f, mCollisionFlags, 1.0f);

mNextMovement = Ogre::Vector3::ZERO;

mNode->setPosition(NxTools::convert( mController->getActor()->getGlobalPosition()));
mNode->setOrientation(mDirection);
}

void character::turn(Ogre::Quaternion _dir) {
if (mDirection_Locked)
return;

mDirection = _dir;
mDirection.x = 0;
mDirection.z = 0;

mDirection.normalise();
}

void character::move(Direction _direction) {

//M@gg! SpeedSet
switch(_direction) {
case FORWARD:
mNextMovement.z = -1 * mSpeed;
break;

case BACKWARD:
mNextMovement.z = mSpeed;
break;

case SIDESTEP_LEFT:
mNextMovement.x = -1 * mSpeed;
break;

case SIDESTEP_RIGHT:
mNextMovement.x = mSpeed;
break;
//M@gg! added
case UP:
mNextMovement.y = mSpeed;
break;

case DOWN:
mNextMovement.y = -1 * mSpeed;
break;
}
}

NxControllerAction character::onShapeHit(const NxControllerShapeHit& hit) {

//mTouching = true;

//if (mOneWayCollisions)
// return NX_ACTION_NONE;

if (1 && hit.shape) {
NxCollisionGroup group = hit.shape->getGroup();
if (group!=owner->findGroupIndex("static")) {
NxActor& actor = hit.shape->getActor();
if (actor.isDynamic()) {
NxF32 coeff = actor.getMass() * hit.length * 10.0f;
//actor.addForceAtPos(hit.dir*coeff,hit.controller->getActor()->getGlobalPosition());
actor.addForceAtLocalPos(hit.dir*coeff, NxVec3(0,0,0), NX_IMPULSE);

}
}
}

return NX_ACTION_NONE;
}


NxControllerAction character::onControllerHit(const NxControllersHit& hit) {

// if (mOneWayCollisions)
// return NX_ACTION_NONE;

if (1 && hit.other) {

}

return NX_ACTION_NONE;
}
}


#if 0
//namespace nxOgre {

character::character(Ogre::String _name, Ogre::String _model, Ogre::Vector3 _bounds, Ogre::Vector3 _pos, scene *_owner, bool _alwaysIgnoreGravity, bool _noOtherCollisions) {
owner = _owner;
mName = _name;

mNode = owner->mSceneMgr->getRootSceneNode()->createChildSceneNode(mName);

if (_model != "") {
mEntity = owner->mSceneMgr->createEntity(mName, _model);
mNode->attachObject(mEntity);
}

// Presets

mUpDirection = NxVec3(0,1,0);
mRun_running = false;
mJump_jumping = false;
mJump_maxTime = 0.05;
mJump_nextTime = 0;
mAlwaysNoGravity = _alwaysIgnoreGravity;
mOneWayCollisions = _noOtherCollisions;

mState = WALKING;
mSharpness = 1.0f;
mDirection = Ogre::Quaternion(1,0,0,0);
mMoveDirection = Ogre::Vector3(0,0,0);
mLastPosition = NxVec3(0,0,0);
mHeight = _bounds.y;
fCount = 0;
mMoveDirection_Locked = false;


NxBoxControllerDesc mDesc;
mDesc.position.x = _pos.x;
mDesc.position.y = _pos.y;
mDesc.position.z = _pos.z;
mDesc.extents.x = _bounds.x;
mDesc.extents.y = _bounds.y;
mDesc.extents.z = _bounds.z;
//mDesc.extents = NxVec3(0.15, 1.75 / 2 ,0.15);

mDesc.upDirection = NX_Y; // mUpDirection;
mDesc.slopeLimit = cosf(NxMath::degToRad(35.0f));
mDesc.skinWidth = 0.0025f;
mDesc.stepOffset = 0.40f;
mDesc.callback = this;


mController = (NxBoxController*)mControlMgr.createController(owner->mScene,mDesc);
mController->setCollision(true);
mController->getActor()->setGroup(owner->findGroupIndex("default"));

}

character::~character() {
mControlMgr.releaseController(*mController);
Ogre::LogManager::getSingleton().logMessage("Controllable '" + mName + "' deleted.");
}

Ogre::Vector3 character::getPosition() {
return Ogre::Vector3(0,0,0);
}

void character::forcePosition(Ogre::Vector3 _pos) {

}

bool character::setPosition(Ogre::Vector3 _pos) {
// Use a ray to look down, see if there is a floor within 10 metres, if not. Return false, if there is
// set the position 0.001 above the floor height.
return false;
}


void character::simulate(float _time) {

mMoveVector = NxVec3(0,0,0);

switch (mState) {
case WALKING: simulateWalking(_time); break;
case SWIMMING: break;
case FLYING: break;
case JUMPING: simulateJumping(_time); break;
case FALLING: simulateFalling(_time); break;
case LADDER: break;
case VEHICLE: break;
case NONE: break;
}

mNode->setPosition(NxTools::convert( mController->getActor()->getGlobalPosition()));
mNode->setOrientation( NxTools::convert( mController->getActor()->getGlobalOrientation() ) );

mTouching = false;

if (mJump_nextTime > 0) {
mJump_nextTime =- _time;
}
}

void character::simulateWalking(float _time) {

mVelocity = mController->getActor()->getGlobalPosition() - mLastPosition;
Ogre::Vector3 v = 15 * (mDirection * mMoveDirection ) * _time;

mMoveVector = NxTools::convert(v);

if (!mAlwaysNoGravity)
mMoveVector.y -= ( 9.80665 * _time );

if (mVelocity.y < 0) {
fCount++;

if (fCount > 3) {
NxRay mRay(mController->getActor()->getGlobalPosition(),NxVec3(0,-1,0));
NxRaycastHit hit;
NxShape *shape = owner->mScene->raycastClosestShape(mRay,NX_ALL_SHAPES,hit);//,0xffffffff,0.4);


if (hit.distance > 2.0) {
//Ogre::LogManager::getSingleton().logMessage("Falling.");
mState = FALLING;
fCount = 0;
mFall_time = _time;
return;
}
else {
fCount = 0;
}
}
}


mMoveDirection = Ogre::Vector3(0,0,0);
mLastPosition = mController->getActor()->getGlobalPosition();
mController->move(mMoveVector, owner->findGroupIndex("default"), 0.001f, mCollisionFlags, mSharpness);

}

void character::simulateFalling(float _time) {

mVelocity = mController->getActor()->getGlobalPosition() - mLastPosition;

if (mVelocity.y >= 0) {
fCount++;

if (fCount > 3) {

//Ogre::LogManager::getSingleton().logMessage("Walking.");
mState = WALKING;
fCount = 0;
mFall_time = 0;
mMoveDirection_Locked = false;
mJump_nextTime = 0.3;
return;

}
}


// Allow a tiny bit of air movement for a bit.
if (mFall_time > 0.05) {
mMoveDirection_Locked = true;
}

Ogre::Vector3 v = 15 * (mDirection * mMoveDirection ) * _time;

mMoveVector = NxTools::convert(v);

mFall_time += _time;
mMoveVector.y -= ((9.80665 * 9.80665 * mFall_time) * _time) * 10.5;


mLastPosition = mController->getActor()->getGlobalPosition();
mController->move(mMoveVector, owner->findGroupIndex("default"), 0.001f, mCollisionFlags, mSharpness);
}

void character::simulateJumping(float _time) {


mJump_time += _time;
// We need to check if we are touching anything, this means we could of hit something.

// We need to check mVelocity.y if it's 0 or < 0 then we are falling and we are no longer jumping,
// If mVelocity.y == 0 then we are walking, else it's a fall.

// If mJump_time > mJump_maxTime then we are falling...

Ogre::Vector3 v = 15 * (mDirection * mMoveDirection ) * _time;
mMoveVector = NxTools::convert(v);
mMoveVector.y += (9.80665 * _time) * 1.75;

//Ogre::LogManager::getSingleton().logMessage(Ogre::StringConverter::toString(mJump_time));

if (mJump_time >= mJump_maxTime) {
// Temp.
mState = FALLING;
mMoveDirection_Locked = false;

// This would probably be altered depending on the length of the jump
// or fall.
mJump_nextTime = 0.3;
}

//if (mTouching) {
//Ogre::LogManager::getSingleton().logMessage("**TOUCHING**");
//}

mLastPosition = mController->getActor()->getGlobalPosition();
mController->move(mMoveVector, owner->findGroupIndex("default"), 0.001f, mCollisionFlags, mSharpness);
}

void character::move(int _direction) {
if (mMoveDirection_Locked)
return;

switch(_direction) {
case FORWARD:
mMoveDirection.z = -1;
break;

case BACKWARD:
mMoveDirection.z = 1;
break;

case SIDESTEP_LEFT:
mMoveDirection.x = -1;
break;

case SIDESTEP_RIGHT:
mMoveDirection.x = 1;
break;
}
}

void character::turn(Ogre::Quaternion _dir) {
if (mMoveDirection_Locked)
return;

mDirection = _dir;
// (TODO) Only applys if character is walking or falling.
mDirection.x = mDirection.z = 0;
//mDirection.w = 1;

mDirection.normalise();
}

void character::jump() {
// Add 1ish .y to next move direction, assuming if character isn't jumping already!
if (mState == WALKING && mJump_nextTime <= 0 ) {
mState = JUMPING;
mJump_time = 0;
fCount = 0;
mMoveDirection_Locked = true;
//Ogre::LogManager::getSingleton().logMessage("Jumping");
}
}

void character::run() {
// mNextDirection x,y * 2 ish.
}

void character::runAlways(bool _v) {
// Set walk modifer to 2, else set it to 1.
}

NxControllerAction character::onShapeHit(const NxControllerShapeHit& hit) {

mTouching = true;

if (mOneWayCollisions)
return NX_ACTION_NONE;

if (1 && hit.shape) {
NxCollisionGroup group = hit.shape->getGroup();
if (group!=owner->findGroupIndex("static")) {
NxActor& actor = hit.shape->getActor();
if (actor.isDynamic()) {


NxF32 coeff = actor.getMass() * hit.length * 10.0f;
actor.addForceAtLocalPos(hit.dir*coeff, NxVec3(0,0,0), NX_IMPULSE);

}
}
}

return NX_ACTION_NONE;
}


NxControllerAction character::onControllerHit(const NxControllersHit& hit) {

if (mOneWayCollisions)
return NX_ACTION_NONE;

if (1 && hit.other) {

}

return NX_ACTION_NONE;
}
}

#endif

betajaen

20-08-2006 09:47:27

Character controllers are really designed for human-like characters, Wouldn't it be more easier and more realistic to use a body that uses forces?

Besides I intended the UP/DOWN thing for ladders and noclip mode.

M@gg!

20-08-2006 10:12:03

Yea, you can do this but as far as I have read it'd be a lot easier when you have full controll over the ship.

But I have no spaceships in my game, so I can't point on my own experience and I'm still not 100% sure by my own.

Even if you'd not use it for a spaceship, as you have all ready pointed out, you can create a spectator on this way very easy.


In the PhysX Documentation about character contollers I've found this and somewhere I've read about spaceships, but I can't find it again:

No direct control: A rigid body is typically controlled with impulses or forces. It is usually not possible to move it directly to its final position, you first have to convert the delta position vector to impulses/forces, apply them, and hope that the character will be where you wanted it to be as a result. Usually it doesn’t work too well, in particular when the physics engine uses an imperfect linear solver.

And some more from the PhysX character controller tutorial:

8 Features of the Character Controller
We have seen some of the features of the character controller. In the next lesson, we will play around more with the features of the character controller. The current feature list includes:

1. Standard FPS control: sliding behaviour as in Quake, etc, i.e. "1st order" control using a displacement vector, not impulses or forces
2. Perfect stability: doesn't jitter in corners, etc.
3. No tunnelling: it should work fine with huge displacement vectors

4. Box and sphere controller: the two supported shapes for characters are box and sphere. The box controller uses a kinematic box that contains the character as well as a swept-box that casts downward to the floor to get the character's under-foot contact information. The sphere controller uses a kinematic sphere that contains the character as well as a swept-sphere that casts downward to the floor to get the character's under-foot contact information.
5. Autostepping: the character automatically climbs small steps or stairs
6. Walkable/non-walkable parts: the motion is automatically stopped on non-walkable parts of the level
7. Character-objects interactions: a.k.a. pushing => the character can push other dynamic objects
8. Different hit callbacks: trigger different game effects when touching a shape or another character
9. Runtime shape resizing

betajaen

20-08-2006 10:37:29

But then you would take away the realism, the whole point of a spaceship in space is for it to rely on inertia from an initial force such as a thruster, then to stop it you apply the thrust on the opposite side of the ship. Same way to move.

However using a character controller, you take that realism away you move to the left and thats it the ship is perfectly stable, also you can't bank either as the character controller is always pointing to the ground.

As for the spectator idea, yeah. But I have another way of doing it.

M@gg!

20-08-2006 12:32:18

you can't bank either as the character controller is always pointing to the ground.

You seem to be right. I can't find a possibility to change it. I'll keep it in mind and see if I can find a way to do it.

As for the spectator idea, yeah. But I have another way of doing it.

Let me gues, you want to use a camera attached to a scene node.