summaryrefslogtreecommitdiff
path: root/apps/plugins/xracer/graphics.c
blob: c1c5f9232b8f1411466be25064352d345f8efcfb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
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
}