summaryrefslogtreecommitdiff
path: root/apps/plugins/xracer/graphics.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/xracer/graphics.c')
-rw-r--r--apps/plugins/xracer/graphics.c275
1 files changed, 275 insertions, 0 deletions
diff --git a/apps/plugins/xracer/graphics.c b/apps/plugins/xracer/graphics.c
new file mode 100644
index 0000000..c1c5f92
--- /dev/null
+++ b/apps/plugins/xracer/graphics.c
@@ -0,0 +1,275 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2015 Franklin Wei
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "plugin.h"
+
+#include "fixedpoint.h"
+#include "lib/pluginlib_bmp.h"
+
+#include "pluginbitmaps/xracer_sprites.h"
+
+#include "xracer.h"
+#include "compat.h"
+#include "graphics.h"
+#include "road.h"
+#include "sprite.h"
+#include "util.h"
+
+/* fill poly takes 4 screenspace coordinates and fills them */
+/* order of arguments matters: */
+/* (x1, y1) (x2, y2) */
+/* (x3, y3) (x4, y4) */
+static inline void fill_poly(int x1, int y1,
+ int x2, int y2,
+ int x3, int y3,
+ int x4, int y4,
+ unsigned color)
+{
+ SET_FOREGROUND(color);
+ FILL_TRI(x1, y1, x2, y2, x3, y3);
+ FILL_TRI(x1, y1, x3, y3, x4, y4);
+}
+
+/* project(): performs 3d projection of pt_3d onto pt_2d given camera parameters */
+/* this takes arguments by value so as to allow easy changing of camera positions at render time*/
+
+static inline int project(int cam_x, int cam_y, int cam_z, int camera_depth, struct point_3d *pt_3d, struct point_2d *pt_2d, float *scale_return)
+{
+ /* calculate the coordinates relative to the camera */
+ int rel_x, rel_y, rel_z;
+ rel_x = pt_3d->x - cam_x;
+ rel_y = pt_3d->y - cam_y;
+ rel_z = pt_3d->z - cam_z;
+ float scale = (float)camera_depth / rel_z;
+ pt_2d->x = (LCD_WIDTH/2) + (scale * rel_x * (LCD_WIDTH/2));
+ pt_2d->y = (LCD_WIDTH/2) - (scale * rel_y * (LCD_HEIGHT/2));
+ pt_2d->w = scale * ROAD_WIDTH * (LCD_WIDTH/2);
+ if(scale_return)
+ *scale_return=scale;
+ return rel_z;
+}
+
+static inline int border_width(int projected_road_width, int lanes)
+{
+ return projected_road_width/MAX(6, 2*lanes);
+}
+
+static inline int lane_marker_width(int projected_road_width, int lanes)
+{
+ return projected_road_width/MAX(32, 8*lanes);
+}
+
+/* fill_gradient(): draws a gradient across the whole screen from top to bottom,
+ changing from top_color to bottom_color */
+
+static void fill_gradient(int top, unsigned top_color, int bottom, unsigned bottom_color)
+{
+ long top_r, top_g, top_b;
+ top_r = RGB_UNPACK_RED(top_color) << FRACBITS;
+ top_g = RGB_UNPACK_GREEN(top_color) << FRACBITS;
+ top_b = RGB_UNPACK_BLUE(top_color) << FRACBITS;
+
+ long bottom_r, bottom_g, bottom_b;
+ bottom_r = RGB_UNPACK_RED(bottom_color) << FRACBITS;
+ bottom_g = RGB_UNPACK_GREEN(bottom_color) << FRACBITS;
+ bottom_b = RGB_UNPACK_BLUE(bottom_color) << FRACBITS;
+
+ bottom <<= FRACBITS;
+ top <<= FRACBITS;
+ long r_step = fp_div((bottom_r-top_r), (bottom-top), FRACBITS);
+ long g_step = fp_div((bottom_g-top_g), (bottom-top), FRACBITS);
+ long b_step = fp_div((bottom_b-top_b), (bottom-top), FRACBITS);
+
+ long r = top_r;
+ long g = top_g;
+ long b = top_b;
+
+ bottom >>= FRACBITS;
+ top >>= FRACBITS;
+
+ for(int i = top; i<bottom; ++i)
+ {
+ SET_FOREGROUND(LCD_RGBPACK( ROUND(r), ROUND(g), ROUND(b) ));
+ DRAW_HLINE(0, LCD_WIDTH, i);
+ r += r_step;
+ g += g_step;
+ b += b_step;
+ }
+}
+
+/* draws a segment on screen given its 2d screen coordinates and other data */
+
+static inline void render_segment(struct point_2d *p1, struct point_2d *p2,
+ unsigned road_color, unsigned border_color,
+ int lanes, unsigned lane_color, unsigned grass_color)
+{
+ int border_1 = border_width(p1->w, lanes);
+ int border_2 = border_width(p2->w, lanes);
+
+ /* draw grass first */
+ SET_FOREGROUND(grass_color);
+ FILL_RECT(0, p2->y, LCD_WIDTH, p1->y - p2->y);
+
+ /* draw borders */
+ fill_poly(p1->x-p1->w-border_1, p1->y, p1->x-p1->w, p1->y,
+ p2->x-p2->w, p2->y, p2->x-p2->w-border_2, p2->y, border_color);
+
+ fill_poly(p1->x+p1->w+border_1, p1->y, p1->x+p1->w, p1->y,
+ p2->x+p2->w, p2->y, p2->x+p2->w+border_2, p2->y, border_color);
+
+ /* draw road */
+ fill_poly(p2->x-p2->w, p2->y, p2->x+p2->w, p2->y, p1->x+p1->w, p1->y, p1->x-p1->w, p1->y, road_color);
+
+ /* draw lanes */
+ if(lanes > 1)
+ {
+ int lane_1 = lane_marker_width(p1->w, lanes);
+ int lane_2 = lane_marker_width(p2->w, lanes);
+
+ int lane_w1, lane_w2, lane_x1, lane_x2;
+ lane_w1 = p1->w*2/lanes;
+ lane_w2 = p2->w*2/lanes;
+ lane_x1 = p1->x - p1->w + lane_w1;
+ lane_x2 = p2->x - p2->w + lane_w2;
+ for(int i=1; i<lanes; lane_x1 += lane_w1, lane_x2 += lane_w2, ++i)
+ {
+ fill_poly(lane_x1 - lane_1/2, p1->y, lane_x1 + lane_1/2, p1->y,
+ lane_x2 + lane_2/2, p2->y, lane_x2 - lane_2/2, p2->y, lane_color);
+ }
+ }
+}
+
+/* project_segment(): does the 3d calculations on a road segment */
+
+static inline bool project_segment(struct road_segment *base, struct road_segment *seg, int x, int dx,
+ struct camera_t *camera, struct point_2d *p1, struct point_2d *p2, int *maxy,
+ int road_length)
+{
+ bool looped = (seg->idx < base->idx);
+ seg->clip = *maxy;
+
+ /* future optimization: do one projection per segment because segment n's p2 is the same as segments n+1's p1*/
+
+ /* input points (3d space) */
+
+ struct point_3d p1_3d={-x, seg->p1_y, seg->p1_z};
+ struct point_3d p2_3d={-x-dx, seg->p2_y, seg->p2_z};
+
+ int camera_offset = (looped ? road_length * SEGMENT_LENGTH - camera->pos.z : 0);
+
+ /* 3d project the 2nd point FIRST because its results can be used to save some work */
+
+ int p2_cz = project(camera->pos.x, camera->pos.y, camera->pos.z - camera_offset, camera->depth, &p2_3d, p2, NULL);
+
+ /* decide whether or not to draw this segment */
+
+ if(p2_cz <= camera->depth || /* behind camera */
+ p2->y >= *maxy) /* clipped */
+ {
+ return false;
+ }
+
+ /* nothing is drawn below this */
+ *maxy = p2->y;
+
+ /* now 3d project the first point */
+
+ /* save the scale factor to draw sprites */
+ project(camera->pos.x, camera->pos.y, camera->pos.z - camera_offset, camera->depth, &p1_3d, p1, &seg->p1_scale);
+
+ if(p2->y >= p1->y) /* backface cull */
+ {
+ return false;
+ }
+ return true;
+}
+
+/* render(): The primary render driver function
+ *
+ * calculates segment coordinates and calls render_segment to draw the segment on-screen
+ * also handles the faking of curves
+ */
+
+void render(struct camera_t *camera, struct road_segment *road, unsigned int road_length, int camera_height)
+{
+ CLEAR_DISPLAY();
+
+ fill_gradient(0, SKY_COLOR1, LCD_HEIGHT, SKY_COLOR2);
+
+ struct road_segment *base = FIND_SEGMENT(camera->pos.z, road, road_length);
+
+ long base_percent = fp_div((camera->pos.z % SEGMENT_LENGTH) << FRACBITS, SEGMENT_LENGTH << FRACBITS, FRACBITS);
+ long dx = - fp_mul(base->curve, base_percent, FRACBITS);
+ long x = 0;
+
+ /* clipping height, nothing is drawn below this */
+ int maxy = LCD_HEIGHT;
+
+ /* move the camera to a fixed point above the road */
+ /* interpolate so as to prevent jumpy camera movement on hills */
+ camera->pos.y = INTERPOLATE(base->p1_y, base->p2_y, base_percent) + camera_height;
+
+ for(int i = 0; i < DRAW_DIST; ++i)
+ {
+ struct road_segment *seg = &road[(base->idx + i) % road_length];
+
+ if(project_segment(base, seg, ROUND(x), ROUND(dx), camera, &seg->p1, &seg->p2, &maxy, road_length))
+ render_segment(&seg->p1, &seg->p2, seg->color, seg->border_color, seg->lanes, seg->lane_color, seg->grass_color);
+ /* curve calculation */
+ x += dx;
+ dx += seg->curve; /* seg->curve is already FP */
+ }
+ /* draw sprites */
+ unsigned char scale_buffer[LCD_WIDTH * LCD_HEIGHT * sizeof(fb_data)];
+#if (LCD_DEPTH >= 4)
+ for(int i = DRAW_DIST - 1; i > 0; --i)
+ {
+ struct road_segment *seg = &road[(base->idx + i) % road_length];
+ struct sprite_t *iter = seg->sprites;
+ while(iter)
+ {
+ struct sprite_data_t *sprite = iter->data;
+ float scale = seg->p1_scale;
+ if(seg->p1.x && seg->p1.y)
+ {
+ struct bitmap in = {
+ .height = sprite->h,
+ .width = sprite->w,
+ .data = (unsigned char*)(xracer_sprites + STRIDE(SCREEN_MAIN, BMPWIDTH_xracer_sprites, BMPHEIGHT_xracer_sprites) * sprite->y + sprite->x)
+ };
+ struct bitmap out = {
+ .height = sprite->h * scale,
+ .width = sprite->w * scale,
+ .data = scale_buffer
+ };
+ if(sprite->h * scale > 1 && sprite->w * scale > 1)
+ {
+ simple_resize_bitmap(&in, &out);
+ rb->lcd_bitmap_transparent((fb_data*)scale_buffer,
+ seg->p1.x - LCD_WIDTH/2, seg->p1.y - LCD_WIDTH/2,
+ sprite->w * scale, sprite->h * scale);
+ }
+ }
+ iter = iter->next;
+ }
+ }
+#endif
+}