/** * @name context_2d_draw_point_sprites * @brief Draws pointsprites using the given options (in batch along a line) * @param ctx - (context_2d *) context to draw to * @param url - (const char *) name of the texture to draw from * @param point_size - (float) point sprite size * @param step_size - (float) step size to take between drawn pointsprites * @param color - (rgba *) color to draw with * @param x1 - (float) starting x-coordinate to draw along * @param y1 - (float) starting y-coordinate to draw along * @param x2 - (float) ending x-coordinate to draw along * @param y2 - (float) ending y-coordinate to draw along * @retval NONE */ void context_2d_draw_point_sprites(context_2d *ctx, const char *url, float point_size, float step_size, rgba *color, float x1, float y1, float x2, float y2) { draw_textures_flush(); context_2d_bind(ctx); texture_2d *tex = texture_manager_load_texture(texture_manager_get(), url); // If texture is not finished loading, if (!tex || !tex->loaded) { return; } static GLfloat *vertex_buffer = NULL; static unsigned int vertex_max = 64; tealeaf_shaders_bind(DRAWING_SHADER); matrix_3x3_multiply_m_f_f_f_f(GET_MODEL_VIEW_MATRIX(ctx), x1, y1, &x1, &y1); matrix_3x3_multiply_m_f_f_f_f(GET_MODEL_VIEW_MATRIX(ctx), x2, y2, &x2, &y2); // Allocate vertex array buffer if (vertex_buffer == NULL) { vertex_buffer = malloc(vertex_max * 2 * sizeof(GLfloat)); } // Add points to the buffer so there are drawing points every X pixels unsigned int count = ceilf(sqrtf((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)) / step_size); if (count < 1) { count = 1; } unsigned int vertex_count = 0; unsigned int i; for (i = 0; i < count; ++i) { if (vertex_count == vertex_max) { vertex_max = 2 * vertex_max; vertex_buffer = realloc(vertex_buffer, vertex_max * 2 * sizeof(GLfloat)); } vertex_buffer[2 * vertex_count + 0] = x1 + (x2 - x1) * ((GLfloat)i / (GLfloat)count); vertex_buffer[2 * vertex_count + 1] = y1 + (y2 - y1) * ((GLfloat)i / (GLfloat)count); vertex_count += 1; } GLTRACE(glActiveTexture(GL_TEXTURE0)); GLTRACE(glBindTexture(GL_TEXTURE_2D, tex->name)); GLTRACE(glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA)); GLTRACE(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GLTRACE(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GLTRACE(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GLTRACE(glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)); // Render the vertex array GLTRACE(glUniform1f(global_shaders[DRAWING_SHADER].point_size, point_size)); GLTRACE(glVertexAttribPointer(global_shaders[DRAWING_SHADER].vertex_coords, 2, GL_FLOAT, GL_FALSE, 0, (float *) vertex_buffer)); float alpha = color->a * ctx->globalAlpha[ctx->mvp]; GLTRACE(glUniform4f(global_shaders[DRAWING_SHADER].draw_color, alpha * color->r, alpha * color->g, alpha * color->b, alpha)); GLTRACE(glDrawArrays(GL_POINTS, 0, vertex_count)); tealeaf_shaders_bind(PRIMARY_SHADER); }
/** * @name context_2d_fillText * @brief fills text on the given context using given options * @param ctx - (context_2d *) ctx to fill text on * @param img - (texture_2d *) bitmap font texture to use * @param srcRect - (const rect_2d *) source rectangle on the texture to draw from * @param destRect - (const rect_2d *) destination rect to draw to * @param alpha - (float) alpha to draw with * @param composite_op - (int) composite operation to draw with * @retval NONE */ void context_2d_fillText(context_2d *ctx, texture_2d *img, const rect_2d *srcRect, const rect_2d *destRect, float alpha) { context_2d_bind(ctx); if (img && img->loaded) { draw_textures_item(ctx, GET_MODEL_VIEW_MATRIX(ctx), img->name, img->width, img->height, img->originalWidth, img->originalHeight, *srcRect, *destRect, *GET_CLIPPING_BOUNDS(ctx), ctx->globalAlpha[ctx->mvp] * alpha, ctx->globalCompositeOperation[ctx->mvp], &ctx->filter_color, ctx->filter_type); } }
/** * @name context_2d_drawImage * @brief darws an image to the given context using given options * @param ctx - (context_2d *) context to draw to * @param srcTex - (int) deprecated * @param url - (const char *) name of the texture to draw from * @param srcRect - (const rect_2d *) source rectangle on the texture to draw from * @param destRect - (const rect_2d *) destination rect to draw to * @param composite_op - (int) composite operation to draw with * @retval NONE */ void context_2d_drawImage(context_2d *ctx, int srcTex, const char *url, const rect_2d *srcRect, const rect_2d *destRect) { context_2d_bind(ctx); texture_2d *tex = texture_manager_load_texture(texture_manager_get(), url); if (tex && tex->loaded) { draw_textures_item(ctx, GET_MODEL_VIEW_MATRIX(ctx), tex->name, tex->width, tex->height, tex->originalWidth, tex->originalHeight, *srcRect, *destRect, * GET_CLIPPING_BOUNDS(ctx), ctx->globalAlpha[ctx->mvp], ctx->globalCompositeOperation[ctx->mvp], &ctx->filter_color, ctx->filter_type); } }
/** * @name context_2d_setClip * @brief sets the clipping rectangle on the given context * @param ctx - (context_2d *) context to set the clipping rectangle on * @param clip - (rect_2d) the clipping rectangle * @retval NONE */ void context_2d_setClip(context_2d *ctx, rect_2d clip) { matrix_3x3 *modelView = GET_MODEL_VIEW_MATRIX(ctx); // LOG("setClip: %f %f %f %f", clip.x, clip.y, clip.width, clip.height); // TODO: clipping with rectangles doesn't work so great with rotated or scaled coordinates... float x1, y1, x2, y2; matrix_3x3_multiply(modelView, clip.x, clip.y, &x1, &y1); matrix_3x3_multiply(modelView, clip.x + clip.width, clip.y + clip.height, &x2, &y2); clip.x = x1; clip.y = y1; clip.width = x2 - x1; clip.height = y2 - y1; int i = ctx->mvp - 1; rect_2d ctx_clip; ctx_clip.x = ctx->clipStack[i].x; ctx_clip.y = ctx->clipStack[i].y; ctx_clip.width = ctx->clipStack[i].width; ctx_clip.height = ctx->clipStack[i].height; //flip the y to be back at top left for easier readability of intersection testing if (ctx->on_screen && clip.height > 0) { ctx_clip.y = -ctx_clip.y + ctx->canvas->framebuffer_height + ctx->canvas->framebuffer_offset_bottom - ctx_clip.height; } //width = -1 if view is not clipping so ignore it if (ctx_clip.width > -1) { //set clip rect to empty if the clip coming in is outside of the clip stack's top clipping rect if (clip.x >= ctx_clip.x + ctx_clip.width || clip.x + clip.width <= ctx_clip.x || clip.y >= ctx_clip.y + ctx_clip.height || clip.y + clip.height <= ctx_clip.y) { clip.x = clip.y = clip.height = clip.width = 0; } else { clip.x = ctx_clip.x > x1 ? ctx_clip.x : x1; clip.y = ctx_clip.y > y1 ? ctx_clip.y : y1; clip.width = (ctx_clip.x + ctx_clip.width < x2 ? ctx_clip.x + ctx_clip.width : x2) - clip.x; clip.height = (ctx_clip.y + ctx_clip.height < y2 ? ctx_clip.y + ctx_clip.height : y2) - clip.y; } } // scissor is with respect to lower-left corner // activeFrameBufferHeight is the height of the off-screen buffer // activeFrameBufferOffsetBottom -- the viewport actually goes past the bottom of the texture // to the nearest power of two, so when we convert to y-coordinates from the lower-left viewport // corner, we need to add the offsetBottom to get to the bottom of the viewable texture if (ctx->on_screen && clip.height > 0) { clip.y = ctx->canvas->framebuffer_height - (clip.height + clip.y) + ctx->canvas->framebuffer_offset_bottom; } rect_2d bounds = {clip.x, clip.y, clip.width, clip.height}; if (rect_2d_equals(GET_CLIPPING_BOUNDS(ctx), &bounds)) { return; } *GET_CLIPPING_BOUNDS(ctx) = bounds; enable_scissor(ctx); }
void context_2d_setTransform(context_2d *ctx, double m11, double m12, double m21, double m22, double dx, double dy) { context_2d_bind(ctx); matrix_3x3 *m = GET_MODEL_VIEW_MATRIX(ctx); m->m00 = m11; m->m01 = m21; m->m10 = m12; m->m11 = m22; m->m02 = dx; m->m12 = dy; }
/** * @name context_2d_clearRect * @brief clears the given rect on the given context * @param ctx - (context_2d *) context to clear a rect from * @param rect - (const rect_2d *) rect to clear from the context * @retval NONE */ void context_2d_clearRect(context_2d *ctx, const rect_2d *rect) { draw_textures_flush(); context_2d_bind(ctx); // Draw a rectangle using triangle strip: // (0,1)-(2,3)-(4,5) and (2,3)-(4,5)-(6,7) // // With coordinates: // 4,5 - 6,7 // | \ | // 0,1 - 2,3 GLfloat v[8]; matrix_3x3_multiply(GET_MODEL_VIEW_MATRIX(ctx), rect, (float *)&v[4], (float *)&v[5], (float *)&v[6], (float *)&v[7], (float *)&v[2], (float *)&v[3], (float *)&v[0], (float *)&v[1]); glBlendFunc(GL_ONE, GL_ZERO); glUniform4f(global_shaders[PRIMARY_SHADER].draw_color, 0, 0, 0, 0); // set color to 0 glVertexAttribPointer(global_shaders[PRIMARY_SHADER].vertex_coords, 2, GL_FLOAT, GL_FALSE, 0, v); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); }
/** * @name context_2d_fillRect * @brief fills a rectangle on the given context using given options * @param ctx - (context_2d *) context to fill a rectangle on * @param rect - (const rect_2d *) rect to be filled * @param color - (const rgba *) color to fill with * @param composite_op - deprecated * @retval NONE */ void context_2d_fillRect(context_2d *ctx, const rect_2d *rect, const rgba *color) { if (use_single_shader) { return; } draw_textures_flush(); context_2d_bind(ctx); tealeaf_shaders_bind(FILL_RECT_SHADER); apply_composite_operation(ctx->globalCompositeOperation[ctx->mvp]); rect_2d_vertices in, out; rect_2d_to_rect_2d_vertices(rect, &in); matrix_3x3_multiply_m_r_r(GET_MODEL_VIEW_MATRIX(ctx), &in, &out); float alpha = color->a * ctx->globalAlpha[ctx->mvp]; // TODO: will pre-multiplied alpha cause a loss-of-precision in color for filling rectangles? GLTRACE(glUniform4f(global_shaders[FILL_RECT_SHADER].draw_color, alpha * color->r, alpha * color->g, alpha * color->b, alpha)); GLTRACE(glVertexAttribPointer(global_shaders[FILL_RECT_SHADER].vertex_coords, 2, GL_FLOAT, GL_FALSE, 0, &out)); GLTRACE(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)); tealeaf_shaders_bind(PRIMARY_SHADER); }
/** * @name context_2d_scale * @brief scales the given context by given options * @param ctx - (context_2d *) context to scale * @param x - (float) amount along the x-coordinate to scale * @param y - (float) amount along the x-coordinate to scale * @retval NONE */ void context_2d_scale(context_2d *ctx, float x, float y) { matrix_3x3_scale(GET_MODEL_VIEW_MATRIX(ctx), x, y); }
void context_2d_transform(context_2d *ctx, float a, float b, float c, float d, float dx, float dy) { matrix_3x3_transform(GET_MODEL_VIEW_MATRIX(ctx), a, b, c, d, dx, dy); }
/** * @name context_2d_translate * @brief translate's the given context by given options * @param ctx - (context_2d *) context to translate * @param x - (float) amount along the x-axis to translate by * @param y - (float) amount along the y-axis to translate by * @retval NONE */ void context_2d_translate(context_2d *ctx, float x, float y) { if (x != 0 || y != 0) { matrix_3x3_translate(GET_MODEL_VIEW_MATRIX(ctx), x, y); } }
/** * @name context_2d_rotate * @brief rotates the given context by given options * @param ctx - (context_2d *) context to rotate * @param angle - (float) angle to rotate by * @retval NONE */ void context_2d_rotate(context_2d *ctx, float angle) { if (angle != 0) { matrix_3x3_rotate(GET_MODEL_VIEW_MATRIX(ctx), angle); } }
/** * @name context_2d_loadIdentity * @brief loads the identity matrix into the given context's model view matrix * @param ctx - (context_2d *) context to load the identity matrix into * @retval NONE */ void context_2d_loadIdentity(context_2d *ctx) { matrix_3x3_identity(GET_MODEL_VIEW_MATRIX(ctx)); }
/** * @name context_2d_setClip * @brief sets the clipping rectangle on the given context * @param ctx - (context_2d *) context to set the clipping rectangle on * @param clip - (rect_2d) the clipping rectangle * @retval NONE */ void context_2d_setClip(context_2d *ctx, rect_2d clip) { matrix_3x3 *modelView = GET_MODEL_VIEW_MATRIX(ctx); #ifdef MATRIX_3x3_ALLOW_SKEW // TODO: Can this be done more efficiently? float clip_x, clip_y, clip_w, clip_h; float x1, y1, x2, y2, x3, y3, x4, y4; matrix_3x3_multiply(modelView, clip.x, clip.y, &x1, &y1); matrix_3x3_multiply(modelView, clip.x + clip.width, clip.y + clip.height, &x2, &y2); matrix_3x3_multiply(modelView, clip.x, clip.y + clip.height, &x3, &y3); matrix_3x3_multiply(modelView, clip.x + clip.width, clip.y, &x4, &y4); clip_x = x1; if (x2 < clip_x) { clip_x = x2; } if (x3 < clip_x) { clip_x = x3; } if (x4 < clip_x) { clip_x = x4; } clip_y = y1; if (y2 < clip_y) { clip_y = y2; } if (y3 < clip_y) { clip_y = y3; } if (y4 < clip_y) { clip_y = y4; } clip_w = x1; if (x2 > clip_w) { clip_w = x2; } if (x3 > clip_w) { clip_w = x3; } if (x4 > clip_w) { clip_w = x4; } clip_h = y1; if (y2 > clip_h) { clip_h = y2; } if (y3 > clip_h) { clip_h = y3; } if (y4 > clip_h) { clip_h = y4; } clip.x = clip_x; clip.y = clip_y; clip.width = clip_w - clip_x; clip.height = clip_h - clip_y; #else float clip0x = clip.x, clip0y = clip.y; float clip1x = clip0x + clip.width, clip1y = clip0y + clip.height; // float x1, y1, x2, y2, x3, y3, x4, y4; // x1 = clip0x * modelView->m00 + clip0y * modelView->m01 + modelView->m02; // y1 = clip0x * modelView->m10 + clip0y * modelView->m11 + modelView->m12; // x2 = clip1x * modelView->m00 + clip0y * modelView->m01 + modelView->m02; // y2 = clip1x * modelView->m10 + clip0y * modelView->m11 + modelView->m12; // x3 = clip1x * modelView->m00 + clip1y * modelView->m01 + modelView->m02; // y3 = clip1x * modelView->m10 + clip1y * modelView->m11 + modelView->m12; // x4 = clip0x * modelView->m00 + clip1y * modelView->m01 + modelView->m02; // y4 = clip0x * modelView->m10 + clip1y * modelView->m11 + modelView->m12; float m00 = modelView->m00, m01 = modelView->m01, m10 = modelView->m10, m11 = modelView->m11; float a = clip0x * m00; float b = clip1x * m10; float c = clip0y * m11; float d = clip0y * m01; float e = clip1x * m00; float f = clip1y * m01; float g = clip0x * m10; float h = clip1y * m11; // If x1 < x2, if ((clip0x < clip1x) ^ (m00 < 0)) { // If x2 < x3, if ((clip0y < clip1y) ^ (m01 < 0)) { // (x1, y2) -> (x3, y4) clip.x = a + d; clip.y = b + c; clip.width = e + f; clip.height = g + h; } else { // (x4, y1) -> (x2, y3) clip.x = a + f; clip.y = g + c; clip.width = e + d; clip.height = b + h; } } else { // If x2 < x3, if ((clip0y < clip1y) ^ (m01 < 0)) { // (x2, y3) -> (x4, y1) clip.x = e + d; clip.y = b + h; clip.width = a + f; clip.height = g + c; } else { // (x3, y4) -> (x1, y2) clip.x = e + f; clip.y = g + h; clip.width = a + d; clip.height = b + c; } } clip.width -= clip.x; clip.height -= clip.y; clip.x += modelView->m02; clip.y += modelView->m12; #endif // Clip with screen bounds if (clip.x < 0) { clip.width += clip.x; if (clip.width < 0) { clip.width = 0; } clip.x = 0; } if (clip.y < 0) { clip.height += clip.y; if (clip.height < 0) { clip.height = 0; } clip.y = 0; } // If entirely clipped, if (clip.width <= 0 || clip.height <= 0) { clip.x = clip.y = clip.width = clip.height = 0; } else { // Lookup parent bounds int i = ctx->mvp - 1; rect_2d ctx_clip; ctx_clip.x = ctx->clipStack[i].x; ctx_clip.y = ctx->clipStack[i].y; ctx_clip.width = ctx->clipStack[i].width; ctx_clip.height = ctx->clipStack[i].height; // If context is on screen, if (ctx->on_screen) { // Flip to frame buffer sense ctx_clip.y = -ctx_clip.y + ctx->canvas->framebuffer_height + ctx->canvas->framebuffer_offset_bottom - ctx_clip.height; } // If parent is clipping, if (ctx_clip.width > -1) { // Calculate (x1, y1) for new and old clip regions float clip1x = clip.x + clip.width, clip1y = clip.y + clip.height; float ctx1x = ctx_clip.x + ctx_clip.width, ctx1y = ctx_clip.y + ctx_clip.height; // If new clip is entirely outside parent, if (clip.x >= ctx1x || clip1x <= ctx_clip.x || clip.y >= ctx1y || clip1y <= ctx_clip.y) { // Empty clip region clip.x = clip.y = clip.width = clip.height = 0; } else { // Trim new clip with parent clip.x = ctx_clip.x > clip.x ? ctx_clip.x : clip.x; clip.y = ctx_clip.y > clip.y ? ctx_clip.y : clip.y; clip.width = (ctx1x < clip1x ? ctx1x : clip1x) - clip.x; clip.height = (ctx1y < clip1y ? ctx1y : clip1y) - clip.y; } } // scissor is with respect to lower-left corner // activeFrameBufferHeight is the height of the off-screen buffer // activeFrameBufferOffsetBottom -- the viewport actually goes past the bottom of the texture // to the nearest power of two, so when we convert to y-coordinates from the lower-left viewport // corner, we need to add the offsetBottom to get to the bottom of the viewable texture if (ctx->on_screen && clip.height > 0) { // Flip from frame buffer sense clip.y = ctx->canvas->framebuffer_height - (clip.height + clip.y) + ctx->canvas->framebuffer_offset_bottom; } } rect_2d bounds = { clip.x, clip.y, clip.width, clip.height }; if (rect_2d_equals(GET_CLIPPING_BOUNDS(ctx), &bounds)) { return; } *GET_CLIPPING_BOUNDS(ctx) = bounds; enable_scissor(ctx); }