Robin Colclough - 2010-11-23

/** Example 019 Mouse and Joystick

This is the complete modified example that shows bugs in DirectX9 and other driver modes.
Look for "bug" to see how to reproduce using this modified sample code. / RCC

This tutorial builds on example 04.Movement which showed how to
handle keyboard events in Irrlicht.  Here we'll handle mouse events
and joystick events, if you have a joystick connected and a device
that supports joysticks.  These are currently Windows, Linux and SDL
devices.
*/

// BUG FOUND BY RCC: SET VARIABLE bug below to true to see bug, use DirectX9, and then set bug=false to see correct operation!!  23.11.2010 Irrlich 1.7.1.

#ifdef _MSC_VER
// We'll define this to stop MSVC complaining about sprintf().
#define _CRT_SECURE_NO_WARNINGS
#pragma comment(lib, "Irrlicht.lib")
#endif

#include <irrlicht.h>
#include "driverChoice.h"

using namespace irr;

/*
Just as we did in example 04.Movement, we'll store the latest state of the
mouse and the first joystick, updating them as we receive events.
*/
class MyEventReceiver : public IEventReceiver
{
public:
// We'll create a struct to record info on the mouse state
struct SMouseState
{
core::position2di Position;
bool LeftButtonDown;
SMouseState() : LeftButtonDown(false) { }
} MouseState;

// This is the one method that we have to implement
virtual bool OnEvent(const SEvent& event)
{
// Remember the mouse state
if (event.EventType == irr::EET_MOUSE_INPUT_EVENT)
{
switch(event.MouseInput.Event)
{
case EMIE_LMOUSE_PRESSED_DOWN:
MouseState.LeftButtonDown = true;
break;

case EMIE_LMOUSE_LEFT_UP:
MouseState.LeftButtonDown = false;
break;

case EMIE_MOUSE_MOVED:
MouseState.Position.X = event.MouseInput.X;
MouseState.Position.Y = event.MouseInput.Y;
break;

default:
// We won't use the wheel
break;
}
}

// The state of each connected joystick is sent to us
// once every run() of the Irrlicht device.  Store the
// state of the first joystick, ignoring other joysticks.
// This is currently only supported on Windows and Linux.
if (event.EventType == irr::EET_JOYSTICK_INPUT_EVENT
&& event.JoystickEvent.Joystick == 0)
{
JoystickState = event.JoystickEvent;
}

return false;
}

const SEvent::SJoystickEvent & GetJoystickState(void) const
{
return JoystickState;
}

const SMouseState & GetMouseState(void) const
{
return MouseState;
}

MyEventReceiver()
{
}

private:
SEvent::SJoystickEvent JoystickState;
};

/*
The event receiver for keeping the pressed keys is ready, the actual responses
will be made inside the render loop, right before drawing the scene. So lets
just create an irr::IrrlichtDevice and the scene node we want to move. We also
create some other additional scene nodes, to show that there are also some
different possibilities to move and animate scene nodes.
*/
int main()
{
// ask user for driver
video::E_DRIVER_TYPE driverType=driverChoiceConsole();
if (driverType==video::EDT_COUNT)
return 1;

// create device
MyEventReceiver receiver;

IrrlichtDevice* device = createDevice(driverType, core::dimension2d<u32>(640, 480), 16, false, false, false, &receiver);

if (device == 0)
return 1; // could not create selected driver.

core::array<SJoystickInfo> joystickInfo;
if(device->activateJoysticks(joystickInfo))
{
std::cout << "Joystick support is enabled and " << joystickInfo.size() << " joystick(s) are present." << std::endl;

for(u32 joystick = 0; joystick < joystickInfo.size(); ++joystick)
{
std::cout << "Joystick " << joystick << ":" << std::endl;
std::cout << "\tName: '" << joystickInfo.Name.c_str() << "'" << std::endl;
std::cout << "\tAxes: " << joystickInfo.Axes << std::endl;
std::cout << "\tButtons: " << joystickInfo.Buttons << std::endl;

std::cout << "\tHat is: ";

switch(joystickInfo.PovHat)
{
case SJoystickInfo::POV_HAT_PRESENT:
std::cout << "present" << std::endl;
break;

case SJoystickInfo::POV_HAT_ABSENT:
std::cout << "absent" << std::endl;
break;

case SJoystickInfo::POV_HAT_UNKNOWN:
default:
std::cout << "unknown" << std::endl;
break;
}
}
}
else
{
std::cout << "Joystick support is not enabled." << std::endl;
}

core::stringw tmp = L"Irrlicht Joystick Example (";
tmp += joystickInfo.size();
tmp += " joysticks)";
device->setWindowCaption(tmp.c_str());

video::IVideoDriver* driver = device->getVideoDriver();
scene::ISceneManager* smgr = device->getSceneManager();

/*
We'll create an arrow mesh and move it around either with the joystick axis/hat,
or make it follow the mouse pointer. */
scene::ISceneNode * node = smgr->addMeshSceneNode(
smgr->addArrowMesh( "Arrow",
video::SColor(255, 255, 0, 0),
video::SColor(255, 0, 255, 0),
16,16,
2.f, 1.3f,
0.1f, 0.6f
)
);
node->setMaterialFlag(video::EMF_LIGHTING, false);

bool bug;
bug = false; // BUG TEST UNDER DIRECTX9!!!!! RCC
bug = true; // BUG TEST UNDER DIRECTX9!!!!! RCC

scene::ISceneNode * cube1 = smgr->addCubeSceneNode( 2.0f );
if (cube1)
{
if ( !bug ) // HERE----------- BUG??????????
cube1->setPosition(core::vector3df(0,0,0));
else
cube1->setPosition(core::vector3df(0,3,0)); // Using DirectX9, if this line runs, 3D vector calculations used to draw the lines below goes askew!!!  Why is the cube position related??

if ( !bug ) // BUG Also, if the texture isn´t set, the cyan line below tracks the arrow node under DirectX9!!!!! Why???????
cube1->setMaterialTexture(0, driver->getTexture("E:/Dev/irrlicht-1.5/media/ViewPoint_logo_officialII.jpg"));
//cube1->setMaterialFlag(video::EMF_GOURAUD_SHADING, true);
//cube1->setMaterialFlag(video::EMF_FOG_ENABLE, false);
//cube1->setMaterialFlag(video::EMF_LIGHTING, true);
cube1->setMaterialFlag(video::EMF_LIGHTING, false);
}

scene::ICameraSceneNode * camera = smgr->addCameraSceneNode();
camera->setPosition(core::vector3df(0, 0, -10));

// As in example 04, we'll use framerate independent movement.
u32 then = device->getTimer()->getTime();
const f32 MOVEMENT_SPEED = 5.f;
core::vector3df mousePosition;
core::line3df ray;

while(device->run())
{
// Work out a frame delta time.
const u32 now = device->getTimer()->getTime();
const f32 frameDeltaTime = (f32)(now - then) / 1000.f; // Time in seconds
then = now;

bool movedWithJoystick = false;
core::vector3df nodePosition = node->getPosition();

if(joystickInfo.size() > 0)
{
f32 moveHorizontal = 0.f; // Range is -1.f for full left to +1.f for full right
f32 moveVertical = 0.f; // -1.f for full down to +1.f for full up.

const SEvent::SJoystickEvent & joystickData = receiver.GetJoystickState();

// We receive the full analog range of the axes, and so have to implement our
// own dead zone.  This is an empirical value, since some joysticks have more
// jitter or creep around the center point than others.  We'll use 5% of the
// range as the dead zone, but generally you would want to give the user the
// option to change this.
const f32 DEAD_ZONE = 0.05f;

moveHorizontal =
(f32)joystickData.Axis / 32767.f;
if(fabs(moveHorizontal) < DEAD_ZONE)
moveHorizontal = 0.f;

moveVertical =
(f32)joystickData.Axis / -32767.f;
if(fabs(moveVertical) < DEAD_ZONE)
moveVertical = 0.f;

// POV hat info is only currently supported on Windows, but the value is
// guaranteed to be 65535 if it's not supported, so we can check its range.
const u16 povDegrees = joystickData.POV / 100;
if(povDegrees < 360)
{
if(povDegrees > 0 && povDegrees < 180)
moveHorizontal = 1.f;
else if(povDegrees > 180)
moveHorizontal = -1.f;

if(povDegrees > 90 && povDegrees < 270)
moveVertical = -1.f;
else if(povDegrees > 270 || povDegrees < 90)
moveVertical = +1.f;
}

if(!core::equals(moveHorizontal, 0.f) || !core::equals(moveVertical, 0.f))
{
nodePosition.X += MOVEMENT_SPEED * frameDeltaTime * moveHorizontal;
nodePosition.Y += MOVEMENT_SPEED * frameDeltaTime * moveVertical;
movedWithJoystick = true;
}
}

// If the arrow node isn't being moved with the joystick, then have it follow the mouse cursor.
if(!movedWithJoystick)
{
// Create a ray through the mouse cursor, from the camera.
ray = smgr->getSceneCollisionManager()->getRayFromScreenCoordinates( receiver.GetMouseState().Position, camera );

// And intersect the ray with a plane around the node facing towards the camera.
core::plane3df plane(nodePosition, core::vector3df(0, 0, -1)); // Create plane3df class
if ( plane.getIntersectionWithLine( ray.start, ray.getVector(), mousePosition ) )
{
// We now have a mouse position in 3d space; move towards it.
core::vector3df toMousePosition(mousePosition - nodePosition);
const f32 availableMovement = MOVEMENT_SPEED * frameDeltaTime;

if(toMousePosition.getLength() <= availableMovement)
nodePosition = mousePosition; // Jump to the final position
else
nodePosition += toMousePosition.normalize() * availableMovement; // Move towards it
}
}

node->setPosition(nodePosition);

// Turn lighting on and off depending on whether the left mouse button is down.
node->setMaterialFlag(video::EMF_LIGHTING, receiver.GetMouseState().LeftButtonDown);

driver->beginScene(true, true, video::SColor(255,113,113,133));

smgr->drawAll(); // draw the 3d scene

driver->draw3DLine( camera->getPosition(), core::vector3df(20, 20, 20), video::SColor(255,0,255,255)); // Cyan
driver->draw3DLine( camera->getPosition(), mousePosition, video::SColor(255,255,0,0)); // Red
driver->draw3DLine( core::vector3df(0, 0, 0), mousePosition, video::SColor(255,255,0,255)); // Magenta
driver->draw3DLine( mousePosition, node->getPosition(), irr::video::SColor(255,0,255,0)); // Green
// driver->draw3DLine( mousePosition, nodePosition, irr::video::SColor(255,0,255,0)); // Green - This shows the vector to move the node to the new cursor position
driver->draw3DLine( ray.end, ray.start, irr::video::SColor(255,255,255,255)); // White

driver->draw3DLine( core::vector3df(-80, 0, 0), core::vector3df(80, 0, 0), video::SColor(255,0,255,255)); // Cyan - Under bug state, this line tracks teh arrow node!!  Why??

driver->endScene();
}

/*
In the end, delete the Irrlicht device.
*/
device->drop();

return 0;
}

/*
**/