OpenGL—ES 1.0 2d rounded rectangle - opengl-es

How to make rounded rectangle in OpenGL, or any polygon with rounded corners?

Using polygons
If using polygons is absolutely required, for example if objects with rounding need to be scaled or zoomed a lot or if amount of rounding needs to be controlled, it is possible to break rectangle into several sub-objects.
There are at least three rectangular parts and four corners. Calculating corner coordinates is easy. Just find a point from circle and build triangles like in picture above.
float anglerad = PI * angle / 180.0f;
float x = sinf(anglerad) * radius;
float y = cosf(anglerad) * radius;
It will still have sharp edges, but more points make corners more round. Small objects need less points than big objects.
Easy route is to use GL_TRIANGLE_FAN for corners. However with OpenGL ES it might be wise to minimize amount of OpenGL calls and just use more vertices as it is possible to build whole object as GL_TRIANGLE_STRIP.
This approached can be used with any shape. With a rectangle, angle of corner is always 90 degrees, but with other shapes angle needs to be calculated from the edges.
Using textures
Another approach is called 9-slice scaling. Rectangle and texture are broken into 9 slices. Actual rounding is in corner of a texture. Idea is that corners are not scaled, but maintain their original size. This approach is widely used pattern in UI-design allowing variable size UI-elements like buttons. Its advantage is that one rectangle needs only these 9 quads to render. But it will look bad if also corners need to be scaled and especially if texture is low resolution.

Bit of a bump but I was stuck on the same problem today, this is what I output, its created with Desktop GL, but should be very easy to convert to GLES, everything is a strip. It is probably not as optimized as it should be but if someone want to have a stab at it please be my guest ;)
typedef struct
{
float x;
float y;
} Vector2f;
void RoundRect( int x,
int y,
int width,
int height,
int radius,
int resolution )
{
float step = ( 2.0f * M_PI ) / resolution,
angle = 0.0f,
x_offset,
y_offset;
int i = 0;
unsigned int index = 0,
segment_count = ( int )( resolution / 4 );
Vector2f *top_left = ( Vector2f * ) malloc( segment_count * sizeof( Vector2f ) ),
*bottom_left = ( Vector2f * ) malloc( segment_count * sizeof( Vector2f ) ),
*top_right = ( Vector2f * ) malloc( segment_count * sizeof( Vector2f ) ),
*bottom_right = ( Vector2f * ) malloc( segment_count * sizeof( Vector2f ) ),
bottom_left_corner = { x + radius,
y - height + radius };
while( i != segment_count )
{
x_offset = cosf( angle );
y_offset = sinf( angle );
top_left[ index ].x = bottom_left_corner.x -
( x_offset * radius );
top_left[ index ].y = ( height - ( radius * 2.0f ) ) +
bottom_left_corner.y -
( y_offset * radius );
top_right[ index ].x = ( width - ( radius * 2.0f ) ) +
bottom_left_corner.x +
( x_offset * radius );
top_right[ index ].y = ( height - ( radius * 2.0f ) ) +
bottom_left_corner.y -
( y_offset * radius );
bottom_right[ index ].x = ( width - ( radius * 2.0f ) ) +
bottom_left_corner.x +
( x_offset * radius );
bottom_right[ index ].y = bottom_left_corner.y +
( y_offset * radius );
bottom_left[ index ].x = bottom_left_corner.x -
( x_offset * radius );
bottom_left[ index ].y = bottom_left_corner.y +
( y_offset * radius );
top_left[ index ].x = roundf( top_left[ index ].x );
top_left[ index ].y = roundf( top_left[ index ].y );
top_right[ index ].x = roundf( top_right[ index ].x );
top_right[ index ].y = roundf( top_right[ index ].y );
bottom_right[ index ].x = roundf( bottom_right[ index ].x );
bottom_right[ index ].y = roundf( bottom_right[ index ].y );
bottom_left[ index ].x = roundf( bottom_left[ index ].x );
bottom_left[ index ].y = roundf( bottom_left[ index ].y );
angle -= step;
++index;
++i;
}
glBegin( GL_TRIANGLE_STRIP );
{
// Top
{
i = 0;
while( i != segment_count )
{
//glColor4f( 1.0f, 0.0f, 0.0f, 1.0f );
glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
glVertex2i( top_left[ i ].x,
top_left[ i ].y );
//glColor4f( 0.0f, 1.0f, 0.0f, 1.0f );
glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
glVertex2i( top_right[ i ].x,
top_right[ i ].y );
++i;
}
}
// In order to stop and restart the strip.
glColor4f( 0.0f, 1.0f, 0.0f, 1.5f );
glVertex2i( top_right[ 0 ].x,
top_right[ 0 ].y );
glColor4f( 0.0f, 1.0f, 0.0f, 1.5f );
glVertex2i( top_right[ 0 ].x,
top_right[ 0 ].y );
// Center
{
//glColor4f( 0.0f, 1.0f, 0.0f, 1.0f );
glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
glVertex2i( top_right[ 0 ].x,
top_right[ 0 ].y );
//glColor4f( 1.0f, 0.0f, 0.0f, 1.0f );
glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
glVertex2i( top_left[ 0 ].x,
top_left[ 0 ].y );
//glColor4f( 0.0f, 0.0f, 1.0f, 1.0f );
glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
glVertex2i( bottom_right[ 0 ].x,
bottom_right[ 0 ].y );
//glColor4f( 1.0f, 1.0f, 0.0f, 1.0f );
glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
glVertex2i( bottom_left[ 0 ].x,
bottom_left[ 0 ].y );
}
// Bottom
i = 0;
while( i != segment_count )
{
//glColor4f( 0.0f, 0.0f, 1.0f, 1.0f );
glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
glVertex2i( bottom_right[ i ].x,
bottom_right[ i ].y );
//glColor4f( 1.0f, 1.0f, 0.0f, 1.0f );
glColor4f( 0.5f, 0.5f, 0.5f, 1.0f );
glVertex2i( bottom_left[ i ].x,
bottom_left[ i ].y );
++i;
}
}
glEnd();
glBegin( GL_LINE_STRIP );
//glColor4f( 0.0f, 1.0f, 1.0f, 1.0f );
glColor4f( 1.0f, 0.5f, 0.0f, 1.0f );
// Border
{
i = ( segment_count - 1 );
while( i > -1 )
{
glVertex2i( top_left[ i ].x,
top_left[ i ].y );
--i;
}
i = 0;
while( i != segment_count )
{
glVertex2i( bottom_left[ i ].x,
bottom_left[ i ].y );
++i;
}
i = ( segment_count - 1 );
while( i > -1 )
{
glVertex2i( bottom_right[ i ].x,
bottom_right[ i ].y );
--i;
}
i = 0;
while( i != segment_count )
{
glVertex2i( top_right[ i ].x,
top_right[ i ].y );
++i;
}
// Close the border.
glVertex2i( top_left[ ( segment_count - 1 ) ].x,
top_left[ ( segment_count - 1 ) ].y );
}
glEnd();
glBegin( GL_LINES );
//glColor4f( 0.0f, 1.0f, 1.0f, 1.0f );
glColor4f( 0.0f, 0.5f, 1.0f, 1.0f );
// Separator
{
// Top bar
glVertex2i( top_right[ 0 ].x,
top_right[ 0 ].y );
glVertex2i( top_left[ 0 ].x,
top_left[ 0 ].y );
// Bottom bar
glVertex2i( bottom_left[ 0 ].x,
bottom_left[ 0 ].y );
glVertex2i( bottom_right[ 0 ].x,
bottom_right[ 0 ].y );
}
glEnd();
free( top_left );
free( bottom_left );
free( top_right );
free( bottom_right );
}
To draw the rounded rectangle simply call something like inside an orthographic view:
RoundRect( 200, /* x */
400, /* y */
400, /* width */
300, /* height */
25, /* Corner radius, at least less than 140? */
64 /* need to be "dividable" by 4 */ );

I needed to draw similar rectangle, but transparent - and code above draws some of triangles overlap. Fixed that, also removed malloc, just to simplify solution. Here is my version:
typedef struct
{
float x;
float y;
} Vector2f;
//
// Draws rounded rectangle.
//
// Slightly tuned version of http://stackoverflow.com/questions/5369507/opengles-1-0-2d-rounded-rectangle
//
#define ROUNDING_POINT_COUNT 8 // Larger values makes circle smoother.
void DrawRoundRect( float x, float y, float width, float height, float* color = 0, float radius = 0.0 )
{
Vector2f top_left[ROUNDING_POINT_COUNT];
Vector2f bottom_left[ROUNDING_POINT_COUNT];
Vector2f top_right[ROUNDING_POINT_COUNT];
Vector2f bottom_right[ROUNDING_POINT_COUNT];
if( radius == 0.0 )
{
radius = min(width, height);
radius *= 0.10; // 10%
}
int i = 0;
float x_offset, y_offset;
float step = ( 2.0f * pi ) / (ROUNDING_POINT_COUNT * 4),
angle = 0.0f;
unsigned int index = 0, segment_count = ROUNDING_POINT_COUNT;
Vector2f bottom_left_corner = { x + radius, y - height + radius };
while( i != segment_count )
{
x_offset = cosf( angle );
y_offset = sinf( angle );
top_left[ index ].x = bottom_left_corner.x -
( x_offset * radius );
top_left[ index ].y = ( height - ( radius * 2.0f ) ) +
bottom_left_corner.y -
( y_offset * radius );
top_right[ index ].x = ( width - ( radius * 2.0f ) ) +
bottom_left_corner.x +
( x_offset * radius );
top_right[ index ].y = ( height - ( radius * 2.0f ) ) +
bottom_left_corner.y -
( y_offset * radius );
bottom_right[ index ].x = ( width - ( radius * 2.0f ) ) +
bottom_left_corner.x +
( x_offset * radius );
bottom_right[ index ].y = bottom_left_corner.y +
( y_offset * radius );
bottom_left[ index ].x = bottom_left_corner.x -
( x_offset * radius );
bottom_left[ index ].y = bottom_left_corner.y +
( y_offset * radius );
top_left[ index ].x = top_left[ index ].x;
top_left[ index ].y = top_left[ index ].y;
top_right[ index ].x = top_right[ index ].x;
top_right[ index ].y = top_right[ index ].y;
bottom_right[ index ].x = bottom_right[ index ].x ;
bottom_right[ index ].y = bottom_right[ index ].y;
bottom_left[ index ].x = bottom_left[ index ].x ;
bottom_left[ index ].y = bottom_left[ index ].y ;
angle -= step;
++index;
++i;
}
static GLubyte clr[] = { 156, 207, 255, 128 }; // Light blue, 50% transparent.
if( color )
glColor4fv(color);
else
glColor4ubv(clr);
glBegin( GL_TRIANGLE_STRIP );
{
// Top
for( i = segment_count - 1 ; i >= 0 ; i--)
{
glVertex2f( top_left[ i ].x, top_left[ i ].y );
glVertex2f( top_right[ i ].x, top_right[ i ].y );
}
// In order to stop and restart the strip.
glVertex2f( top_right[ 0 ].x, top_right[ 0 ].y );
glVertex2f( top_right[ 0 ].x, top_right[ 0 ].y );
// Center
glVertex2f( top_right[ 0 ].x, top_right[ 0 ].y );
glVertex2f( top_left[ 0 ].x, top_left[ 0 ].y );
glVertex2f( bottom_right[ 0 ].x, bottom_right[ 0 ].y );
glVertex2f( bottom_left[ 0 ].x, bottom_left[ 0 ].y );
// Bottom
for( i = 0; i != segment_count ; i++ )
{
glVertex2f( bottom_right[ i ].x, bottom_right[ i ].y );
glVertex2f( bottom_left[ i ].x, bottom_left[ i ].y );
}
}
glEnd();
} //DrawRoundRect

The following code is coping from my own project, I have added some comments to explain in the code.
If will draw a gradient rounded rectangle without border.
#define GLW_SMALL_ROUNDED_CORNER_SLICES 5 // How many vertexes you want of each corner
#define glwR(rgb) ((float)(((rgb) >> 16) & 0xff) / 255)
#define glwG(rgb) ((float)(((rgb) >> 8) & 0xff) / 255)
#define glwB(rgb) ((float)(((rgb)) & 0xff) / 255)
typedef struct glwVec2 {
float x;
float y;
} glwVec2;
static glwVec2 glwRoundedCorners[GLW_SMALL_ROUNDED_CORNER_SLICES] = {{0}}; // This array keep the generated vertexes of one corner
static void createRoundedCorners(glwVec2 *arr, int num) {
// Generate the corner vertexes
float slice = M_PI / 2 / num;
int i;
float a = 0;
for (i = 0; i < num; a += slice, ++i) {
arr[i].x = cosf(a);
arr[i].y = sinf(a);
}
}
createRoundedCorners(glwRoundedCorners, GLW_SMALL_ROUNDED_CORNER_SLICES);
void glwDrawRoundedRectGradientFill(float x, float y, float width, float height,
float radius, unsigned int topColor, unsigned int bottomColor) {
float left = x;
float top = y;
float bottom = y + height - 1;
float right = x + width - 1;
int i;
glDisable(GL_TEXTURE_2D);
glBegin(GL_QUAD_STRIP);
// Draw left rounded side.
for (i = 0; i < GLW_SMALL_ROUNDED_CORNER_SLICES; ++i) {
glColor3f(glwR(bottomColor), glwG(bottomColor), glwB(bottomColor));
glVertex2f(left + radius - radius * glwRoundedCorners[i].x,
bottom - radius + radius * glwRoundedCorners[i].y);
glColor3f(glwR(topColor), glwG(topColor), glwB(topColor));
glVertex2f(left + radius - radius * glwRoundedCorners[i].x,
top + radius - radius * glwRoundedCorners[i].y);
}
// Draw right rounded side.
for (i = GLW_SMALL_ROUNDED_CORNER_SLICES - 1; i >= 0; --i) {
glColor3f(glwR(bottomColor), glwG(bottomColor), glwB(bottomColor));
glVertex2f(right - radius + radius * glwRoundedCorners[i].x,
bottom - radius + radius * glwRoundedCorners[i].y);
glColor3f(glwR(topColor), glwG(topColor), glwB(topColor));
glVertex2f(right - radius + radius * glwRoundedCorners[i].x,
top + radius - radius * glwRoundedCorners[i].y);
}
glEnd();
}
If you want draw the border, here is the code.
static void glwDrawRightTopVertexs(float left, float top, float right,
float bottom, float radius) {
int i;
for (i = GLW_SMALL_ROUNDED_CORNER_SLICES - 1; i >= 0; --i) {
glVertex2f(right - radius + radius * glwRoundedCorners[i].x,
top + radius - radius * glwRoundedCorners[i].y);
}
}
static void glwDrawRightBottomVertexs(float left, float top, float right,
float bottom, float radius) {
int i;
for (i = 0; i < GLW_SMALL_ROUNDED_CORNER_SLICES; ++i) {
glVertex2f(right - radius + radius * glwRoundedCorners[i].x,
bottom - radius + radius * glwRoundedCorners[i].y);
}
}
static void glwDrawLeftBottomVertexs(float left, float top, float right,
float bottom, float radius) {
int i;
for (i = GLW_SMALL_ROUNDED_CORNER_SLICES - 1; i >= 0; --i) {
glVertex2f(left + radius - radius * glwRoundedCorners[i].x,
bottom - radius + radius * glwRoundedCorners[i].y);
}
}
static void glwDrawLeftTopVertexs(float left, float top, float right,
float bottom, float radius) {
int i;
for (i = 0; i < GLW_SMALL_ROUNDED_CORNER_SLICES; ++i) {
glVertex2f(left + radius - radius * glwRoundedCorners[i].x,
top + radius - radius * glwRoundedCorners[i].y);
}
}
void glwDrawRoundedRectBorder(float x, float y, float width, float height,
float radius, unsigned int color) {
float left = x;
float top = y;
float bottom = y + height - 1;
float right = x + width - 1;
glDisable(GL_TEXTURE_2D);
glColor3f(glwR(color), glwG(color), glwB(color));
glBegin(GL_LINE_LOOP);
glVertex2f(left, top + radius);
glwDrawLeftTopVertexs(left, top, right, bottom, radius);
glVertex2f(left + radius, top);
glVertex2f(right - radius, top);
glwDrawRightTopVertexs(left, top, right, bottom, radius);
glVertex2f(right, top + radius);
glVertex2f(right, bottom - radius);
glwDrawRightBottomVertexs(left, top, right, bottom, radius);
glVertex2f(right - radius, bottom);
glVertex2f(left + radius, bottom);
glwDrawLeftBottomVertexs(left, top, right, bottom, radius);
glVertex2f(left, bottom - radius);
glEnd();
}

#define PI_2 1.57079632679490f
#define SIN(x) SDL_sinf (x)
#define COS(x) SDL_cosf (x)
typedef struct _g2d_vertex_t g2d_vertex_t;
struct _g2d_vertex_t {
float x, y;
};
// pVertices - destination buffer
// nVertices - buffer size
// dx - width
// dy - height
// r - radius
// returnes the number of used vertices
int
__cdecl buildRoundedRect (g2d_vertex_t * pVertices, int nVertices, float dx, float dy, float r) {
float a, da;
int i1, i2, i3, i4, n;
if (nVertices < 4) { return 0; }
if (nVertices == 4) {
pVertices [0].x = 0.f; pVertices [0].y = 0.f;
pVertices [1].x = dx; pVertices [1].y = 0.f;
pVertices [2].x = dx; pVertices [2].y = dy;
pVertices [3].x = 0.f; pVertices [3].y = dy;
return nVertices;
}
n = nVertices >> 2;
if (r > dx / 2.f) { r = dx / 2.f; }
if (r > dy / 2.f) { r = dy / 2.f; }
a = 0.f;
da = PI_2 / (float) (n - 1);
for (i1 = 0, i2 = (n << 1) - 1, i3 = n << 1, i4 = (n << 2) - 1; i1 < n; i1++, i2--, i3++, i4--, a += da) {
float cosA = COS (a), sinA = SIN (a);
pVertices [i1].x = (dx - r) + r * cosA; pVertices [i1].y = (dy - r) + r * sinA;
pVertices [i2].x = r - r * cosA; pVertices [i2].y = (dy - r) + r * sinA;
pVertices [i3].x = r - r * cosA; pVertices [i3].y = r - r * sinA;
pVertices [i4].x = (dx - r) + r * cosA; pVertices [i4].y = r - r * sinA;
}
return n << 2;
}
void drawRoundedRect () {
g2d_vertex_t vertices [50];
glColor3f (0.3f, 0.5f, 0.2f);
glVertexPointer (2, GL_FLOAT, 0, vertices);
glEnableClientState (GL_VERTEX_ARRAY);
glDrawArrays (GL_LINE_LOOP, 0, buildRoundedRect (vertices, 50 /* max count of vertices to use: 4 - 50 */, 150.f, 80.f, 20.f));
}

I came across this fixing a crash in some open-source software - the non-GL version worked fine but basically the intention was to implement a rounded rectangle but the developer was too lazy for that and decided to force a crash instead :-(
Although I think #vime's answer is succinct and complete, I have seen lots of similar examples, none of which gave me any confidence and that I thought were non-obvious, so here's mine for the record... the calling function
implements the 4-corners ( code snippet )...
glBegin(GL_POLYGON);
// top-left corner
DrawGLRoundedCorner(x, y + radius, 3 * PI / 2, PI / 2, radius);
// top-right
DrawGLRoundedCorner(x + size_x - radius, y, 0.0, PI / 2, radius);
// bottom-right
DrawGLRoundedCorner(x + size_x, y + size_y - radius, PI / 2, PI / 2, radius);
// bottom-left
DrawGLRoundedCorner(x + radius, y + size_y, PI, PI / 2, radius);
glEnd();
... and the arc-section function DrawGLRoundedCorner(). Note that this assumes that glBegin() has already been called and plots both the start and the end of the arc - which is why you don't need to explicitly add the vertices at the end of the sides.
void DrawGLRoundedCorner(int x, int y, double sa, double arc, float r) {
// centre of the arc, for clockwise sense
float cent_x = x + r * cos(sa + PI / 2);
float cent_y = y + r * sin(sa + PI / 2);
// build up piecemeal including end of the arc
int n = ceil(N_ROUNDING_PIECES * arc / PI * 2);
for (int i = 0; i <= n; i++) {
double ang = sa + arc * (double)i / (double)n;
// compute the next point
float next_x = cent_x + r * sin(ang);
float next_y = cent_y - r * cos(ang);
glVertex2f(next_x, next_y);
}
}
By using a different glBegin such as with GL_LINE_LOOP I think you would end up with a non-filled rounded rectangle. For larger corner radii there might be a need to use various anti-aliasing hints or the like to make it look prettier, but there are other posts regarding that.
Hope that helps someone.

You can also make triangles instead of rectangles to bevel the edges.

Related

What is this called and how to achieve! Visuals in processing

Hey does anyone know how to achieve this effect using processing or what this is called?
I have been trying to use the wave gradient example in the processing library and implementing Perlin noise but I can not get close to the gif quality.
I know the artist used processing but can not figure out how!
Link to gif:
https://giphy.com/gifs/processing-jodeus-QInYLzY33wMwM
The effect is reminescent of Op Art (optical illusion art): I recommend reading/learning more about this fascinating genre and artists like:
Bridget Riley
(Bridget Riley, Intake, 1964)
(Bridget Riley, Hesistate, 1964,
Copyright: (c) Bridget Riley 2018. All rights reserved. / Photo (c) Tate)
Victor Vasarely
(Victor Vasarely, Zebra Couple)
(Victor Vasarely, VegaII)
Frank Stella
(Frank Stella, Untitled 1965, Image curtesy of Art Gallery NSW)
and more
You notice this waves are reminiscent/heavily inspired by Bridget Riley's work.
I also recommend checking out San Charoenchai;s album visualiser for Beach House - 7
As mentioned in my comment: you should post your attempt.
Waves and perlin noise could work for sure.
There are many ways to achieve a similar look.
Here's tweaked version of Daniel Shiffman's Noise Wave example:
int numWaves = 24;
float[] yoff = new float[numWaves]; // 2nd dimension of perlin noise
float[] yoffIncrements = new float[numWaves];
void setup() {
size(640, 360);
noStroke();
for(int i = 0 ; i < numWaves; i++){
yoffIncrements[i] = map(i, 0, numWaves - 1, 0.01, 0.03);
}
}
void draw() {
background(0);
float waveHeight = height / numWaves;
for(int i = 0 ; i < numWaves; i++){
float waveY = i * waveHeight;
fill(i % 2 == 0 ? color(255) : color(0));
// We are going to draw a polygon out of the wave points
beginShape();
float xoff = 0; // Option #1: 2D Noise
// float xoff = yoff; // Option #2: 1D Noise
// Iterate over horizontal pixels
for (float x = 0; x <= width + 30; x += 20) {
// Calculate a y value according to noise, map to
float y = map(noise(xoff, yoff[i]), 0, 1, waveY , waveY + (waveHeight * 3)); // Option #1: 2D Noise
// float y = map(noise(xoff), 0, 1, 200,300); // Option #2: 1D Noise
// Set the vertex
vertex(x, y);
// Increment x dimension for noise
xoff += 0.05;
}
// increment y dimension for noise
yoff[i] += yoffIncrements[i];
vertex(width, height);
vertex(0, height);
endShape(CLOSE);
}
}
Notice the quality of the noise wave in comparison to the image you're trying to emulate: there is a constant rhythm to it. To me that is a hint that it's using cycling sine waves changing phase and amplitude (potentially even adding waves together).
I've written an extensive answer on animating sine waves here
(Reuben Margolin's kinectic sculpture system demo)
From your question it sounds like you would be comfortable implementing a sine wave animation. It it helps, here's an example of adding two waves together:
void setup(){
size(600,600);
noStroke();
}
void draw(){
background(0);
// how many waves per sketch height
int heightDivisions = 30;
// split the sketch height into equal height sections
float heightDivisionSize = (float)height / heightDivisions;
// for each height division
for(int j = 0 ; j < heightDivisions; j++){
// use % 2 to alternate between black and white
// see https://processing.org/reference/modulo.html and
// https://processing.org/reference/conditional.html for more
fill(j % 2 == 0 ? color(255) : color(0));
// offset drawing on Y axis
translate(0,(j * heightDivisionSize));
// start a wave shape
beginShape();
// first vertex is at the top left corner
vertex(0,height);
// how many horizontal (per wave) divisions ?
int widthDivisions = 12;
// equally space the points on the wave horizontally
float widthDivsionSize = (float)width / widthDivisions;
// for each point on the wave
for(int i = 0; i <= widthDivisions; i++){
// calculate different phases
// play with arithmetic operators to make interesting wave additions
float phase1 = (frameCount * 0.01) + ((i * j) * 0.025);
float phase2 = (frameCount * 0.05) + ((i + j) * 0.25);
// calculate vertex x position
float x = widthDivsionSize * i;
// multiple sine waves
// (can use cos() and use other ratios too
// 150 in this case is the wave amplitude (e.g. from -150 to + 150)
float y = ((sin(phase1) * sin(phase2) * 150));
// draw calculated vertex
vertex(x,y);
}
// last vertex is at bottom right corner
vertex(width,height);
// finish the shape
endShape();
}
}
The result:
Minor note on performance: this could be implemented more efficiently using PShape, however I recommend playing with the maths/geometry to find the form you're after, then as a last step think of optimizing it.
My intention is not to show you how to create an exact replica, but to show there's more to Op Art than an effect and hopefully inspire you to explore other methods of achieving something similar in the hope that you will discover your own methods and outcomes: something new and of your own through fun happy accidents.
In terms of other techniques/avenues to explore:
displacement maps:
Using an alternating black/white straight bars texture on wavy 3D geometry
using shaders:
Shaders are a huge topic on their own, but it's worth noting:
There's a very good Processing Shader Tutorial
You might be able to explore frament shaders on shadertoy, tweak the code in browser then make slight changes so you can run them in Processing.
Here are a few quick examples:
https://www.shadertoy.com/view/Wts3DB
tweaked for black/white waves in Processing as shader-Wts3DB.frag
// https://www.shadertoy.com/view/Wts3DB
uniform vec2 iResolution;
uniform float iTime;
#define COUNT 6.
#define COL_BLACK vec3(23,32,38) / 255.0
#define SF 1./min(iResolution.x,iResolution.y)
#define SS(l,s) smoothstep(SF,-SF,l-s)
#define hue(h) clamp( abs( fract(h + vec4(3,2,1,0)/3.) * 6. - 3.) -1. , 0., 1.)
// Original noise code from https://www.shadertoy.com/view/4sc3z2
#define MOD3 vec3(.1031,.11369,.13787)
vec3 hash33(vec3 p3)
{
p3 = fract(p3 * MOD3);
p3 += dot(p3, p3.yxz+19.19);
return -1.0 + 2.0 * fract(vec3((p3.x + p3.y)*p3.z, (p3.x+p3.z)*p3.y, (p3.y+p3.z)*p3.x));
}
float simplex_noise(vec3 p)
{
const float K1 = 0.333333333;
const float K2 = 0.166666667;
vec3 i = floor(p + (p.x + p.y + p.z) * K1);
vec3 d0 = p - (i - (i.x + i.y + i.z) * K2);
vec3 e = step(vec3(0.0), d0 - d0.yzx);
vec3 i1 = e * (1.0 - e.zxy);
vec3 i2 = 1.0 - e.zxy * (1.0 - e);
vec3 d1 = d0 - (i1 - 1.0 * K2);
vec3 d2 = d0 - (i2 - 2.0 * K2);
vec3 d3 = d0 - (1.0 - 3.0 * K2);
vec4 h = max(0.6 - vec4(dot(d0, d0), dot(d1, d1), dot(d2, d2), dot(d3, d3)), 0.0);
vec4 n = h * h * h * h * vec4(dot(d0, hash33(i)), dot(d1, hash33(i + i1)), dot(d2, hash33(i + i2)), dot(d3, hash33(i + 1.0)));
return dot(vec4(31.316), n);
}
void mainImage( vec4 fragColor, vec2 fragCoord )
{
}
void main(void) {
//vec2 uv = vec2(gl_FragColor.x / iResolution.y, gl_FragColor.y / iResolution.y);
vec2 uv = gl_FragCoord.xy / iResolution.y;
float m = 0.;
float t = iTime *.5;
vec3 col;
for(float i=COUNT; i>=0.; i-=1.){
float edge = simplex_noise(vec3(uv * vec2(2., 0.) + vec2(0, t + i*.15), 3.))*.2 + (.95/COUNT)*i;
float mi = SS(edge, uv.y) - SS(edge + .095, uv.y);
m += mi;
if(mi > 0.){
col = vec3(1.0);
}
}
col = mix(COL_BLACK, col, m);
gl_FragColor = vec4(col,1.0);
// mainImage(gl_FragColor,gl_FragCoord);
}
loaded in Processing as:
PShader shader;
void setup(){
size(300,300,P2D);
noStroke();
shader = loadShader("shader-Wts3DB.frag");
shader.set("iResolution",(float)width, float(height));
}
void draw(){
background(0);
shader.set("iTime",frameCount * 0.05);
shader(shader);
rect(0,0,width,height);
}
https://www.shadertoy.com/view/MtsXzl
tweaked as shader-MtsXzl.frag
//https://www.shadertoy.com/view/MtsXzl
#define SHOW_GRID 1
const float c_scale = 0.5;
const float c_rate = 2.0;
#define FLT_MAX 3.402823466e+38
uniform vec3 iMouse;
uniform vec2 iResolution;
uniform float iTime;
//=======================================================================================
float CubicHermite (float A, float B, float C, float D, float t)
{
float t2 = t*t;
float t3 = t*t*t;
float a = -A/2.0 + (3.0*B)/2.0 - (3.0*C)/2.0 + D/2.0;
float b = A - (5.0*B)/2.0 + 2.0*C - D / 2.0;
float c = -A/2.0 + C/2.0;
float d = B;
return a*t3 + b*t2 + c*t + d;
}
//=======================================================================================
float hash(float n) {
return fract(sin(n) * 43758.5453123);
}
//=======================================================================================
float GetHeightAtTile(vec2 T)
{
float rate = hash(hash(T.x) * hash(T.y))*0.5+0.5;
return (sin(iTime*rate*c_rate) * 0.5 + 0.5) * c_scale;
}
//=======================================================================================
float HeightAtPos(vec2 P)
{
vec2 tile = floor(P);
P = fract(P);
float CP0X = CubicHermite(
GetHeightAtTile(tile + vec2(-1.0,-1.0)),
GetHeightAtTile(tile + vec2(-1.0, 0.0)),
GetHeightAtTile(tile + vec2(-1.0, 1.0)),
GetHeightAtTile(tile + vec2(-1.0, 2.0)),
P.y
);
float CP1X = CubicHermite(
GetHeightAtTile(tile + vec2( 0.0,-1.0)),
GetHeightAtTile(tile + vec2( 0.0, 0.0)),
GetHeightAtTile(tile + vec2( 0.0, 1.0)),
GetHeightAtTile(tile + vec2( 0.0, 2.0)),
P.y
);
float CP2X = CubicHermite(
GetHeightAtTile(tile + vec2( 1.0,-1.0)),
GetHeightAtTile(tile + vec2( 1.0, 0.0)),
GetHeightAtTile(tile + vec2( 1.0, 1.0)),
GetHeightAtTile(tile + vec2( 1.0, 2.0)),
P.y
);
float CP3X = CubicHermite(
GetHeightAtTile(tile + vec2( 2.0,-1.0)),
GetHeightAtTile(tile + vec2( 2.0, 0.0)),
GetHeightAtTile(tile + vec2( 2.0, 1.0)),
GetHeightAtTile(tile + vec2( 2.0, 2.0)),
P.y
);
return CubicHermite(CP0X, CP1X, CP2X, CP3X, P.x);
}
//=======================================================================================
vec3 NormalAtPos( vec2 p )
{
float eps = 0.01;
vec3 n = vec3( HeightAtPos(vec2(p.x-eps,p.y)) - HeightAtPos(vec2(p.x+eps,p.y)),
2.0*eps,
HeightAtPos(vec2(p.x,p.y-eps)) - HeightAtPos(vec2(p.x,p.y+eps)));
return normalize( n );
}
//=======================================================================================
float RayIntersectSphere (vec4 sphere, in vec3 rayPos, in vec3 rayDir)
{
//get the vector from the center of this circle to where the ray begins.
vec3 m = rayPos - sphere.xyz;
//get the dot product of the above vector and the ray's vector
float b = dot(m, rayDir);
float c = dot(m, m) - sphere.w * sphere.w;
//exit if r's origin outside s (c > 0) and r pointing away from s (b > 0)
if(c > 0.0 && b > 0.0)
return -1.0;
//calculate discriminant
float discr = b * b - c;
//a negative discriminant corresponds to ray missing sphere
if(discr < 0.0)
return -1.0;
//ray now found to intersect sphere, compute smallest t value of intersection
float collisionTime = -b - sqrt(discr);
//if t is negative, ray started inside sphere so clamp t to zero and remember that we hit from the inside
if(collisionTime < 0.0)
collisionTime = -b + sqrt(discr);
return collisionTime;
}
//=======================================================================================
vec3 DiffuseColor (in vec3 pos)
{
#if SHOW_GRID
pos = mod(floor(pos),2.0);
return vec3(mod(pos.x, 2.0) < 1.0 ? 1.0 : 0.0);
#else
return vec3(0.1, 0.8, 0.9);
#endif
}
//=======================================================================================
vec3 ShadePoint (in vec3 pos, in vec3 rayDir, float time, bool fromUnderneath)
{
vec3 diffuseColor = DiffuseColor(pos);
vec3 reverseLightDir = normalize(vec3(1.0,1.0,-1.0));
vec3 lightColor = vec3(1.0);
vec3 ambientColor = vec3(0.05);
vec3 normal = NormalAtPos(pos.xz);
normal *= fromUnderneath ? -1.0 : 1.0;
// diffuse
vec3 color = diffuseColor;
float dp = dot(normal, reverseLightDir);
if(dp > 0.0)
color += (diffuseColor * lightColor);
return color;
}
//=======================================================================================
vec3 HandleRay (in vec3 rayPos, in vec3 rayDir, in vec3 pixelColor, out float hitTime)
{
float time = 0.0;
float lastHeight = 0.0;
float lastY = 0.0;
float height;
bool hitFound = false;
hitTime = FLT_MAX;
bool fromUnderneath = false;
vec2 timeMinMax = vec2(0.0, 20.0);
time = timeMinMax.x;
const int c_numIters = 100;
float deltaT = (timeMinMax.y - timeMinMax.x) / float(c_numIters);
vec3 pos = rayPos + rayDir * time;
float firstSign = sign(pos.y - HeightAtPos(pos.xz));
for (int index = 0; index < c_numIters; ++index)
{
pos = rayPos + rayDir * time;
height = HeightAtPos(pos.xz);
if (sign(pos.y - height) * firstSign < 0.0)
{
fromUnderneath = firstSign < 0.0;
hitFound = true;
break;
}
time += deltaT;
lastHeight = height;
lastY = pos.y;
}
if (hitFound) {
time = time - deltaT + deltaT*(lastHeight-lastY)/(pos.y-lastY-height+lastHeight);
pos = rayPos + rayDir * time;
pixelColor = ShadePoint(pos, rayDir, time, fromUnderneath);
hitTime = time;
}
return pixelColor;
}
//=======================================================================================
void main()
{
// scrolling camera
vec3 cameraOffset = vec3(iTime, 0.5, iTime);
//----- camera
vec2 mouse = iMouse.xy / iResolution.xy;
vec3 cameraAt = vec3(0.5,0.5,0.5) + cameraOffset;
float angleX = iMouse.z > 0.0 ? 6.28 * mouse.x : 3.14 + iTime * 0.25;
float angleY = iMouse.z > 0.0 ? (mouse.y * 6.28) - 0.4 : 0.5;
vec3 cameraPos = (vec3(sin(angleX)*cos(angleY), sin(angleY), cos(angleX)*cos(angleY))) * 5.0;
// float angleX = 0.8;
// float angleY = 0.8;
// vec3 cameraPos = vec3(0.0,0.0,0.0);
cameraPos += vec3(0.5,0.5,0.5) + cameraOffset;
vec3 cameraFwd = normalize(cameraAt - cameraPos);
vec3 cameraLeft = normalize(cross(normalize(cameraAt - cameraPos), vec3(0.0,sign(cos(angleY)),0.0)));
vec3 cameraUp = normalize(cross(cameraLeft, cameraFwd));
float cameraViewWidth = 6.0;
float cameraViewHeight = cameraViewWidth * iResolution.y / iResolution.x;
float cameraDistance = 6.0; // intuitively backwards!
// Objects
vec2 rawPercent = (gl_FragCoord.xy / iResolution.xy);
vec2 percent = rawPercent - vec2(0.5,0.5);
vec3 rayTarget = (cameraFwd * vec3(cameraDistance,cameraDistance,cameraDistance))
- (cameraLeft * percent.x * cameraViewWidth)
+ (cameraUp * percent.y * cameraViewHeight);
vec3 rayDir = normalize(rayTarget);
float hitTime = FLT_MAX;
vec3 pixelColor = vec3(1.0, 1.0, 1.0);
pixelColor = HandleRay(cameraPos, rayDir, pixelColor, hitTime);
gl_FragColor = vec4(clamp(pixelColor,0.0,1.0), 1.0);
}
and the mouse interactive Processing sketch:
PShader shader;
void setup(){
size(300,300,P2D);
noStroke();
shader = loadShader("shader-MtsXzl.frag");
shader.set("iResolution",(float)width, float(height));
}
void draw(){
background(0);
shader.set("iTime",frameCount * 0.05);
shader.set("iMouse",(float)mouseX , (float)mouseY, mousePressed ? 1.0 : 0.0);
shader(shader);
rect(0,0,width,height);
}
Shadertoy is great way to play/learn: have fun !
Update
Here's a quick test tweaking Daniel Shiffman's 3D Terrain Generation example to add a stripped texture and basic sine waves instead of perlin noise:
// Daniel Shiffman
// http://codingtra.in
// http://patreon.com/codingtrain
// Code for: https://youtu.be/IKB1hWWedMk
int cols, rows;
int scl = 20;
int w = 2000;
int h = 1600;
float flying = 0;
float[][] terrain;
PImage texture;
void setup() {
size(600, 600, P3D);
textureMode(NORMAL);
noStroke();
cols = w / scl;
rows = h/ scl;
terrain = new float[cols][rows];
texture = getBarsTexture(512,512,96);
}
void draw() {
flying -= 0.1;
float yoff = flying;
for (int y = 0; y < rows; y++) {
float xoff = 0;
for (int x = 0; x < cols; x++) {
//terrain[x][y] = map(noise(xoff, yoff), 0, 1, -100, 100);
terrain[x][y] = map(sin(xoff) * sin(yoff), 0, 1, -60, 60);
xoff += 0.2;
}
yoff += 0.2;
}
background(0);
translate(width/2, height/2+50);
rotateX(PI/9);
translate(-w/2, -h/2);
for (int y = 0; y < rows-1; y++) {
beginShape(TRIANGLE_STRIP);
texture(texture);
for (int x = 0; x < cols; x++) {
float u0 = map(x,0,cols-1,0.0,1.0);
float u1 = map(x+1,0,cols-1,0.0,1.0);
float v0 = map(y,0,rows-1,0.0,1.0);
float v1 = map(y+1,0,rows-1,0.0,1.0);
vertex(x*scl, y*scl, terrain[x][y], u0, v0);
vertex(x*scl, (y+1)*scl, terrain[x][y+1], u1, v1);
}
endShape();
}
}
PGraphics getBarsTexture(int textureWidth, int textureHeight, int numBars){
PGraphics texture = createGraphics(textureWidth, textureHeight);
int moduleSide = textureWidth / numBars;
texture.beginDraw();
texture.background(0);
texture.noStroke();
for(int i = 0; i < numBars; i+= 2){
texture.rect(0, i * moduleSide, textureWidth, moduleSide);
}
texture.endDraw();
return texture;
}

Convert floating-point numbers to decimal digits in GLSL

As others have discussed, GLSL lacks any kind of printf debugging.
But sometimes I really want to examine numeric values while debugging my shaders.
I've been trying to create a visual debugging tool.
I found that it's possible to render an arbitrary series of digits fairly easily in a shader, if you work with a sampler2D in which the digits 0123456789 have been rendered in monospace. Basically, you just juggle your x coordinate.
Now, to use this to examine a floating-point number, I need an algorithm for converting a float to a sequence of decimal digits, such as you might find in any printf implementation.
Unfortunately, as far as I understand the topic, these algorithms seem to need to represent the
floating-point number in a higher-precision format, and I don't see how this is going to be
possible in GLSL where I seem to have only 32-bit floats available.
For this reason, I think this question is not a duplicate of any general "how does printf work" question, but rather specifically about how such algorithms can be made to work under the constraints of GLSL. I've seen this question and answer, but have no idea what's going on there.
The algorithms I've tried aren't very good.
My first try, marked Version A (commented out) seemed pretty bad:
to take three random examples, RenderDecimal(1.0) rendered as 1.099999702, RenderDecimal(2.5) gave me
2.599999246 and RenderDecimal(2.6) came out as 2.699999280.
My second try, marked Version B, seemed
slightly better: 1.0 and 2.6 both come out fine, but RenderDecimal(2.5) still mismatches an apparent
rounding-up of the 5 with the fact that the residual is 0.099.... The result appears as 2.599000022.
My minimal/complete/verifiable example, below, starts with some shortish GLSL 1.20 code, and then
I happen to have chosen Python 2.x for the rest, just to get the shaders compiled and the textures loaded and rendered. It requires the pygame, NumPy, PyOpenGL and PIL third-party packages. Note that the Python is really just boilerplate and could be trivially (though tediously) re-written in C or anything else. Only the GLSL code at the top is critical for this question, and for this reason I don't think the python or python 2.x tags would be helpful.
It requires the following image to be saved as digits.png:
vertexShaderSource = """\
varying vec2 vFragCoordinate;
void main(void)
{
vFragCoordinate = gl_Vertex.xy;
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}
"""
fragmentShaderSource = """\
varying vec2 vFragCoordinate;
uniform vec2 uTextureSize;
uniform sampler2D uTextureSlotNumber;
float OrderOfMagnitude( float x )
{
return x == 0.0 ? 0.0 : floor( log( abs( x ) ) / log( 10.0 ) );
}
void RenderDecimal( float value )
{
// Assume that the texture to which uTextureSlotNumber refers contains
// a rendering of the digits '0123456789' packed together, such that
const vec2 startOfDigitsInTexture = vec2( 0, 0 ); // the lower-left corner of the first digit starts here and
const vec2 sizeOfDigit = vec2( 100, 125 ); // each digit spans this many pixels
const float nSpaces = 10.0; // assume we have this many digits' worth of space to render in
value = abs( value );
vec2 pos = vFragCoordinate - startOfDigitsInTexture;
float dpstart = max( 0.0, OrderOfMagnitude( value ) );
float decimal_position = dpstart - floor( pos.x / sizeOfDigit.x );
float remainder = mod( pos.x, sizeOfDigit.x );
if( pos.x >= 0 && pos.x < sizeOfDigit.x * nSpaces && pos.y >= 0 && pos.y < sizeOfDigit.y )
{
float digit_value;
// Version B
float dp, running_value = value;
for( dp = dpstart; dp >= decimal_position; dp -= 1.0 )
{
float base = pow( 10.0, dp );
digit_value = mod( floor( running_value / base ), 10.0 );
running_value -= digit_value * base;
}
// Version A
//digit_value = mod( floor( value * pow( 10.0, -decimal_position ) ), 10.0 );
vec2 textureSourcePosition = vec2( startOfDigitsInTexture.x + remainder + digit_value * sizeOfDigit.x, startOfDigitsInTexture.y + pos.y );
gl_FragColor = texture2D( uTextureSlotNumber, textureSourcePosition / uTextureSize );
}
// Render the decimal point
if( ( decimal_position == -1.0 && remainder / sizeOfDigit.x < 0.1 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) ||
( decimal_position == 0.0 && remainder / sizeOfDigit.x > 0.9 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) )
{
gl_FragColor = texture2D( uTextureSlotNumber, ( startOfDigitsInTexture + sizeOfDigit * vec2( 1.5, 0.5 ) ) / uTextureSize );
}
}
void main(void)
{
gl_FragColor = texture2D( uTextureSlotNumber, vFragCoordinate / uTextureSize );
RenderDecimal( 2.5 ); // for current demonstration purposes, just a constant
}
"""
# Python (PyOpenGL) code to demonstrate the above
# (Note: the same OpenGL calls could be made from any language)
import os, sys, time
import OpenGL
from OpenGL.GL import *
from OpenGL.GLU import *
import pygame, pygame.locals # just for getting a canvas to draw on
try: from PIL import Image # PIL.Image module for loading image from disk
except ImportError: import Image # old PIL didn't package its submodules on the path
import numpy # for manipulating pixel values on the Python side
def CompileShader( type, source ):
shader = glCreateShader( type )
glShaderSource( shader, source )
glCompileShader( shader )
result = glGetShaderiv( shader, GL_COMPILE_STATUS )
if result != 1:
raise Exception( "Shader compilation failed:\n" + glGetShaderInfoLog( shader ) )
return shader
class World:
def __init__( self, width, height ):
self.window = pygame.display.set_mode( ( width, height ), pygame.OPENGL | pygame.DOUBLEBUF )
# compile shaders
vertexShader = CompileShader( GL_VERTEX_SHADER, vertexShaderSource )
fragmentShader = CompileShader( GL_FRAGMENT_SHADER, fragmentShaderSource )
# build shader program
self.program = glCreateProgram()
glAttachShader( self.program, vertexShader )
glAttachShader( self.program, fragmentShader )
glLinkProgram( self.program )
# try to activate/enable shader program, handling errors wisely
try:
glUseProgram( self.program )
except OpenGL.error.GLError:
print( glGetProgramInfoLog( self.program ) )
raise
# enable alpha blending
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE )
glEnable( GL_DEPTH_TEST )
glEnable( GL_BLEND )
glBlendEquation( GL_FUNC_ADD )
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA )
# set projection and background color
gluOrtho2D( 0, width, 0, height )
glClearColor( 0.0, 0.0, 0.0, 1.0 )
self.uTextureSlotNumber_addr = glGetUniformLocation( self.program, 'uTextureSlotNumber' )
self.uTextureSize_addr = glGetUniformLocation( self.program, 'uTextureSize' )
def RenderFrame( self, *textures ):
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT )
for t in textures: t.Draw( world=self )
pygame.display.flip()
def Close( self ):
pygame.display.quit()
def Capture( self ):
w, h = self.window.get_size()
rawRGB = glReadPixels( 0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE )
return Image.frombuffer( 'RGB', ( w, h ), rawRGB, 'raw', 'RGB', 0, 1 ).transpose( Image.FLIP_TOP_BOTTOM )
class Texture:
def __init__( self, source, slot=0, position=(0,0,0) ):
# wrangle array
source = numpy.array( source )
if source.dtype.type not in [ numpy.float32, numpy.float64 ]: source = source.astype( float ) / 255.0
while source.ndim < 3: source = numpy.expand_dims( source, -1 )
if source.shape[ 2 ] == 1: source = source[ :, :, [ 0, 0, 0 ] ] # LUMINANCE -> RGB
if source.shape[ 2 ] == 2: source = source[ :, :, [ 0, 0, 0, 1 ] ] # LUMINANCE_ALPHA -> RGBA
if source.shape[ 2 ] == 3: source = source[ :, :, [ 0, 1, 2, 2 ] ]; source[ :, :, 3 ] = 1.0 # RGB -> RGBA
# now it can be transferred as GL_RGBA and GL_FLOAT
# housekeeping
self.textureSize = [ source.shape[ 1 ], source.shape[ 0 ] ]
self.textureSlotNumber = slot
self.textureSlotCode = getattr( OpenGL.GL, 'GL_TEXTURE%d' % slot )
self.listNumber = slot + 1
self.position = list( position )
# transfer texture content
glActiveTexture( self.textureSlotCode )
self.textureID = glGenTextures( 1 )
glBindTexture( GL_TEXTURE_2D, self.textureID )
glEnable( GL_TEXTURE_2D )
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA32F, self.textureSize[ 0 ], self.textureSize[ 1 ], 0, GL_RGBA, GL_FLOAT, source[ ::-1 ] )
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST )
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST )
# define surface
w, h = self.textureSize
glNewList( self.listNumber, GL_COMPILE )
glBegin( GL_QUADS )
glColor3f( 1, 1, 1 )
glNormal3f( 0, 0, 1 )
glVertex3f( 0, h, 0 )
glVertex3f( w, h, 0 )
glVertex3f( w, 0, 0 )
glVertex3f( 0, 0, 0 )
glEnd()
glEndList()
def Draw( self, world ):
glPushMatrix()
glTranslate( *self.position )
glUniform1i( world.uTextureSlotNumber_addr, self.textureSlotNumber )
glUniform2f( world.uTextureSize_addr, *self.textureSize )
glCallList( self.listNumber )
glPopMatrix()
world = World( 1000, 800 )
digits = Texture( Image.open( 'digits.png' ) )
done = False
while not done:
world.RenderFrame( digits )
for event in pygame.event.get():
# Press 'q' to quit or 's' to save a timestamped snapshot
if event.type == pygame.locals.QUIT: done = True
elif event.type == pygame.locals.KEYUP and event.key in [ ord( 'q' ), 27 ]: done = True
elif event.type == pygame.locals.KEYUP and event.key in [ ord( 's' ) ]:
world.Capture().save( time.strftime( 'snapshot-%Y%m%d-%H%M%S.png' ) )
world.Close()
+1 for interesting problem. Was curious so I tried to code this. I need the use of arrays so I chose #version 420 core. My app is rendering single quad covering screen with coordinates <-1,+1>. I am using whole ASCII 8x8 pixel 32x8 characters font texture I created some years ago:
The vertex is simple:
//---------------------------------------------------------------------------
// Vertex
//---------------------------------------------------------------------------
#version 420 core
//---------------------------------------------------------------------------
layout(location=0) in vec4 vertex;
out vec2 pos; // screen position <-1,+1>
void main()
{
pos=vertex.xy;
gl_Position=vertex;
}
//---------------------------------------------------------------------------
Fragment is a bit more complicated:
//---------------------------------------------------------------------------
// Fragment
//---------------------------------------------------------------------------
#version 420 core
//---------------------------------------------------------------------------
in vec2 pos; // screen position <-1,+1>
out vec4 gl_FragColor; // fragment output color
uniform sampler2D txr_font; // ASCII 32x8 characters font texture unit
uniform float fxs,fys; // font/screen resolution ratio
//---------------------------------------------------------------------------
const int _txtsiz=32; // text buffer size
int txt[_txtsiz],txtsiz; // text buffer and its actual size
vec4 col; // color interface for txt_print()
//---------------------------------------------------------------------------
void txt_decimal(float x) // print float x into txt
{
int i,j,c; // l is size of string
float y,a;
const float base=10;
// handle sign
if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; }
else { txt[txtsiz]='+'; txtsiz++; }
// divide to int(x).fract(y) parts of number
y=x; x=floor(x); y-=x;
// handle integer part
i=txtsiz; // start of integer part
for (;txtsiz<_txtsiz;)
{
a=x;
x=floor(x/base);
a-=base*x;
txt[txtsiz]=int(a)+'0'; txtsiz++;
if (x<=0.0) break;
}
j=txtsiz-1; // end of integer part
for (;i<j;i++,j--) // reverse integer digits
{
c=txt[i]; txt[i]=txt[j]; txt[j]=c;
}
// handle fractional part
for (txt[txtsiz]='.',txtsiz++;txtsiz<_txtsiz;)
{
y*=base;
a=floor(y);
y-=a;
txt[txtsiz]=int(a)+'0'; txtsiz++;
if (y<=0.0) break;
}
txt[txtsiz]=0; // string terminator
}
//---------------------------------------------------------------------------
void txt_print(float x0,float y0) // print txt at x0,y0 [chars]
{
int i;
float x,y;
// fragment position [chars] relative to x0,y0
x=0.5*(1.0+pos.x)/fxs; x-=x0;
y=0.5*(1.0-pos.y)/fys; y-=y0;
// inside bbox?
if ((x<0.0)||(x>float(txtsiz))||(y<0.0)||(y>1.0)) return;
// get font texture position for target ASCII
i=int(x); // char index in txt
x-=float(i);
i=txt[i];
x+=float(int(i&31));
y+=float(int(i>>5));
x/=32.0; y/=8.0; // offset in char texture
col=texture2D(txr_font,vec2(x,y));
}
//---------------------------------------------------------------------------
void main()
{
col=vec4(0.0,1.0,0.0,1.0); // background color
txtsiz=0;
txt[txtsiz]='F'; txtsiz++;
txt[txtsiz]='l'; txtsiz++;
txt[txtsiz]='o'; txtsiz++;
txt[txtsiz]='a'; txtsiz++;
txt[txtsiz]='t'; txtsiz++;
txt[txtsiz]=':'; txtsiz++;
txt[txtsiz]=' '; txtsiz++;
txt_decimal(12.345);
txt_print(1.0,1.0);
gl_FragColor=col;
}
//---------------------------------------------------------------------------
Here my CPU side uniforms:
glUniform1i(glGetUniformLocation(prog_id,"txr_font"),0);
glUniform1f(glGetUniformLocation(prog_id,"fxs"),(8.0)/float(xs));
glUniform1f(glGetUniformLocation(prog_id,"fys"),(8.0)/float(ys));
where xs,ys is my screen resolution. Font is 8x8 in unit 0
Here output for the test fragment code:
If your floating point accuracy is decreased due to HW implementation then you should consider printing in hex where no accuracy loss is present (using binary access). That could be converted to decadic base on integers later ...
see:
string hex2dec conversion on integer math
[Edit2] old style GLSL shaders
I tried to port to old style GLSL and suddenly it works (before it would not compile with arrays present but when I think of it I was trying char[] which was the real reason).
//---------------------------------------------------------------------------
// Vertex
//---------------------------------------------------------------------------
varying vec2 pos; // screen position <-1,+1>
void main()
{
pos=gl_Vertex.xy;
gl_Position=gl_Vertex;
}
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
// Fragment
//---------------------------------------------------------------------------
varying vec2 pos; // screen position <-1,+1>
uniform sampler2D txr_font; // ASCII 32x8 characters font texture unit
uniform float fxs,fys; // font/screen resolution ratio
//---------------------------------------------------------------------------
const int _txtsiz=32; // text buffer size
int txt[_txtsiz],txtsiz; // text buffer and its actual size
vec4 col; // color interface for txt_print()
//---------------------------------------------------------------------------
void txt_decimal(float x) // print float x into txt
{
int i,j,c; // l is size of string
float y,a;
const float base=10.0;
// handle sign
if (x<0.0) { txt[txtsiz]='-'; txtsiz++; x=-x; }
else { txt[txtsiz]='+'; txtsiz++; }
// divide to int(x).fract(y) parts of number
y=x; x=floor(x); y-=x;
// handle integer part
i=txtsiz; // start of integer part
for (;txtsiz<_txtsiz;)
{
a=x;
x=floor(x/base);
a-=base*x;
txt[txtsiz]=int(a)+'0'; txtsiz++;
if (x<=0.0) break;
}
j=txtsiz-1; // end of integer part
for (;i<j;i++,j--) // reverse integer digits
{
c=txt[i]; txt[i]=txt[j]; txt[j]=c;
}
// handle fractional part
for (txt[txtsiz]='.',txtsiz++;txtsiz<_txtsiz;)
{
y*=base;
a=floor(y);
y-=a;
txt[txtsiz]=int(a)+'0'; txtsiz++;
if (y<=0.0) break;
}
txt[txtsiz]=0; // string terminator
}
//---------------------------------------------------------------------------
void txt_print(float x0,float y0) // print txt at x0,y0 [chars]
{
int i;
float x,y;
// fragment position [chars] relative to x0,y0
x=0.5*(1.0+pos.x)/fxs; x-=x0;
y=0.5*(1.0-pos.y)/fys; y-=y0;
// inside bbox?
if ((x<0.0)||(x>float(txtsiz))||(y<0.0)||(y>1.0)) return;
// get font texture position for target ASCII
i=int(x); // char index in txt
x-=float(i);
i=txt[i];
x+=float(int(i-((i/32)*32)));
y+=float(int(i/32));
x/=32.0; y/=8.0; // offset in char texture
col=texture2D(txr_font,vec2(x,y));
}
//---------------------------------------------------------------------------
void main()
{
col=vec4(0.0,1.0,0.0,1.0); // background color
txtsiz=0;
txt[txtsiz]='F'; txtsiz++;
txt[txtsiz]='l'; txtsiz++;
txt[txtsiz]='o'; txtsiz++;
txt[txtsiz]='a'; txtsiz++;
txt[txtsiz]='t'; txtsiz++;
txt[txtsiz]=':'; txtsiz++;
txt[txtsiz]=' '; txtsiz++;
txt_decimal(12.345);
txt_print(1.0,1.0);
gl_FragColor=col;
}
//---------------------------------------------------------------------------
First of all I want to mention that the amazing solution of Spektre is almost perfect and even more a general solution for text output. I gave his answer an upvote.
As an alternative, I present a minimally invasive solution, and improve the code of the question.
I do not want to conceal the fact that I have studied the solution of Spektre and integrated into my solution.
// Assume that the texture to which uTextureSlotNumber refers contains
// a rendering of the digits '0123456789' packed together, such that
const vec2 startOfDigitsInTexture = vec2( 100, 125 ); // the lower-left corner of the first digit starts here and
const vec2 sizeOfDigit = vec2( 0.1, 0.2 ); // each digit spans this many pixels
const float nSpaces = 10.0; // assume we have this many digits' worth of space to render in
void RenderDigit( int strPos, int digit, vec2 pos )
{
float testStrPos = pos.x / sizeOfDigit.x;
if ( testStrPos >= float(strPos) && testStrPos < float(strPos+1) )
{
float start = sizeOfDigit.x * float(digit);
vec2 textureSourcePosition = vec2( startOfDigitsInTexture.x + start + mod( pos.x, sizeOfDigit.x ), startOfDigitsInTexture.y + pos.y );
gl_FragColor = texture2D( uTextureSlotNumber, textureSourcePosition / uTextureSize );
}
}
The function ValueToDigits interprets a floating point number an fills up an array with the digits.
Each number in the array is in (0, 9).
const int MAX_DIGITS = 32;
int digits[MAX_DIGITS];
int noOfDigits = 0;
int posOfComma = 0;
void Reverse( int start, int end )
{
for ( ; start < end; ++ start, -- end )
{
int digit = digits[start];
digits[start] = digits[end];
digits[end] = digit;
}
}
void ValueToDigits( float value )
{
const float base = 10.0;
int start = noOfDigits;
value = abs( value );
float frac = value; value = floor(value); frac -= value;
// integral digits
for ( ; value > 0.0 && noOfDigits < MAX_DIGITS; ++ noOfDigits )
{
float newValue = floor( value / base );
digits[noOfDigits] = int( value - base * newValue );
value = newValue;
}
Reverse( start, noOfDigits-1 );
posOfComma = noOfDigits;
// fractional digits
for ( ; frac > 0.0 && noOfDigits < MAX_DIGITS; ++ noOfDigits )
{
frac *= base;
float digit = floor( frac );
frac -= digit;
digits[noOfDigits] = int( digit );
}
}
Call ValueToDigits in your original function and find the digit and textur coordinates for the current fragment.
void RenderDecimal( float value )
{
// fill the array of digits with the floating point value
ValueToDigits( value );
// Render the digits
vec2 pos = vFragCoordinate.xy - startOfDigitsInTexture;
if( pos.x >= 0 && pos.x < sizeOfDigit.x * nSpaces && pos.y >= 0 && pos.y < sizeOfDigit.y )
{
// render the digits
for ( int strPos = 0; strPos < noOfDigits; ++ strPos )
RenderDigit( strPos, digits[strPos], pos );
}
// Render the decimal point
float testStrPos = pos.x / sizeOfDigit.x;
float remainder = mod( pos.x, sizeOfDigit.x );
if( ( testStrPos >= float(posOfComma) && testStrPos < float(posOfComma+1) && remainder / sizeOfDigit.x < 0.1 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) ||
( testStrPos >= float(posOfComma-1) && testStrPos < float(posOfComma) && remainder / sizeOfDigit.x > 0.9 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) )
{
gl_FragColor = texture2D( uTextureSlotNumber, ( startOfDigitsInTexture + sizeOfDigit * vec2( 1.5, 0.5 ) ) / uTextureSize );
}
}
Here's my updated fragment shader, which can be dropped into the listing in my original question. It implements the decimal-digit-finding algorithm Spektre proposed, in a way that is even compatible with the legacy GLSL 1.20 dialect I'm using. Without that constraint, Spektre's solution is, of course, much more elegant and powerful.
varying vec2 vFragCoordinate;
uniform vec2 uTextureSize;
uniform sampler2D uTextureSlotNumber;
float Digit( float x, int position, float base )
{
int i;
float digit;
if( position < 0 )
{
x = fract( x );
for( i = -1; i >= position; i-- )
{
if( x <= 0.0 ) { digit = 0.0; break; }
x *= base;
digit = floor( x );
x -= digit;
}
}
else
{
x = floor( x );
float prevx;
for( i = 0; i <= position; i++ )
{
if( x <= 0.0 ) { digit = 0.0; break; }
prevx = x;
x = floor( x / base );
digit = prevx - base * x;
}
}
return digit;
}
float OrderOfMagnitude( float x )
{
return x == 0.0 ? 0.0 : floor( log( abs( x ) ) / log( 10.0 ) );
}
void RenderDecimal( float value )
{
// Assume that the texture to which uTextureSlotNumber refers contains
// a rendering of the digits '0123456789' packed together, such that
const vec2 startOfDigitsInTexture = vec2( 0, 0 ); // the lower-left corner of the first digit starts here and
const vec2 sizeOfDigit = vec2( 100, 125 ); // each digit spans this many pixels
const float nSpaces = 10.0; // assume we have this many digits' worth of space to render in
value = abs( value );
vec2 pos = vFragCoordinate - startOfDigitsInTexture;
float dpstart = max( 0.0, OrderOfMagnitude( value ) );
int decimal_position = int( dpstart - floor( pos.x / sizeOfDigit.x ) );
float remainder = mod( pos.x, sizeOfDigit.x );
if( pos.x >= 0.0 && pos.x < sizeOfDigit.x * nSpaces && pos.y >= 0.0 && pos.y < sizeOfDigit.y )
{
float digit_value = Digit( value, decimal_position, 10.0 );
vec2 textureSourcePosition = vec2( startOfDigitsInTexture.x + remainder + digit_value * sizeOfDigit.x, startOfDigitsInTexture.y + pos.y );
gl_FragColor = texture2D( uTextureSlotNumber, textureSourcePosition / uTextureSize );
}
// Render the decimal point
if( ( decimal_position == -1 && remainder / sizeOfDigit.x < 0.1 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) ||
( decimal_position == 0 && remainder / sizeOfDigit.x > 0.9 && abs( pos.y ) / sizeOfDigit.y < 0.1 ) )
{
gl_FragColor = texture2D( uTextureSlotNumber, ( startOfDigitsInTexture + sizeOfDigit * vec2( 1.5, 0.5 ) ) / uTextureSize );
}
}
void main(void)
{
gl_FragColor = texture2D( uTextureSlotNumber, vFragCoordinate / uTextureSize );
RenderDecimal( 2.5 ); // for current demonstration purposes, just a constant
}

help with drawing a wedge with opengl es

I'm trying to do some basic opengl es programming to get started on the basics.
I have a drawing function tries to draw a wedge of a circle. Something is going wrong because its actually just drawing a circle.
I'm still just trying to grasp the basics of opengl es here. Heres what I have so far.
- (void)drawView
{
[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glViewport(0, 0, 60, 60);
int i;
float angle_start=90;
float angle_stop=180;
int segments=360;
float const angle_step = (angle_stop - angle_start)/segments;
GLfloat *arc_vertices;
arc_vertices = malloc(2*sizeof(GLfloat) * (segments+2));
arc_vertices[0] = arc_vertices[1] = 0.0;
for(i=0; i<segments+1; i++) {
arc_vertices[2 + 2*i ] = cos(angle_start + i*angle_step);
arc_vertices[2 + 2*i + 1] = sin(angle_start + i*angle_step);
}
glVertexPointer(2, GL_FLOAT, 0, arc_vertices);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_TRIANGLE_FAN, 0, segments+2);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
free(arc_vertices);
}
sin() and cos() take radians as input:
float angle_start=90;
float angle_stop=180;
int segments=360;
float const angle_step = (angle_stop - angle_start)/segments;
GLfloat* verts = (GLfloat*)malloc(2*sizeof(GLfloat) * (segments+2));
unsigned int pos = 0;
verts[pos++] = 0;
verts[pos++] = 0;
float radius = 10;
for( unsigned int i = 0; i < segments; ++i )
{
float rads = (angle_start + i*angle_step) * (3.14159 / 180);
verts[pos++] = ( cos( rads ) * radius );
verts[pos++] = ( sin( rads ) * radius );
}
glVertexPointer(2, GL_FLOAT, 0, verts);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
glDrawArrays(GL_TRIANGLE_FAN, 0, segments+1);
glDisableClientState(GL_VERTEX_ARRAY);
I see something wrong. You access vertices[i] and vertices[i+1], but i always increments by 1.
Try replacing
GLfloat vertices[720];
with
GLfloat vertices[2*720];
and replace
vertices[i]=p1;
vertices[i+1]=p2;
by
vertices[2*i]=p1;
vertices[2*i+1]=p2;
this works.
Anti aliasing is horrible but it works.
[credit1
-(void)drawcircelofSlice2
{
amt+=20;
if(amt>360.0)
{
amt=0;
}
[EAGLContext setCurrentContext:context];
glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
glViewport(20, 20, 50,50);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(30.0f, 30.0f, -1.5f, 1.5f, -1.0f, 1.0f);
glMatrixMode(GL_MODELVIEW);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
float x=0;
float y=0;
//float radius=20;
float lowAngle=0;
float highAngle=(amt/360) *360;
// float highAngle=360;
float numcirclePts=360;
lowAngle=DEGREES_TO_RADIANS(lowAngle);
highAngle=DEGREES_TO_RADIANS(highAngle);
float res=numcirclePts;
float angle=lowAngle;
float anglerange=highAngle-lowAngle;
float angleAdder=anglerange/ res;
int k=0;
GLfloat verts[720];
for (int i = 0; i < numcirclePts; i++){
verts[k] = x + cos(angle) ;
verts[k+1] = y - sin(angle) ;
angle += angleAdder;
k+=2;
}
verts[0] = x;
verts[1] = y;
k = 2;
for (int i = 2; i < numcirclePts; i++){
verts[k] = verts[k];
verts[k+1] = verts[k+1];
k+=2;
}
glVertexPointer(2, GL_FLOAT, 0, verts);
glEnableClientState(GL_VERTEX_ARRAY);
glColor4f(0.0f, 0.0f, 1.0f, 0.0f);
glDrawArrays(GL_TRIANGLE_FAN, 0, numcirclePts);
glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
glDisableClientState(GL_VERTEX_ARRAY);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}

Help with drawing a sphere in OpenGL ES

I have the following code, but I can't find the bug. It seems to be a memory-related issue. The original code is taken from the 3d library which comes with the OpenGL superbible, and i'm trying to adapt it for openGL es. Any ideas as to why it's segfaulting all the time?
- (void)drawSphereOfRadius:(GLfloat)fRadius nbSlices:(GLint)iSlices nbStacks:(GLint)iStacks
{
GLfloat *vertexPointer = malloc(sizeof(GLfloat) * iStacks * iSlices * 3 * 2);
GLfloat drho = (GLfloat)(3.141592653589) / (GLfloat) iStacks;
GLfloat dtheta = 2.0f * (GLfloat)(3.141592653589) / (GLfloat) iSlices;
GLfloat ds = 1.0f / (GLfloat) iSlices;
GLfloat dt = 1.0f / (GLfloat) iStacks;
GLfloat t = 1.0f;
GLfloat s = 0.0f;
GLint i, j; // Looping variables
int idx = 0;
for (i = 0; i < iStacks; i++)
{
GLfloat rho = (GLfloat)i * drho;
GLfloat srho = (GLfloat)(sin(rho));
GLfloat crho = (GLfloat)(cos(rho));
GLfloat srhodrho = (GLfloat)(sin(rho + drho));
GLfloat crhodrho = (GLfloat)(cos(rho + drho));
s = 0.0f;
for ( j = 0; j <= iSlices; j++)
{
GLfloat theta = (j == iSlices) ? 0.0f : j * dtheta;
GLfloat stheta = (GLfloat)(-sin(theta));
GLfloat ctheta = (GLfloat)(cos(theta));
GLfloat x = stheta * srho;
GLfloat y = ctheta * srho;
GLfloat z = crho;
glNormal3f(x, y, z);
vertexPointer[idx] = x * fRadius;
vertexPointer[idx + 1] = y * fRadius;
vertexPointer[idx + 2] = z * fRadius;
x = stheta * srhodrho;
y = ctheta * srhodrho;
z = crhodrho;
s += ds;
glNormal3f(x, y, z);
vertexPointer[idx + 3] = x * fRadius;
vertexPointer[idx + 4] = y * fRadius;
vertexPointer[idx + 5] = z * fRadius;
idx += 6;
}
t -= dt;
}
glVertexPointer(3, GL_FLOAT, 0, vertexPointer);
glEnableClientState(GL_VERTEX_ARRAY);
glDrawArrays(GL_TRIANGLE_STRIP, 0, iStacks * iSlices * 2 );
free(vertexPointer);
//glPopMatrix();
}
Your j loop is doing iSlices + 1 iterations so you need to allocate
sizeof(GLfloat) * iStacks * ( iSlices + 1 ) * 3 * 2
bytes for vertexPointer.

How to draw a dotted line using OpenGL ES 1?

To draw a dotted line in OpenGL I can use glLineStipple, but how do I achieve the same effect in OpenGL ES 1?
Lines can be textured, just like triangles. Enable alpha testing, apply an alpha texture, set up some texture coordinates, and enjoy.
Actually i have realized the doted line or the dashed line using for loops but it still make non sense to use it as a line type link to the drawing method, here is the code of my doted line and dashed line below:
doted line:
(void)drawVerticalDotedInternalGrid{
float a,b;
int drawCount =0;
GLfloat dotedInternalGrid[1296];
for (a = -0.5f; a <= 0.5f; a +=0.5f) {
for (b = -0.875f; b <=0.925f; b += 0.025f)
{
dotedInternalGrid[drawCount] = b;
drawCount++;
dotedInternalGrid[drawCount] = a;
drawCount++;
};
};
glPointSize(1.0f);
glColor4f(0.863f,0.863f,0.863f,0.8f); //line color
glVertexPointer(2, GL_FLOAT, 0, dotedInternalGrid);
glEnableClientState(GL_VERTEX_ARRAY);
glDrawArrays(GL_POINTS, 0, 648);
glDisableClientState(GL_VERTEX_ARRAY);
}
dashed line:
(void)drawVerticalDashedInternalGridH{
GLfloat dashedLine[1296];
float a,b;
int i =0;
//-0.4----0.4 // -0.875----0.900
for (a = -0.4f; a <= 0.4f; a +=0.1f) {
for (b =-0.825f; b <=0.950f; b+=0.025f) {
dashedLine[i] = b;
i++;
dashedLine[i] = a;
i++;
};
};
//glLineWidth(1.0f);
glColor4f(0.863f,0.863f,0.863f,1.f); //line color
glVertexPointer(2, GL_FLOAT, 0, dashedLine);
glEnableClientState(GL_VERTEX_ARRAY);
glDrawArrays(GL_LINES, 0, 648);
glDisableClientState(GL_VERTEX_ARRAY);
}
of course ye can see the code is drawing in a rectangle area of certain coordinates,the bother things is how to figure out the dotedInternalGrid[1296]; this size of array dynamically for draw method use and the number of lines to draw as well.
To explain it easily, I have put drawHorizontalDashedLine() first.
To understand, click this image.
I cannot put an image on this post because of my reputation.
Visualizing the Vertices
+(void)drawHorizontalDashedLine:(GLfloat)x1 x2:(GLfloat)x2 y:(GLfloat)y {
//Parameters
GLfloat DASH_LENGTH = 4.0f;
GLfloat GAP_LENGTH = 2.0f;
GLfloat LINE_THICKNESS = 1.5f;
//Calculate how many dashes require to draw the whole line
GLfloat fHorizontalLength = fabsf(x2-x1);
int nDashedLineCount = fHorizontalLength / (DASH_LENGTH + GAP_LENGTH);
int nVerticesSize = nDashedLineCount * 4; //A dashed line has 4 values(2 points)
//Vertex
GLfloat vertices[nVerticesSize];
//The first dashed line vertices
vertices[0] = (x1 < x2)? x1 : x2;
vertices[1] = y;
vertices[2] = (x1 < x2)? x1 : x2 + DASH_LENGTH;
vertices[3] = y;
//The other vertices of dashed lines
for (int nIndex=4; nIndex < nVerticesSize; nIndex=nIndex+4) {
vertices[nIndex] = vertices[nIndex-2] + GAP_LENGTH;
vertices[nIndex+1] = y;
vertices[nIndex+2] = vertices[nIndex] + DASH_LENGTH;
vertices[nIndex+3] = y;
//NSLog(#"Point1(%.2f, %.2f)", vertices[nIndex], vertices[nIndex+1]);
//NSLog(#"Point2(%.2f, %.2f)", vertices[nIndex+2], vertices[nIndex+3]);
}
//Draw the arrays
glPushMatrix();
glLineWidth(LINE_THICKNESS);
glVertexPointer (2, GL_FLOAT, 0, vertices);
glDrawArrays (GL_LINES, 0, nVerticesSize/2);
glPopMatrix();
}
drawDashedLine().
I used the trigonometric function to get lengths.
+(void)drawDashedLine:(CGPoint)point1 point2:(CGPoint)point2 {
//Parameters
GLfloat DASH_LENGTH = 3.0f;
GLfloat GAP_LENGTH = 1.0f;
GLfloat LINE_THICKNESS = 1.5f;
//Calculate how many dashes require to draw the whole line
GLfloat fWidth = point2.x - point1.x;
GLfloat fHeight = point2.y - point1.y;
GLfloat fRadian = atan2(fHeight, fWidth);
float fLineLength = sqrtf(powf(fWidth, 2) + powf(fHeight, 2));
int nDashedLineCount = fabsf(fLineLength / (DASH_LENGTH + GAP_LENGTH));
int nVerticesSize = nDashedLineCount * 4; //A dashed line has 4 values(2 points)
//Vertex
GLfloat vertices[nVerticesSize];
//The first dashed line vertices
vertices[0] = point1.x;
vertices[1] = point1.y;
vertices[2] = point1.x + cosf(fRadian) * DASH_LENGTH;
vertices[3] = point1.y + sinf(fRadian) * DASH_LENGTH;
//The other vertices of dashed lines
for (int nIndex=4; nIndex < nVerticesSize; nIndex=nIndex+4) {
vertices[nIndex] = vertices[nIndex-2] + cosf(fRadian) * GAP_LENGTH;
vertices[nIndex+1] = vertices[nIndex-1] + sinf(fRadian) * GAP_LENGTH;
vertices[nIndex+2] = vertices[nIndex] + cosf(fRadian) * DASH_LENGTH;
vertices[nIndex+3] = vertices[nIndex+1] + sinf(fRadian) * DASH_LENGTH;
//NSLog(#"DrawDash Point1(%.2f, %.2f)", vertices[nIndex], vertices[nIndex+1]);
//NSLog(#"DrawDash Point2(%.2f, %.2f)", vertices[nIndex+2], vertices[nIndex+3]);
}
//Draw the arrays
glPushMatrix();
glLineWidth(LINE_THICKNESS);
glVertexPointer (2, GL_FLOAT, 0, vertices);
glDrawArrays (GL_LINES, 0, nVerticesSize/2);
glPopMatrix();
}
glPushAttrib(GL_ENABLE_BIT);
# glPushAttrib is done to return everything to normal after drawing
glLineStipple(1, 0xAAAA); # [1]
glEnable(GL_LINE_STIPPLE);
glBegin(GL_LINES);
glVertex3f(-.5,.5,-.5);
glVertex3f(.5,.5,-.5);
glEnd();
glPopAttrib();

Resources