鼠标事件 捕捉指针的移动 定义一个回调函数mouse_callback,记录指针的位置信息。用glfwSetCursorPosCallback绑定回调函数,就能在鼠标移动时,捕捉到指针的实时坐标。
1 2 3 4 void mouse_callback (GLFWwindow * window, double xpos, double ypos) { lastX = xpos; lastY = ypos; }
1 glfwSetCursorPosCallback(window, mouse_callback);
捕捉鼠标的点击 定义一个回调函数mouse_button_callback,处理鼠标的点击事件。首先,要将指针坐标转换成OpenGL的标准坐标,范围是[-1, 1]。然后,根据按键的不同调用不同的函数。点击左键时,调用addPoint添加一个控制点;点击右键时,调用deletePoint删除最后一个控制点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void mouse_button_callback (GLFWwindow* window, int button, int action, int mods) { if (action == GLFW_PRESS) { float clickX = (lastX - windowWidth / 2 ) / (windowWidth / 2 ); float clickY = -(lastY - windowHeight / 2 ) / (windowHeight / 2 ); switch (button) { case GLFW_MOUSE_BUTTON_LEFT: addPoint(clickX, clickY); break ; case GLFW_MOUSE_BUTTON_RIGHT: deletePoint(); break ; default : return ; } } }
然后用glfwSetMouseButtonCallback绑定回调函数,就能捕获鼠标点击事件。
1 glfwSetMouseButtonCallback(window, mouse_button_callback);
控制点的变化 控制点的增加 要增加控制点,先按照顶点数组的格式创建一个包含坐标信息的vector,然后将这个vector插入到控制点vector中。添加完之后,调用calculateBezier函数重新计算bezier曲线,做到实时更新曲线。
1 2 3 4 5 void addPoint (float x, float y) { vector <float > newPoint = { x, y, 0 , 1 , 1 , 1 }; controlPoints.insert(controlPoints.end(), newPoint.begin(), newPoint.end()); calculateBezier(); }
控制点的删除 根据定义的顶点数组格式,要删除最后一个控制点,只需要删除控制点vector最后6个元素即可。删除之后,调用calculateBezier函数重新计算bezier曲线,做到实时更新曲线。
1 2 3 4 5 6 void deletePoint () { if (!controlPoints.empty()) { controlPoints.erase(controlPoints.begin() + controlPoints.size() - 6 , controlPoints.end()); calculateBezier(); } }
Bezier曲线的计算 首先,要清空原有的bezier曲线。然后,根据Bezier曲线的公式计算即可,其中bernstein函数是伯恩斯坦基函数。t的增加步长设为0.0001。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void calculateBezier () { bezierPoints.clear(); int n = controlPoints.size() / 6 - 1 ; if (n > 1 ) { for (float t = 0 ; t <= 1 ; t += 0.0001 ) { float x = 0 , y = 0 ; for (int i = 0 ; i <= n; i++) { float b = bernstein(i, n, t); x += controlPoints[i * 6 ] * b; y += controlPoints[i * 6 + 1 ] * b; } vector <float > newPoint = { x, y, 0 , 1 , 1 , 1 }; bezierPoints.insert(bezierPoints.end(), newPoint.begin(), newPoint.end()); } } }
1 2 3 4 5 6 7 8 float bernstein (int i, int n, float t) { float result = 1 ; for (int j = 0 ; j < i; j++) { result *= ((float )(n - j) / (float )(i - j)); } result *= pow (t, i) * pow (1 - t, n - i); return result; }
显示 显示控制点 用GL_POINTS图元,绘制控制点数组中的所有点,用GL_LINE_STRIP图元绘制相邻控制点的连线。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 if (!controlPoints.empty()) { glViewport(0 , 0 , windowWidth, windowHeight); glGenBuffers(1 , &pointVBO); glGenVertexArrays(1 , &pointVAO); glBindVertexArray(pointVAO); glBindBuffer(GL_ARRAY_BUFFER, pointVBO); glBufferData(GL_ARRAY_BUFFER, sizeof (float ) * controlPoints.size(), &controlPoints[0 ], GL_STATIC_DRAW); glVertexAttribPointer(0 , 3 , GL_FLOAT, GL_FALSE, 6 * sizeof (float ), (void *)0 ); glEnableVertexAttribArray(0 ); glVertexAttribPointer(1 , 3 , GL_FLOAT, GL_FALSE, 6 * sizeof (float ), (void *)(3 * sizeof (float ))); glEnableVertexAttribArray(1 ); glPointSize(10 ); glDrawArrays(GL_POINTS, 0 , controlPoints.size() / 6 ); for (int i = 0 ; i < controlPoints.size() / 6 - 1 ; i++) { glDrawArrays(GL_LINE_STRIP, i, 2 ); } glBindVertexArray(0 ); }
显示Bezier曲线 用GL_POINTS图元,绘制Bezier曲线数组里面的所有点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 if (!bezierPoints.empty()) { glGenBuffers(1 , &bezierVBO); glGenVertexArrays(1 , &bezierVAO); glBindVertexArray(bezierVAO); glBindBuffer(GL_ARRAY_BUFFER, bezierVBO); glBufferData(GL_ARRAY_BUFFER, sizeof (float )* bezierPoints.size(), &bezierPoints[0 ], GL_STATIC_DRAW); glVertexAttribPointer(0 , 3 , GL_FLOAT, GL_FALSE, 6 * sizeof (float ), (void *)0 ); glEnableVertexAttribArray(0 ); glVertexAttribPointer(1 , 3 , GL_FLOAT, GL_FALSE, 6 * sizeof (float ), (void *)(3 * sizeof (float ))); glEnableVertexAttribArray(1 ); glPointSize(1 ); glDrawArrays(GL_POINTS, 0 , bezierPoints.size() / 6 ); glBindVertexArray(0 ); }
Bonus:动态呈现Bezier曲线的生成过程 计算 根据Bezier曲线的原理,每一层的辅助线由该层相邻的辅助点连接而成,每一层的辅助点是由上一层的每一条辅助线取比例t所得,最顶层的辅助点和辅助线是之前画出的控制点和控制线,最底层的辅助点就是比例t对应的Bezier曲线上的点。根据这个原理和t的值,可以通过两层循环算出所有的辅助点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void getVisualizePoints (float t) { visualizePoints = vector <float >(controlPoints); int n = controlPoints.size() / 6 - 1 ; int offset = 0 ; for (int i = 0 ; i < n; i++) { for (int j = 0 ; j < n - i; j++) { float x = (1 - t) * visualizePoints[(j + offset) * 6 ] + t * visualizePoints[(j + 1 + offset) * 6 ]; float y = (1 - t) * visualizePoints[(j + offset) * 6 + 1 ] + t * visualizePoints[(j + 1 + offset) * 6 + 1 ]; std ::vector <float > newPoint = { x, y, 0 , 1 , 1 , 1 }; visualizePoints.insert(visualizePoints.end(), newPoint.begin(), newPoint.end()); } offset += (n - i + 1 ); } }
显示 在渲染循环中,通过时间来控制t的值,使t从0到1循环变化。然后,调用getVisualizePoints获得所有的辅助点。最后,用GL_LINE_STRIP图元绘制所有的辅助线,用GL_POINTS图元绘制所有的辅助点。绘制过程与控制点的绘制类似。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 float t = (float )(time - (int )time / 10 * 10 ) / 10 ;getVisualizePoints(t); if (!visualizePoints.empty()) { glGenBuffers(1 , &visualizeVBO); glGenVertexArrays(1 , &visualizeVAO); glBindVertexArray(visualizeVAO); glBindBuffer(GL_ARRAY_BUFFER, visualizeVBO); glBufferData(GL_ARRAY_BUFFER, sizeof (float )* visualizePoints.size(), &visualizePoints[0 ], GL_STATIC_DRAW); glVertexAttribPointer(0 , 3 , GL_FLOAT, GL_FALSE, 6 * sizeof (float ), (void *)0 ); glEnableVertexAttribArray(0 ); glVertexAttribPointer(1 , 3 , GL_FLOAT, GL_FALSE, 6 * sizeof (float ), (void *)(3 * sizeof (float ))); glEnableVertexAttribArray(1 ); int n = controlPoints.size() / 6 - 1 ; float offset = n + 1 ; for (int i = 0 ; i < n - 1 ; i++) { for (int j = 0 ; j < n - i - 1 ; j++) { glDrawArrays(GL_LINE_STRIP, j + offset, 2 ); } offset += (n - i); } glPointSize(5 ); glDrawArrays(GL_POINTS, 0 , visualizePoints.size() / 6 ); glBindVertexArray(0 ); }