【OpenGL(SharpGL)】支持任意相机可平移缩放的轨迹球实现

阅读目录(Content)

2016-07-08
2016-02-10
1. 轨迹球原理
2. 轨迹球实现1) 计算投影点2) 计算夹角和旋转轴3. 额外功能实现

【OpenGL(SharpGL)】支持任意相机可平移缩放的轨迹球

(本文PDF版在这里。)

在3D程序中,轨迹球(ArcBall)可以让你只用鼠标来控制模型(旋转),便于观察。在这里(http://www.yakergong.net/nehe/ )有nehe的轨迹球教程。

本文提供一个本人编写的轨迹球类(ArcBall.cs),它可以直接应用到任何camera下,还可以同时实现缩放和平移。工程源代码在文末。
回到顶部(go to top)
2016-07-08

再次更新了轨迹球代码,重命名为ArcBallManipulater。
复制代码

1 ///
2 /// Rotate model using arc-ball method.
3 ///
4 public class ArcBallManipulater : Manipulater, IMouseHandler
5 {
6
7 private ICamera camera;
8 private GLCanvas canvas;
9
10 private MouseEventHandler mouseDownEvent;
11 private MouseEventHandler mouseMoveEvent;
12 private MouseEventHandler mouseUpEvent;
13 private MouseEventHandler mouseWheelEvent;
14
15 private vec3 _vectorRight;
16 private vec3 _vectorUp;
17 private vec3 _vectorBack;
18 private float _length, _radiusRadius;
19 private CameraState cameraState = new CameraState();
20 private mat4 totalRotation = mat4.identity();
21 private vec3 _startPosition, _endPosition, _normalVector = new vec3(0, 1, 0);
22 private int _width;
23 private int _height;
24 private bool mouseDownFlag;
25
26 public float MouseSensitivity { get; set; }
27
28 public MouseButtons BindingMouseButtons { get; set; }
29 private MouseButtons lastBindingMouseButtons;
30
31 ///
32 /// Rotate model using arc-ball method.
33 ///
34 ///
35 public ArcBallManipulater(MouseButtons bindingMouseButtons = MouseButtons.Left)
36 {
37 this.MouseSensitivity = 0.1f;
38 this.BindingMouseButtons = bindingMouseButtons;
39
40 this.mouseDownEvent = new MouseEventHandler(((IMouseHandler)this).canvas_MouseDown);
41 this.mouseMoveEvent = new MouseEventHandler(((IMouseHandler)this).canvas_MouseMove);
42 this.mouseUpEvent = new MouseEventHandler(((IMouseHandler)this).canvas_MouseUp);
43 this.mouseWheelEvent = new MouseEventHandler(((IMouseHandler)this).canvas_MouseWheel);
44 }
45
46 private void SetCamera(vec3 position, vec3 target, vec3 up)
47 {
48 _vectorBack = (position - target).normalize();
49 _vectorRight = up.cross(_vectorBack).normalize();
50 _vectorUp = _vectorBack.cross(_vectorRight).normalize();
51
52 this.cameraState.position = position;
53 this.cameraState.target = target;
54 this.cameraState.up = up;
55 }
56
57 class CameraState
58 {
59 public vec3 position;
60 public vec3 target;
61 public vec3 up;
62
63 public bool IsSameState(ICamera camera)
64 {
65 if (camera.Position != this.position) { return false; }
66 if (camera.Target != this.target) { return false; }
67 if (camera.UpVector != this.up) { return false; }
68
69 return true;
70 }
71 }
72
73 public mat4 GetRotationMatrix()
74 {
75 return totalRotation;
76 }
77
78 public override void Bind(ICamera camera, GLCanvas canvas)
79 {
80 if (camera == null || canvas == null) { throw new ArgumentNullException(); }
81
82 this.camera = camera;
83 this.canvas = canvas;
84
85 canvas.MouseDown += this.mouseDownEvent;
86 canvas.MouseMove += this.mouseMoveEvent;
87 canvas.MouseUp += this.mouseUpEvent;
88 canvas.MouseWheel += this.mouseWheelEvent;
89
90 SetCamera(camera.Position, camera.Target, camera.UpVector);
91 }
92
93 public override void Unbind()
94 {
95 if (this.canvas != null && (!this.canvas.IsDisposed))
96 {
97 this.canvas.MouseDown -= this.mouseDownEvent;
98 this.canvas.MouseMove -= this.mouseMoveEvent;
99 this.canvas.MouseUp -= this.mouseUpEvent;
100 this.canvas.MouseWheel -= this.mouseWheelEvent;
101 this.canvas = null;
102 this.camera = null;
103 }
104 }
105
106 void IMouseHandler.canvas_MouseWheel(object sender, MouseEventArgs e)
107 {
108 }
109
110 void IMouseHandler.canvas_MouseDown(object sender, MouseEventArgs e)
111 {
112 this.lastBindingMouseButtons = this.BindingMouseButtons;
113 if ((e.Button & this.lastBindingMouseButtons) != MouseButtons.None)
114 {
115 var control = sender as Control;
116 this.SetBounds(control.Width, control.Height);
117
118 if (!cameraState.IsSameState(this.camera))
119 {
120 SetCamera(this.camera.Position, this.camera.Target, this.camera.UpVector);
121 }
122
123 this._startPosition = GetArcBallPosition(e.X, e.Y);
124
125 mouseDownFlag = true;
126 }
127 }
128
129 private void SetBounds(int width, int height)
130 {
131 this._width = width; this._height = height;
132 _length = width > height ? width : height;
133 var rx = (width / 2) / _length;
134 var ry = (height / 2) / _length;
135 _radiusRadius = (float)(rx * rx + ry * ry);
136 }
137
138 void IMouseHandler.canvas_MouseMove(object sender, MouseEventArgs e)
139 {
140 if (mouseDownFlag && ((e.Button & this.lastBindingMouseButtons) != MouseButtons.None))
141 {
142 if (!cameraState.IsSameState(this.camera))
143 {
144 SetCamera(this.camera.Position, this.camera.Target, this.camera.UpVector);
145 }
146
147 this._endPosition = GetArcBallPosition(e.X, e.Y);
148 var cosAngle = _startPosition.dot(_endPosition) / (_startPosition.length() * _endPosition.length());
149 if (cosAngle > 1.0f) { cosAngle = 1.0f; }
150 else if (cosAngle < -1) { cosAngle = -1.0f; }
151 var angle = MouseSensitivity * (float)(Math.Acos(cosAngle) / Math.PI * 180);
152 _normalVector = _startPosition.cross(_endPosition).normalize();
153 if (!
154 ((_normalVector.x == 0 && _normalVector.y == 0 && _normalVector.z == 0)
155 || float.IsNaN(_normalVector.x) || float.IsNaN(_normalVector.y) || float.IsNaN(_normalVector.z)))
156 {
157 _startPosition = _endPosition;
158
159 mat4 newRotation = glm.rotate(angle, _normalVector);
160 this.totalRotation = newRotation * totalRotation;
161 }
162 }
163 }
164
165 private vec3 GetArcBallPosition(int x, int y)
166 {
167 float rx = (x - _width / 2) / _length;
168 float ry = (_height / 2 - y) / _length;
169 float zz = _radiusRadius - rx * rx - ry * ry;
170 float rz = (zz > 0 ? (float)Math.Sqrt(zz) : 0.0f);
171 var result = new vec3(
172 rx * _vectorRight.x + ry * _vectorUp.x + rz * _vectorBack.x,
173 rx * _vectorRight.y + ry * _vectorUp.y + rz * _vectorBack.y,
174 rx * _vectorRight.z + ry * _vectorUp.z + rz * _vectorBack.z
175 );
176 //var position = new vec3(rx, ry, rz);
177 //var matrix = new mat3(_vectorRight, _vectorUp, _vectorBack);
178 //result = matrix * position;
179
180 return result;
181 }
182
183 void IMouseHandler.canvas_MouseUp(object sender, MouseEventArgs e)
184 {
185 if ((e.Button & this.lastBindingMouseButtons) != MouseButtons.None)
186 {
187 mouseDownFlag = false;
188 }
189 }
190
191 }

复制代码

注意,在GetArcBallPosition(int x, int y);中,获取位置实际上是一个坐标变换的过程,所以可以用矩阵*向量实现。详见被注释掉的代码。
复制代码

1 private vec3 GetArcBallPosition(int x, int y)
2 {
3 float rx = (x - _width / 2) / _length;
4 float ry = (_height / 2 - y) / _length;
5 float zz = _radiusRadius - rx * rx - ry * ry;
6 float rz = (zz > 0 ? (float)Math.Sqrt(zz) : 0.0f);
7 var result = new vec3(
8 rx * _vectorRight.x + ry * _vectorUp.x + rz * _vectorBack.x,
9 rx * _vectorRight.y + ry * _vectorUp.y + rz * _vectorBack.y,
10 rx * _vectorRight.z + ry * _vectorUp.z + rz * _vectorBack.z
11 );
12 // Get position using matrix * vector.
13 //var position = new vec3(rx, ry, rz);
14 //var matrix = new mat3(_vectorRight, _vectorUp, _vectorBack);
15 //result = matrix * position;
16
17 return result;
18 }

复制代码

回到顶部(go to top)
2016-02-10

我已在CSharpGL中集成了最新的轨迹球代码。轨迹球只负责旋转。
复制代码

1 using GLM;
2 using System;
3 using System.Collections.Generic;
4 using System.Diagnostics;
5 using System.Drawing;
6 using System.IO;
7 using System.Linq;
8 using System.Text;
9 using System.Threading.Tasks;
10
11 namespace CSharpGL.Objects.Cameras
12 {
13 ///
14 /// 用鼠标旋转模型。
15 ///
16 public class ArcBallRotator
17 {
18 vec3 _vectorCenterEye;
19 vec3 _vectorUp;
20 vec3 _vectorRight;
21 float _length, _radiusRadius;
22 CameraState cameraState = new CameraState();
23 mat4 totalRotation = mat4.identity();
24 vec3 _startPosition, _endPosition, _normalVector = new vec3(0, 1, 0);
25 int _width;
26 int _height;
27
28 float mouseSensitivity = 0.1f;
29
30 public float MouseSensitivity
31 {
32 get { return mouseSensitivity; }
33 set { mouseSensitivity = value; }
34 }
35
36 ///
37 /// 标识鼠标是否按下
38 ///
39 public bool MouseDownFlag { get; private set; }
40
41 ///
42 ///
43 ///
44 public ICamera Camera { get; set; }
45
46
47 const string listenerName = “ArcBallRotator”;
48
49 ///
50 /// 用鼠标旋转模型。
51 ///
52 /// 当前场景所用的摄像机。
53 public ArcBallRotator(ICamera camera)
54 {
55 this.Camera = camera;
56
57 SetCamera(camera.Position, camera.Target, camera.UpVector);
58 #if DEBUG
59 const string filename = “ArcBallRotator.log”;
60 if (File.Exists(filename)) { File.Delete(filename); }
61 Debug.Listeners.Add(new TextWriterTraceListener(filename, listenerName));
62 Debug.WriteLine(DateTime.Now, listenerName);
63 Debug.Flush();
64 #endif
65 }
66
67 private void SetCamera(vec3 position, vec3 target, vec3 up)
68 {
69 _vectorCenterEye = position - target;
70 _vectorCenterEye.Normalize();
71 _vectorUp = up;
72 _vectorRight = _vectorUp.cross(_vectorCenterEye);
73 _vectorRight.Normalize();
74 _vectorUp = _vectorCenterEye.cross(_vectorRight);
75 _vectorUp.Normalize();
76
77 this.cameraState.position = position;
78 this.cameraState.target = target;
79 this.cameraState.up = up;
80 }
81
82 class CameraState
83 {
84 public vec3 position;
85 public vec3 target;
86 public vec3 up;
87
88 public bool IsSameState(ICamera camera)
89 {
90 if (camera.Position != this.position) { return false; }
91 if (camera.Target != this.target) { return false; }
92 if (camera.UpVector != this.up) { return false; }
93
94 return true;
95 }
96 }
97
98 public void SetBounds(int width, int height)
99 {
100 this._width = width; this._height = height;
101 _length = width > height ? width : height;
102 var rx = (width / 2) / _length;
103 var ry = (height / 2) / _length;
104 _radiusRadius = (float)(rx * rx + ry * ry);
105 }
106
107 ///
108 /// 必须先调用()方法。
109 ///
110 ///
111 ///
112 public void MouseDown(int x, int y)
113 {
114 Debug.WriteLine("");
115 Debug.WriteLine("=================>MouseDown:", listenerName);
116 if (!cameraState.IsSameState(this.Camera))
117 {
118 SetCamera(this.Camera.Position, this.Camera.Target, this.Camera.UpVector);
119 Debug.WriteLine(string.Format(
120 “update camera state: {0}, {1}, {2}”,
121 this.cameraState.position, this.cameraState.target, this.cameraState.up), listenerName);
122 }
123
124 this._startPosition = GetArcBallPosition(x, y);
125 Debug.WriteLine(string.Format(“Start position: {0}”, this._startPosition), listenerName);
126
127 MouseDownFlag = true;
128
129 Debug.WriteLine("-------------------MouseDown end.", listenerName);
130 }
131
132 private vec3 GetArcBallPosition(int x, int y)
133 {
134 var rx = (x - _width / 2) / _length;
135 var ry = (_height / 2 - y) / _length;
136 var zz = _radiusRadius - rx * rx - ry * ry;
137 var rz = (zz > 0 ? Math.Sqrt(zz) : 0);
138 var result = new vec3(
139 (float)(rx * _vectorRight.x + ry * _vectorUp.x + rz * _vectorCenterEye.x),
140 (float)(rx * _vectorRight.y + ry * _vectorUp.y + rz * _vectorCenterEye.y),
141 (float)(rx * _vectorRight.z + ry * _vectorUp.z + rz * _vectorCenterEye.z)
142 );
143 return result;
144 }
145
146
147 public void MouseMove(int x, int y)
148 {
149 if (MouseDownFlag)
150 {
151 Debug.WriteLine(" =>MouseMove:", listenerName);
152 if (!cameraState.IsSameState(this.Camera))
153 {
154 SetCamera(this.Camera.Position, this.Camera.Target, this.Camera.UpVector);
155 Debug.WriteLine(string.Format(
156 " update camera state: {0}, {1}, {2}",
157 this.cameraState.position, this.cameraState.target, this.cameraState.up), listenerName);
158 }
159
160 this._endPosition = GetArcBallPosition(x, y);
161 Debug.WriteLine(string.Format(
162 " End position: {0}", this._endPosition), listenerName);
163 var cosAngle = _startPosition.dot(_endPosition) / (_startPosition.Magnitude() * _endPosition.Magnitude());
164 if (cosAngle > 1) { cosAngle = 1; }
165 else if (cosAngle < -1) { cosAngle = -1; }
166 Debug.Write(string.Format(" cos angle: {0}", cosAngle), listenerName);
167 var angle = mouseSensitivity * (float)(Math.Acos(cosAngle) / Math.PI * 180);
168 Debug.WriteLine(string.Format(
169 “, angle: {0}”, angle), listenerName);
170 _normalVector = _startPosition.cross(_endPosition);
171 _normalVector.Normalize();
172 if ((_normalVector.x == 0 && _normalVector.y == 0 && _normalVector.z == 0)
173 || float.IsNaN(_normalVector.x) || float.IsNaN(_normalVector.y) || float.IsNaN(_normalVector.z))
174 {
175 Debug.WriteLine(" no movement recorded.", listenerName);
176 }
177 else
178 {
179 Debug.WriteLine(string.Format(
180 " normal vector: {0}", _normalVector), listenerName);
181 _startPosition = _endPosition;
182
183 mat4 newRotation = glm.rotate(angle, _normalVector);
184 Debug.WriteLine(string.Format(
185 " new rotation matrix: {0}", newRotation), listenerName);
186 this.totalRotation = newRotation * totalRotation;
187 Debug.WriteLine(string.Format(
188 " total rotation matrix: {0}", totalRotation), listenerName);
189 }
190 Debug.WriteLine(" -------------------MouseMove end.", listenerName);
191 }
192 }
193
194 public void MouseUp(int x, int y)
195 {
196 Debug.WriteLine("=>MouseUp:", listenerName);
197 MouseDownFlag = false;
198 Debug.WriteLine("-------------------MouseUp end.", listenerName);
199 Debug.WriteLine("");
200 Debug.Flush();
201 }
202
203 public mat4 GetRotationMatrix()
204 {
205 return totalRotation;
206 }
207 }
208 }

复制代码

回到顶部(go to top)

  1. 轨迹球原理

clip_image003[4]clip_image004[4]

上面是我黑来的两张图,拿来说明轨迹球的原理。

看左边这个,网格代表绘制3D模型的窗口,上面放了个半球,这个球就是轨迹球。假设鼠标在网格上的某点A,过A点作网格所在平面的垂线,与半球相交于点P,P就是A在轨迹球上的投影。鼠标从A1点沿直线移动到A2点,对应着轨迹球上的点P1沿球面移动到了P2。那么,从球心O到P1和P2分别有两个向量OP1和OP2。OP1旋转到了OP2,我们就认为是模型也按照这个方式作同样的旋转。这就是轨迹球的旋转思路。

右边这个图没用上…
回到顶部(go to top)
2. 轨迹球实现

实现轨迹球,首先要求出鼠标点A1、A2投影到轨迹球上的点P1、P2的坐标,然后计算两个向量A1P1和A2P2之间的夹角以及旋转轴,最后让模型按照求出的夹角和旋转轴,调用glRotate就可以了。

  1. 计算投影点

在摄像机上应用轨迹球,才能实现适应任意位置摄像机的ArcBall类。

在相机上应用轨迹球

如图所示,红绿蓝三色箭头的交点是摄像机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点的坐标,只不过是一个数学问题了。

当然,开始的时候要设置相机位置。
复制代码

1 public void SetCamera(float eyex, float eyey, float eyez,
2 float centerx, float centery, float centerz,
3 float upx, float upy, float upz)
4 {
5 _vectorCenterEye = new Vertex(eyex - centerx, eyey - centery, eyez - centerz);
6 _vectorCenterEye.Normalize();
7 _vectorUp = new Vertex(upx, upy, upz);
8 _vectorRight = _vectorUp.VectorProduct(_vectorCenterEye);
9 _vectorRight.Normalize();
10 _vectorUp = _vectorCenterEye.VectorProduct(_vectorRight);
11 _vectorUp.Normalize();
12 }

复制代码

根据鼠标在屏幕上的位置投影点的计算方法如下。
复制代码

1 private Vertex GetArcBallPosition(int x, int y)
2 {
3 var rx = (x - _width / 2) / _length;
4 var ry = (_height / 2 - y) / _length;
5 var zz = _radiusRadius - rx * rx - ry * ry;
6 var rz = (zz > 0 ? Math.Sqrt(zz) : 0);
7 var result = new Vertex(
8 (float)(rx * _vectorRight.X + ry * _vectorUp.X + rz * _vectorCenterEye.X),
9 (float)(rx * _vectorRight.Y + ry * _vectorUp.Y + rz * _vectorCenterEye.Y),
10 (float)(rx * _vectorRight.Z + ry * _vectorUp.Z + rz * _vectorCenterEye.Z)
11 );
12 return result;
13 }

复制代码

这里主要应用了向量的思想,向量(Eye-P) = 向量(Eye-A) + 向量(A-P)。而向量(Eye-A)和向量(A-P)都是可以通过单位长度的Up、Center-Eye和Right向量求得的。
2) 计算夹角和旋转轴

首先,设置鼠标按下事件
复制代码

1 public void MouseDown(int x, int y)
2 {
3 this._startPosition = GetArcBallPosition(x, y);
4
5 mouseDownFlag = true;
6 }

复制代码

然后,设置鼠标移动事件。此时P1P2两个点都有了,旋转轴和夹角就都可以计算了。
复制代码

1 public void MouseMove(int x, int y)
2 {
3 if (mouseDownFlag)
4 {
5 this._endPosition = GetArcBallPosition(x, y);
6 var cosAngle = _startPosition.ScalarProduct(_endPosition) / (_startPosition.Magnitude() * _endPosition.Magnitude());
7 if (cosAngle > 1) { cosAngle = 1; }
8 else if (cosAngle < -1) { cosAngle = -1; }
9 var angle = 10 * (float)(Math.Acos(cosAngle) / Math.PI * 180);
10 System.Threading.Interlocked.Exchange(ref _angle, angle);
11 _normalVector = _startPosition.VectorProduct(_endPosition);
12 _startPosition = _endPosition;
13 }
14 }

复制代码

然后,设置鼠标弹起的事件。

1 public void MouseUp(int x, int y)
2 {
3 mouseDownFlag = false;
4 }

在使用opengl(sharpgl)绘制的时候,调用
复制代码

1 public void TransformMatrix(OpenGL gl)
2 {
3 gl.PushMatrix();
4 gl.LoadIdentity();
5 gl.Rotate(2 * _angle, _normalVector.X, _normalVector.Y, _normalVector.Z);
6 System.Threading.Interlocked.Exchange(ref _angle, 0);
7 gl.MultMatrix(_lastTransform);
8 gl.GetDouble(Enumerations.GetTarget.ModelviewMatix, _lastTransform);
9 gl.PopMatrix();
10 gl.Translate(_translateX, _translateY, _translateZ);
11 gl.MultMatrix(_lastTransform);
12 gl.Scale(Scale, Scale, Scale);
13 }

复制代码

  1. 额外功能实现

缩放很容易实现,直接设置Scale属性即可。

沿着屏幕上下左右前后地移动,则需要参照着camera的方向动了。
复制代码

1 public void GoUp(float interval)
2 {
3 this._translateX += this._vectorUp.X * interval;
4 this._translateY += this._vectorUp.Y * interval;
5 this._translateZ += this._vectorUp.Z * interval;
6 }

复制代码

其余方向与此类似,不再浪费篇幅。

工程源代码在此。(http://files.cnblogs.com/bitzhuwei/Arcball6662014-02-07_20-07-00.rar)

【OpenGL(SharpGL)】支持任意相机可平移缩放的轨迹球实现相关推荐

  1. OpenGL(sharpGL)支持任意相机可平移缩放的轨迹球原理描述及源码

    本文提供一个本人编写的轨迹球类(ArcBall.cs),它可以直接应用到任何 camera 下,还可以同时实现缩放和平移. 工程源代码在文末. 1.轨迹球原理: 上面是我黑来的两张图,拿来说明轨迹球的 ...

  2. Sherlock视觉,支持任意相机,我们是专业Sherlock视觉 sherlock支持二次开发,同时也支持 VB,VC,C#的界面封装

    Sherlock视觉,支持任意相机,我们是专业Sherlock视觉 sherlock支持二次开发,同时也支持 VB,VC,C#的界面封装,同时sherlock还可以是一个平台,就是可以自己写算法,插入 ...

  3. OpenGL鼠标轨迹球

    转自:http://www.lubanren.net/weblog/post/283.html 什么是鼠标轨迹球 类似AutoCAD里的"动态观察",三维模型都是要投影到二维的屏幕 ...

  4. PAOGD个人作业3——OpenGL,实现模型自身的旋转,平移,缩放

    中山大学数据科学与计算机学院本科生实验报告 (2019年春季学期) 课程名称 PAOGD 任课老师 郑贵锋 年级 16 专业(方向) 软件工程(计算机应用方向) 学号 16340132 姓名 梁颖霖 ...

  5. OpenGL相机自由移动旋转缩放,四元数,欧拉角,LookAt

    OpenGL相机自由移动旋转缩放,四元数,欧拉角,LookAt 定义相机 摄像机位置 右轴 上轴 Look At 自由移动相机 左右移动 移动速度 视角移动 欧拉角 通过欧拉角计算实际的方向向量 缩放 ...

  6. opengl加载和绘制STL模型+旋转 平移 缩放 光照

    一.开发环境说明 操作系统:windows 开发软件:Visual Studio 2017 编程语言:基于控制台下的opengl 用到的库: glut opengl环境配置查看博客 二.STL文件介绍 ...

  7. [Python从零到壹] 三十八.图像处理基础篇之图像几何变换(平移缩放旋转)

    欢迎大家来到"Python从零到壹",在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界.所有文章都将结合案例.代码和作者的经验讲 ...

  8. qgraphicsview鼠标移动图片_交互式QGraphicsView(平移/缩放/旋转)-阿里云开发者社区...

    简述 Graphics View提供了一个平台用于大量自定义 2D 图元的管理与交互框架包括一个事件传播架构支持场景 Scene 中的图元 Item 进行精确的双精度交互功能.Item 可以处理键盘事 ...

  9. openGL-读取off、stl、obj文件并旋转平移缩放操作

    说明 很多朋友反馈,加载完毕后是一片空白-不用担心.我认为你很大程度上已经配置成功.这时候你可以试一试点击数字1.2.3进行查看. 此外我建议你使用点面数目规模较小的模型例如bunny等而不是使用ki ...

最新文章

  1. 客制化键盘编程_客制化键盘劝退指南
  2. 【自定义组件】如何引用自定义组件
  3. MAC OS 命令行使用详解
  4. Spring 3 MVC深入研究
  5. 最里层的是child元素
  6. ROS学习——2编写简单的发布者和订阅者
  7. zookeeper部署
  8. 生信技能树linux虚拟机,生信人的linux考试-生信技能树学习笔记
  9. STM32F030使用RTC周期性唤醒STOP模式
  10. Active Boot Disk(windows系统维护工具箱)官方光盘镜像版V19.0 | 电脑维修工具箱软件下载
  11. js数组倒序reverse
  12. CSS 3.0实现八卦图
  13. python中ipaddress库用法详解
  14. js随机跳转域名_从代码角度看各类子域名收集工具
  15. XSLT基础 XSL 与 XSLT
  16. Vue入门 - 环境搭建Vue项目创建
  17. 通过shell脚本消耗cpu与内存
  18. 拨号vps为什么会掉线
  19. Spring详细教程
  20. STM32F407 USB虚拟双串口

热门文章

  1. android 接收js对象数组,java怎么接收js 数组对象数组对象
  2. MFC 对话框之UpdateData
  3. 解决联想电脑右键文件卡顿问题
  4. 易地推拓客分享我们的招生团队需要具备什么样的素养
  5. UE5 5.0正式版 新功能详解
  6. MAC 终端设置的样式第二次打开就不一样了
  7. uniapp获取元素高度
  8. 什么是web语义化?
  9. 手机磁吸背夹散热器制冷快速方案
  10. 移植Python2.7到ARM-LINUX嵌入式平台