上回分解-_-!,Android OpenGL射线拾取&手势旋转(一)。

3)Renderer:RayPickRenderer.java
OpenGL渲染器,比较多的东西都在这里面了。
  1. public class RayPickRenderer implements Renderer {
  2. private Context mContext;
  3. private Cube cube;
  4. int texture = -1;
  5. public float mfAngleX = 0.0f;
  6. public float mfAngleY = 0.0f;
  7. public float gesDistance = 0.0f;
  8. // 观察者、中心和上方
  9. private Vector3f mvEye = new Vector3f(0, 0, 7f), mvCenter = new Vector3f(0,
  10. 0, 0), mvUp = new Vector3f(0, 1, 0);
  11. private OnSurfacePickedListener onSurfacePickedListener;
  12. public RayPickRenderer(Context context) {
  13. mContext = context;
  14. cube = new Cube();
  15. }
  16. /**
  17. * 逐帧渲染
  18. */
  19. @Override
  20. public void onDrawFrame(GL10 gl) {
  21. gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT); // 清除屏幕和深度缓存
  22. gl.glLoadIdentity(); // 重置当前的模型观察矩阵
  23. // 紧接着设置模型视图矩阵
  24. setUpCamera(gl);
  25. gl.glPushMatrix();
  26. {
  27. // 渲染物体
  28. drawModel(gl);
  29. }
  30. gl.glPopMatrix();
  31. // gl.glPushMatrix();
  32. // {
  33. // // 渲染射线
  34. // PickFactory.getPickRay().draw(gl);
  35. // }
  36. // gl.glPopMatrix();
  37. gl.glPushMatrix();
  38. {
  39. // 渲染选中的三角形
  40. drawPickedTriangle(gl);
  41. }
  42. gl.glPopMatrix();
  43. updatePick();
  44. }
  45. /**
  46. * 设置相机矩阵
  47. *
  48. * @param gl
  49. */
  50. private void setUpCamera(GL10 gl) {
  51. // 设置模型视图矩阵
  52. gl.glMatrixMode(GL10.GL_MODELVIEW);
  53. gl.glLoadIdentity();
  54. // GLU.gluLookAt(gl, mfEyeX, mfEyeY, mfEyeZ, mfCenterX, mfCenterY,
  55. // mfCenterZ, 0, 1, 0);//系统提供
  56. Matrix4f.gluLookAt(mvEye, mvCenter, mvUp, AppConfig.gMatView);
  57. gl.glLoadMatrixf(AppConfig.gMatView.asFloatBuffer());
  58. }
  59. // private Matrix4f matRotX = new Matrix4f();
  60. // private Matrix4f matRotY = new Matrix4f();
  61. private Matrix4f matRot = new Matrix4f();
  62. private Vector3f point;
  63. /**
  64. * 渲染模型
  65. */
  66. private void drawModel(GL10 gl) {
  67. // 首先对模型进行变换
  68. // -------使用系统函数进行变换
  69. // gl.glRotatef(mfAngleX, 1, 0, 0);//绕X轴旋转
  70. // gl.glRotatef(mfAngleY, 0, 1, 0);//绕Y轴旋转
  71. // -------托管方式进行变换
  72. // matRotX.setIdentity();
  73. // matRotY.setIdentity();
  74. // matRotX.rotX((float) (mfAngleX * Math.PI / 180));
  75. // matRotY.rotY((float) (mfAngleY * Math.PI / 180));
  76. // AppConfig.gMatModel.set(matRotX);
  77. // AppConfig.gMatModel.mul(matRotY);
  78. /* 以下方式完全按照手势方向旋转 */
  79. matRot.setIdentity();
  80. // 世界坐标系的向量点
  81. point = new Vector3f(mfAngleX, mfAngleY, 0);
  82. try {
  83. // 转换到模型内部的点,先要求逆
  84. matInvertModel.set(AppConfig.gMatModel);
  85. matInvertModel.invert();
  86. matInvertModel.transform(point, point);
  87. float d = Vector3f.distance(new Vector3f(), point);
  88. // 再减少误差
  89. if (Math.abs(d - gesDistance) <= 1E-4) {
  90. // 绕这个单位向量旋转(由于误差可能会产生缩放而使得模型消失不见)
  91. matRot.glRotatef((float) (gesDistance * Math.PI / 180), point.x
  92. / d, point.y / d, point.z / d);
  93. // 旋转后在原基础上再转
  94. if (0 != gesDistance) {
  95. AppConfig.gMatModel.mul(matRot);
  96. }
  97. }
  98. } catch (Exception e) {
  99. // 由于四舍五入求逆矩阵失败
  100. }
  101. gesDistance = 0;
  102. gl.glMultMatrixf(AppConfig.gMatModel.asFloatBuffer());
  103. // 设置默认颜色
  104. gl.glColor4f(1.0f, 1.0f, 1.0f, 0.0f);
  105. gl.glBindTexture(GL10.GL_TEXTURE_2D, texture);
  106. gl.glEnable(GL10.GL_TEXTURE_2D);
  107. gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
  108. gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
  109. gl.glVertexPointer(3, GL10.GL_FLOAT, 0,
  110. cube.getCoordinate(Cube.VERTEX_BUFFER));
  111. gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0,
  112. cube.getCoordinate(Cube.TEXTURE_BUFFER));
  113. gl.glDrawElements(GL10.GL_TRIANGLE_STRIP, 24, GL10.GL_UNSIGNED_BYTE,
  114. cube.getIndices());
  115. gl.glDisable(GL10.GL_TEXTURE_2D);
  116. gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
  117. gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
  118. // 渲染坐标系
  119. drawCoordinateSystem(gl);
  120. }
  121. private Vector3f transformedSphereCenter = new Vector3f();
  122. private Ray transformedRay = new Ray();
  123. private Matrix4f matInvertModel = new Matrix4f();
  124. private Vector3f[] mpTriangle = { new Vector3f(), new Vector3f(),
  125. new Vector3f() };
  126. private FloatBuffer mBufPickedTriangle = IBufferFactory
  127. .newFloatBuffer(3 * 3);
  128. /**
  129. * 更新拾取事件
  130. */
  131. private void updatePick() {
  132. if (!AppConfig.gbNeedPick) {
  133. return;
  134. }
  135. AppConfig.gbNeedPick = false;
  136. // 更新最新的拾取射线
  137. PickFactory.update(AppConfig.gScreenX, AppConfig.gScreenY);
  138. // 获得最新的拾取射线
  139. Ray ray = PickFactory.getPickRay();
  140. // 首先把模型的绑定球通过模型矩阵,由模型局部空间变换到世界空间
  141. AppConfig.gMatModel.transform(cube.getSphereCenter(),
  142. transformedSphereCenter);
  143. // 触碰的立方体面的标记为无
  144. cube.surface = -1;
  145. // 首先检测拾取射线是否与模型绑定球发生相交
  146. // 这个检测很快,可以快速排除不必要的精确相交检测
  147. if (ray.intersectSphere(transformedSphereCenter, cube.getSphereRadius())) {
  148. // 如果射线与绑定球发生相交,那么就需要进行精确的三角面级别的相交检测
  149. // 由于我们的模型渲染数据,均是在模型局部坐标系中
  150. // 而拾取射线是在世界坐标系中
  151. // 因此需要把射线转换到模型坐标系中
  152. // 这里首先计算模型矩阵的逆矩阵
  153. matInvertModel.set(AppConfig.gMatModel);
  154. matInvertModel.invert();
  155. // 把射线变换到模型坐标系中,把结果存储到transformedRay中
  156. ray.transform(matInvertModel, transformedRay);
  157. // 将射线与模型做精确相交检测
  158. if (cube.intersect(transformedRay, mpTriangle)) {
  159. // 如果找到了相交的最近的三角形
  160. AppConfig.gbTrianglePicked = true;
  161. // 触碰了哪一个面
  162. Log.i("触碰的立方体面", "=标记=" + cube.surface);
  163. // 回调
  164. if (null != onSurfacePickedListener) {
  165. onSurfacePickedListener.onSurfacePicked(cube.surface);
  166. }
  167. // 填充数据到被选取三角形的渲染缓存中
  168. mBufPickedTriangle.clear();
  169. for (int i = 0; i < 3; i++) {
  170. IBufferFactory
  171. .fillBuffer(mBufPickedTriangle, mpTriangle[i]);
  172. // Log.i("点" + i, mpTriangle[i].x + "\t" + mpTriangle[i].y
  173. // + "\t" + mpTriangle[i].z);
  174. }
  175. mBufPickedTriangle.position(0);
  176. }
  177. } else {
  178. AppConfig.gbTrianglePicked = false;
  179. }
  180. }
  181. /**
  182. * 渲染选中的三角形
  183. */
  184. private void drawPickedTriangle(GL10 gl) {
  185. if (!AppConfig.gbTrianglePicked) {
  186. return;
  187. }
  188. // 由于返回的拾取三角形数据是出于模型坐标系中
  189. // 因此需要经过模型变换,将它们变换到世界坐标系中进行渲染
  190. // 设置模型变换矩阵
  191. gl.glMultMatrixf(AppConfig.gMatModel.asFloatBuffer());
  192. // 设置三角形颜色,alpha为0.7
  193. gl.glColor4f(1.0f, 0.0f, 0.0f, 0.7f);
  194. // 开启Blend混合模式
  195. gl.glEnable(GL10.GL_BLEND);
  196. gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
  197. // 禁用无关属性,仅仅使用纯色填充
  198. gl.glDisable(GL10.GL_DEPTH_TEST);
  199. gl.glDisable(GL10.GL_TEXTURE_2D);
  200. // 开始绑定渲染顶点数据
  201. gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
  202. gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufPickedTriangle);
  203. // 提交渲染
  204. gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
  205. // 重置相关属性
  206. gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
  207. gl.glEnable(GL10.GL_DEPTH_TEST);
  208. gl.glDisable(GL10.GL_BLEND);
  209. }
  210. /**
  211. * 渲染坐标系
  212. */
  213. private void drawCoordinateSystem(GL10 gl) {
  214. // 暂时禁用深度测试
  215. gl.glDisable(GL10.GL_DEPTH_TEST);
  216. // 设置点和线的宽度
  217. gl.glLineWidth(2.0f);
  218. // 仅仅启用顶点数据
  219. gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
  220. FloatBuffer fb = IBufferFactory.newFloatBuffer(3 * 2);
  221. fb.put(new float[] { 0, 0, 0, 1.4f, 0, 0 });
  222. fb.position(0);
  223. // 渲染X轴
  224. gl.glColor4f(1.0f, 0.0f, 0.0f, 1.0f);// 设置红色
  225. gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fb);
  226. // 提交渲染
  227. gl.glDrawArrays(GL10.GL_LINES, 0, 2);
  228. fb.clear();
  229. fb.put(new float[] { 0, 0, 0, 0, 1.4f, 0 });
  230. fb.position(0);
  231. // 渲染Y轴
  232. gl.glColor4f(0.0f, 1.0f, 0.0f, 1.0f);// 设置绿色
  233. gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fb);
  234. // 提交渲染
  235. gl.glDrawArrays(GL10.GL_LINES, 0, 2);
  236. fb.clear();
  237. fb.put(new float[] { 0, 0, 0, 0, 0, 1.4f });
  238. fb.position(0);
  239. // 渲染Z轴
  240. gl.glColor4f(0.0f, 0.0f, 1.0f, 1.0f);// 设置蓝色
  241. gl.glVertexPointer(3, GL10.GL_FLOAT, 0, fb);
  242. // 提交渲染
  243. gl.glDrawArrays(GL10.GL_LINES, 0, 2);
  244. // 重置
  245. gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
  246. gl.glLineWidth(1.0f);
  247. gl.glEnable(GL10.GL_DEPTH_TEST);
  248. }
  249. /**
  250. * 创建绘图表面时调用
  251. */
  252. @Override
  253. public void onSurfaceCreated(GL10 gl, EGLConfig config) {
  254. // 全局性设置
  255. gl.glEnable(GL10.GL_DITHER);
  256. gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
  257. // 设置清屏背景颜色
  258. // gl.glClearColor(0, 0, 0, 0);
  259. gl.glClearColor(0.5f, 0.5f, 0.5f, 1);
  260. // 设置着色模型为平滑着色
  261. gl.glShadeModel(GL10.GL_SMOOTH);
  262. // 启用背面剪裁
  263. gl.glEnable(GL10.GL_CULL_FACE);
  264. gl.glCullFace(GL10.GL_BACK);
  265. // 启用深度测试
  266. gl.glEnable(GL10.GL_DEPTH_TEST);
  267. // 禁用光照和混合
  268. gl.glDisable(GL10.GL_LIGHTING);
  269. gl.glDisable(GL10.GL_BLEND);
  270. loadTexture(gl);
  271. AppConfig.gMatModel.setIdentity();
  272. }
  273. /**
  274. * 当绘图表面尺寸发生改变时调用
  275. */
  276. @Override
  277. public void onSurfaceChanged(GL10 gl, int width, int height) {
  278. // 设置视口
  279. gl.glViewport(0, 0, width, height);
  280. AppConfig.gpViewport[0] = 0;
  281. AppConfig.gpViewport[1] = 0;
  282. AppConfig.gpViewport[2] = width;
  283. AppConfig.gpViewport[3] = height;
  284. // 设置投影矩阵
  285. float ratio = (float) width / height;// 屏幕宽高比
  286. gl.glMatrixMode(GL10.GL_PROJECTION);
  287. gl.glLoadIdentity();
  288. // GLU.gluPerspective(gl, 45.0f, ratio, 1, 5000);系统提供
  289. Matrix4f.gluPersective(45.0f, ratio, 1, 10, AppConfig.gMatProject);
  290. gl.glLoadMatrixf(AppConfig.gMatProject.asFloatBuffer());
  291. AppConfig.gMatProject.fillFloatArray(AppConfig.gpMatrixProjectArray);
  292. // 每次修改完GL_PROJECTION后,最好将当前矩阵模型设置回GL_MODELVIEW
  293. gl.glMatrixMode(GL10.GL_MODELVIEW);
  294. }
  295. private void loadTexture(GL10 gl) {
  296. // 启用纹理映射
  297. gl.glClearDepthf(1.0f);
  298. // 允许2D贴图,纹理
  299. gl.glEnable(GL10.GL_TEXTURE_2D);
  300. try {
  301. IntBuffer intBuffer = IntBuffer.allocate(1);
  302. // 创建纹理
  303. gl.glGenTextures(1, intBuffer);
  304. texture = intBuffer.get();
  305. // 设置要使用的纹理
  306. gl.glBindTexture(GL10.GL_TEXTURE_2D, texture);
  307. // 打开二进制流
  308. InputStream is = mContext.getResources().openRawResource(
  309. R.drawable.snow_leopard);
  310. Bitmap mBitmap = BitmapFactory.decodeStream(is);
  311. // Log.i("宽度|高度", mBitmap.getWidth() + "|" + mBitmap.getHeight());
  312. // 生成纹理
  313. GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0);
  314. // 线形滤波
  315. gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
  316. GL10.GL_LINEAR);
  317. gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
  318. GL10.GL_LINEAR);
  319. is.close();
  320. } catch (IOException e) {
  321. // TODO Auto-generated catch block
  322. e.printStackTrace();
  323. }
  324. }
  325. public void setOnSurfacePickedListener(
  326. OnSurfacePickedListener onSurfacePickedListener) {
  327. this.onSurfacePickedListener = onSurfacePickedListener;
  328. }
  329. }

4)主Activity:RayPickActivity
继承各面点击的监听接口,提示了个Toast就OK了。很简单,当时都没注释。

  1. public class RayPickActivity extends Activity implements
  2. OnSurfacePickedListener {
  3. private GLSurfaceView mGLSurfaceView;
  4. @Override
  5. public void onCreate(Bundle savedInstanceState) {
  6. super.onCreate(savedInstanceState);
  7. mGLSurfaceView = new MyGLSurfaceView(this, this);
  8. mGLSurfaceView.getHolder().setFormat(PixelFormat.TRANSLUCENT);
  9. setContentView(mGLSurfaceView);
  10. mGLSurfaceView.requestFocus();
  11. mGLSurfaceView.setFocusableInTouchMode(true);
  12. }
  13. @Override
  14. protected void onResume() {
  15. super.onResume();
  16. mGLSurfaceView.onResume();
  17. }
  18. @Override
  19. protected void onPause() {
  20. super.onPause();
  21. mGLSurfaceView.onPause();
  22. }
  23. private Handler myHandler = new Handler() {
  24. @Override
  25. public void handleMessage(Message msg) {
  26. Toast.makeText(RayPickActivity.this, "选中了" + msg.what + "面",
  27. Toast.LENGTH_SHORT).show();
  28. }
  29. };
  30. @Override
  31. public void onSurfacePicked(int which) {
  32. myHandler.sendEmptyMessage(which);
  33. }
  34. }
5)Matrix4f内的glRotatef(…)方法
公式如下图,其中其中C为cosθ,S为sinθ,A为单位化的旋转轴。

但这是左手坐标系下的公式,之前有改过右手坐标系的但不能实现效果。其他几个绕X、Y、Z轴的也是这样。
这应该是因为OpenGL坐标系原点在左下角,而窗口坐标系的原点为右上角,程序在Y轴上做了修正的关系导致的。这次可以高抬左手,大拇指向右、食指向下、弯曲中指90°,是不是各正轴都对应上了呢^^。
  1. /**
  2. * 绕任意单位轴旋转任意角度的矩阵
  3. */
  4. public final void glRotatef(float angle, float x, float y, float z) {
  5. float sinAngle, cosAngle;
  6. sinAngle = (float) Math.sin((double) angle);
  7. cosAngle = (float) Math.cos((double) angle);
  8. this.m00 = cosAngle + (1 - cosAngle) * x * x;
  9. this.m01 = (1 - cosAngle) * x * y - sinAngle * z;
  10. this.m02 = (1 - cosAngle) * x * z + sinAngle * y;
  11. this.m03 = 0;
  12. this.m10 = (1 - cosAngle) * x * y + sinAngle * z;
  13. this.m11 = cosAngle + (1 - cosAngle) * y * y;
  14. this.m12 = (1 - cosAngle) * y * z - sinAngle * x;
  15. this.m13 = 0;
  16. this.m20 = (1 - cosAngle) * x * z - sinAngle * y;
  17. this.m21 = (1 - cosAngle) * y * z + sinAngle * x;
  18. this.m22 = cosAngle + (1 - cosAngle) * z * z;
  19. this.m23 = 0;
  20. this.m30 = 0;
  21. this.m31 = 0;
  22. this.m32 = 0;
  23. this.m33 = 1;
  24. }
三、后记
小弟那时做这个,貌似是为了做个3D魔方啊。然后无聊可以在那转啊转啊的,不过愿望很美好……总之,我没做下去,忘记当时又忙活啥去了T^T。
现在也没这个激情继续做这个呢,希望这篇文档能给那些有同样想法的人给予一定的帮助吧,加油^^。

附件:http://down.51cto.com/data/2359933

本文转自winorlose2000 51CTO博客,原文链接:http://blog.51cto.com/vaero/790637,如需转载请自行联系原作者

Android OpenGL射线拾取手势旋转(二)相关推荐

  1. [OpenGL]射线拾取RayPicking---(1)生成射线

    Vries的教程是我看过的最好的可编程管线OpenGL教程,没有之一.但没有讲关于拾取(Picking)的章节,而这个功能的确很重要,就自己试着写写看了. 这节有三个很重要的参考网址,基本可以算照抄了 ...

  2. Android OpenGL ES视频渲染(一)GLSurfaceView

    相关文章:Android OpenGL ES视频渲染(二)EGL+OpenGL Android中视频渲染有几种方式,之前的文章使用的是nativewindow(包括softwareRender).今天 ...

  3. 触摸旋转 Android,Opengl es Android 3D 手指触控旋转object

    明确原理: 先确定一下OpenGL里面的坐标为:x轴正方向为屏幕向右,y轴正方向为屏幕向上,z轴正方向为屏幕由里向外. 当手指在X轴方向上移动时,画面中物体应绕Y轴方向旋转,则为gl.glRotate ...

  4. Android openGl开发详解(二)

    https://zhuanlan.zhihu.com/p/35192609 Android openGl开发详解(二)--通过SurfaceView,TextureView,GlSurfaceView ...

  5. Android OpenGL三——旋转和触控事件

    OpenGL中添加Motion 书接上文,Android OpenGL二 -- 使用投影和相机变换 在屏幕上绘制对象,是OpenGL比较基本的特性.如果你只想做这些,你完全可以使用包括Canvas和D ...

  6. 【Android OpenGL ES】阅读hello-gl2代码(二)Java代码

    AndroidManifest.xml <?xml version="1.0" encoding="utf-8"?> <manifest xm ...

  7. Android openGl开发详解(二)——通过SurfaceView,TextureView,GlSurfaceView显示相机预览(附Demo)

    最近公司在做自定义相机这一块,之前使用的是第三方,后来需求变更,第三方不支持添加动态贴纸,所以只能自己扩展.当然网上有很多例子,但是关于添加动态贴纸的例子几乎找不到,反正我是没找到(欲哭无泪).当然, ...

  8. android设置雷达网各层颜色,GitHub - androidTH/RadarChart: 支持自由定制外观、手势旋转的雷达图表 android radarchart...

    RadarView 一个可以自由定制.旋转交互的Android雷达图Lib 一些特性 支持手势旋转(可关闭) 支持动画的方式添加展现数据 支持对各层雷达网添加背景 支持自定义雷达网层数 支持使用圆形作 ...

  9. Android OpenGL ES 学习(二) -- 图形渲染管线和GLSL

    OpenGL 学习教程 Android OpenGL ES 学习(一) – 基本概念 Android OpenGL ES 学习(二) – 图形渲染管线和GLSL Android OpenGL ES 学 ...

最新文章

  1. .NET Core 2.1改进了性能,并提供了新的部署选项
  2. a good approach to make demonstrations at the baidu netdisk
  3. Linux学习笔记(二)
  4. 年度大盘点:机器学习开源项目及框架
  5. 钉钉上手体会:阿里实用主义的野望
  6. 2017.10.10 杀人游戏 思考记录
  7. Deploy Oracle 10.2.0.5 DataGuard on Red Hat Enterprise Linux 6.4
  8. 剑指offer(C++)-JZ55:二叉树的深度(数据结构-树)
  9. ENVI 监督分类Max stdev from Mean 参数IDL中的设置
  10. 自动驾驶 8-4: 最小二乘法和最大似然法 Least Squares and the Method of Maximum Likelihood
  11. 人工智能面试问题整理
  12. 职工信息管理系统设计c语言,c语言之职工信息管理系统设计.doc
  13. mib browser使用
  14. bat文件隐藏/显示文件夹
  15. C++单元测试框架Gtest的配置与使用过程
  16. 18岁创业从哪入手?读透这5点则事半功倍!
  17. 作为项目经理你应该掌握的关键链法
  18. 全志A64触摸屏驱动调试
  19. 数据库的列类型与字段属性
  20. win10中安装JDK8以及环境配置

热门文章

  1. python能够处理的最大整数是多少_python中能输出的最大整数位是多少
  2. 公司--As Imp的写法
  3. 刷新序号公共方法 公司内部用
  4. es6 数组找最大值_自学Java笔记_day04_第四章 数组
  5. 第十六届全国大学生智能车竞赛技术报告 | 单车拉力组- 上海海事大学-骑摩托的蒙娜丽莎
  6. 第十七届智能车竞赛何时开始呀?
  7. 2021年春季学期-信号与系统-第十一次作业参考答案-第一小题
  8. 全国大学生智能车竞赛申请沁恒RISC-V MCU样品说明
  9. 使用高精度旋转编码器BH60测量步进电机转动角度
  10. DRV8834用于驱动双电机