ArcBall in mogre

andyhebear1

05-01-2010 13:47:00

/*********************************************************
* Time :2009-12-30
* Author:Rains
* QQ :233685340
* Memo : And especially thanks to the original author "Bradley Smith" for help
* the source come from http://rainwarrior.thenoos.net/dragon/arcball.html
* and I convert to C# and using it in Mogre,My Web is http://hi.baidu.com/andyhebear
* Welcome to AC with me!
* 1:rolate the object旋转物体
* 2:rolate the camera旋转摄象机

**********************************************************/

using System;
using System.Collections.Generic;
using System.Text;

using Mogre;

namespace IRobotQ {
public class ArcBallOgre {
protected Camera mCamera;
protected SceneNode mTarget;
protected Matrix4 mVPInverse; // ( Projection * View ).Inverse()
protected Quaternion mQLast, mQNext, mQCombinedResult;
protected Vector3 mVStart, mVCurrent;
protected Vector3 mRealEye; // eye real world position
protected Vector3 mEye; // eye vector relative to center
protected float mSphere; // sphere radius
protected float mSphere2; // square of sphere radius
protected Vector3 mCenter; // sphere center, in world coordinate
protected float mZoom; // store eye distance
protected float mZoom2; // square of zoom
protected Vector3 mEyeDir; // distance to eye
protected float mEdge; // plane of visible edge
protected bool mIfFlipResult; // be used when t < 0
protected bool mIsDragging;
protected SceneNode m_CameraCenter;

public ArcBallOgre(SceneManager mgr) {
// Arcball::Arcball()
//: mCamera(0), mTarget(0)
//, mQLast(Quaternion::IDENTITY)
//, mQNext(Quaternion::IDENTITY)
//, mQCombinedResult(Quaternion::IDENTITY)
//, mVStart(Vector3::ZERO), mVCurrent(Vector3::ZERO)
//, mRealEye(Vector3::ZERO), mEye(Vector3::ZERO)
//, mSphere(0.0), mSphere2(0.0), mCenter(Vector3::ZERO)
//, mZoom(0.0), mZoom2(0.0)
//, mEyeDir(Vector3::ZERO), mEdge(0.0), mIfFlipResult(false), mIsDragging(false)
//{}
this.m_CameraCenter = mgr.CreateSceneNode();
}
//
public void setZoom(Camera cam, SceneNode target) {
mCamera = cam;
mTarget = target;
//创建一个节点
this.m_CameraCenter.Position = target.Position;
this.m_CameraCenter.Orientation = target.Orientation;
this.m_CameraCenter.AttachObject(cam);
}
public void reset() {
mQLast = Quaternion.IDENTITY;
mQCombinedResult = Quaternion.IDENTITY;
}
public void start(float mx, float my) {
mIsDragging = true;
update();
// saves a copy of the current rotation for comparison
mQLast = mTarget.Orientation;
mVStart = sphereCoords(mx, my);
}
public void move(float mx, float my) {
//ab_curr = sphere_coords( mx, my );
mVCurrent = sphereCoords(mx, my);

if (mVCurrent == mVStart) {
// avoid potential rare divide by tiny
mQCombinedResult = mQLast;
mTarget.Orientation = mQCombinedResult;

return;
}

// use a dot product to get the angle between them
// use a cross product to get the vector to rotate around
float cos2a = mVStart.DotProduct(mVCurrent);
// I have encounter the problem, numerical problem
if (cos2a >= 1.0) {
mQCombinedResult = mQLast;
mTarget.Orientation = mQCombinedResult;

return;
}
// double-angle formulas (Trigonometric functions)
float sina = Mogre.Math.Sqrt((1.0f - cos2a) * 0.5f);
float cosa = Mogre.Math.Sqrt((1.0f + cos2a) * 0.5f);
Vector3 cross = (mVStart.CrossProduct(mVCurrent).NormalisedCopy) * sina;

// just in case @@
if (sina <= (1e-06 * 1e-06)) { // 1e-06 * 1e-06 from Vector3::isZeroLength
mQCombinedResult = mQLast;
mTarget.Orientation = mQCombinedResult;

return;
}

if (mIfFlipResult)
cross = cross * -1.0f;

mQNext.x = cross.x;
mQNext.y = cross.y;
mQNext.z = cross.z;
mQNext.w = cosa;

// final, object's orientation = (quatNext * quatLast) * vertex_pos
// multiply quatLast first!
mQCombinedResult = mQNext * mQLast;

mTarget.Orientation = mQCombinedResult;

}

public Quaternion getCombinedResult() { return mQCombinedResult; }
public Quaternion getCurrent() { return mQNext; }
public Quaternion getLastTime() { return mQLast; }

// States
public bool isDragging() { return mIsDragging; }
public void endDrag() {
this.m_CameraCenter.RemoveAllChildren();
mIsDragging = false;
}
//

protected void update() {
Vector3 s = mTarget._getWorldAABB().Size;
mSphere = 0.5f * (System.Math.Max(s.x, System.Math.Max(s.y, s.z)));

// if sphere radius too small, use this instead
if (mSphere < 0.0001f)
mSphere = 0.0001f;

mSphere2 = mSphere * mSphere;
mCenter = mTarget._getWorldAABB().Center;

mRealEye = mCamera.RealPosition;
mEye = mRealEye - mCenter;

// just in case
if (mEye.IsZeroLength) {
mEye.x += 0.0001f;
mEye.y += 0.0001f;
mEye.z += 0.0001f;
}

mZoom2 = mEye.DotProduct(mEye);
mZoom = Mogre.Math.Sqrt(mZoom2);

mEyeDir = mEye * (1.0f / mZoom);
mEdge = mSphere2 / mZoom;

mVPInverse = (mCamera.ProjectionMatrix * mCamera.GetViewMatrix(true)).Inverse();

mIfFlipResult = false;
}

// Helper Functions
protected Vector3 sphereCoords(float mx, float my) {
//assert(mx <= 1.0 && my <= 1.0);//修改为下面方法
if (mx <= 1f && my <= 1f) {
//mx = 1f;
//my = 1f;
}
Vector3 objCoordinate;
// perform inverse of viewport transform, for transforming objCoordinate
// back to homogenous clip space
objCoordinate.x = (2.0f * mx) - 1.0f;
objCoordinate.y = 1.0f - (2.0f * my);
objCoordinate.z = 0.0f; //

// convert objCoordinate from "homogenous clip space" to "world space"
//
// especially note that the multiply is [4x4] * [3x1], ogre Matrix4 class
// defined the operation, you could check what it was done internally
objCoordinate = mVPInverse * objCoordinate;

// relative to sphere center
objCoordinate -= mCenter;

Vector3 m = objCoordinate - mEye;

// mouse position represents ray: eye + t*m
// intersecting with a sphere centered at the origin
//
// Formula :
// P(t) = S + tV -- (1), the ray, S, V belong to R3, t belongs to R
// (X - Cx)^2 + (Y - Cy)^2 + (Z - Cz)^2 = r^2 -- (2)
//
// inorder to let things simple, we subtract (Cx, Cy, Cz) from S first
// => P(t) = S' + tV -- (3), and S' = S - (Cx, Cy, Cz)
//
// (3) => (2)
// (V^2)t^2 + (2*S'V)t + (S'^2 - r^2) = 0, then find t

float a = m.DotProduct(m); // = m*m;
// eyePos' = eyePos - (Cx, Cy, Cz)
float b = mEye.DotProduct(m); // (ab_eye*m);
float root = (b * b) - a * (mZoom2 - mSphere2);
if (root <= 0.0)
return edgeCoords(m);
float t = (0.0f - b - Mogre.Math.Sqrt(root)) / a;

// this will happen if the eye inside the object's radius
if (t < 0.0)
mIfFlipResult = true;

return (mEye + (m * t)).NormalisedCopy;
}
protected Vector3 edgeCoords(Vector3 m) {
// find the intersection of the edge plane and the ray

float t = (mEdge - mZoom) / mEyeDir.DotProduct(m);
Vector3 a = mEye + (m * t);
// find the direction of the eye-axis from that point
// along the edge plane
Vector3 c = (mEyeDir * mEdge) - a;

// find the intersection of the sphere with the ray going from
// the plane outside the sphere toward the eye-axis.
float ac = a.DotProduct(c);
float c2 = c.DotProduct(c);
float q = (0.0f - ac - Mogre.Math.Sqrt(ac * ac - c2 * (a.DotProduct(a) - mSphere2))) / c2;

return (a + (c * q)).NormalisedCopy;
}

}

}

use it like that
* if( ms.buttonDown( MB_Left ) ) {
if( false == mArcball2.isDragging() ) {
mArcball2.start(
Real( ms.X.abs ) / Real( vp->getActualWidth() ),
Real( ms.Y.abs ) / Real( vp->getActualHeight() ) );
}
else {
// pass in normalised mouse position
mArcball2.move(
Real( ms.X.abs ) / Real( vp->getActualWidth() ),
Real( ms.Y.abs ) / Real( vp->getActualHeight() ) );
}
} else {
if( mArcball2.isDragging() ) {
mArcball2.endDrag();
}
}

* and when select the scenenode 物体选中的时候
* mArcball2.reset();
mArcball2.setZoom( mCamera, mOgreHead );

smiley80

06-01-2010 15:53:39

Wow, good job. That's pretty useful.