/*************************************************************************** * __________ __ ___. * 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 "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; if(rel_z != 0) { 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; } return -1; } 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; iw, 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; iy, 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) { 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 < camera->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 */ } }