(C++) - Scene Node Camera Arcball

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
anirul
Posts: 30
Joined: Wed Oct 07, 2009 6:03 am
Location: Geneva
Contact:

(C++) - Scene Node Camera Arcball

Post by anirul »

So, as I said in a previous post I was struggling with arcball camera, and as it seams to be a recurring problem I'm posting my code, that is more or less working now!

An arcball camera is a camera that is around a point like on google earth per example. you can use it to look at an object. By default just click and drag with the mouse and you should see the effect happening.

Still some test to be done and in case you find a bug (and or a fix) you would be welcome to post! I know per example that I haven't try arcball that don't revolve around the origin, and I haven't tried camera that are not on the z-axe.

I tried to make something similar to CameraFPS and CameraMaya so that it is easily plugged in any project.

I suppressed the comments headers for clarity but this code is under BSD license as far as I am concerned.

ISceneNodeAnimatorCameraArcball.h (interface header)

Code: Select all


#ifndef ISCENENODEANIMATORCAMERAARCBALL_H_
#define ISCENENODEANIMATORCAMERAARCBALL_H_

namespace irr {
	namespace scene {

		class ISceneNodeAnimatorCameraArcball : public ISceneNodeAnimator {
		public:
			virtual void captureMouse() = 0;
			virtual bool isMouseCaptured() const = 0;
			virtual void freeMouse() = 0;
			virtual void setCaptureMouseEvent(EMOUSE_INPUT_EVENT event) = 0;
			virtual EMOUSE_INPUT_EVENT getCaptureMouseEvent() const = 0;
			virtual void setFreeMouseEvent(EMOUSE_INPUT_EVENT event) = 0;
			virtual EMOUSE_INPUT_EVENT getFreeMouseEvent() const = 0;
		};

	} // end scene
} // end irr

#endif /* ISCENENODEANIMATORCAMERAARCBALL_H_ */
CSceneNodeAnimatorCameraArcball.h (Class header implementing the interface).

Code: Select all


#ifndef CSCENENODEANIMATORCAMERAARCBALL_H_
#define CSCENENODEANIMATORCAMERAARCBALL_H_

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

namespace irr {
	namespace scene {

		class CSceneNodeAnimatorCameraArcball : public ISceneNodeAnimatorCameraArcball {
		public:
			//! Constructor
			CSceneNodeAnimatorCameraArcball(
				gui::ICursorControl* cursorControl,
				ICameraSceneNode* camera,
				EMOUSE_INPUT_EVENT captureEvent = EMIE_LMOUSE_PRESSED_DOWN,
				EMOUSE_INPUT_EVENT freeEvent = EMIE_LMOUSE_LEFT_UP);

			//! Destructor
			virtual ~CSceneNodeAnimatorCameraArcball();

			//! Capture the mouse
			virtual void captureMouse();

			//! tell if the mouse is captured
			virtual bool isMouseCaptured() const { return mouseDrag; }

			//! Free the mouse
			virtual void freeMouse();

			//! set the capture event for the mouse
			virtual void setCaptureMouseEvent(EMOUSE_INPUT_EVENT event)
				{ CaptureMouseEvent = event; }

			//! get the capture event of the mouse
			virtual EMOUSE_INPUT_EVENT getCaptureMouseEvent() const
				{ return CaptureMouseEvent; }

			//! set the free event for the mouse
			virtual void setFreeMouseEvent(EMOUSE_INPUT_EVENT event)
				{ FreeMouseEvent = event; }

			//! get the free event for the mouse
			virtual EMOUSE_INPUT_EVENT getFreeMouseEvent() const
				{ return FreeMouseEvent; }

			//! Animate the scene node, currently only work on cameras
			virtual void animateNode(ISceneNode* node, u32 timeMs);

			//! Event receiver
			virtual bool OnEvent(const SEvent& event);

			//! This animator will receive events when attached to the active camera
			virtual bool isEventReceiverEnabled() const
				{ return true; }

			//! Returns the type of this animator
			virtual ESCENE_NODE_ANIMATOR_TYPE getType() const
				{ return ESNAT_CAMERA_FPS; }

			//! create clone of this animator
			virtual ISceneNodeAnimator* createClone(
				ISceneNode* node,
				ISceneManager* newManager = 0);
		private :
			core::vector3df position2vector(const core::position2df& pos) const;
			gui::ICursorControl* CursorControl;
			core::position2df CursorDown;
			core::quaternion QuatOri;
			core::vector3df CamOri;
			core::vector3df CamUp;
			ICameraSceneNode* Camera;
			bool InvertAxes;
			bool mouseDrag;
			EMOUSE_INPUT_EVENT CaptureMouseEvent;
			EMOUSE_INPUT_EVENT FreeMouseEvent;
		};

	}
}

#endif /* CSCENENODEANIMATORCAMERAARCBALL_H_ */
CSceneNodeAnimatorCameraArcball.cpp (actual implementation)

Code: Select all


#include "CSceneNodeAnimatorCameraArcball.h"

irr::scene::CSceneNodeAnimatorCameraArcball::CSceneNodeAnimatorCameraArcball(
	gui::ICursorControl* cursorControl,
	ICameraSceneNode* camera,
	EMOUSE_INPUT_EVENT captureEvent,
	EMOUSE_INPUT_EVENT freeEvent)
	:	CursorControl(cursorControl),
	 	Camera(camera),
	 	mouseDrag(false),
	 	CaptureMouseEvent(captureEvent),
	 	FreeMouseEvent(freeEvent)
{
	CamOri = Camera->getPosition();
	CamUp = Camera->getUpVector();
	QuatOri.makeIdentity();

	// should solve some camera miss placement effect
	core::vector3df begin(0, 0, 1);
	InvertAxes = (begin.dotProduct(Camera->getTarget() - CamOri) > 0);
	QuatOri.rotationFromTo(begin, CamOri);
}

irr::scene::CSceneNodeAnimatorCameraArcball::~CSceneNodeAnimatorCameraArcball() {}

irr::core::vector3df irr::scene::CSceneNodeAnimatorCameraArcball::position2vector(
	const irr::core::position2df& pos) const
{
	float fx = -(0.5 - pos.X);
	float fy = ((InvertAxes)?1:-1) * (0.5 - pos.Y);

	float fz   = 0.0f;
	float mag = fx*fx + fy*fy;

	if( mag > 1.0f)
	{
		float scale = 1.0f / sqrtf(mag);
		fx *= scale;
		fy *= scale;
	}
	else
		fz = sqrtf( 1.0f - mag );

	return irr::core::vector3df(fx, fy, fz);
}

void irr::scene::CSceneNodeAnimatorCameraArcball::animateNode(ISceneNode *node, u32 timeMs)
{
	if (!node || node->getType() != ESNT_CAMERA)
		return;

	if (!mouseDrag)
		return;

	if (!Camera->isInputReceiverEnabled())
		return;

	ISceneManager* smgr = Camera->getSceneManager();
	if (smgr && smgr->getActiveCamera() != Camera)
		return;

	core::vector3df target = Camera->getTarget();
	core::vector3df pos = Camera->getPosition() - target;
	core::vector3df up = Camera->getUpVector();
	core::quaternion quat;

	if (CursorControl) {
		core::position2df CursorPos = CursorControl->getRelativePosition();
		core::vector3df from = position2vector(CursorDown);
		core::vector3df to = position2vector(CursorPos);
		quat.rotationFromTo(from, to);
		CursorDown = CursorPos;
	}

	QuatOri = quat * QuatOri;
	pos = QuatOri * CamOri;
	up = QuatOri * CamUp;

	Camera->setPosition(pos + target);
	Camera->setUpVector(up);
}

void irr::scene::CSceneNodeAnimatorCameraArcball::captureMouse() {
	CursorDown = CursorControl->getRelativePosition();
	mouseDrag = true;
}

void irr::scene::CSceneNodeAnimatorCameraArcball::freeMouse() {
	mouseDrag = false;
}

bool irr::scene::CSceneNodeAnimatorCameraArcball::OnEvent(const irr::SEvent & event)
{
	if (event.EventType == EET_MOUSE_INPUT_EVENT) {
		if (event.MouseInput.Event == CaptureMouseEvent) {
			captureMouse();
			return true;
		}
		if (event.MouseInput.Event == FreeMouseEvent) {
			freeMouse();
			return true;
		}
	}
	return false;
}

irr::scene::ISceneNodeAnimator* irr::scene::CSceneNodeAnimatorCameraArcball::createClone(
	ISceneNode *node,
	ISceneManager *newManager)
{
	CSceneNodeAnimatorCameraArcball* newAnimator =
		new CSceneNodeAnimatorCameraArcball(CursorControl, Camera);
	return newAnimator;
}
voila! Have fun! (I will post an example of use in a few minutes).
Last edited by anirul on Tue Jun 08, 2010 4:46 pm, edited 1 time in total.
anirul
Posts: 30
Joined: Wed Oct 07, 2009 6:03 am
Location: Geneva
Contact:

example

Post by anirul »

here is a small example (modif of the SceneNode example with a Icosahedron).

Code: Select all


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

class CIcoSceneNode : public irr::scene::ISceneNode {
	irr::core::aabbox3d<irr::f32> Box;
	irr::video::S3DVertex Vertices[12];
	irr::u16 Indices[60];
	irr::video::SMaterial Material;
public :
	CIcoSceneNode(
		irr::scene::ISceneNode* parent,
		irr::scene::ISceneManager* mgr,
		irr::s32 id)
		:	irr::scene::ISceneNode(parent, mgr, id)
	{
		Material.Wireframe = false;
		Material.Lighting = false;
		// generate icosahedron
		float t = (1 + sqrtf(5.0f))/2.0f;
		float s = sqrtf(1 + t*t);
		irr::video::SColor red(0xffff0000);
		irr::video::SColor green(0xff00ff00);
		irr::video::SColor blue(0xff0000ff);
		// create the 12 vertices
		Vertices[0] = irr::video::S3DVertex(irr::core::vector3df(t/s, 1/s, 0), irr::core::vector3df(t/s, 1/s, 0), red, irr::core::vector2df(0.0f, 0.0f));
		Vertices[1] = irr::video::S3DVertex(irr::core::vector3df(-t/s, 1/s, 0), irr::core::vector3df(-t/s, 1/s, 0), green, irr::core::vector2df(0.0f, 0.0f));
		Vertices[2] = irr::video::S3DVertex(irr::core::vector3df(t/s, -1/s, 0), irr::core::vector3df(t/s, -1/s, 0), blue, irr::core::vector2df(0.0f, 0.0f));
		Vertices[3] = irr::video::S3DVertex(irr::core::vector3df(-t/s, -1/s, 0), irr::core::vector3df(-t/s, -1/s, 0), red, irr::core::vector2df(0.0f, 0.0f));
		Vertices[4] = irr::video::S3DVertex(irr::core::vector3df(1/s, 0, t/s), irr::core::vector3df(1/s, 0, t/s), green, irr::core::vector2df(0.0f, 0.0f));
		Vertices[5] = irr::video::S3DVertex(irr::core::vector3df(1/s, 0, -t/s), irr::core::vector3df(1/s, 0, -t/s), blue, irr::core::vector2df(0.0f, 0.0f));
		Vertices[6] = irr::video::S3DVertex(irr::core::vector3df(-1/s, 0, t/s), irr::core::vector3df(-1/s, 0, t/s), red, irr::core::vector2df(0.0f, 0.0f));
		Vertices[7] = irr::video::S3DVertex(irr::core::vector3df(-1/s, 0, -t/s), irr::core::vector3df(-1/s, 0, -t/s), green, irr::core::vector2df(0.0f, 0.0f));
		Vertices[8] = irr::video::S3DVertex(irr::core::vector3df(0, t/s, 1/s), irr::core::vector3df(0, t/s, 1/s), blue, irr::core::vector2df(0.0f, 0.0f));
		Vertices[9] = irr::video::S3DVertex(irr::core::vector3df(0, -t/s, 1/s), irr::core::vector3df(0, -t/s, 1/s), red, irr::core::vector2df(0.0f, 0.0f));
		Vertices[10] = irr::video::S3DVertex(irr::core::vector3df(0, t/s, -1/s), irr::core::vector3df(0, t/s, -1/s), green, irr::core::vector2df(0.0f, 0.0f));
		Vertices[11] = irr::video::S3DVertex(irr::core::vector3df(0, -t/s, -1/s), irr::core::vector3df(0, -t/s, -1/s), blue, irr::core::vector2df(0.0f, 0.0f));
		Box.reset(Vertices[0].Pos);
		for (irr::s32 i = 1; i < 12; ++i)
			Box.addInternalPoint(Vertices[i].Pos);
		// create the 20 triangles
		Indices[0] = 0; Indices[1] = 8; Indices[2] = 4;
		Indices[3] = 1; Indices[4] = 10; Indices[5] = 7;
		Indices[6] = 2; Indices[7] = 9; Indices[8] = 11;
		Indices[9] = 7; Indices[10] =  3; Indices[11] =  1;
		Indices[12] = 0; Indices[13] =  5; Indices[14] =  10;
		Indices[15] = 3; Indices[16] =  9; Indices[17] =  6;
		Indices[18] = 3; Indices[19] =  11; Indices[20] = 9;
		Indices[21] = 8; Indices[22] =  6; Indices[23] =  4;
		Indices[24] = 2; Indices[25] =  4; Indices[26] =  9;
		Indices[27] = 3; Indices[28] =  7; Indices[29] =  11;
		Indices[30] = 4; Indices[31] =  2; Indices[32] =  0;
		Indices[33] = 9; Indices[34] =  4; Indices[35] =  6;
		Indices[36] = 2; Indices[37] =  11; Indices[38] = 5;
		Indices[39] = 0; Indices[40] =  10; Indices[41] = 8;
		Indices[42] = 5; Indices[43] =  0; Indices[44] =  2;
		Indices[45] = 10; Indices[46] = 5; Indices[47] =  7;
		Indices[48] = 1; Indices[49] =  6; Indices[50] =  8;
		Indices[51] = 1; Indices[52] =  8; Indices[53] =  10;
		Indices[54] = 6; Indices[55] =  1; Indices[56] =  3;
		Indices[57] = 11; Indices[58] = 7; Indices[59] =  5;
	}

	virtual void OnRegisterSceneNode() {
		if (IsVisible)
			SceneManager->registerNodeForRendering(this);
		ISceneNode::OnRegisterSceneNode();
	}

	virtual void render() {
		irr::video::IVideoDriver* driver = SceneManager->getVideoDriver();

		driver->setMaterial(Material);
		driver->setTransform(irr::video::ETS_WORLD, AbsoluteTransformation);
		driver->drawVertexPrimitiveList(
			&Vertices[0], 12,
			&Indices[0], 20,
			irr::video::EVT_STANDARD,
			irr::scene::EPT_TRIANGLES,
			irr::video::EIT_16BIT);
	}

	virtual const irr::core::aabbox3d<irr::f32>& getBoundingBox() const {
		return Box;
	}

	virtual irr::u32 getMaterialCount() const {
		return 1;
	}

	virtual irr::video::SMaterial& getMaterial(irr::u32 i) {
		return Material;
	}
};

int main(int ac, char** av) {
	irr::IrrlichtDevice* device = irr::createDevice(
		irr::video::EDT_OPENGL,
		irr::core::dimension2d<irr::u32>(640, 480));
	if (!device)
		return -1;

	device->setWindowCaption(L"IcoSceneNode + Arcball - Demo");

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

	{
		irr::scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(
			0,
			irr::core::vector3df(0, 0, 4),
			irr::core::vector3df(0, 0, 0));
		if (camera) {
			irr::scene::CSceneNodeAnimatorCameraArcball* snaca =
				new irr::scene::CSceneNodeAnimatorCameraArcball(
					device->getCursorControl(),
					camera);
			irr::scene::ISceneNodeAnimator* anim = dynamic_cast<irr::scene::ISceneNodeAnimator*>(snaca);
			camera->addAnimator(anim);
			anim->drop();
		}
	}

	CIcoSceneNode* ico = new CIcoSceneNode(smgr->getRootSceneNode(), smgr, 666);
	ico->drop();

	while (device->run()) {
		driver->beginScene(true, true, irr::video::SColor(0, 100, 100, 100));
		smgr->drawAll();
		driver->endScene();
	}
	device->drop();
	return 0;
}
anirul
Posts: 30
Joined: Wed Oct 07, 2009 6:03 am
Location: Geneva
Contact:

Some fixes

Post by anirul »

Now it should work as long as the camera is NOT on the Y axe and whichever the center is.

Code: Select all

void irr::scene::CSceneNodeAnimatorCameraArcball::animateNode(ISceneNode *node, u32 timeMs)
{
	if (!node || node->getType() != ESNT_CAMERA)
		return;

	if (!mouseDrag)
		return;

	if (!Camera->isInputReceiverEnabled())
		return;

	ISceneManager* smgr = Camera->getSceneManager();
	if (smgr && smgr->getActiveCamera() != Camera)
		return;

	core::vector3df target = Camera->getTarget();
	core::vector3df pos; // = Camera->getPosition() - target;
	core::vector3df up; // = Camera->getUpVector();
	core::quaternion quat;
	quat.makeIdentity();

	// compute the new quaternion
	if (CursorControl) {
		core::position2df CursorPos = CursorControl->getRelativePosition();
		core::vector3df from = position2vector(CursorDown);
		core::vector3df to = position2vector(CursorPos);
		quat.rotationFromTo(from, to);
		CursorDown = CursorPos;
	}

	// should solve some camera miss placement effect
	core::vector3df begin(0, 0, -1);
	core::quaternion quatAxe;
	quatAxe.rotationFromTo(CamOri, begin);

	// increment the movement quaternion
	QuatOri = quat * QuatOri;

	// move position into the origine Axes
	pos = quatAxe * CamOri;
	up = quatAxe * CamUp;

	// move position with the incremented quaternion
	pos = QuatOri * pos;
	up = QuatOri * up;

	// inverte the quatAxe
	quatAxe.makeInverse();

	// put it back into the position coordinates
	pos = quatAxe * pos;
	up = quatAxe * up;

	// move the center according to target
	Camera->setPosition(pos + target);
	Camera->setUpVector(up);
}
netpipe
Posts: 670
Joined: Fri Jun 06, 2008 12:50 pm
Location: Edmonton, Alberta, Canada
Contact:

Post by netpipe »

awesome! , was going to try and fix this but you did it first :)
Live long and phosphor!
-- https://github.com/netpipe/Luna Game Engine Status 95%
anirul
Posts: 30
Joined: Wed Oct 07, 2009 6:03 am
Location: Geneva
Contact:

fix

Post by anirul »

There is still some bugs like the fact that if you place the camera on the Y axis it fucks things. I also notices that in case of many animator you got weird reactions, the order of animator is maybe the cause and then the arcball should be attached first.

Cool somebody look at it. I'm really hoping this can help! Maybe this could be a nice addition to the irrlicht library having a arcball camera is a feature you can find in many 3D libraries around.
Josie
Posts: 44
Joined: Sat Jul 25, 2009 4:08 am
Location: Griffin, Georgia, US
Contact:

Post by Josie »

Excellent work.
Post Reply