/*
 * vp_view.c
 *
 * Routines to compute quantities derived from the view transformation.
 *
 * Copyright (c) 1994 The Board of Trustees of The Leland Stanford
 * Junior University.  All rights reserved.
 *
 * Permission to use, copy, modify and distribute this software and its
 * documentation for any purpose is hereby granted without fee, provided
 * that the above copyright notice and this permission notice appear in
 * all copies of this software and that you do not sell the software.
 * Commercial licensing is available by contacting the author.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * Author:
 *    Phil Lacroute
 *    Computer Systems Laboratory
 *    Electrical Engineering Dept.
 *    Stanford University
 *
 * This version of the VolPack library is a modified version of the
 * original available from Stanford University.  Modifications were
 * made by the Center for Computational Sciences and Engineering,
 * Lawrence Berkeley National Laboratory.  Modifications to VolPack
 * (c) 2000 The Regents of the University of California (through
 * E.O. Lawrence Berkeley National Laboratory), subject to approval by
 * the U.S. Department of Energy.  Your use of this software is under
 * license from Stanford University with respect to the underlying
 * original VolPack code (see copyright notice and permission above)
 * and The Regents of the University of California with respect to
 * modifications thereto (see AmrVis license.txt file for applicable
 * license terms).  Contact Berkeley Lab's Center for Computational
 * Sciences and Engineering at webmaster@mothra.lbl.gov or Berkeley
 * Lab's Technology Transfer Department at TTD@lbl.gov for questions
 * or to receive more information.
 *
 */

/*
 * $Date: 2000/10/02 18:12:04 $
 * $Revision: 1.2 $
 */

#include "vp_global.h"

static int FactorAffineView ANSI_ARGS((vpContext *vpc, vpMatrix4 vm));
static int FactorPerspectiveView ANSI_ARGS((vpContext *vpc, vpMatrix4 vm));
static void ComputeAffineOpacityCorrection ANSI_ARGS((vpContext *vpc,
    double shear_i, double shear_j, float table[VP_OPACITY_MAX+1]));
static void CheckRenderBuffers ANSI_ARGS((vpContext *vpc));
static void ComputeLightViewTransform ANSI_ARGS((vpContext *vpc,vpMatrix4 vm));
static int FactorLightView ANSI_ARGS((vpContext *vpc, vpMatrix4 vm));
static void CheckShadowBuffer ANSI_ARGS((vpContext *vpc));

/*
 * VPFactorView
 *
 * Factor the viewing matrix.
 */

vpResult
VPFactorView(vpc)
vpContext *vpc;
{
    vpMatrix4 vm;
    int retcode;

    if (vpc->factored_view_ready) {
	CheckRenderBuffers(vpc);
	return(VP_OK);
    }

    /* compute the overall view transformation */
    VPComputeViewTransform(vpc, vm);

    /* check if transformation is affine and factor it */
    if (fabs(vm[3][0]) < VP_EPS && fabs(vm[3][1]) < VP_EPS &&
	fabs(vm[3][2]) < VP_EPS && fabs(vm[3][3]-1.) < VP_EPS) {
	if ((retcode = FactorAffineView(vpc, vm)) != VP_OK)
	    return(retcode);
	ComputeAffineOpacityCorrection(vpc, vpc->shear_i, vpc->shear_j,
				       vpc->affine_opac_correct);
    } else {
	FactorPerspectiveView(vpc, vm);
    }
    CheckRenderBuffers(vpc);

    /* compute viewing transformation from the point of view of the light
       source (for calculating shadows) */
    if ((retcode = VPCheckShadows(vpc)) != VP_OK)
	return(retcode);
    if (vpc->enable_shadows) {
	ComputeLightViewTransform(vpc, vm);
	if ((retcode = FactorLightView(vpc, vm)) != VP_OK)
	    return(retcode);
	ComputeAffineOpacityCorrection(vpc, vpc->shadow_shear_i,
				       vpc->shadow_shear_j,
				       vpc->shadow_opac_correct);
	CheckShadowBuffer(vpc);
    }
    return(VP_OK);
}

/*
 * VPComputeViewTransform
 *
 * Compute the overall view transformation.
 */

void
VPComputeViewTransform(vpc, vm)
vpContext *vpc;
vpMatrix4 vm;	/* storage for result */
{
    vpMatrix4 prematrix;		/* transform volume to unit cube */
    vpMatrix4 viewportm;		/* viewport matrix */
    vpMatrix4 tmp1m, tmp2m, tmp3m;	/* temporary matrices */
    int maxdim;
    double scale;

    /* compute prematrix */
    vpIdentity4(prematrix);
    maxdim = vpc->xlen;
    if (vpc->ylen > maxdim)
	maxdim = vpc->ylen;
    if (vpc->zlen > maxdim)
	maxdim = vpc->zlen;
    scale = 1. / (double)maxdim;
    prematrix[0][0] = scale;
    prematrix[1][1] = scale;
    prematrix[2][2] = scale;
    prematrix[0][3] = -vpc->xlen * scale * 0.5;
    prematrix[1][3] = -vpc->ylen * scale * 0.5;
    prematrix[2][3] = -vpc->zlen * scale * 0.5;

    /* compute the viewport matrix */
    vpIdentity4(viewportm);
    viewportm[0][0] = 0.5 * vpc->image_width;
    viewportm[0][3] = 0.5 * vpc->image_width;
    viewportm[1][1] = 0.5 * vpc->image_height;
    viewportm[1][3] = 0.5 * vpc->image_height;
    viewportm[2][2] = -0.5;	/* minus sign: switch to left-handed coords. */
    viewportm[2][3] = 0.5;

    /* compute the view matrix */
    vpMatrixMult4(tmp1m, vpc->transforms[VP_MODEL], prematrix);
    vpMatrixMult4(tmp2m, vpc->transforms[VP_VIEW], tmp1m);
    vpMatrixMult4(tmp3m, vpc->transforms[VP_PROJECT], tmp2m);
    vpMatrixMult4(vm, viewportm, tmp3m);
}

/*
 * FactorAffineView
 *
 * Factor an affine viewing matrix into two parts:
 *  1) A shear and translation which map object coordinates into
 *     intermediate image coordinates.
 *  2) An affine warp which transforms intermediate image coordinates to
 *     image coordinates.
 * Return value is a result code.
 */

static int
FactorAffineView(vpc, vm)
vpContext *vpc;
vpMatrix4 vm;
{
    vpMatrix4 p;		/* permutation matrix */
    vpMatrix4 pvm;		/* permutation of the viewing matrix */
    int icount, jcount, kcount;	/* dimensions of volume in rotated space */
    vpVector4 xobj, yobj, zobj;
    vpVector4 xim, yim, zim;
    double x, y, z, denom;

    Debug((vpc, VPDEBUG_VIEW, "FactorAffineView\n"));

    vpc->affine_view = 1;

    /*
     * Transform the unit x, y and z object-coordinate vectors into image
     * space and see which one is most aligned with the view direction
     * (which is the z-axis in image coordinates).
     */
    vpSetVector4(xobj, 1., 0., 0., 0.);
    vpSetVector4(yobj, 0., 1., 0., 0.);
    vpSetVector4(zobj, 0., 0., 1., 0.);
    vpMatrixVectorMult4(xim, vm, xobj);
    vpMatrixVectorMult4(yim, vm, yobj);
    vpMatrixVectorMult4(zim, vm, zobj);
    x = fabs((vpNormalize3(xim) == VPERROR_SINGULAR) ? 0. : xim[2]);
    y = fabs((vpNormalize3(yim) == VPERROR_SINGULAR) ? 0. : yim[2]);
    z = fabs((vpNormalize3(zim) == VPERROR_SINGULAR) ? 0. : zim[2]);
    if (x >= y) {
	if (x >= z) {
	    vpc->best_view_axis = VP_X_AXIS;
	} else {
	    vpc->best_view_axis = VP_Z_AXIS;
	}
    } else {
	if (y >= z) {
	    vpc->best_view_axis = VP_Y_AXIS;
	} else {
	    vpc->best_view_axis = VP_Z_AXIS;
	}
    }
    switch (vpc->axis_override) {
    case VP_X_AXIS:
	if (x < VP_EPS)
	    return(VPSetError(vpc, VPERROR_SINGULAR));
	vpc->best_view_axis = VP_X_AXIS;
	break;
    case VP_Y_AXIS:
	if (y < VP_EPS)
	    return(VPSetError(vpc, VPERROR_SINGULAR));
	vpc->best_view_axis = VP_Y_AXIS;
	break;
    case VP_Z_AXIS:
	if (z < VP_EPS)
	    return(VPSetError(vpc, VPERROR_SINGULAR));
	vpc->best_view_axis = VP_Z_AXIS;
	break;
    default:
	break;
    }

    /* permute the rows of the viewing matrix so that the third axis is
       most parallel to the viewing direction */
    bzero(p, sizeof(vpMatrix4));
    switch (vpc->best_view_axis) {
    case VP_X_AXIS:
	p[0][2] = 1.;
	p[1][0] = 1.;
	p[2][1] = 1.;
	p[3][3] = 1.;
	icount = vpc->ylen;
	jcount = vpc->zlen;
	kcount = vpc->xlen;
	break;
    case VP_Y_AXIS:
	p[0][1] = 1.;
	p[1][2] = 1.;
	p[2][0] = 1.;
	p[3][3] = 1.;
	icount = vpc->zlen;
	jcount = vpc->xlen;
	kcount = vpc->ylen;
	break;
    case VP_Z_AXIS:
	p[0][0] = 1.;
	p[1][1] = 1.;
	p[2][2] = 1.;
	p[3][3] = 1.;
	icount = vpc->xlen;
	jcount = vpc->ylen;
	kcount = vpc->zlen;
	break;
    }
    vpMatrixMult4(pvm, vm, p);

    /* compute the shear coefficients */
    denom = pvm[0][0]*pvm[1][1] - pvm[0][1]*pvm[1][0];
    if (fabs(denom) < VP_EPS)
	return(VPSetError(vpc, VPERROR_SINGULAR));
    vpc->shear_i = (pvm[0][2]*pvm[1][1] - pvm[0][1]*pvm[1][2]) / denom;
    vpc->shear_j = (pvm[0][0]*pvm[1][2] - pvm[1][0]*pvm[0][2]) / denom;
    if (pvm[2][0]*vpc->shear_i + pvm[2][1]*vpc->shear_j - pvm[2][2] > 0)
	vpc->reverse_slice_order = 0;
    else
	vpc->reverse_slice_order = 1;

    /* compute the intermediate image size */
    vpc->intermediate_width = icount + 1 + (int)ceil((kcount-1)*
			      fabs(vpc->shear_i));
    vpc->intermediate_height = jcount + 1 + (int)ceil((kcount-1)*
			       fabs(vpc->shear_j));

    /* compute the translation coefficients */
    if (vpc->shear_i >= 0.)
	vpc->trans_i = 1.;
    else
	vpc->trans_i = 1. - vpc->shear_i * (kcount - 1);
    if (vpc->shear_j >= 0.)
	vpc->trans_j = 1.;
    else
	vpc->trans_j = 1. - vpc->shear_j * (kcount - 1);

    /* compute the depth coefficients */
    vpc->depth_di = pvm[2][0];
    vpc->depth_dj = pvm[2][1];
    vpc->depth_dk = pvm[2][2];
    vpc->depth_000 = pvm[2][3];

    /* compute the mapping from compositing space to image space */
    vpc->warp_2d[0][0] = pvm[0][0];
    vpc->warp_2d[0][1] = pvm[0][1];
    vpc->warp_2d[0][2] = pvm[0][3] - pvm[0][0]*vpc->trans_i -
			 pvm[0][1]*vpc->trans_j;
    vpc->warp_2d[1][0] = pvm[1][0];
    vpc->warp_2d[1][1] = pvm[1][1];
    vpc->warp_2d[1][2] = pvm[1][3] - pvm[1][0]*vpc->trans_i - 
			 pvm[1][1]*vpc->trans_j;
    vpc->warp_2d[2][0] = 0.;
    vpc->warp_2d[2][1] = 0.;
    vpc->warp_2d[2][2] = 1.;

    vpc->factored_view_ready = 1;

    Debug((vpc, VPDEBUG_VIEW, "  best_view_axis: %c%c\n", 
	   vpc->reverse_slice_order ? '-' : '+',
	   vpc->best_view_axis == VP_X_AXIS ? 'x' :
	   (vpc->best_view_axis == VP_Y_AXIS ? 'y' : 'z')));
    Debug((vpc, VPDEBUG_VIEW, "  shear factors: %g %g\n",
	   vpc->shear_i, vpc->shear_j));
    Debug((vpc, VPDEBUG_VIEW, "  translation: %g %g\n",
	   vpc->trans_i, vpc->trans_j));
    Debug((vpc, VPDEBUG_VIEW, "  depth: d000: %g\n", vpc->depth_000));
    Debug((vpc, VPDEBUG_VIEW, "         di:   %g\n", vpc->depth_di));
    Debug((vpc, VPDEBUG_VIEW, "         dj:   %g\n", vpc->depth_dj));
    Debug((vpc, VPDEBUG_VIEW, "         dk:   %g\n", vpc->depth_dk));
    Debug((vpc, VPDEBUG_VIEW, "  intermediate image size: %d %d\n",
	   vpc->intermediate_width, vpc->intermediate_height));
    return(VP_OK);
}

/*
 * FactorPerspectiveView
 *
 * Factor a perspective view matrix into two parts:
 *  1) A shear, translation and scale which map object coordinates into
 *     intermediate image coordinates.
 *  2) A perspective warp which transforms intermediate image coordinates to
 *     image coordinates.
 */

static int
FactorPerspectiveView(vpc, vm)
vpContext *vpc;
vpMatrix4 vm;
{
    vpc->affine_view = 0;
    return(VP_OK);

#ifdef notdef
    Matrix4 p;		/* permutation matrix */
    Matrix4 pvm;	/* permutation of the viewing matrix */
    Matrix4 m2d;	/* final warp */
    Matrix4 t;
    double alpha1, alpha2, alpha3, alpha4;
    int icount, jcount, kcount;	/* dimensions of volume in rotated space */
    Vector4 xobj, yobj, zobj;
    double x, y, z, denom;
    double i0, j0, i1, j1;
    double imin, imax, jmin, jmax;

    Debug((DEBUG_VIEW, "FactorPerspectiveView\n"));

    rbuf->perspective_proj = 1;

    /*
     * Transform the unit x, y and z object-coordinate vectors into image
     * space and see which one is most aligned with the view direction
     * (which is the z-axis in image coordinates).
     */
    xobj[0] = 1.; xobj[1] = 0.; xobj[2] = 0.; xobj[3] = 0.;
    yobj[0] = 0.; yobj[1] = 1.; yobj[2] = 0.; yobj[3] = 0.;
    zobj[0] = 0.; zobj[1] = 0.; zobj[2] = 1.; zobj[3] = 0.;
    TransformVector4(xobj, view->view_matrix);
    TransformVector4(yobj, view->view_matrix);
    TransformVector4(zobj, view->view_matrix);
    /* normalize each vector to unit length and compare the absolute value
       of the z component; note that the w component drops out */
    xobj[2] = fabs(xobj[2]);
    yobj[2] = fabs(yobj[2]);
    zobj[2] = fabs(zobj[2]);
    x = (xobj[2] < EPS) ? 0. :
	(xobj[2] / sqrt(xobj[0]*xobj[0] + xobj[1]*xobj[1] + xobj[2]*xobj[2]));
    y = (yobj[2] < EPS) ? 0. :
	(yobj[2] / sqrt(yobj[0]*yobj[0] + yobj[1]*yobj[1] + yobj[2]*yobj[2]));
    z = (zobj[2] < EPS) ? 0. :
	(zobj[2] / sqrt(zobj[0]*zobj[0] + zobj[1]*zobj[1] + zobj[2]*zobj[2]));
    if (x >= y) {
	if (x >= z) {
	    rbuf->best_view_axis = VP_XAXIS;
	} else {
	    rbuf->best_view_axis = VP_ZAXIS;
	}
    } else {
	if (y >= z) {
	    rbuf->best_view_axis = VP_YAXIS;
	} else {
	    rbuf->best_view_axis = VP_ZAXIS;
	}
    }

    /* permute the rows of the viewing matrix so that the third axis is
       most parallel to the viewing direction */
    bzero(p, sizeof(Matrix4));
    switch (rbuf->best_view_axis) {
    case VP_XAXIS:
	p[0][2] = 1.;
	p[1][0] = 1.;
	p[2][1] = 1.;
	p[3][3] = 1.;
	icount = ylen;
	jcount = zlen;
	kcount = xlen;
	break;
    case VP_YAXIS:
	p[0][1] = 1.;
	p[1][2] = 1.;
	p[2][0] = 1.;
	p[3][3] = 1.;
	icount = zlen;
	jcount = xlen;
	kcount = ylen;
	break;
    case VP_ZAXIS:
	p[0][0] = 1.;
	p[1][1] = 1.;
	p[2][2] = 1.;
	p[3][3] = 1.;
	icount = xlen;
	jcount = ylen;
	kcount = zlen;
	break;
    default:
	VPBug("wierd value for best_view_axis in FactorPerspectiveView\n");
    }
    MatrixMult4(pvm, view->view_matrix, p);

    /* compute the magic alpha coefficients */
    alpha1 = pvm[3][1] * (pvm[0][3]*pvm[1][2] - pvm[0][2]*pvm[1][3]) +
	     pvm[3][2] * (pvm[0][1]*pvm[1][3] - pvm[0][3]*pvm[1][1]) +
	     pvm[3][3] * (pvm[0][2]*pvm[1][1] - pvm[0][1]*pvm[1][2]);
    alpha2 = pvm[3][0] * (pvm[0][2]*pvm[1][3] - pvm[0][3]*pvm[1][2]) +
	     pvm[3][2] * (pvm[0][3]*pvm[1][0] - pvm[0][0]*pvm[1][3]) +
	     pvm[3][3] * (pvm[0][0]*pvm[1][2] - pvm[0][2]*pvm[1][0]);
    alpha3 = pvm[3][0] * (pvm[0][1]*pvm[1][2] - pvm[0][2]*pvm[1][1]) +
	     pvm[3][1] * (pvm[0][2]*pvm[1][0] - pvm[0][0]*pvm[1][2]) +
	     pvm[3][2] * (pvm[0][0]*pvm[1][1] - pvm[0][1]*pvm[1][0]);
    alpha4 = pvm[3][0] * (pvm[0][1]*pvm[1][3] - pvm[0][3]*pvm[1][1]) +
	     pvm[3][1] * (pvm[0][3]*pvm[1][0] - pvm[0][0]*pvm[1][3]) +
	     pvm[3][3] * (pvm[0][0]*pvm[1][1] - pvm[0][1]*pvm[1][0]);

    /* determine the order of the slices */
    if (pvm[2][2] - (pvm[2][0]*alpha1 + pvm[2][1]*alpha2)/(alpha3+alpha4) > 0)
	rbuf->reverse_k_order = 1;
    else
	rbuf->reverse_k_order = 0;

    /* compute the scale coefficients */
    rbuf->w_factor = alpha3 / alpha4;
    if (rbuf->reverse_k_order)
	rbuf->normalize_scale = 1. + rbuf->w_factor*(kcount-1);
    else
	rbuf->normalize_scale = 1.;

    /* compute the bounding box of the image in compositing space */
    denom = 1. / (alpha4 + alpha3*(kcount-1));
    i0 = rbuf->normalize_scale*alpha1*(kcount-1) * denom;
    j0 = rbuf->normalize_scale*alpha2*(kcount-1) * denom;
    i1 = rbuf->normalize_scale*(alpha4*icount + alpha1*(kcount-1)) * denom;
    j1 = rbuf->normalize_scale*(alpha4*jcount + alpha2*(kcount-1)) * denom;
    imin = MIN(0, i0);
    imax = MAX(rbuf->normalize_scale*icount, i1);
    jmin = MIN(0, j0);
    jmax = MAX(rbuf->normalize_scale*jcount, j1);

    /* compute the size of the intermediate image */
    rbuf->intermediate_width = (int)ceil(imax - imin);
    rbuf->intermediate_height = (int)ceil(jmax - jmin);

    /* compute the translation and shear coefficients */
    rbuf->shear_i = (rbuf->normalize_scale*alpha1 - alpha3*imin) / alpha4;
    rbuf->shear_j = (rbuf->normalize_scale*alpha2 - alpha3*jmin) / alpha4;
    rbuf->trans_i = -imin;
    rbuf->trans_j = -jmin;

    /* compute the depth coefficients */
    rbuf->depth_di = pvm[2][0];
    rbuf->depth_dj = pvm[2][1];
    rbuf->depth_dk = pvm[2][2];
    rbuf->depth_000 = pvm[2][3];
    rbuf->w_di = pvm[3][0];
    rbuf->w_dj = pvm[3][1];
    rbuf->w_dk = pvm[3][2];
    rbuf->w_000 = pvm[3][3];

    /* compute the mapping from compositing space to image space */
    Identity4(t);
    t[0][0] = 1. / rbuf->normalize_scale;
    t[1][1] = 1. / rbuf->normalize_scale;
    t[0][2] = -alpha1 / alpha4;
    t[1][2] = -alpha2 / alpha4;
    t[3][2] = -alpha3 / alpha4;
    t[0][3] = imin / rbuf->normalize_scale;
    t[1][3] = jmin / rbuf->normalize_scale;
    MatrixMult4(m2d, pvm, t);

    rbuf->warp_2d[0][0] = m2d[0][0];
    rbuf->warp_2d[1][0] = m2d[1][0];
    rbuf->warp_2d[2][0] = m2d[3][0];
    rbuf->warp_2d[0][1] = m2d[0][1];
    rbuf->warp_2d[1][1] = m2d[1][1];
    rbuf->warp_2d[2][1] = m2d[3][1];
    rbuf->warp_2d[0][2] = m2d[0][3];
    rbuf->warp_2d[1][2] = m2d[1][3];
    rbuf->warp_2d[2][2] = m2d[3][3];
#endif /* notdef */
}

/*
 * ComputeAffineOpacityCorrection
 *
 * Precompute a lookup table which corrects opacity for an affine viewing
 * transformation.  (Opacity correction accounts for variations in the
 * apparent thickness of a voxel depending on viewpoint.)
 */

static void
ComputeAffineOpacityCorrection(vpc, shear_i, shear_j, table)
vpContext *vpc;
double shear_i;
double shear_j;
float table[VP_OPACITY_MAX+1];
{
    float voxel_size;
    int i;

    Debug((vpc, VPDEBUG_OPCCORRECT,
	   "Computing affine opacity correction table.\n"));
    voxel_size = sqrt(1 + shear_i*shear_i + shear_j*shear_j);
    for (i = 0; i <= VP_OPACITY_MAX; i++) {
#ifdef NO_OPAC_CORRECT
	table[i] = (double)i / (double)VP_OPACITY_MAX;
#else
	table[i] = 1.-pow(1.-(double)i/(double)VP_OPACITY_MAX,voxel_size);
#endif
    }
}

/*
 * CheckRenderBuffers
 *
 * Resize the buffers used during rendering, if necessary.
 */

static void
CheckRenderBuffers(vpc)
vpContext *vpc;
{
    int new_max_width, new_max_height, new_max_scan;
    int resize = 0;

    /* determine if resizing is necessary */
    if (vpc->intermediate_width > vpc->max_intermediate_width) {
	new_max_width = MAX(vpc->intermediate_width,
			    vpc->int_image_width_hint);
	resize = 1;
    } else {
	new_max_width = MAX(vpc->max_intermediate_width,
			    vpc->int_image_width_hint);
    }
    if (vpc->intermediate_height > vpc->max_intermediate_height) {
	new_max_height = MAX(vpc->intermediate_height,
			     vpc->int_image_height_hint);
	resize = 1;
    } else {
	new_max_height = MAX(vpc->max_intermediate_height,
			     vpc->int_image_height_hint);
    }
    new_max_scan = vpc->xlen;
    if (vpc->ylen > new_max_scan)
	new_max_scan = vpc->ylen;
    if (vpc->zlen > new_max_scan)
	new_max_scan = vpc->zlen;
    if (new_max_scan > vpc->max_scan_length)
	resize = 1;
    if (vpc->color_channels != vpc->intermediate_color_channels)
	resize = 1;

    /* resize */
    if (resize)
	VPResizeRenderBuffers(vpc, new_max_width, new_max_height,new_max_scan);
}

/*
 * VPResizeRenderBuffers
 *
 * Resize the rendering buffers.
 */

void
VPResizeRenderBuffers(vpc, max_width, max_height, max_scan)
vpContext *vpc;
int max_width;	/* new width of the intermediate image */
int max_height;	/* new height of the intermediate image */
int max_scan;	/* new max. scanline length */
{
    /* free old buffers */
    if (vpc->int_image.gray_intim != NULL) {
	Debug((vpc, VPDEBUG_RBUF, "Freeing old RenderBuffer(%d,%d,%d,%d)\n",
	       vpc->max_intermediate_width, vpc->max_intermediate_height,
	       vpc->max_scan_length, vpc->intermediate_color_channels));
	Dealloc(vpc, vpc->int_image.gray_intim);
    }

    /* allocate new buffers */
    Debug((vpc, VPDEBUG_RBUF, "Allocating RenderBuffer(%d,%d,%d,%d)\n",
	   max_width, max_height, max_scan, vpc->color_channels));
    vpc->max_intermediate_width = max_width;
    vpc->max_intermediate_height = max_height;
    vpc->max_scan_length = max_scan;
    vpc->intermediate_color_channels = vpc->color_channels;
    if (max_width > 0) {
	if (vpc->color_channels == 1) {
	    Alloc(vpc, vpc->int_image.gray_intim, GrayIntPixel *,
		  max_width * max_height * sizeof(GrayIntPixel), "int_image");
	} else {
	    Alloc(vpc, vpc->int_image.rgb_intim, RGBIntPixel *,
		  max_width * max_height * sizeof(RGBIntPixel), "int_image");
	}
    } else {
	vpc->int_image.gray_intim = NULL;
    }
}

/*
 * VPResizeDepthCueTable
 *
 * Resize the depth cueing table.
 */

void
VPResizeDepthCueTable(vpc, entries, copy)
vpContext *vpc;
int entries;	/* new number of table entries */
int copy;	/* if true, copy old entries */
{
    float *new_dc_table;

    Debug((vpc, VPDEBUG_DEPTHCUE, "resizing dctable to %d entries (%s)\n",
	   entries, copy ? "copy" : "nocopy"));
    if (entries == 0) {
	if (vpc->dc_table != NULL) {
	    Dealloc(vpc, vpc->dc_table);
	    vpc->dc_table = NULL;
	}
	vpc->dc_table_len = 0;
    } else {
	Alloc(vpc, new_dc_table, float *, entries * sizeof(float), "dc_table");
	if (vpc->dc_table != NULL) {
	    if (copy && vpc->dc_table_len > 0) {
		bcopy(vpc->dc_table, new_dc_table,
		      MIN(vpc->dc_table_len, entries) * sizeof(float));
	    }
	    Dealloc(vpc, vpc->dc_table);
	}
	vpc->dc_table = new_dc_table;
	vpc->dc_table_len = entries;
    }
}

/*
 * VPComputeDepthCueTable
 *
 * Compute entries in the depth cueing lookup table.  
 */

void
VPComputeDepthCueTable(vpc, first, last)
vpContext *vpc;
int first;	/* first entry to compute */
int last;	/* last entry to compute */
{
    int c;
    double delta_depth, front_factor, density;

    Debug((vpc, VPDEBUG_DEPTHCUE, "computing dctable entries %d to %d\n",
	   first, last));
    delta_depth = vpc->dc_quantization;
    front_factor = vpc->dc_front_factor;
    density = vpc->dc_density;
    for (c = first; c <= last; c++)
	vpc->dc_table[c] = front_factor * exp(-density*(1.0 - c*delta_depth));
}

/*
 * VPSliceDepthCueRatio
 *
 * Return the ratio of the depth cueing factor for two adjacent slices
 * for an affine view.  A constant factor is applied to all voxels in a
 * slice, and then a fixup is applied to the pixels of the intermediate
 * image.  This produces the correct answer without having to compute
 * the depth of each voxel.
 */

float
VPSliceDepthCueRatio(vpc)
vpContext *vpc;
{
    float delta_depth;		/* change in depth between adjacent slices */
    float slice_dc_ratio;	/* return value */

    if (!vpc->dc_enable)
	return(1.);
    delta_depth = vpc->depth_dk - vpc->depth_di*vpc->shear_i -
		  vpc->depth_dj*vpc->shear_j;
    if (vpc->reverse_slice_order)
	delta_depth = -delta_depth;
    /* slice_dc_ratio = exp(-vpc->dc_density * (-delta_depth)) */
    slice_dc_ratio = exp(vpc->dc_density * delta_depth);
    Debug((vpc, VPDEBUG_DEPTHCUE, "slice_dc_ratio = %f\n", slice_dc_ratio));
    return(slice_dc_ratio);
}

/*
 * VPDepthCueIntImage
 *
 * Perform depth cueing on the intermediate image.
 */

void
VPDepthCueIntImage(vpc, slicenum)
vpContext *vpc;
int slicenum;	/* slice number corresponding to location of int. image */
{
    float pixel_depth_quant;	/* depth of current pixel in image
				   (multiplied by depth_quant) */
    int pixel_depth_int;	/* pixel_depth truncated to an integer */
    float left_depth;		/* depth of pixel on left edge of current
				   scanline in image */
    float left_depth_quant;	/* left_depth * depth_quant */
    float *dc_table;		/* depth cueing table */
    float depth_di, depth_dj;	/* change in depth for a unit change in each
				   rotated object space coordinate */
    float depth_quant;		/* number of quantization levels for depth */
    float depth_di_quant;	/* depth_di * depth_quant */
    float depth_dj_quant;	/* depth_dj * depth_quant */
    float max_depth;		/* maximum (closest) depth in image */
    int max_depth_int;		/* maximum quantized depth */
    int i, j;			/* intermediate image coordinates */
    float slice_u, slice_v;	/* sheared object space coordinates */
    GrayIntPixel *gray_intim;	/* image data (grayscale) */
    RGBIntPixel *rgb_intim;	/* image data (RGB) */
    int width, height;		/* size of intermediate image */
    int c;
#ifdef DEBUG
    float pix_depth;
#endif

    Debug((vpc, VPDEBUG_DEPTHCUE, "depth cueing intermediate image\n"));

    /* check the size of the depth cueing table and enlarge if necessary */
    width = vpc->intermediate_width;
    height = vpc->intermediate_height;
    depth_quant = 1.0 / vpc->dc_quantization;
    depth_di = vpc->depth_di;
    depth_dj = vpc->depth_dj;
    slice_u = vpc->shear_i * slicenum + vpc->trans_i;
    slice_v = vpc->shear_j * slicenum + vpc->trans_j;
    left_depth = vpc->depth_000 + vpc->depth_dk*slicenum -
		 slice_u*depth_di - slice_v*depth_dj;
    if (depth_di > 0) {
	if (depth_dj > 0) {
	    max_depth = left_depth + depth_di*width + depth_dj*height;
	} else {
	    max_depth = left_depth + depth_di*width;
	}
    } else {
	if (depth_dj > 0) {
	    max_depth = left_depth + depth_dj*height;
	} else {
	    max_depth = left_depth;
	}
    }
    max_depth_int = max_depth * depth_quant;
    if (max_depth_int >= vpc->dc_table_len) {
	c = vpc->dc_table_len;
	VPResizeDepthCueTable(vpc, max_depth_int+1, 1);
	VPComputeDepthCueTable(vpc, c, vpc->dc_table_len-1);
    }
    dc_table = vpc->dc_table;
    depth_di_quant = depth_di * depth_quant;
    depth_dj_quant = depth_dj * depth_quant;
    left_depth_quant = left_depth * depth_quant;

#ifdef DEBUG
    Debug((vpc, VPDEBUG_DEPTHCUE, "depth cueing at image corners:\n"));
    pix_depth = left_depth + 0*depth_di + 0*depth_dj;
    pixel_depth_int = (int)(pix_depth * depth_quant);
    if (pixel_depth_int < 0) pixel_depth_int = 0;
    Debug((vpc, VPDEBUG_DEPTHCUE,
	   "  %3d %3d: depth = %10.6f, factor = %10.6f, table[%d] = %10.6f\n",
	   0, 0, pix_depth,
	   vpc->dc_front_factor * exp(-vpc->dc_density * (1.0 - pix_depth)),
	   pixel_depth_int, dc_table[pixel_depth_int]));
    pix_depth = left_depth + width*depth_di + 0*depth_dj;
    pixel_depth_int = (int)(pix_depth * depth_quant);
    if (pixel_depth_int < 0) pixel_depth_int = 0;
    Debug((vpc, VPDEBUG_DEPTHCUE,
	   "  %3d %3d: depth = %10.6f, factor = %10.6f, table[%d] = %10.6f\n",
	   width, 0, pix_depth,
	   vpc->dc_front_factor * exp(-vpc->dc_density * (1.0 - pix_depth)),
	   pixel_depth_int, dc_table[pixel_depth_int]));
    pix_depth = left_depth + width*depth_di + height*depth_dj;
    pixel_depth_int = (int)(pix_depth * depth_quant);
    if (pixel_depth_int < 0) pixel_depth_int = 0;
    Debug((vpc, VPDEBUG_DEPTHCUE,
	   "  %3d %3d: depth = %10.6f, factor = %10.6f, table[%d] = %10.6f\n",
	   width, height, pix_depth,
	   vpc->dc_front_factor * exp(-vpc->dc_density * (1.0 - pix_depth)),
	   pixel_depth_int, dc_table[pixel_depth_int]));
    pix_depth = left_depth + 0*depth_di + height*depth_dj;
    pixel_depth_int = (int)(pix_depth * depth_quant);
    if (pixel_depth_int < 0) pixel_depth_int = 0;
    Debug((vpc, VPDEBUG_DEPTHCUE,
	   "  %3d %3d: depth = %10.6f, factor = %10.6f, table[%d] = %10.6f\n",
	   0, height, pix_depth,
	   vpc->dc_front_factor * exp(-vpc->dc_density * (1.0 - pix_depth)),
	   pixel_depth_int, dc_table[pixel_depth_int]));
#endif /* DEBUG */

    /* foreach pixel, compute depth and scale color by dc factor */
    if (vpc->color_channels == 1) {
	gray_intim = vpc->int_image.gray_intim;
	for (j = height; j > 0; j--) {
	    pixel_depth_quant = left_depth_quant;
	    left_depth_quant += depth_dj_quant;
	    for (i = width; i > 0; i--) {
		pixel_depth_int = pixel_depth_quant;
		pixel_depth_quant += depth_di_quant;
		if (pixel_depth_int < 0)
		    pixel_depth_int = 0;
		if (pixel_depth_int >= vpc->dc_table_len) {
		    VPBug("VPDepthCueIntImage: depth too large (%d >= %d)",
			  pixel_depth_int, vpc->dc_table_len);
		}
		gray_intim->clrflt *= dc_table[pixel_depth_int];
		gray_intim++;
	    } /* for i */
	} /* for j */
    } else {
	rgb_intim = vpc->int_image.rgb_intim;
	for (j = height; j > 0; j--) {
	    pixel_depth_quant = left_depth_quant;
	    left_depth_quant += depth_dj_quant;
	    for (i = width; i > 0; i--) {
		pixel_depth_int = pixel_depth_quant;
		pixel_depth_quant += depth_di_quant;
		if (pixel_depth_int < 0)
		    pixel_depth_int = 0;
		if (pixel_depth_int >= vpc->dc_table_len) {
		    VPBug("VPDepthCueIntImage: depth too large (%d >= %d)",
			  pixel_depth_int, vpc->dc_table_len);
		}
		rgb_intim->rclrflt *= dc_table[pixel_depth_int];
		rgb_intim->gclrflt *= dc_table[pixel_depth_int];
		rgb_intim->bclrflt *= dc_table[pixel_depth_int];
		rgb_intim++;
	    } /* for i */
	} /* for j */
    }
}

/*
 * ComputeLightViewTransform
 *
 * Compute the view transformation from the point of view of the one
 * light source that produces shadows.
 */

static void
ComputeLightViewTransform(vpc, vm)
vpContext *vpc;
vpMatrix4 vm;	/* storage for result */
{
    vpMatrix4 prematrix;	/* transform volume to unit cube */
    vpMatrix4 viewportm;		/* viewport matrix */
    vpVector3 vpn;		/* view plane normal */
    vpVector3 vup;		/* view up vector */
    vpVector3 tmp1v, tmp2v;	/* temporary vectors */
    vpMatrix4 view;		/* transform world coordinates to
				   eye coordinates, with view
				   direction equal to light vector */
    vpMatrix4 tmp1m, tmp2m;	/* temporary matrices */
    double lx, ly, lz;		/* components of light vector */
    int light_index;
    int maxdim;
    double scale;

    /* check for errors */
    ASSERT(vpc->shadow_light_num >= VP_LIGHT0 &&
	   vpc->shadow_light_num <= VP_LIGHT5);
    ASSERT(vpc->light_enable[vpc->shadow_light_num - VP_LIGHT0]);

    /* compute prematrix */
    vpIdentity4(prematrix);
    maxdim = vpc->xlen;
    if (vpc->ylen > maxdim)
	maxdim = vpc->ylen;
    if (vpc->zlen > maxdim)
	maxdim = vpc->zlen;
    scale = 1. / (double)maxdim;
    prematrix[0][0] = scale;
    prematrix[1][1] = scale;
    prematrix[2][2] = scale;
    prematrix[0][3] = -vpc->xlen * scale * 0.5;
    prematrix[1][3] = -vpc->ylen * scale * 0.5;
    prematrix[2][3] = -vpc->zlen * scale * 0.5;

    /* compute the world-to-eye coordinate transformation */
    light_index = vpc->shadow_light_num - VP_LIGHT0;
    lx = vpc->light_vector[light_index][0];
    ly = vpc->light_vector[light_index][1];
    lz = vpc->light_vector[light_index][2];
    vpSetVector3(vpn, lx, ly, lz);
    if (fabs(lx) < fabs(ly)) {
	if (fabs(lx) < fabs(lz)) {
	    vpSetVector3(vup, 1.0, 0.0, 0.0);
	} else {
	    vpSetVector3(vup, 0.0, 0.0, 1.0);
	}
    } else {
	if (fabs(ly) < fabs(lz)) {
	    vpSetVector3(vup, 0.0, 1.0, 0.0);
	} else {
	    vpSetVector3(vup, 0.0, 0.0, 1.0);
	}
    }
    vpCrossProduct(tmp1v, vup, vpn);
    vpCrossProduct(tmp2v, vpn, tmp1v);

    vpIdentity4(view);
    view[0][0] = tmp1v[0];
    view[0][1] = tmp1v[1];
    view[0][2] = tmp1v[2];
    view[1][0] = tmp2v[0];
    view[1][1] = tmp2v[1];
    view[1][2] = tmp2v[2];
    view[2][0] = vpn[0];
    view[2][1] = vpn[1];
    view[2][2] = vpn[2];

    /* initialize matrix to switch to left-handed coords. */
    vpIdentity4(viewportm);
    viewportm[2][2] = -1.0;

    /* compute the view matrix */
    vpMatrixMult4(tmp1m, vpc->transforms[VP_MODEL], prematrix);
    vpMatrixMult4(tmp2m, view, tmp1m);
    vpMatrixMult4(vm, viewportm, tmp2m);
}

/*
 * FactorLightView
 *
 * Factor an affine viewing matrix that specifies the view seen by
 * a light source.  Most of the parameters of the factorization are
 * taken from the factorization of the normal viewing matrix.
 */

static int
FactorLightView(vpc, vm)
vpContext *vpc;
vpMatrix4 vm;
{
    vpMatrix4 p;		/* permutation matrix */
    vpMatrix4 pvm;		/* permutation of the viewing matrix */
    int icount, jcount, kcount;	/* dimensions of volume in rotated space */
    double denom;

    Debug((vpc, VPDEBUG_SHADOW, "FactorLightView\n"));

    /* permute the rows of the viewing matrix according to the best viewing
       axis for the viewing direction */
    bzero(p, sizeof(vpMatrix4));
    switch (vpc->best_view_axis) {
    case VP_X_AXIS:
	p[0][2] = 1.;
	p[1][0] = 1.;
	p[2][1] = 1.;
	p[3][3] = 1.;
	icount = vpc->ylen;
	jcount = vpc->zlen;
	kcount = vpc->xlen;
	break;
    case VP_Y_AXIS:
	p[0][1] = 1.;
	p[1][2] = 1.;
	p[2][0] = 1.;
	p[3][3] = 1.;
	icount = vpc->zlen;
	jcount = vpc->xlen;
	kcount = vpc->ylen;
	break;
    case VP_Z_AXIS:
	p[0][0] = 1.;
	p[1][1] = 1.;
	p[2][2] = 1.;
	p[3][3] = 1.;
	icount = vpc->xlen;
	jcount = vpc->ylen;
	kcount = vpc->zlen;
	break;
    }
    vpMatrixMult4(pvm, vm, p);

    /* compute the shear coefficients */
    denom = pvm[0][0]*pvm[1][1] - pvm[0][1]*pvm[1][0];
    if (fabs(denom) < VP_EPS)
	return(VPSetError(vpc, VPERROR_SINGULAR));
    vpc->shadow_shear_i = (pvm[0][2]*pvm[1][1] - pvm[0][1]*pvm[1][2]) / denom;
    vpc->shadow_shear_j = (pvm[0][0]*pvm[1][2] - pvm[1][0]*pvm[0][2]) / denom;

    /* check that light direction is compatible with compositing direction */
    if (pvm[2][0]*vpc->shadow_shear_i + pvm[2][1]*vpc->shadow_shear_j -
	pvm[2][2] > 0) {
	if (vpc->reverse_slice_order != 0)
	    return(VPERROR_BAD_SHADOW);
    } else {
	if (vpc->reverse_slice_order != 1)
	    return(VPERROR_BAD_SHADOW);
    }

    /* compute the shadow buffer image size */
    vpc->shadow_width = (int)ceil((kcount-1)*fabs(vpc->shadow_shear_i)) +
	icount + 1;
    vpc->shadow_height = (int)ceil((kcount-1)*fabs(vpc->shadow_shear_j)) +
	jcount + 1;

    /* compute the translation coefficients */
    if (vpc->shadow_shear_i >= 0.)
	vpc->shadow_trans_i = 1.;
    else
	vpc->shadow_trans_i = 1. - vpc->shadow_shear_i * (kcount - 1);
    if (vpc->shadow_shear_j >= 0.)
	vpc->shadow_trans_j = 1.;
    else
	vpc->shadow_trans_j = 1. - vpc->shadow_shear_j * (kcount - 1);

    Debug((vpc, VPDEBUG_SHADOW, "  shadow shear factors: %g %g\n",
	   vpc->shadow_shear_i, vpc->shadow_shear_j));
    Debug((vpc, VPDEBUG_SHADOW, "  shadow translation: %g %g\n",
	   vpc->shadow_trans_i, vpc->shadow_trans_j));

    return(VP_OK);
}

/*
 * CheckShadowBuffer
 *
 * Resize the shadow buffer, if necessary.
 */

static void
CheckShadowBuffer(vpc)
vpContext *vpc;
{
    int new_max_width, new_max_height;
    int resize = 0;

    /* determine if resizing is necessary */
    if (vpc->shadow_width > vpc->max_shadow_width) {
	new_max_width = MAX(vpc->shadow_width, vpc->shadow_width_hint);
	resize = 1;
    } else {
	new_max_width = MAX(vpc->max_shadow_width, vpc->shadow_width_hint);
    }
    if (vpc->shadow_height > vpc->max_shadow_height) {
	new_max_height = MAX(vpc->shadow_height, vpc->shadow_height_hint);
	resize = 1;
    } else {
	new_max_height = MAX(vpc->max_shadow_height, vpc->shadow_height_hint);
    }

    /* resize */
    if (resize)
	VPResizeShadowBuffer(vpc, new_max_width, new_max_height);
}

/*
 * VPResizeShadowBuffer
 *
 * Resize the shadow buffer.
 */

void
VPResizeShadowBuffer(vpc, max_width, max_height)
vpContext *vpc;
int max_width;	/* new width of the intermediate image */
int max_height;	/* new height of the intermediate image */
{
    /* free old buffers */
    if (vpc->shadow_buffer != NULL) {
	Dealloc(vpc, vpc->shadow_buffer);
    }

    /* allocate new buffers */
    vpc->max_shadow_width = max_width;
    vpc->max_shadow_height = max_height;
    if (max_width > 0) {
	Alloc(vpc, vpc->shadow_buffer, GrayIntPixel *,
	      max_width * max_height * sizeof(GrayIntPixel), "shadow_buffer");
    } else {
	vpc->shadow_buffer = NULL;
    }
}
