本文提供一个本人编写的轨迹球类(ArcBall.cs),它可以直接应用到任何 camera 下,还可以同时实现缩放和平移。 工程源代码在文末。
1.轨迹球原理:
上面是我黑来的两张图,拿来说明轨迹球的原理。看左边这个,网格代表绘制 3D 模型的窗口,上面放了个半球,这个球就是轨迹球。 假设鼠标在网格上的某点 A,过 A 点作网格所在平面的垂线,与半球相交于点 P, P 就是 A 在轨迹球上的投影。鼠标从 A1 点沿直线移动到 A2 点,对应着轨迹球上的点 P1 沿球面移动到了P2。那么,从球心 O 到 P1 和 P2 分别有两个向量 OP1 和 OP2。 OP1 旋转到了 OP2,我们就认为是模型也按照这个方式作同样的旋转。这就是轨迹球的旋转思路。右边这个图没用上...
2.轨迹球实现
实现轨迹球,首先要求出鼠标点 A1、 A2 投影到轨迹球上的点 P1、 P2 的坐标,然后计算两个向量 A1P1 和 A2P2 之间的夹角以及旋转轴,最后让模型按照求出的夹角和旋转轴,调用glRotate 就可以了
如图所示,红绿蓝三色箭头的交点是摄像机 eye 的位置,红色箭头指向 center 的位置,绿色 箭头指向 up 的位置,蓝色箭头指向右侧。说明: 1.Up 是可能在蓝色 Right 箭头的垂面内的任意方向的,这里我们要把它调整为与红色视线垂直的 Up,即上图所示的 Up。 2.绿色和蓝色箭头组成的平面即为程序窗口所在位置,因为 Eye 就在这里嘛。而且 Up 指的就是屏幕正上方, Right 指就是屏幕正右方。 3.显然轨迹球的半球在图中矩形所在这一侧,球心就是 Eye。
鼠标在 Up 和 Right 所在的平面移动,当它位于 A 点时, 投影到轨迹球的点 P。现在已知的是 Eye、 Center、原始 Up、 A 点在屏幕上的坐标、 向量 Eye-P 的长度、向量 AP 的长度。现在要求 P 点的坐标,只不过是一个数学问题了。
参考源码(C#)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using SharpGL.SceneGraph.Cameras;
using SharpGL.SceneGraph;
using SharpGL.SceneGraph.Lighting;
using SharpGL;
using SharpGL.SceneGraph.Core;
using SharpGL.SceneGraph.Assets;
using SharpGL.SceneGraph.Quadrics;
using SharpGL.SceneGraph.Effects;
namespace SharpGLWinformsApplication1
{
public partial class FormMain : Form
{
private ArcBallEffect objectArcBallEffect;
private ArcBallEffect axisArcBallEffect;
public FormMain()
{
InitializeComponent();
this.sceneControl1.MouseWheel += sceneControl1_MouseWheel;
}
void sceneControl1_MouseWheel(object sender, MouseEventArgs e)
{
objectArcBallEffect.ArcBall.Scale -= e.Delta * 0.001f;
}
const float near = 0.01f;
const float far = 10000;
private bool mouseDownFlag;
private void InitElements(Scene scene)
{
var objectRoot = new SharpGL.SceneGraph.Primitives.Folder() { Name = "Root" };
scene.SceneContainer.AddChild(objectRoot);
// This implements free rotation(with translation and rotation).
var camera = GetCamera();
objectArcBallEffect = new ArcBallEffect(
camera.Position.X, camera.Position.Y, camera.Position.Z,
camera.Target.X, camera.Target.Y, camera.Target.Z,
camera.UpVector.X, camera.UpVector.Y, camera.UpVector.Z);
objectRoot.AddEffect(objectArcBallEffect);
var axisRoot = new SharpGL.SceneGraph.Primitives.Folder() { Name = "axis root" };
scene.SceneContainer.AddChild(axisRoot);
axisArcBallEffect = new ArcBallEffect(camera.Position.X,
camera.Position.Y, camera.Position.Z,
camera.Target.X, camera.Target.Y, camera.Target.Z,
camera.UpVector.X, camera.UpVector.Y, camera.UpVector.Z);
axisRoot.AddEffect(axisArcBallEffect);
InitLight(objectRoot);
InitAxis(objectRoot);
InitAxis(axisRoot);
InitFrameElement(6, 24, 7, objectRoot);
InitGridElement(1.5f, 3, 0, 3, 24, objectRoot);
}
private void InitGridElement(float x, float y, float z, float width, float length, SceneElement parent)
{
var folder = new SharpGL.SceneGraph.Primitives.Folder() { Name = "Grid" };
parent.AddChild(folder);
var grid = new GridElement(x, y, z, width, length);
folder.AddChild(grid);
}
private void InitFrameElement(int width, int length, int height, SceneElement parent)
{
var folder = new SharpGL.SceneGraph.Primitives.Folder() { Name = "Frame" };
parent.AddChild(folder);
var frame = new FrameElement(width, length, height);
folder.AddChild(frame);
}
private void InitAxis(SceneElement parent)
{
var folder = new SharpGL.SceneGraph.Primitives.Folder() { Name = "Axis" };
parent.AddChild(folder);
// X轴
Material red = new Material();
red.Emission = Color.Red;
red.Diffuse = Color.Red;
Cylinder x1 = new Cylinder() { Name = "X1" };
x1.BaseRadius = 0.05;
x1.TopRadius = 0.05;
x1.Height = 1.5;
x1.Transformation.RotateY = 90f;
x1.Material = red;
folder.AddChild(x1);
Cylinder x2 = new Cylinder() { Name = "X2" };
x2.BaseRadius = 0.1;
x2.TopRadius = 0;
x2.Height = 0.2;
x2.Transformation.TranslateX = 1.5f;
x2.Transformation.RotateY = 90f;
x2.Material = red;
folder.AddChild(x2);
// Y轴
Material green = new Material();
green.Emission = Color.Green;
green.Diffuse = Color.Green;
Cylinder y1 = new Cylinder() { Name = "Y1" };
y1.BaseRadius = 0.05;
y1.TopRadius = 0.05;
y1.Height = 1.5;
y1.Transformation.RotateX = -90f;
y1.Material = green;
folder.AddChild(y1);
Cylinder y2 = new Cylinder() { Name = "Y2" };
y2.BaseRadius = 0.1;
y2.TopRadius = 0;
y2.Height = 0.2;
y2.Transformation.TranslateY = 1.5f;
y2.Transformation.RotateX = -90f;
y2.Material = green;
folder.AddChild(y2);
// Z轴
Material blue = new Material();
blue.Emission = Color.Blue;
blue.Diffuse = Color.Blue;
Cylinder z1 = new Cylinder() { Name = "Z1" };
z1.BaseRadius = 0.05;
z1.TopRadius = 0.05;
z1.Height = 1.5;
z1.Material = blue;
folder.AddChild(z1);
Cylinder z2 = new Cylinder() { Name = "Z2" };
z2.BaseRadius = 0.1;
z2.TopRadius = 0;
z2.Height = 0.2;
z2.Transformation.TranslateZ = 1.5f;
z2.Material = blue;
folder.AddChild(z2);
}
private void InitLight(SceneElement parent)
{
Light light1 = new Light()
{
Name = "Light 1",
On = true,
Position = new Vertex(-9, -9, 11),
GLCode = OpenGL.GL_LIGHT0
};
Light light2 = new Light()
{
Name = "Light 2",
On = true,
Position = new Vertex(9, -9, 11),
GLCode = OpenGL.GL_LIGHT1
};
Light light3 = new Light()
{
Name = "Light 3",
On = true,
Position = new Vertex(0, 15, 15),
GLCode = OpenGL.GL_LIGHT2
};
var folder = new SharpGL.SceneGraph.Primitives.Folder() { Name = "Lights" };
parent.AddChild(folder);
folder.AddChild(light1);
folder.AddChild(light2);
folder.AddChild(light3);
}
private void sceneControl1_MouseDown(object sender, MouseEventArgs e)
{
this.mouseDownFlag = true;
objectArcBallEffect.ArcBall.SetBounds(this.sceneControl1.Width, this.sceneControl1.Height);
objectArcBallEffect.ArcBall.MouseDown(e.X, e.Y);
axisArcBallEffect.ArcBall.SetBounds(this.sceneControl1.Width, this.sceneControl1.Height);
axisArcBallEffect.ArcBall.MouseDown(e.X, e.Y);
}
private void sceneControl1_MouseMove(object sender, MouseEventArgs e)
{
objectArcBallEffect.ArcBall.MouseMove(e.X, e.Y);
axisArcBallEffect.ArcBall.MouseMove(e.X, e.Y);
}
private void sceneControl1_MouseUp(object sender, MouseEventArgs e)
{
this.mouseDownFlag = false;
objectArcBallEffect.ArcBall.MouseUp(e.X, e.Y);
axisArcBallEffect.ArcBall.MouseUp(e.X, e.Y);
}
private void sceneControl1_KeyDown(object sender, KeyEventArgs e)
{
const float interval = 1;
if (e.KeyCode == Keys.W || e.KeyCode == Keys.Up)
{
this.objectArcBallEffect.ArcBall.GoUp(interval);
}
else if (e.KeyCode == Keys.S || e.KeyCode == Keys.Down)
{
this.objectArcBallEffect.ArcBall.GoDown(interval);
}
else if (e.KeyCode == Keys.A || e.KeyCode == Keys.Left)
{
this.objectArcBallEffect.ArcBall.GoLeft(interval);
}
else if (e.KeyCode == Keys.D || e.KeyCode == Keys.Right)
{
this.objectArcBallEffect.ArcBall.GoRight(interval);
}
}
private LookAtCamera GetCamera()
{
return this.sceneControl1.Scene.CurrentCamera as LookAtCamera;
}
private void FormMain_Resize(object sender, EventArgs e)
{
this.objectArcBallEffect.ArcBall.SetBounds(this.sceneControl1.Width, this.sceneControl1.Height);
var gl = this.sceneControl1.OpenGL;
var axis = gl.UnProject(50, 50, 0.1);
axisArcBallEffect.ArcBall.SetTranslate(axis[0], axis[1], axis[2]);
axisArcBallEffect.ArcBall.Scale = 0.001f;
}
private void sceneControl1_OpenGLInitialized(object sender, EventArgs e)
{
var scene = this.sceneControl1.Scene;
scene.SceneContainer.Children.Clear();
scene.RenderBoundingVolumes = false;
// 设置视角
var camera = GetCamera();
camera.Near = near;
camera.Far = far;
camera.Position = new Vertex(12.5f, -1.5f, 11.5f);
camera.Target = new Vertex(4.5f, 7, 2.5f);
camera.UpVector = new Vertex(0.000f, 0.000f, 1.000f);
InitElements(scene);
axisArcBallEffect.ArcBall.SetTranslate(
12.490292456095853f, -1.5011389593859834f, 11.489356270615454f);
axisArcBallEffect.ArcBall.Scale = 0.001f;
}
private void FormMain_Load(object sender, EventArgs e)
{
}
}
}