Basic 3D graphics for the MBED application-shield on-board LCD (initial/incomplete).
Diff: g3d_render.cpp
- Revision:
- 5:2aaaf4e78a53
- Child:
- 6:0bd002c936bb
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/g3d_render.cpp Wed Nov 25 23:58:07 2015 +0000 @@ -0,0 +1,617 @@ +/* + * g3d_render.cpp -- rendering stuff for G3D library + * Copyright (C) 2015 Fred Barnes, University of Kent + * GPL >= 2.0 + */ + + +#include "mbed.h" +#include "C12832.h" +#include "gfx3d.h" + +#define DISPLAY_WIDTH (128) +#define DISPLAY_HEIGHT (32) + +#define ZB_YSHIFT (7) + +/* grotty: single global Z buffer that matches the LCD size */ +static int16_t zbuffer[DISPLAY_HEIGHT * DISPLAY_WIDTH]; + +#define ZBUFFER(X,Y) zbuffer[(Y << ZB_YSHIFT) | X] + +/** + * clears the Z buffer (sets all to maximum) + */ +void gfx3d_clear_zb (void) +{ + int i; + int lim = (DISPLAY_HEIGHT * DISPLAY_WIDTH) >> 1; + uint32_t *halfbuf = (uint32_t *)zbuffer; + + for (i=0; i<lim; i++) { + halfbuf[i] = 0x7fff7fff; + } +} + +#if 0 +/** + * fixes scan-line starts/ends in a g3d_polyscan_t structure based on edge + */ +static void gfx3d_polyfix (g3d_polyscan_t *dst, int16_t x1, int16_t y1, int32_t z1, int16_t x2, int16_t y2, int32_t z2, const float co_a, const float co_b, const float co_c, const float co_d) +{ + if (y2 < y1) { + /* swap around: make sure we go in the same direction (down) */ + int16_t t16; + int32_t t32; + + t16 = x1; + x1 = x2; + x2 = t16; + + t16 = y1; + y1 = y2; + y2 = t16; + + t32 = z1; + z1 = z2; + z2 = t32; + } + + /* scan limit */ + if (y1 < dst->scan_s) { + dst->scan_s = y1; + } + if (y2 > dst->scan_e) { + dst->scan_e = y2; + } + if (y1 == y2) { + /* flat polygon */ + dst->scans[y1][0] = x1; + dst->scans[y1][1] = x2; + dst->zscan[y1][0] = z1; + dst->zscan[y1][1] = z2; + } else { + /* vaguely complex polygon */ + int x, y, z, step, z_step; + + x = (int)x1 << 16; + step = ((int)(x2 - x1) << 16) / (int)(y2 - y1); + x += step; + + z = z1; + z_step = (z2 - z1) / ((y2 - y1) + 1); + y1++; + + for (y=y1; y<=y2; y++) { + if ((y >= 0) && (y < 32)) { + int shx = x >> 16; + + if (x < 0) { + shx = shx - 65536; + } + if (dst->scans[y][0] == 0xff) { + /* start of scan */ + uint8_t x8 = (uint8_t)(shx & 0xff); + + if (x8 == 0xff) { + x8 = 128; + } + dst->scans[y][0] = x8; + if (co_c != 0.0f) { + dst->zscan[y][0] = (int32_t)(((-(co_a * (float)shx) - (co_b * (float)y)) - co_d) / co_c); + } else { + dst->zscan[y][0] = z; + } + } else { + /* end of scan */ + uint8_t x8 = (uint8_t)(shx & 0xff); + + if (x8 == 0xff) { + x8 = 128; + } + dst->scans[y][1] = x8; + if (co_c != 0.0f) { + dst->zscan[y][1] = (int32_t)(((-(co_a * (float)shx) - (co_b * (float)y)) - co_d) / co_c); + } else { + dst->zscan[y][1] = z; + } + } + } + x += step; + z += z_step; + } + } +} + + +/** + * takes a polygon structure and generates a set of scanline data from it + */ +void gfx3d_polyscan (const g3d_poly_t *src, g3d_polyscan_t *dst) +{ + int i; + float co_a, co_b, co_c, co_d; + float x1 = (float)src->x[0]; + float y1 = (float)src->y[0]; + float z1 = (float)src->z[0]; + float x2 = (float)src->x[1]; + float y2 = (float)src->y[1]; + float z2 = (float)src->z[1]; + float x3 = (float)src->x[2]; + float y3 = (float)src->y[2]; + float z3 = (float)src->z[2]; + + for (i=0; i<32; i++) { + dst->scans[i][0] = 0xff; + dst->scans[i][1] = 0xff; + dst->zscan[i][0] = 0; + dst->zscan[i][1] = 0; + } + dst->scan_s = 32; + dst->scan_e = 0; + dst->norm = src->norm; + + /* coefficients for plane equations, Herne & Baker p308 */ + co_a = ((y1 * (z2 - z3)) + (y2 * (z3 - z1))) + (y3 * (z1 - z2)); + co_b = ((z1 * (x2 - x3)) + (z2 * (x3 - x1))) + (z3 * (x1 - x2)); + co_c = ((x1 * (y2 - y3)) + (x2 * (y3 - y1))) + (x3 * (y1 - y2)); + co_d = ((-x1 * ((y2 * z3) - (y3 * z2))) - (x2 * ((y3 * z1) - (y1 * z3)))) - (x3 * ((y1 * z2) - (y2 * z1))); + + gfx3d_polyfix (dst, src->x[0], src->y[0], src->z[0], src->x[1], src->y[1], src->z[1], co_a, co_b, co_c, co_d); + gfx3d_polyfix (dst, src->x[1], src->y[1], src->z[1], src->x[2], src->y[2], src->z[2], co_a, co_b, co_c, co_d); + gfx3d_polyfix (dst, src->x[2], src->y[2], src->z[2], src->x[0], src->y[0], src->z[0], co_a, co_b, co_c, co_d); + + if (dst->scan_s < 0) { + dst->scan_s = 0; + } else if (dst->scan_s >= 32) { + dst->scan_s = 31; + } + if (dst->scan_e < 0) { + dst->scan_e = 0; + } else if (dst->scan_e >= 32) { + dst->scan_e = 31; + } + +} +#endif + +static inline void gfx3d_swap_points (g3d_2p3_t *p1, g3d_2p3_t *p2) +{ + uint32_t *v1 = (uint32_t *)p1; + uint32_t *v2 = (uint32_t *)p2; + uint32_t tmp; + + tmp = *v1; + *v1 = *v2; + *v2 = tmp; + + v1++, v2++; + tmp = *v1; + *v1 = *v2; + *v2 = tmp; +} + +static inline void gfx3d_swap_txpoints (uint16_t *p1, uint16_t *p2) +{ + uint16_t tmp = *p1; + + *p1 = *p2; + *p2 = tmp; + return; +} + +void gfx3d_sort_poly (g3d_poly_t *p) +{ + /* arranges points in the polygon into left->right order -- crude */ + if (p->pts[1].x < p->pts[0].x) { + /* point in 1 left of 0, swap */ + gfx3d_swap_points (&(p->pts[0]), &(p->pts[1])); + gfx3d_swap_txpoints (&(p->tx_pts[0]), &(p->tx_pts[1])); + } + if (p->pts[2].x < p->pts[0].x) { + /* point in 2 left of 0, swap */ + gfx3d_swap_points (&(p->pts[0]), &(p->pts[2])); + gfx3d_swap_txpoints (&(p->tx_pts[0]), &(p->tx_pts[2])); + } + if (p->pts[2].x < p->pts[1].x) { + /* point in 2 left of 1, swap */ + gfx3d_swap_points (&(p->pts[1]), &(p->pts[2])); + gfx3d_swap_txpoints (&(p->tx_pts[1]), &(p->tx_pts[2])); + } +} + + +static inline void gfx3d_edgebuf_z_fixin (const g3d_2p3_t *p0, const g3d_2p3_t *p1, uint8_t *yedge, uint16_t *zedge, uint16_t xoff) +{ + int32_t y = (p0->y << 16) | 0x8000; /* half-way... */ + int32_t ydelta; + int32_t z = (p0->z << 16) | 0x8000; /* half-way... */ + int32_t zdelta; + int x; + + if (p0->x == p1->x) { + /* same X position -- should have been dealt with */ + return; + } + + ydelta = ((int32_t)(p1->y - p0->y) << 16) / (int32_t)(p1->x - p0->x); + zdelta = ((int32_t)(p1->z - p0->z) << 16) / (int32_t)(p1->x - p0->x); + + for (x = p0->x; x <= p1->x; x++) { + int16_t rc_y; + int16_t rc_z; + + if (x < 0) { + y += ydelta; + z += zdelta; + continue; + } + if (x >= DISPLAY_WIDTH) { + /* can't have any more */ + return; + } + + rc_y = (y >> 16); + if (rc_y < 0) { + y += ydelta; + z += zdelta; + continue; + } + if (rc_y >= DISPLAY_HEIGHT) { + y += ydelta; + z += zdelta; + continue; + } + yedge[x - xoff] = rc_y; + y += ydelta; + + rc_z = (z >> 16); + zedge[x - xoff] = rc_z; + z += zdelta; + } +} + + +/** + * takes a polygon and fills an edge-buffer with it. Assumes triangular and sorted poly. + */ +void gfx3d_edgebuf_z (const g3d_poly_t *src, g3d_edgebuf_t *dst) +{ + if (src->pts[0].x < 0) { + /* left-hand point off-screen */ + dst->xoff = 0; + } else { + dst->xoff = src->pts[0].x; + } + dst->s_end = (src->pts[2].x - src->pts[0].x) + 1; + + if (src->pts[0].x == src->pts[2].x) { + /* vertical line only */ + if (src->pts[0].y <= src->pts[1].y) { + /* p0 is above p1 */ + if (src->pts[1].y < 0) { + /* off top */ + dst->s_end = 0; + } else if (src->pts[0].y >= DISPLAY_HEIGHT) { + /* off bottom */ + dst->s_end = 0; + } else { + if (src->pts[0].y < 0) { + dst->start[0] = 0; + } else { + dst->start[0] = src->pts[0].y; + } + if (src->pts[1].y >= DISPLAY_HEIGHT) { + dst->end[0] = DISPLAY_HEIGHT - 1; + } else { + dst->end[0] = src->pts[1].y; + } + /* if we over-shot, this will be wrong */ + dst->zstart[0] = src->pts[0].z; + dst->zend[0] = src->pts[1].z; + } + } else { + if (src->pts[0].y < 0) { + /* off top */ + dst->s_end = 0; + } else if (src->pts[1].y >= DISPLAY_HEIGHT) { + /* off bottom */ + dst->s_end = 0; + } else { + if (src->pts[1].y < 0) { + dst->start[0] = 0; + } else { + dst->start[0] = src->pts[1].y; + } + if (src->pts[0].y >= DISPLAY_HEIGHT) { + dst->end[0] = DISPLAY_HEIGHT - 1; + } else { + dst->end[0] = src->pts[0].y; + } + /* if we over-shot, this will be wrong */ + dst->zstart[0] = src->pts[1].z; + dst->zend[0] = src->pts[0].z; + } + } + return; + } + + /* figure out whether the middle point belongs above or below the longest edge */ + if (((src->pts[2].y - src->pts[0].y) * (src->pts[1].x - src->pts[0].x)) < ((src->pts[1].y - src->pts[0].y) * (src->pts[2].x - src->pts[0].x))) { + /* middle point is on the top-portion */ + gfx3d_edgebuf_z_fixin (&(src->pts[0]), &(src->pts[1]), dst->start, dst->zstart, dst->xoff); + gfx3d_edgebuf_z_fixin (&(src->pts[1]), &(src->pts[2]), dst->start, dst->zstart, dst->xoff); + gfx3d_edgebuf_z_fixin (&(src->pts[0]), &(src->pts[2]), dst->end, dst->zend, dst->xoff); + } else { + /* middle point is on the bottom-portion */ + gfx3d_edgebuf_z_fixin (&(src->pts[0]), &(src->pts[2]), dst->start, dst->zstart, dst->xoff); + gfx3d_edgebuf_z_fixin (&(src->pts[0]), &(src->pts[1]), dst->end, dst->zend, dst->xoff); + gfx3d_edgebuf_z_fixin (&(src->pts[1]), &(src->pts[2]), dst->end, dst->zend, dst->xoff); + } +} + + +/** + * takes a polygon, a texture and an LCD and draws it + */ +void gfx3d_polytxmap (const g3d_poly_t *src, uint8_t *txbuf, C12832 &lcd) +{ + /* assert: polygon points are in order left-to-right */ + int16_t x, use_z = src->pts[0].z; + int32_t dydx_ab, dydx_ac, dydx_bc; + int32_t dudx_ab, dudx_ac, dudx_bc; + int32_t dvdx_ab, dvdx_ac, dvdx_bc; + + if (src->pts[1].x > src->pts[0].x) { + /* we have a left-hand side */ + dydx_ab = ((src->pts[1].y - src->pts[0].y) << 16) / (src->pts[1].x - src->pts[0].x); + dudx_ab = (((int32_t)(src->tx_pts[1] & 0xff) - (int32_t)(src->tx_pts[0] & 0xff)) << 16) / (int32_t)(src->pts[1].x - src->pts[0].x); + dvdx_ab = (((int32_t)(src->tx_pts[1] >> 8) - (int32_t)(src->tx_pts[0] >> 8)) << 16) / (int32_t)(src->pts[1].x - src->pts[0].x); + } else { + dydx_ab = 0; + dudx_ab = 0; + dvdx_ab = 0; + } + if (src->pts[2].x > src->pts[0].x) { + /* we have the long X edge */ + dydx_ac = ((src->pts[2].y - src->pts[0].y) << 16) / (src->pts[2].x - src->pts[0].x); + dudx_ac = (((int32_t)(src->tx_pts[2] & 0xff) - (int32_t)(src->tx_pts[0] & 0xff)) << 16) / (int32_t)(src->pts[2].x - src->pts[0].x); + dvdx_ac = (((int32_t)(src->tx_pts[2] >> 8) - (int32_t)(src->tx_pts[0] >> 8)) << 16) / (int32_t)(src->pts[2].x - src->pts[0].x); + } else { + dydx_ac = 0; + dudx_ac = 0; + dvdx_ac = 0; + } + if (src->pts[2].x > src->pts[1].x) { + /* we have a right-hand side */ + dydx_bc = ((src->pts[2].y - src->pts[1].y) << 16) / (src->pts[2].x - src->pts[1].x); + dudx_bc = (((int32_t)(src->tx_pts[2] & 0xff) - (int32_t)(src->tx_pts[1] & 0xff)) << 16) / (int32_t)(src->pts[2].x - src->pts[1].x); + dvdx_bc = (((int32_t)(src->tx_pts[2] >> 8) - (int32_t)(src->tx_pts[1] >> 8)) << 16) / (int32_t)(src->pts[2].x - src->pts[1].x); + } else { + dydx_bc = 0; + dudx_bc = 0; + dvdx_bc = 0; + } + + int32_t y_top = (src->pts[0].y << 16) + 0x8000; + int32_t y_bot = (src->pts[0].y << 16) + 0x8000; + int32_t tx_top_x = (src->tx_pts[0] & 0xff) << 16; + int32_t tx_top_y = (src->tx_pts[0] >> 8) << 16; + int32_t tx_bot_x = (src->tx_pts[0] & 0xff) << 16; + int32_t tx_bot_y = (src->tx_pts[0] >> 8) << 16; + + /* FIXME: the texture gradients (tx_dudy, tx_dvdy) are constant for any particular triangle */ + + /* left-hand side */ + for (x=src->pts[0].x; x<src->pts[1].x; x++) { + int16_t ys = (y_bot > y_top) ? 1 : -1; + int16_t pix_y = (y_top >> 16); + int16_t bpix_y = (y_bot >> 16) + ys; /* make sure we include the bottom pixel */ + int16_t y; + + int32_t stx_y = tx_top_y; + int32_t stx_x = tx_top_x; + + int32_t tx_dudy, tx_dvdy; + + if (y_top != y_bot) { + tx_dudy = (tx_bot_x - tx_top_x) / (((y_bot - y_top) >> 16) * ys); + tx_dvdy = (tx_bot_y - tx_top_y) / (((y_bot - y_top) >> 16) * ys); + } else { + tx_dudy = 0; + tx_dvdy = 0; + } + + + y_top += dydx_ab; y_bot += dydx_ac; + tx_top_x += dudx_ab; tx_top_y += dvdx_ab; + tx_bot_x += dudx_ac; tx_bot_y += dvdx_ac; + + if ((x < 0) || (x >= DISPLAY_WIDTH)) { + continue; + } + + for (y=pix_y; y != bpix_y; y += ys) { + if ((y < 0) || (y >= DISPLAY_HEIGHT)) { + stx_x += tx_dudy; + stx_y += tx_dvdy; + continue; + } + if (use_z > ZBUFFER (x, y)) { + stx_x += tx_dudy; + stx_y += tx_dvdy; + continue; + } + ZBUFFER (x, y) = use_z; + + int16_t tpix_y = (stx_y >> 16) & (DISPLAY_HEIGHT - 1); + int16_t tpix_x = (stx_x >> 16) & (DISPLAY_WIDTH - 1); + + if (g3d_texture_bit (txbuf, tpix_x, tpix_y)) { + lcd.pixel_nochk (x, y, 1); + } else { + lcd.pixel_nochk (x, y, 0); + } + + stx_x += tx_dudy; + stx_y += tx_dvdy; + } + } + + y_top = (src->pts[1].y << 16) + 0x8000; + tx_top_x = (src->tx_pts[1] & 0xff) << 16; + tx_top_y = (src->tx_pts[1] >> 8) << 16; + + /* right-hand side */ + for (; x<src->pts[2].x; x++) { + int16_t ys = (y_bot > y_top) ? 1 : -1; + int16_t pix_y = (y_top >> 16); + int16_t bpix_y = (y_bot >> 16) + ys; + int16_t y; + + int32_t stx_y = tx_top_y; + int32_t stx_x = tx_top_x; + + int32_t tx_dudy, tx_dvdy; + + if (y_top != y_bot) { + tx_dudy = (tx_bot_x - tx_top_x) / (((y_bot - y_top) >> 16) * ys); + tx_dvdy = (tx_bot_y - tx_top_y) / (((y_bot - y_top) >> 16) * ys); + } else { + tx_dudy = 0; + tx_dvdy = 0; + } + + y_top += dydx_bc; y_bot += dydx_ac; + tx_top_x += dudx_bc; tx_top_y += dvdx_bc; + tx_bot_x += dudx_ac; tx_bot_y += dvdx_ac; + + if ((x < 0) || (x >= DISPLAY_WIDTH)) { + continue; + } + + for (y=pix_y; y != bpix_y; y += ys) { + if ((y < 0) || (y >= DISPLAY_HEIGHT)) { + stx_x += tx_dudy; + stx_y += tx_dvdy; + continue; + } + if (use_z > ZBUFFER (x, y)) { + stx_x += tx_dudy; + stx_y += tx_dvdy; + continue; + } + ZBUFFER (x, y) = use_z; + + int16_t tpix_y = (stx_y >> 16) & (DISPLAY_HEIGHT - 1); + int16_t tpix_x = (stx_x >> 16) & (DISPLAY_WIDTH - 1); + + if (g3d_texture_bit (txbuf, tpix_x, tpix_y)) { + lcd.pixel_nochk (x, y, 1); + } else { + lcd.pixel_nochk (x, y, 0); + } + + stx_x += tx_dudy; + stx_y += tx_dvdy; + } + } +} + + +/** + * takes an edge-buffer and draws its wireframe on the given LCD (start and end). + */ +void gfx3d_wireedge (const g3d_edgebuf_t *edge, C12832 &lcd) +{ + int x; + + if (edge->s_end == 1) { + /* special case: vertical line */ + int xp = edge->xoff; + int y0 = edge->start[0]; + int y1 = edge->end[0]; + int ys = (y1 >= y0) ? 1 : -1; + int y; + + int16_t z0 = edge->zstart[0]; + int16_t z1 = edge->zend[0]; + int32_t zval = (z0 << 16) | 0x8000; /* half-way.. */ + + if ((y0 == y1) || ((y0 + ys) == y1)) { + /* one or two point, since we're narrow, just y0 */ + if (z0 <= ZBUFFER (xp, y0)) { + ZBUFFER (xp, y0) = z0; + lcd.pixel (xp, y0, 1); + } + } else { + /* long vertical poly, set all points except at y1 */ + int32_t zdelta = ((int32_t)(z1 - z0) << 16) / (int32_t)(y1 - y0); + int16_t rc_z = z0; + + for (y = y0; y != y1; y += ys) { + zval += zdelta; + rc_z = (zval >> 16); + if (rc_z <= ZBUFFER (xp, y)) { + ZBUFFER (xp, y) = rc_z; + lcd.pixel (xp, y, 1); + } + } + } + return; + } + + for (x=0; x<edge->s_end; x++) { + int xp = x + edge->xoff; + int y0 = edge->start[x]; + int y1 = edge->end[x]; + int ys = (y1 >= y0) ? 1 : -1; + int y; + + int16_t z0 = edge->zstart[x]; + int16_t z1 = edge->zend[x]; + + int32_t zval = (z0 << 16) | 0x8000; /* half-way.. */ + + if (y0 == y1) { + /* point, but we don't plot assuming it belongs to the adjoining polygon */ + } else if ((y0 + ys) == y1) { + /* two-point poly, just plot y0 */ + if (z0 <= ZBUFFER (xp, y0)) { + ZBUFFER (xp, y0) = z0; + lcd.pixel (xp, y0, 1); + } + } else { + /* more than two points, shrink y1 to avoid plotting the last one here */ + int32_t zdelta = ((int32_t)(z1 - z0) << 16) / (int32_t)(y1 - y0); + int16_t rc_z = z0; + + y1 -= ys; + + /* start at y0 */ + y = y0; + if (rc_z <= ZBUFFER (xp, y)) { + ZBUFFER (xp, y) = rc_z; + lcd.pixel (xp, y, 1); + } + + for (y += ys; y != y1; y += ys) { + zval += zdelta; + rc_z = (zval >> 16); + if (rc_z <= ZBUFFER (xp, y)) { + ZBUFFER (xp, y) = rc_z; + lcd.pixel (xp, y, 0); + } + } + /* last pixel at y1,z1 */ + rc_z = z1; + if (rc_z <= ZBUFFER (xp, y1)) { + ZBUFFER (xp, y1) = rc_z; + lcd.pixel (xp, y1, 1); + } + } + + /* end of x-loop */ + } +} +