From 2a42c64a3514731d20218294bc5c72fa4b8b0613 Mon Sep 17 00:00:00 2001 From: vrld Date: Tue, 22 Mar 2011 01:08:31 +0100 Subject: [PATCH] Replace line drawing via GL_LINE with custom line drawing (issue #153) Allows for correctly scaleable lines, (very) big line widths. Corners in rectangles/triangles/... join up nicely (issue #197). --HG-- branch : minor --- src/modules/graphics/opengl/Graphics.cpp | 439 ++++++------------ src/modules/graphics/opengl/Graphics.h | 45 +- src/modules/graphics/opengl/wrap_Graphics.cpp | 95 ++-- 3 files changed, 209 insertions(+), 370 deletions(-) diff --git a/src/modules/graphics/opengl/Graphics.cpp b/src/modules/graphics/opengl/Graphics.cpp index eb2476690..1d6d0cc94 100644 --- a/src/modules/graphics/opengl/Graphics.cpp +++ b/src/modules/graphics/opengl/Graphics.cpp @@ -20,6 +20,7 @@ #include #include +#include #include "Graphics.h" @@ -36,7 +37,7 @@ namespace opengl { Graphics::Graphics() - : currentFont(0) + : currentFont(0), lineWidth(1) { // Indicates that there is no screen // created yet. @@ -100,19 +101,8 @@ namespace opengl //get color mode glGetTexEnviv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, &mode); s.colorMode = (mode == GL_MODULATE) ? Graphics::COLOR_MODULATE : Graphics::COLOR_REPLACE; - //get the line width (directly to corresponding variable) - glGetFloatv(GL_LINE_WIDTH, &s.lineWidth); //get line style - s.lineStyle = (glIsEnabled(GL_LINE_SMOOTH) == GL_TRUE) ? Graphics::LINE_SMOOTH : Graphics::LINE_ROUGH; - //get line stipple - s.stipple = (glIsEnabled(GL_LINE_STIPPLE) == GL_TRUE); - if (s.stipple) - { - //get the stipple repeat - glGetIntegerv(GL_LINE_STIPPLE_REPEAT, &s.stippleRepeat); - //get the stipple pattern - glGetIntegerv(GL_LINE_STIPPLE_PATTERN, &s.stipplePattern); - } + s.lineStyle = (glIsEnabled(GL_POLYGON_SMOOTH) == GL_TRUE) ? Graphics::LINE_SMOOTH : Graphics::LINE_ROUGH; //get the point size glGetFloatv(GL_POINT_SIZE, &s.pointSize); //get point style @@ -136,11 +126,7 @@ namespace opengl setBackgroundColor(s.backgroundColor); setBlendMode(s.blendMode); setColorMode(s.colorMode); - setLine(s.lineWidth, s.lineStyle); - if (s.stipple) - setLineStipple(s.stipplePattern, s.stippleRepeat); - else - setLineStipple(); + setLine(lineWidth, s.lineStyle); setPoint(s.pointSize, s.pointStyle); if (s.scissor) setScissor(s.scissorBox[0], s.scissorBox[1], s.scissorBox[2], s.scissorBox[3]); @@ -260,8 +246,7 @@ namespace opengl glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Enable line/point smoothing. - glEnable(GL_LINE_SMOOTH); - glHint(GL_LINE_SMOOTH_HINT, GL_NICEST); + setLineStyle(LINE_SMOOTH); glEnable(GL_POINT_SMOOTH); glHint(GL_POINT_SMOOTH_HINT, GL_NICEST); @@ -608,45 +593,30 @@ namespace opengl void Graphics::setLineWidth( float width ) { - glLineWidth(width); + lineWidth = width; } void Graphics::setLineStyle(Graphics::LineStyle style ) { + // XXX: actually enables antialiasing for _all_ polygons. + // may need investigation if wanted or not + // maybe rename to something else? if(style == LINE_ROUGH) - glDisable (GL_LINE_SMOOTH); + glDisable (GL_POLYGON_SMOOTH); else // type == LINE_SMOOTH { - glEnable (GL_LINE_SMOOTH); - glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); + glEnable (GL_POLYGON_SMOOTH); + glHint (GL_POLYGON_SMOOTH_HINT, GL_NICEST); } } void Graphics::setLine( float width, Graphics::LineStyle style ) { - glLineWidth(width); + setLineWidth(width); if(style == 0) return; - - if(style == LINE_ROUGH) - glDisable (GL_LINE_SMOOTH); - else // type == LINE_SMOOTH - { - glEnable (GL_LINE_SMOOTH); - glHint (GL_LINE_SMOOTH_HINT, GL_NICEST); - } - } - - void Graphics::setLineStipple() - { - glDisable(GL_LINE_STIPPLE); - } - - void Graphics::setLineStipple(unsigned short pattern, int repeat) - { - glEnable(GL_LINE_STIPPLE); - glLineStipple((GLint)repeat, (GLshort)pattern); + setLineStyle(style); } float Graphics::getLineWidth() @@ -658,25 +628,12 @@ namespace opengl Graphics::LineStyle Graphics::getLineStyle() { - if(glIsEnabled(GL_LINE_SMOOTH) == GL_TRUE) + if(glIsEnabled(GL_POLYGON_SMOOTH) == GL_TRUE) return LINE_SMOOTH; else return LINE_ROUGH; } - int Graphics::getLineStipple(lua_State * L) - { - if(glIsEnabled(GL_LINE_STIPPLE) == GL_FALSE) - return 0; - - GLint factor, pattern; - glGetIntegerv(GL_LINE_STIPPLE_PATTERN, &pattern); - glGetIntegerv(GL_LINE_STIPPLE_REPEAT, &factor); - lua_pushinteger(L, pattern); - lua_pushinteger(L, factor); - return 2; - } - void Graphics::setPointSize( float size ) { glPointSize((GLfloat)size); @@ -805,149 +762,90 @@ namespace opengl glEnable(GL_TEXTURE_2D); } - void Graphics::line( float x1, float y1, float x2, float y2 ) + // calculate line boundary intersection vertices for current line + // dependent on the current *and next* line segment + static void pushIntersectionPoints(Vector *vertices, int pos, float halfwidth, const Vector& p, const Vector& q, const Vector& r) { - glDisable(GL_TEXTURE_2D); - glPushMatrix(); - glBegin(GL_LINES); - glVertex2f(x1, y1); - glVertex2f(x2, y2); - glEnd(); - glPopMatrix(); - glEnable(GL_TEXTURE_2D); + // calculate line directions + Vector s = (q - p); + Vector t = (r - q); + + // calculate vertex displacement vectors + Vector d1 = s.getNormal(); + Vector d2 = t.getNormal(); + d1.normalize(); + d2.normalize(); + float det_norm = d1 ^ d2; + d1 *= halfwidth; + d2 *= halfwidth; + + // lines parallel -> assume intersection at displacement points + if (fabs(det_norm) <= .03) { + vertices[pos] = q - d2; + vertices[pos + 1] = q + d2; + return; + } + + // real intersection -> calculate boundary intersection points + float det = s ^ t; + Vector d = d1 - d2; + Vector b = s - d; // s = q - p + Vector c = s + d; + float lambda = (b ^ t) / det; + float mu = (c ^ t) / det; + + // ordering for GL_TRIANGLE_STRIP + vertices[pos] = p - d1 + s * mu; + vertices[pos+1] = p + d1 + s * lambda; } - int Graphics::polyline( lua_State * L) + void Graphics::polyline(const float* coords, size_t count, bool looping) { - // Get number of params. - int args = lua_gettop(L); - bool table = false; + Vector *vertices = new Vector[count]; // two vertices for every line end-point + Vector p,q,r; - if (args == 1) { // we've got a table, hopefully - int type = lua_type(L, 1); - if (type != LUA_TTABLE) - return luaL_error(L, "Function requires a table or series of numbers"); - table = true; - args = lua_objlen(L, 1); + r = Vector(coords[0], coords[1]); + if (looping) q = Vector(coords[count-4], coords[count-3]); + else q = r * 2 - Vector(coords[2], coords[3]); + + for (size_t i = 0; i+3 < count; i += 2) { + p = q; + q = r; + r = Vector(coords[i+2], coords[i+3]); + pushIntersectionPoints(vertices, i, lineWidth/2, p,q,r); } - if (args % 2) // an odd number of arguments, no good for a polyline - return luaL_error(L, "Number of vertices must be a multiple of two"); - else if (args < 4) - return luaL_error(L, "Need at least two vertices to draw a line"); + p = q; + q = r; + if (looping) r = Vector(coords[2], coords[3]); + else r += (q-p); + pushIntersectionPoints(vertices, count-2, lineWidth/2, p,q,r); - // right, let's draw this polyline, then glDisable(GL_TEXTURE_2D); - glBegin(GL_LINE_STRIP); - if (table) { - for (int i = 1; i < args; i += 2) { - lua_pushnumber(L, i); // x coordinate - lua_rawget(L, 1); - lua_pushnumber(L, i+1); // y coordinate - lua_rawget(L, 1); - glVertex2f((GLfloat)lua_tonumber(L, -2), (GLfloat)lua_tonumber(L, -1)); - lua_pop(L, 2); - } - } else { - for (int i = 1; i < args; i+=2) { - glVertex2f((GLfloat)lua_tonumber(L, i), (GLfloat)lua_tonumber(L, i+1)); - } - } - glEnd(); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_FLOAT, 0, (const GLvoid*)vertices); + glDrawArrays(GL_TRIANGLE_STRIP, 0, count); + glDisableClientState(GL_VERTEX_ARRAY); glEnable(GL_TEXTURE_2D); - return 0; + + delete[] vertices; } void Graphics::triangle(DrawMode mode, float x1, float y1, float x2, float y2, float x3, float y3 ) { - glDisable(GL_TEXTURE_2D); - glPushMatrix(); - - switch(mode) - { - case DRAW_LINE: - glBegin(GL_LINE_LOOP); - glVertex2f(x1, y1); - glVertex2f(x2, y2); - glVertex2f(x3, y3); - glEnd(); - break; - - default: - case DRAW_FILL: - glBegin(GL_TRIANGLES); - glVertex2f(x1, y1); - glVertex2f(x2, y2); - glVertex2f(x3, y3); - glEnd(); - break; - } - - glPopMatrix(); - glEnable(GL_TEXTURE_2D); + float coords[] = { x1,y1, x2,y2, x3,y3, x1,y1 }; + polygon(mode, coords, 4 * 2); } void Graphics::rectangle(DrawMode mode, float x, float y, float w, float h) { - glDisable(GL_TEXTURE_2D); - glPushMatrix(); - - switch(mode) - { - case DRAW_LINE: - // offsets here because OpenGL is being a bitch about line drawings - glBegin(GL_LINE_LOOP); - glVertex2f(x, y); - glVertex2f(x, y+h-1); - glVertex2f(x+w-1, y+h-1); - glVertex2f(x+w-1, y); - glEnd(); - break; - - default: - case DRAW_FILL: - glBegin(GL_QUADS); - glVertex2f(x, y); - glVertex2f(x, y+h); - glVertex2f(x+w, y+h); - glVertex2f(x+w, y); - glEnd(); - break; - } - - glPopMatrix(); - glEnable(GL_TEXTURE_2D); + quad(mode, x,y, x,y+h, x+w,y+h, x+w,y); } void Graphics::quad(DrawMode mode, float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4 ) { - glDisable(GL_TEXTURE_2D); - glPushMatrix(); - - switch(mode) - { - case DRAW_LINE: - glBegin(GL_LINE_LOOP); - glVertex2f(x1, y1); - glVertex2f(x2, y2); - glVertex2f(x3, y3); - glVertex2f(x4, y4); - glEnd(); - break; - - default: - case DRAW_FILL: - glBegin(GL_QUADS); - glVertex2f(x1, y1); - glVertex2f(x2, y2); - glVertex2f(x3, y3); - glVertex2f(x4, y4); - glEnd(); - break; - } - - glPopMatrix(); - glEnable(GL_TEXTURE_2D); + float coords[] = { x1,y1, x2,y2, x3,y3, x4,y4, x1,y1 }; + polygon(mode, coords, 5 * 2); } void Graphics::circle(DrawMode mode, float x, float y, float radius, int points) @@ -955,144 +853,77 @@ namespace opengl float two_pi = static_cast(LOVE_M_PI * 2); if(points <= 0) points = 1; float angle_shift = (two_pi / points); + float phi = .0f; - glDisable(GL_TEXTURE_2D); - glPushMatrix(); - - glTranslatef(x, y, 0.0f); - - switch(mode) - { - case DRAW_LINE: - glBegin(GL_LINE_LOOP); - - for(float i = 0; i < two_pi; i+= angle_shift) - glVertex2f(radius * sin(i),radius * cos(i)); - - glEnd(); - break; - - default: - case DRAW_FILL: - glBegin(GL_TRIANGLE_FAN); - - for(float i = 0; i < two_pi; i+= angle_shift) - glVertex2f(radius * sin(i),radius * cos(i)); - - glEnd(); - break; + float *coords = new float[2 * (points + 1)]; + for (int i = 0; i < points; ++i, phi += angle_shift) { + coords[2*i] = x + radius * cos(phi); + coords[2*i+1] = y + radius * sin(phi); } - glPopMatrix(); - glEnable(GL_TEXTURE_2D); + coords[2*points] = coords[0]; + coords[2*points+1] = coords[1]; + + polygon(mode, coords, (points + 1) * 2); + + delete[] coords; } void Graphics::arc(DrawMode mode, float x, float y, float radius, float angle1, float angle2, int points) { - float angle = angle2 - angle1; - if (angle == 0) return; - while (angle < 0) angle += LOVE_M_PI * 2; + angle1 = fmod(angle1, 2 * LOVE_M_PI); + angle2 = fmod(angle2, 2 * LOVE_M_PI); + if (angle1 == angle2) + return; + else if (angle1 > angle2) + angle2 += LOVE_M_PI * 2; + + if(points <= 0) points = 1; - float angle_shift = (angle / points); - - glDisable(GL_TEXTURE_2D); - glPushMatrix(); - - glTranslatef(x, y, 0.0f); - - switch(mode) - { - case DRAW_LINE: - glBegin(GL_LINE_LOOP); - - glVertex2f(0, 0); - - for(float i = 0; i <= angle+angle_shift/2; i+= angle_shift) - glVertex2f(radius * cos(i+angle1),radius * sin(i+angle1)); - - glEnd(); - break; - - default: - case DRAW_FILL: - glBegin(GL_TRIANGLE_FAN); - - glVertex2f(0, 0); - - for(float i = 0; i <= angle+angle_shift/2; i+= angle_shift) - glVertex2f(radius * cos(i+angle1),radius * sin(i+angle1)); - - glEnd(); - break; + float angle_shift = ((angle2 - angle1) / points); + float phi = angle1; + + // GL_POLYGON can only fill-draw convex polygons, so we need to do stuff manually here + if (mode == DRAW_LINE) { + float *coords = new float[(points + 3) * 2]; + coords[0] = coords[2 * points + 4] = x; + coords[1] = coords[2 * points + 5] = y; + for (int i = 0; i <= points; ++i, phi += angle_shift) { + coords[2 * (i+1)] = x + radius * cos(phi); + coords[2 * (i+1) + 1] = y - radius * sin(phi); + } + polyline(coords, (points + 3) * 2); // artifacts at sharp angles if set to looping + + delete[] coords; + } else { + glDisable(GL_TEXTURE_2D); + glBegin(GL_TRIANGLE_FAN); + glVertex2f(x, y); + for (int i = 0; i <= points; ++i, phi += angle_shift) + glVertex2f(x + radius * cos(phi), y - radius * sin(phi)); + glEnd(); + glEnable(GL_TEXTURE_2D); } - - glPopMatrix(); - glEnable(GL_TEXTURE_2D); + } - int Graphics::polygon( lua_State * L ) + /// @param mode the draw mode + /// @param coords the coordinate array + /// @param count the number of coordinates/size of the array + void Graphics::polygon(DrawMode mode, const float* coords, size_t count) { - // Get number of params. - int n = lua_gettop(L); - - // Need at least two params. - if( n < 2 ) - return luaL_error(L, "Error: function needs at least two parameters."); - - DrawMode mode; - - const char * str = luaL_checkstring(L, 1); - if(!getConstant(str, mode)) - return luaL_error(L, "Invalid draw mode: %s", str); - - // Get the type of the second argument. - int luatype = lua_type(L, 2); - - // Perform additional type checking. - switch(luatype) - { - case LUA_TNUMBER: - if( n-1 < 6 ) return luaL_error(L, "Error: function requires at least 3 vertices."); - if( ((n-1)%2) != 0 ) return luaL_error(L, "Error: number of vertices must be a multiple of two."); - break; - case LUA_TTABLE: - if( (lua_objlen(L, 2)%2) != 0 ) return luaL_error(L, "Error: number of vertices must be a multiple of two."); - break; - default: - return luaL_error(L, "Error: number type or table expected."); + // coords is an array of a closed loop of vertices, i.e. + // coords[count-2] = coords[0], coords[count-1] = coords[1] + if (mode == DRAW_LINE) { + polyline(coords, count, true); + } else { + glDisable(GL_TEXTURE_2D); + glEnableClientState(GL_VERTEX_ARRAY); + glVertexPointer(2, GL_FLOAT, 0, (const GLvoid*)coords); + glDrawArrays(GL_POLYGON, 0, count / 2); // opengl will close the polygon for us + glDisableClientState(GL_VERTEX_ARRAY); + glEnable(GL_TEXTURE_2D); } - - - glDisable(GL_TEXTURE_2D); - - glBegin((mode==DRAW_LINE) ? GL_LINE_LOOP : GL_POLYGON); - - switch(luatype) - { - case LUA_TNUMBER: - for(int i = 2; isetLineStipple(); - return 0; - } - - unsigned short pattern = (unsigned short)luaL_checkint(L, 1); - int repeat = luaL_optint(L, 2, 1); - instance->setLineStipple(pattern, repeat); - return 0; - } - int w_getLineWidth(lua_State * L) { lua_pushnumber(L, instance->getLineWidth()); @@ -585,11 +571,6 @@ namespace opengl return 1; } - int w_getLineStipple(lua_State * L) - { - return instance->getLineStipple(L); - } - int w_setPointSize(lua_State * L) { float size = (float)luaL_checknumber(L, 1); @@ -776,15 +757,32 @@ namespace opengl int w_line(lua_State * L) { int args = lua_gettop(L); - if( args == 1 || args > 4) { - instance->polyline(L); - } else { - float x1 = (float)luaL_checknumber(L, 1); - float y1 = (float)luaL_checknumber(L, 2); - float x2 = (float)luaL_checknumber(L, 3); - float y2 = (float)luaL_checknumber(L, 4); - instance->line(x1, y1, x2, y2); + bool is_table = false; + if (args == 1 && lua_istable(L, 1)) { + args = lua_objlen(L, 1); + is_table = true; } + if (args % 2 != 0) + return luaL_error(L, "Number of vertices must be a multiple of two"); + else if (args < 4) + return luaL_error(L, "Need at least two vertices to draw a line"); + + float* coords = new float[args]; + if (is_table) { + for (int i = 0; i < args; ++i) { + lua_pushnumber(L, i + 1); + lua_rawget(L, 1); + coords[i] = lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + for (int i = 0; i < args; ++i) + coords[i] = lua_tonumber(L, i + 1); + } + + instance->polyline(coords, args); + + delete[] coords; return 0; } @@ -873,7 +871,46 @@ namespace opengl int w_polygon(lua_State * L) { - return instance->polygon(L); + int args = lua_gettop(L) - 1; + + Graphics::DrawMode mode; + const char * str = luaL_checkstring(L, 1); + if(!Graphics::getConstant(str, mode)) + return luaL_error(L, "Invalid draw mode: %s", str); + + bool is_table = false; + float* coords; + if (args == 1 && lua_istable(L, 2)) { + args = lua_objlen(L, 2); + is_table = true; + } + + if (args % 2 != 0) + return luaL_error(L, "Number of vertices must be a multiple of two"); + else if (args < 6) + return luaL_error(L, "Need at least three vertices to draw a polygon"); + + // fetch coords + coords = new float[args + 2]; + if (is_table) { + for (int i = 0; i < args; ++i) { + lua_pushnumber(L, i + 1); + lua_rawget(L, 2); + coords[i] = lua_tonumber(L, -1); + lua_pop(L, 1); + } + } else { + for (int i = 0; i < args; ++i) + coords[i] = lua_tonumber(L, i + 1); + } + + // make a closed loop + coords[args] = coords[0]; + coords[args+1] = coords[1]; + instance->polygon(mode, coords, args+2); + delete[] coords; + + return 0; } int w_push(lua_State *) @@ -950,10 +987,8 @@ namespace opengl { "setLineWidth", w_setLineWidth }, { "setLineStyle", w_setLineStyle }, { "setLine", w_setLine }, - { "setLineStipple", w_setLineStipple }, { "getLineWidth", w_getLineWidth }, { "getLineStyle", w_getLineStyle }, - { "getLineStipple", w_getLineStipple }, { "setPointSize", w_setPointSize }, { "setPointStyle", w_setPointStyle }, { "setPoint", w_setPoint },