Basic 3D graphics for the MBED application-shield on-board LCD (initial/incomplete).

Dependents:   co657_lcdplay

g3d_render.cpp

Committer:
co657_frmb
Date:
2015-11-29
Revision:
9:db4ec6f7d8b2
Parent:
8:55ee7af49f47

File content as of revision 9:db4ec6f7d8b2:

/*
 *  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 (containing a texture) and an LCD and draws it.
 *
 *  @param src      Source polygon.
 *  @param lcd      Display LCD to use (size fixed in various places..).
 */
void gfx3d_polytxmap (const g3d_poly_t *src, 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;
    }

    /* Note:
     *  - mostly 16.16 fixpoint stuff.
     *  - could optimise more by better pixel plotting (directly into the buffer).
     */

    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;
    int32_t tx_dudy, tx_dvdy;
    int16_t ys;
    
    /* the texture gradients (tx_dudy, tx_dvdy) are constant for any particular triangle */

    int32_t tx_ctemp, tx_tall, p_y, tp_x, tp_y;
    
    if (src->pts[2].x == src->pts[0].x) {
        /* skinny */
        tx_ctemp = 0;
    } else {
        tx_ctemp = ((src->pts[1].x - src->pts[0].x) << 16) / (src->pts[2].x - src->pts[0].x);       /* factor of AB to AC, (fixpoint 0->1) */
    }
    
    p_y = y_top + ((src->pts[2].y - src->pts[0].y) * tx_ctemp);
    tp_x = ((int32_t)(src->tx_pts[0] & 0xff) << 16) + ((int32_t)((src->tx_pts[2] & 0xff) - (src->tx_pts[0] & 0xff)) * tx_ctemp);
    tp_y = ((int32_t)(src->tx_pts[0] >> 8) << 16) + ((int32_t)((src->tx_pts[2] >> 8) - (src->tx_pts[0] >> 8)) * tx_ctemp);
    
    tx_tall = abs ((p_y - (src->pts[1].y << 16)) >> 8);                    /* height of tallest scan -- positive 24.8 fixpoint */
    
    if (tx_tall == 0) {
        /* flat in effect */
        tx_dudy = 0;
        tx_dvdy = 0;
        ys = 1;
    } else {
        tx_dudy = (tp_x - ((int32_t)(src->tx_pts[1] & 0xff) << 16));
        tx_dudy = (tx_dudy / tx_tall) << 8;
        tx_dvdy = (tp_y - ((int32_t)(src->tx_pts[1] >> 8) << 16));
        tx_dvdy = (tx_dvdy / tx_tall) << 8;
        ys = (p_y > (src->pts[1].y << 16)) ? 1 : -1;
    }
    
    /* left-hand side */
    for (x=src->pts[0].x; x<src->pts[1].x; x++) {
        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;
        int16_t tpix_y, tpix_x;

        int32_t stx_y = tx_top_y;
        int32_t stx_x = tx_top_x;

        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)) {
                goto yl_loop_advance;
            }
            if (use_z > ZBUFFER (x, y)) {
                goto yl_loop_advance;
            }                
            ZBUFFER (x, y) = use_z;
            
            tpix_y = (stx_y >> 16) & (DISPLAY_HEIGHT - 1);
            tpix_x = (stx_x >> 16) & (DISPLAY_WIDTH - 1);
        
            lcd.pixel_nochk_norm (x, y, g3d_texture_bit (src->txptr, tpix_x, tpix_y));
yl_loop_advance:            
            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 pix_y = (y_top >> 16);
        int16_t bpix_y = (y_bot >> 16) + ys;
        int16_t y;
        int16_t tpix_y, tpix_x;

        int32_t stx_y = tx_top_y;
        int32_t stx_x = tx_top_x;

        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)) {
                goto yr_loop_advance;
            }
            if (use_z > ZBUFFER (x, y)) {
                goto yr_loop_advance;
            }                
            ZBUFFER (x, y) = use_z;

            tpix_y = (stx_y >> 16) & (DISPLAY_HEIGHT - 1);
            tpix_x = (stx_x >> 16) & (DISPLAY_WIDTH - 1);

            lcd.pixel_nochk_norm (x, y, g3d_texture_bit (src->txptr, tpix_x, tpix_y));

yr_loop_advance:
            stx_x += tx_dudy;
            stx_y += tx_dvdy;
        }
    }
}


/**
 *  takes a polygon, drawing it on the given LCD with shading based on normal value.
 *
 *  @param src      Source polygon.
 *  @param lcd      Display LCD to use (size fixed in various places..).
 */
void gfx3d_polynormmap (const g3d_poly_t *src, 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;
    
    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);
    } else {
        dydx_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);
    } else {
        dydx_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);
    } else {
        dydx_bc = 0;
    }

    /* Note:
     *  - mostly 16.16 fixpoint stuff.
     *  - could optimise more by better pixel plotting (directly into the buffer).
     */

    int32_t y_top = (src->pts[0].y << 16) + 0x8000;
    int32_t y_bot = (src->pts[0].y << 16) + 0x8000;
    int16_t ys;
    int32_t tx_ctemp, tx_tall, p_y;
    int bitstep = (32 - __CLZ (src->norm)) >> 2;
    int pcount = 0;
    
    if (bitstep == 0) {
        bitstep = 1;
    }
    if (src->pts[2].x == src->pts[0].x) {
        /* skinny */
        tx_ctemp = 0;
    } else {
        tx_ctemp = ((src->pts[1].x - src->pts[0].x) << 16) / (src->pts[2].x - src->pts[0].x);       /* factor of AB to AC, (fixpoint 0->1) */
    }
    
    p_y = y_top + ((src->pts[2].y - src->pts[0].y) * tx_ctemp);
    tx_tall = abs ((p_y - (src->pts[1].y << 16)) >> 8);                    /* height of tallest scan -- positive 24.8 fixpoint */
    
    if (tx_tall == 0) {
        /* flat in effect */
        ys = 1;
    } else {
        ys = (p_y > (src->pts[1].y << 16)) ? 1 : -1;
    }
    
    /* left-hand side */
    for (x=src->pts[0].x; x<src->pts[1].x; x++) {
        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;

        y_top += dydx_ab;       y_bot += dydx_ac;
        
        if ((x < 0) || (x >= DISPLAY_WIDTH)) {
            continue;
        }

        pcount = 0;
        for (y=pix_y; y != bpix_y; y += ys) {
            if ((y < 0) || (y >= DISPLAY_HEIGHT)) {
                continue;
            }
            if (use_z > ZBUFFER (x, y)) {
                continue;
            }                
            ZBUFFER (x, y) = use_z;
            
            lcd.pixel_nochk_norm (x, y, (pcount == 0) ? 1 : 0);
            if (!pcount) {
                pcount = bitstep;
            }
            pcount--;
        }
    }
    
    y_top = (src->pts[1].y << 16) + 0x8000;
    
    /* right-hand side */
    for (; x<src->pts[2].x; x++) {
        int16_t pix_y = (y_top >> 16);
        int16_t bpix_y = (y_bot >> 16) + ys;
        int16_t y;

        y_top += dydx_bc;       y_bot += dydx_ac;
               
        if ((x < 0) || (x >= DISPLAY_WIDTH)) {
            continue;
        }

        pcount = 0;
        for (y=pix_y; y != bpix_y; y += ys) {
            if ((y < 0) || (y >= DISPLAY_HEIGHT)) {
                continue;
            }
            if (use_z > ZBUFFER (x, y)) {
                continue;
            }                
            ZBUFFER (x, y) = use_z;

            lcd.pixel_nochk_norm (x, y, (pcount == 0) ? 1 : 0);
            if (!pcount) {
                pcount = bitstep;
            }
            pcount--;
        }
    }
}


/**
 *  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 (xp >= DISPLAY_WIDTH) {
            /* off right-hand edge */
            return;
        } else if (xp < 0) {
            /* off left-hand edge */
            return;
        }
        
        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_nochk_norm (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_nochk_norm (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 ((xp >= DISPLAY_WIDTH) || (xp < 0)) {
            continue;
        }

        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_nochk_norm (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_nochk_norm (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_nochk_norm (xp, y, 0);
                }
            }
            /* last pixel at y1,z1 */
            rc_z = z1;
            if (rc_z <= ZBUFFER (xp, y1)) {
                ZBUFFER (xp, y1) = rc_z;
                lcd.pixel_nochk_norm (xp, y1, 1);
            }
        }
        
        /* end of x-loop */
    }
}