Basic 3D graphics for the MBED application-shield on-board LCD (initial/incomplete).
gfx3d.cpp
- Committer:
- co657_frmb
- Date:
- 2015-11-18
- Revision:
- 4:7a9f0515d0a0
- Parent:
- 3:2d8982c06eee
- Child:
- 5:2aaaf4e78a53
File content as of revision 4:7a9f0515d0a0:
/* * gfx3d.cpp -- 3D stuff for MBED (just playing!) * Copyright (C) 2015 Fred Barnes, University of Kent <frmb@kent.ac.uk> */ /** gfx3d library * * This is a library for primitive 3D graphics. No classes, just C functions. */ #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; } } /* * inlined 16-bit minimum, returns argument number */ static inline int int16_min3 (int16_t v0, int16_t v1, int16_t v2) { if (v0 < v1) { if (v0 < v2) { return 0; } return 2; } if (v0 < v2) { return 1; } if (v1 < v2) { return 1; } return 2; } /* * inlined 16-bit maximum, returns argument number */ static inline int int16_max3 (int16_t v0, int16_t v1, int16_t v2) { if (v0 > v1) { if (v0 > v2) { return 0; } return 2; } if (v0 > v2) { return 1; } if (v1 > v2) { return 1; } return 2; } /** rotates a base set of points into a new set (demoscene style) * * @param src Source points. * @param dst Destination points. * @param npnts Number of points. * @param a Angle to rotate by (0-255). */ void gfx3d_rotate_demo (const g3d_p3_t *src, g3d_p3_t *dst, const int npnts, const angle_t a) { float sinval = gfx3d_sin (a); float cosval = gfx3d_cos (a); int i; for (i=0; i<npnts; i++) { float x1 = (src[i].x * cosval) + (src[i].y * sinval); float y1 = (src[i].y * cosval) - (src[i].x * sinval); float z1 = (src[i].z * cosval) - (x1 * sinval); float t; dst[i].x = (x1 * cosval) + (src[i].z * sinval); t = (y1 * cosval) + (z1 * sinval); dst[i].z = (z1 * cosval) - (y1 * sinval); dst[i].y = t; } } /* * translates a set of 3D points. 'src' and 'dst' can be the same */ void gfx3d_translate (const g3d_p3_t *src, g3d_p3_t *dst, const int npnts, const g3d_p3_t tx) { int i; if (tx.x != 0.0f) { for (i=0; i<npnts; i++) { dst[i].x = src[i].x + tx.x; } } else if (src != dst) { for (i=0; i<npnts; i++) { dst[i].x = src[i].x; } } if (tx.y != 0.0f) { for (i=0; i<npnts; i++) { dst[i].y = src[i].y + tx.y; } } else if (src != dst) { for (i=0; i<npnts; i++) { dst[i].y = src[i].y; } } if (tx.z != 0.0f) { for (i=0; i<npnts; i++) { dst[i].z = src[i].z + tx.z; } } else if (src != dst) { for (i=0; i<npnts; i++) { dst[i].z = src[i].z; } } } /** * projects a set of 3D points into a 2D space (pretty crude) */ void gfx3d_project (const g3d_p3_t *src, g3d_2p3_t *dst, const int npnts) { int i; for (i=0; i<npnts; i++) { float ez = src[i].z; dst[i].z = (int16_t)((ez + G3D_ZBADD) * G3D_ZBSCALE); ez += G3D_Z_DEPTH; dst[i].x = (int16_t)((src[i].x / ez) * G3D_X_SCALE) + G3D_X2_SHIFT; dst[i].y = (int16_t)((src[i].y / ez) * G3D_Y_SCALE) + G3D_Y2_SHIFT; } } /** * takes a set of 8 projected points and creates a set of <=12 triangular polygons representing the surface of a cube */ void gfx3d_cubify_points (const g3d_2p3_t *src, g3d_poly_t *dst, int *npoly, const int backfaces) { static const int cubemap3[12][3] = {{1,0,4}, {1,5,4}, {2,6,5}, {2,1,5}, {3,2,6}, {3,7,6}, {3,7,4}, {3,0,4}, {3,2,1}, {3,0,1}, {7,4,5}, {7,6,5}}; static const int cubemap4[6][4] = {{1,0,4,5}, {2,1,5,6}, {3,2,6,7}, {0,3,7,4}, {3,0,1,2}, {6,5,4,7}}; int i, pidx; int norms[6]; *npoly = 12; /* assume all to start with */ /* compute normals */ for (i=0; i<6; i++) { const int *face = cubemap4[i]; int norm = ((src[face[3]].y - src[face[0]].y) * (src[face[1]].x - src[face[0]].x)) - ((src[face[1]].y - src[face[0]].y) * (src[face[3]].x - src[face[0]].x)); if (!backfaces && (norm >= 0)) { /* not showing this one */ *npoly = *npoly - 2; } norms[i] = norm; } /* wind polygons */ pidx = 0; for (i=0; (i<12) && (pidx < *npoly); i++) { if (!backfaces && (norms[i>>1] >= 0)) { /* not showing this one */ } else { int p; dst[pidx].norm = norms[i>>1]; for (p=0; p<3; p++) { dst[pidx].pts[p].x = src[cubemap3[i][p]].x; dst[pidx].pts[p].y = src[cubemap3[i][p]].y; dst[pidx].pts[p].z = src[cubemap3[i][p]].z; } pidx++; } } } #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; } 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])); } 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])); } 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])); } } 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; } 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; } 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 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 */ } } /** * takes a polygon and draws its wireframe on the given LCD. */ void gfx3d_wirepoly (const g3d_poly_t *src, C12832 &lcd) { #if G3D_MAX_POLY_POINTS == 3 lcd.line (src->pts[0].x, src->pts[0].y, src->pts[1].x, src->pts[1].y, 1); lcd.line (src->pts[1].x, src->pts[1].y, src->pts[2].x, src->pts[2].y, 1); lcd.line (src->pts[2].x, src->pts[2].y, src->pts[0].x, src->pts[0].y, 1); #endif } /** * takes a polygon and draws its wireframe on the given LCD, taking Z buffering into consideration */ void gfx3d_wirepoly_z (const g3d_poly_t *src, C12832 &lcd) { #if G3D_MAX_POLY_POINTS == 3 #endif } /** * takes a set of 8 projected points and draws a wireframe cube on the given LCD. */ void gfx3d_wirecube (const g3d_2p3_t *src, C12832 &lcd) { lcd.line (src[0].x, src[0].y, src[1].x, src[1].y, 1); lcd.line (src[1].x, src[1].y, src[2].x, src[2].y, 1); lcd.line (src[2].x, src[2].y, src[3].x, src[3].y, 1); lcd.line (src[3].x, src[3].y, src[0].x, src[0].y, 1); lcd.line (src[4].x, src[4].y, src[5].x, src[5].y, 1); lcd.line (src[5].x, src[5].y, src[6].x, src[6].y, 1); lcd.line (src[6].x, src[6].y, src[7].x, src[7].y, 1); lcd.line (src[7].x, src[7].y, src[4].x, src[4].y, 1); lcd.line (src[0].x, src[0].y, src[4].x, src[4].y, 1); lcd.line (src[1].x, src[1].y, src[5].x, src[5].y, 1); lcd.line (src[2].x, src[2].y, src[6].x, src[6].y, 1); lcd.line (src[3].x, src[3].y, src[7].x, src[7].y, 1); }