/*
 * vp_file.c
 *
 * Routines for loading and storing volume data in disk files.
 *
 * 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:03 $
 * $Revision: 1.4 $
 */

#include "vp_global.h"

static int StoreRLEVoxels ANSI_ARGS((vpContext *vpc, int fd,
    RLEVoxels *rle_voxels));
static int LoadRLEVoxels ANSI_ARGS((vpContext *vpc, int fd,
    RLEVoxels *rle_voxels, int offsets, int swab));
static void SwapWords ANSI_ARGS((void *data, unsigned size));
static void SwapVoxels ANSI_ARGS((vpContext *vpc, void *voxels,
    int num_voxels, int fields, int bytes_per_voxel));
#ifdef DEBUG
void VPCheckScanOffsets ANSI_ARGS((RLEVoxels *rle_voxels,
    int rle_bytes_per_voxel));
#endif
static void SwapOctreeNode ANSI_ARGS((vpContext *vpc, int level, void *node));
static int StoreTable ANSI_ARGS((vpContext *vpc, int fd, float *ptr,
    unsigned size));
static int LoadTable ANSI_ARGS((vpContext *vpc, int fd, float **ptr_ptr,
    unsigned *size_ptr));

/*******************************************************************
 * Classified Volume Files.                                        *
 *******************************************************************/

/* file header structure */
typedef struct {
    unsigned magic;		/* magic number for identification */
    unsigned xlen;		/* voxels in each dimension */
    unsigned ylen;
    unsigned zlen;
    unsigned bytes_per_voxel;	/* size of a classified voxel */
    unsigned num_shade_fields;	/* number of fields in a classified voxel
				   (not including opacity) */
    unsigned num_x_runs;	/* number of run lengths for X view */
    unsigned num_x_voxels;	/* number of nonzero voxels for X view */
    unsigned num_x_offsets;	/* number of offsets per slice for X view */
    unsigned num_y_runs;	/* number of run lengths for Y view */
    unsigned num_y_voxels;	/* number of nonzero voxels for Y view */
    unsigned num_y_offsets;	/* number of offsets per slice for Y view */
    unsigned num_z_runs;	/* number of run lengths for Z view */
    unsigned num_z_voxels;	/* number of nonzero voxels for Z view */
    unsigned num_z_offsets;	/* number of offsets per slice for Z view */
    float min_opacity;		/* low opacity threshold */
} RLEVoxelHdr;

/*
 * File layout:
 *   RLEVoxelHdr hdr;
 *   unsigned field_size[hdr.num_shade_fields];	   (size of each voxel field)
 *   unsigned field_offset[hdr.num_shade_fields];  (offset for each field)
 *   unsigned field_max[hdr.num_shade_fields];     (max. value of each field)
 *   padding to align to double word
 *   unsigned char x_run_lengths[hdr.num_x_runs];  (run lengths for X view)
 *   padding to align to double word
 *   char x_data[hdr.num_x_voxels*hdr.bytes_per_voxel]; (voxel data for X view)
 *   padding to align to double word
 *   ScanOffset x_offsets[hdr.num_x_offsets];	   (scanline offset for X view)
 *   padding to align to double word
 *   unsigned char y_run_lengths[hdr.num_y_runs];  (run lengths for Y view)
 *   padding to align to double word
 *   char y_data[hdr.num_y_voxels*hdr.bytes_per_voxel]; (voxel data for Y view)
 *   padding to align to double word
 *   ScanOffset y_offsets[hdr.num_y_offsets];	   (scanline offset for Y view)
 *   padding to align to double word
 *   unsigned char z_run_lengths[hdr.num_z_runs];  (run lengths for Z view)
 *   padding to align to double word
 *   char z_data[hdr.num_z_voxels*hdr.bytes_per_voxel]; (voxel data for Z view)
 *   padding to align to double word
 *   ScanOffset z_offsets[hdr.num_z_offsets];	   (scanline offset for Z view)
 *
 * The padding ensures that voxel data can be mapped into memory
 * without any word alignment problems.
 */

/*
 * vpStoreClassifiedVolume
 *
 * Store a run-length encoded, classified volume to a file.
 */

vpResult
vpStoreClassifiedVolume(vpc, fd)
vpContext *vpc;	/* context containing the volume */
int fd;		/* UNIX file descriptor open for writing */
{
    RLEVoxelHdr header;
    unsigned field_data[3*VP_MAX_FIELDS];
    int nsf, c;
    unsigned size;
    char pad_data[8];
    int pad_bytes;
    int retcode;

    /* check for errors */
    if ((retcode = VPCheckVoxelFields(vpc)) != VP_OK)
	return(retcode);

    /* write header */
    header.magic = VP_CVFILE_MAGIC;
    header.xlen = vpc->xlen;
    header.ylen = vpc->ylen;
    header.zlen = vpc->zlen;
    header.bytes_per_voxel = vpc->rle_bytes_per_voxel;
    header.num_shade_fields = vpc->num_shade_fields;
    if (vpc->rle_x == NULL) {
	header.num_x_runs = 0;
	header.num_x_voxels = 0;
	header.num_x_offsets = 0;
    } else {
	if ((retcode = VPCheckClassifiedVolume(vpc, VP_X_AXIS)) != VP_OK)
	    return(retcode);
	header.num_x_runs = vpc->rle_x->run_count;
	header.num_x_voxels = vpc->rle_x->data_count;
	header.num_x_offsets = vpc->rle_x->scan_offsets_per_slice;
    }
    if (vpc->rle_y == NULL) {
	header.num_y_runs = 0;
	header.num_y_voxels = 0;
	header.num_y_offsets = 0;
    } else {
	if ((retcode = VPCheckClassifiedVolume(vpc, VP_Y_AXIS)) != VP_OK)
	    return(retcode);
	header.num_y_runs = vpc->rle_y->run_count;
	header.num_y_voxels = vpc->rle_y->data_count;
	header.num_y_offsets = vpc->rle_y->scan_offsets_per_slice;
    }
    if (vpc->rle_z == NULL) {
	header.num_z_runs = 0;
	header.num_z_voxels = 0;
	header.num_z_offsets = 0;
    } else {
	if ((retcode = VPCheckClassifiedVolume(vpc, VP_Z_AXIS)) != VP_OK)
	    return(retcode);
	header.num_z_runs = vpc->rle_z->run_count;
	header.num_z_voxels = vpc->rle_z->data_count;
	header.num_z_offsets = vpc->rle_z->scan_offsets_per_slice;
    }
    header.min_opacity = vpc->min_opacity;
    if (vpc->write_func(fd, &header, sizeof(header)) != sizeof(header))
	return(VPSetError(vpc, VPERROR_IO));

    /* write voxel layout information */
    nsf = vpc->num_shade_fields;
    for (c = 0; c < nsf; c++) {
	field_data[c] = vpc->field_size[c];
	field_data[nsf + c] = vpc->field_offset[c];
	field_data[2*nsf + c] = vpc->field_max[c];
    }
    size = 3*nsf*sizeof(unsigned);
    if (vpc->write_func(fd, field_data, size) != size)
	return(VPSetError(vpc, VPERROR_IO));

    /* padding after header */
    pad_bytes = (8 - ((sizeof(header) + size) % 8)) & 0x7;
    if (pad_bytes > 0) {
	bzero(pad_data, pad_bytes);
	if (vpc->write_func(fd, pad_data, pad_bytes) != pad_bytes)
	    return(VPSetError(vpc, VPERROR_IO));
    }

    /* write data */
    if (vpc->rle_x != NULL) {
	if ((c = StoreRLEVoxels(vpc, fd, vpc->rle_x)) != VP_OK)
	    return(c);
    }
    if (vpc->rle_y != NULL) {
	if ((c = StoreRLEVoxels(vpc, fd, vpc->rle_y)) != VP_OK)
	    return(c);
    }
    if (vpc->rle_z != NULL) {
	if ((c = StoreRLEVoxels(vpc, fd, vpc->rle_z)) != VP_OK)
	    return(c);
    }

    return(VP_OK);
}

/*
 * StoreRLEVoxels
 *
 * Write an RLEVoxels structure to a file.
 */

static int
StoreRLEVoxels(vpc, fd, rle_voxels)
vpContext *vpc;
int fd;
RLEVoxels *rle_voxels;
{
    int size;
    char pad_data[8];
    int pad_bytes;

    bzero(pad_data, sizeof(pad_data));
    if (rle_voxels->run_count > 0) {
	size = rle_voxels->run_count;
	if (vpc->write_func(fd, rle_voxels->run_lengths, size) != size)
	    return(VPSetError(vpc, VPERROR_IO));

	pad_bytes = (8 - (size % 8)) & 0x7;
	if (pad_bytes > 0) {
	    if (vpc->write_func(fd, pad_data, pad_bytes) != pad_bytes)
		return(VPSetError(vpc, VPERROR_IO));
	}
    }
    if (rle_voxels->data_count > 0) {
	size = rle_voxels->data_count * vpc->rle_bytes_per_voxel;
	if (vpc->write_func(fd, rle_voxels->data, size) != size)
	    return(VPSetError(vpc, VPERROR_IO));

	pad_bytes = (8 - (size % 8)) & 0x7;
	if (pad_bytes > 0) {
	    if (vpc->write_func(fd, pad_data, pad_bytes) != pad_bytes)
		return(VPSetError(vpc, VPERROR_IO));
	}
    }
    if (rle_voxels->scan_offsets_per_slice > 0) {
	size = rle_voxels->scan_offsets_per_slice * rle_voxels->klen *
	    sizeof(ScanOffset);
	if (vpc->write_func(fd, rle_voxels->scan_offsets, size) != size)
	    return(VPSetError(vpc, VPERROR_IO));

	pad_bytes = (8 - (size % 8)) & 0x7;
	if (pad_bytes > 0) {
	    if (vpc->write_func(fd, pad_data, pad_bytes) != pad_bytes)
		return(VPSetError(vpc, VPERROR_IO));
	}
    }
    return(VP_OK);
}

/*
 * vpLoadClassifiedVolume
 *
 * Load a run-length encoded, classified volume from a file.
 */

vpResult
vpLoadClassifiedVolume(vpc, fd)
vpContext *vpc;	/* context to store the volume into */
int fd;		/* UNIX file descriptor open for reading */
{
    RLEVoxelHdr header;
    unsigned field_data[3*VP_MAX_FIELDS];
    int nsf, c, swab;
    unsigned size;
    unsigned char *data;
    char pad_data[8];
    int pad_bytes;
    unsigned x_run_offset;
    unsigned x_data_offset;
    unsigned x_offset_offset;
    unsigned y_run_offset;
    unsigned y_data_offset;
    unsigned y_offset_offset;
    unsigned z_run_offset;
    unsigned z_data_offset;
    unsigned z_offset_offset;
    int current_offset;
    int destroy_old_volume;

    /* read header */
    if (vpc->read_func(fd, &header, sizeof(header)) != sizeof(header))
	return(VPSetError(vpc, VPERROR_IO));
    swab = 0;
    if (header.magic != VP_CVFILE_MAGIC) {
	SwapWords(&header, sizeof(header));
	if (header.magic != VP_CVFILE_MAGIC)
	    return(VPSetError(vpc, VPERROR_BAD_FILE));
	swab = 1;
    }

    /* read voxel layout information */
    size = 3 * header.num_shade_fields * sizeof(unsigned);
    if (vpc->read_func(fd, field_data, size) != size)
	return(VPSetError(vpc, VPERROR_IO));
    if (swab)
	SwapWords(field_data, size);

    /* padding after header */
    pad_bytes = (8 - ((sizeof(header) + size) % 8)) & 0x7;
    if (pad_bytes > 0) {
	if (vpc->read_func(fd, pad_data, pad_bytes) != pad_bytes)
	    return(VPSetError(vpc, VPERROR_IO));
    }

    /* check for consistency with old volume data */
    destroy_old_volume = 0;
    if (vpc->xlen != header.xlen || vpc->ylen != header.ylen ||
	vpc->zlen != header.zlen ||
	vpc->raw_bytes_per_voxel < header.bytes_per_voxel ||
	vpc->num_voxel_fields < header.num_shade_fields)
	destroy_old_volume = 1;
    nsf = header.num_shade_fields;
    for (c = 0; c < nsf; c++) {
	if (vpc->field_size[c] != field_data[c] ||
	    vpc->field_offset[c] != field_data[nsf + c] ||
	    vpc->field_max[c] != field_data[2*nsf + c])
	    destroy_old_volume = 1;
    }
    if (destroy_old_volume) {
	vpDestroyClassifiedVolume(vpc);
	vpDestroyMinMaxOctree(vpc);
	vpc->raw_voxels = NULL;
	vpc->raw_voxels_size = 0;
	vpc->xstride = 0;
	vpc->ystride = 0;
	vpc->zstride = 0;
    }

    /* load new volume size */
    if (destroy_old_volume) {
	vpc->xlen = header.xlen;
	vpc->ylen = header.ylen;
	vpc->zlen = header.zlen;
	vpc->raw_bytes_per_voxel = header.bytes_per_voxel;
	nsf = header.num_shade_fields;
	vpc->num_voxel_fields = nsf;
	for (c = 0; c < nsf; c++) {
	    vpc->field_size[c] = field_data[c];
	    vpc->field_offset[c] = field_data[nsf + c];
	    vpc->field_max[c] = field_data[2*nsf + c];
	}
    }
    vpc->num_shade_fields = nsf;
    vpc->min_opacity = header.min_opacity;
    vpc->rle_bytes_per_voxel = header.bytes_per_voxel;

    /* load new volume data */
    if (vpc->mmap_func != NULL && !swab) {
	/* compute file offsets */
	current_offset = sizeof(header) + size;
	current_offset += (8 - (current_offset % 8)) & 0x7;
	x_run_offset = current_offset;
	current_offset += header.num_x_runs;
	current_offset += (8 - (current_offset % 8)) & 0x7;
	x_data_offset = current_offset;
	current_offset += header.num_x_voxels * header.bytes_per_voxel;
	current_offset += (8 - (current_offset % 8)) & 0x7;
	x_offset_offset = current_offset;
	current_offset += header.num_x_offsets * sizeof(ScanOffset);
	current_offset += (8 - (current_offset % 8)) & 0x7;
	y_run_offset = current_offset;
	current_offset += header.num_y_runs;
	current_offset += (8 - (current_offset % 8)) & 0x7;
	y_data_offset = current_offset;
	current_offset += header.num_y_voxels * header.bytes_per_voxel;
	current_offset += (8 - (current_offset % 8)) & 0x7;
	y_offset_offset = current_offset;
	current_offset += header.num_y_offsets * sizeof(ScanOffset);
	current_offset += (8 - (current_offset % 8)) & 0x7;
	z_run_offset = current_offset;
	current_offset += header.num_z_runs;
	current_offset += (8 - (current_offset % 8)) & 0x7;
	z_data_offset = current_offset;
	current_offset += header.num_z_voxels * header.bytes_per_voxel;
	current_offset += (8 - (current_offset % 8)) & 0x7;
	z_offset_offset = current_offset;
	current_offset += header.num_z_offsets * sizeof(ScanOffset);

	/* memory-map the data */
	if ((data = (unsigned char*)vpc->mmap_func(fd, current_offset,
				   vpc->client_data)) == NULL)
	    return(VPSetError(vpc, VPERROR_IO));

	/* assign pointers to x view data */
	vpc->rle_x = VPCreateRLEVoxels(vpc, header.ylen, header.zlen,
				       header.xlen, 0, 0, 0);
	vpc->rle_x->run_count = header.num_x_runs;
	if (header.num_x_runs > 0)
	    vpc->rle_x->run_lengths = (unsigned char *)(data + x_run_offset);
	vpc->rle_x->data_count = header.num_x_voxels;
	if (header.num_x_voxels > 0)
	    vpc->rle_x->data = (void *)(data + x_data_offset);
	vpc->rle_x->scan_offsets_per_slice = header.num_x_offsets;
	if (header.num_x_offsets > 0)
	    vpc->rle_x->scan_offsets = (ScanOffset *)(data + x_offset_offset);
	vpc->rle_x->mmapped = 1;

	/* assign pointers to y view data */
	vpc->rle_y = VPCreateRLEVoxels(vpc, header.zlen, header.xlen,
				       header.ylen, 0, 0, 0);
	vpc->rle_y->run_count = header.num_y_runs;
	if (header.num_y_runs > 0)
	    vpc->rle_y->run_lengths = (unsigned char *)(data + y_run_offset);
	vpc->rle_y->data_count = header.num_y_voxels;
	if (header.num_y_voxels > 0)
	    vpc->rle_y->data = (void *)(data + y_data_offset);
	vpc->rle_y->scan_offsets_per_slice = header.num_y_offsets;
	if (header.num_y_offsets > 0)
	    vpc->rle_y->scan_offsets = (ScanOffset *)(data + y_offset_offset);
	vpc->rle_y->mmapped = 1;

	/* assign pointers to z view data */
	vpc->rle_z = VPCreateRLEVoxels(vpc, header.xlen, header.ylen,
				       header.zlen, 0, 0, 0);
	vpc->rle_z->run_count = header.num_z_runs;
	if (header.num_z_runs > 0)
	    vpc->rle_z->run_lengths = (unsigned char *)(data + z_run_offset);
	vpc->rle_z->data_count = header.num_z_voxels;
	if (header.num_z_voxels > 0)
	    vpc->rle_z->data = (void *)(data + z_data_offset);
	vpc->rle_z->scan_offsets_per_slice = header.num_z_offsets;
	if (header.num_z_offsets > 0)
	    vpc->rle_z->scan_offsets = (ScanOffset *)(data + z_offset_offset);
	vpc->rle_z->mmapped = 1;
    } else {
	/* read the x view data into memory */
	if (header.num_x_runs != 0) {
	    vpc->rle_x = VPCreateRLEVoxels(vpc, header.ylen, header.zlen,
		header.xlen, header.num_x_voxels, header.num_x_runs,
		header.bytes_per_voxel);
	    if ((c = LoadRLEVoxels(vpc, fd, vpc->rle_x, header.num_x_offsets,
				   swab)) != VP_OK)
		return(c);
	}

	/* read the y view data into memory */
	if (header.num_y_runs != 0) {
	    vpc->rle_y = VPCreateRLEVoxels(vpc, header.zlen, header.xlen,
		header.ylen, header.num_y_voxels, header.num_y_runs,
		header.bytes_per_voxel);
	    if ((c = LoadRLEVoxels(vpc, fd, vpc->rle_y, header.num_y_offsets,
				   swab)) != VP_OK)
		return(c);
	}

	/* read the z view data into memory */
	if (header.num_z_runs != 0) {
	    vpc->rle_z = VPCreateRLEVoxels(vpc, header.xlen, header.ylen,
		header.zlen, header.num_z_voxels, header.num_z_runs,
		header.bytes_per_voxel);
	    if ((c = LoadRLEVoxels(vpc, fd, vpc->rle_z, header.num_z_offsets,
				   swab)) != VP_OK)
		return(c);
	}
    }
#ifdef DEBUG
    if (vpc->rle_x != NULL) {
	printf("Checking X scanline offsets....\n");
	VPCheckScanOffsets(vpc->rle_x, vpc->rle_bytes_per_voxel);
    }
    if (vpc->rle_y != NULL) {
	printf("Checking Y scanline offsets....\n");
	VPCheckScanOffsets(vpc->rle_y, vpc->rle_bytes_per_voxel);
    }
    if (vpc->rle_z != NULL) {
	printf("Checking Z scanline offsets....\n");
	VPCheckScanOffsets(vpc->rle_z, vpc->rle_bytes_per_voxel);
    }
#endif
    return(VP_OK);
}

/*
 * LoadRLEVoxels
 *
 * Load an RLEVoxels structure from a file.
 */

static int
LoadRLEVoxels(vpc, fd, rle_voxels, offsets, swab)
vpContext *vpc;
int fd;
RLEVoxels *rle_voxels;
int offsets;
int swab;
{
    int size;
    char pad_data[8];
    int pad_bytes;

    if (rle_voxels->run_count > 0) {
	size = rle_voxels->run_count;
	if (vpc->read_func(fd, rle_voxels->run_lengths, size) != size)
	    return(VPSetError(vpc, VPERROR_IO));

	pad_bytes = (8 - (size % 8)) & 0x7;
	if (pad_bytes > 0) {
	    if (vpc->read_func(fd, pad_data, pad_bytes) != pad_bytes)
		return(VPSetError(vpc, VPERROR_IO));
	}
    }
    if (rle_voxels->data_count > 0) {
	size = rle_voxels->data_count * vpc->rle_bytes_per_voxel;
	if (vpc->read_func(fd, rle_voxels->data, size) != size)
	    return(VPSetError(vpc, VPERROR_IO));
	if (swab)
	    SwapVoxels(vpc, rle_voxels->data, rle_voxels->data_count,
		       vpc->num_shade_fields, vpc->rle_bytes_per_voxel);

	pad_bytes = (8 - (size % 8)) & 0x7;
	if (pad_bytes > 0) {
	    if (vpc->read_func(fd, pad_data, pad_bytes) != pad_bytes)
		return(VPSetError(vpc, VPERROR_IO));
	}
    }
    if (offsets > 0) {
	rle_voxels->scan_offsets_per_slice = offsets;
	size = rle_voxels->klen * offsets * sizeof(ScanOffset);
	Alloc(vpc, rle_voxels->scan_offsets, ScanOffset *, size,
	      "scan_offsets");
	if (vpc->read_func(fd, rle_voxels->scan_offsets, size) != size)
	    return(VPSetError(vpc, VPERROR_IO));
	if (swab)
	    SwapWords(rle_voxels->scan_offsets, size);

	pad_bytes = (8 - (size % 8)) & 0x7;
	if (pad_bytes > 0) {
	    if (vpc->read_func(fd, pad_data, pad_bytes) != pad_bytes)
		return(VPSetError(vpc, VPERROR_IO));
	}
    }
    return(VP_OK);
}

/*
 * SwapWords
 *
 * Byte-swap word data to change the endianess.
 */

static void
SwapWords(data, size)
void *data;
unsigned size;
{
    unsigned char *ptr;
    int tmp1, tmp2;

    ptr = (unsigned char*)data;
    while (size >= 4) {
	tmp1 = ptr[0]; ptr[0] = ptr[3]; ptr[3] = tmp1;
	tmp2 = ptr[1]; ptr[1] = ptr[2]; ptr[2] = tmp2;
	size -= 4;
	ptr += 4;
    }
}

/*
 * SwapVoxels
 *
 * Byte-swap voxel data to change the endianess.
 */

static void
SwapVoxels(vpc, voxels, num_voxels, fields, bytes_per_voxel)
vpContext *vpc;		/* context */
void *voxels;		/* array of voxels */
int num_voxels;		/* number of voxels in the array */
int fields;		/* number of fields in voxel */
int bytes_per_voxel;	/* size of voxel in bytes */
{
    int f, size, offset;
    unsigned char *voxel_ptr;
    int tmp1, tmp2;

    /* check if any of the fields of the voxel need swapping */
    size = 0;
    for (f = 0; f < fields; f++) {
	if (vpc->field_size[f] > size)
	    size = vpc->field_size[f];
    }
    if (size <= 1)
	return;

    /* do the swapping */
    voxel_ptr = (unsigned char*)voxels;
    while (num_voxels-- > 0) {
	for (f = 0; f < fields; f++) {
	    size = vpc->field_size[f];
	    if (size == 1)
		continue;
	    offset = vpc->field_offset[f];
	    if (size == 2) {
		tmp1 = voxel_ptr[offset];
		voxel_ptr[offset] = voxel_ptr[offset+1];
		voxel_ptr[offset+1] = tmp1;
	    } else {
		tmp1 = voxel_ptr[offset];
		voxel_ptr[offset] = voxel_ptr[offset+3];
		voxel_ptr[offset+3] = tmp1;
		tmp2 = voxel_ptr[offset+1];
		voxel_ptr[offset+1] = voxel_ptr[offset+2];
		voxel_ptr[offset+2] = tmp2;
	    }
	}
	voxel_ptr += bytes_per_voxel;
    }
}

/*******************************************************************
 * Min-Max Octree Files.                                           *
 *******************************************************************/

/* file header structure */
typedef struct {
    unsigned magic;		/* magic number for identification */
    unsigned xlen;		/* voxels in each dimension */
    unsigned ylen;
    unsigned zlen;
    int num_clsfy_params;	/* # of params for classification */
    int levels;			/* number of levels in octree */
    int root_node_size;		/* voxels/side for root level */
    int base_node_size;		/* voxels/side for base level */
    int range_bytes_per_node;	/* bytes/node for min/max data */
    int base_bytes_per_node;	/* bytes/node for base level */
    int nonbase_bytes_per_node; /* bytes/node for non-base level */
    int status_offset;		/* offset to status field */
    int child_offset;		/* offset to child field */
    unsigned octree_bytes;	/* bytes of storage for the octree */
} MinMaxOctreeHdr;

/*
 * File layout:
 *   MinMaxOctreeHdr hdr;
 *   unsigned param_size[hdr.num_clsfy_params];	(size of each parameter, bytes)
 *   unsigned param_max[hdr.num_clsfy_params];  (max. value of each parameter)
 *   unsigned node_offset[hdr.num_clsfy_params];(node offset to min/max data)
 *   char data[octree_bytes];	(octree data)
 */

/*
 * vpStoreMinMaxOctree
 *
 * Store a min-max octree to a file.
 */

vpResult
vpStoreMinMaxOctree(vpc, fd)
vpContext *vpc;	/* context containing the octree */
int fd;		/* UNIX file descriptor open for writing */
{
    MinMaxOctreeHdr header;
    unsigned field_data[3*VP_MAX_FIELDS];
    int ncp, c;
    unsigned size;

    if (vpc->mm_octree == NULL)
	return(VPSetError(vpc, VPERROR_BAD_SIZE));

    /* write header */
    bzero(&header, sizeof(MinMaxOctreeHdr));
    header.magic = VP_OCTFILE_MAGIC;
    header.xlen = vpc->xlen;
    header.ylen = vpc->ylen;
    header.zlen = vpc->zlen;
    header.num_clsfy_params = vpc->num_clsfy_params;
    header.levels = vpc->mm_octree->levels;
    header.root_node_size = vpc->mm_octree->root_node_size;
    header.base_node_size = vpc->mm_octree->base_node_size;
    header.range_bytes_per_node = vpc->mm_octree->range_bytes_per_node;
    header.base_bytes_per_node = vpc->mm_octree->base_bytes_per_node;
    header.nonbase_bytes_per_node = vpc->mm_octree->nonbase_bytes_per_node;
    header.status_offset = vpc->mm_octree->status_offset;
    header.child_offset = vpc->mm_octree->child_offset;
    header.octree_bytes = vpc->mm_octree->octree_bytes;
    if (vpc->write_func(fd, &header, sizeof(header)) != sizeof(header))
	return(VPSetError(vpc, VPERROR_IO));

    /* write parameter size/offset information */
    ncp = vpc->num_clsfy_params;
    for (c = 0; c < ncp; c++) {
	field_data[c] = vpc->field_size[vpc->param_field[c]];
	field_data[ncp + c] = vpc->field_max[vpc->param_field[c]];
	field_data[2*ncp + c] = vpc->mm_octree->node_offset[c];
    }
    size = 3*ncp*sizeof(unsigned);
    if (vpc->write_func(fd, field_data, size) != size)
	return(VPSetError(vpc, VPERROR_IO));

    /* write octree data */
    size = vpc->mm_octree->octree_bytes;
    if (vpc->write_func(fd, vpc->mm_octree->root, size) != size)
	return(VPSetError(vpc, VPERROR_IO));

    return(VP_OK);
}

/*
 * vpLoadMinMaxOctree
 *
 * Load a min-max octree from a file.
 */

vpResult
vpLoadMinMaxOctree(vpc, fd)
vpContext *vpc;	/* context to store the octree into */
int fd;		/* UNIX file descriptor open for reading */
{
    MinMaxOctreeHdr header;
    unsigned field_data[3*VP_MAX_FIELDS];
    int ncp, c, swab;
    unsigned size;

    /* read header */
    if (vpc->read_func(fd, &header, sizeof(header)) != sizeof(header))
	return(VPSetError(vpc, VPERROR_IO));
    swab = 0;
    if (header.magic != VP_OCTFILE_MAGIC) {
	SwapWords(&header, sizeof(header));
	if (header.magic != VP_OCTFILE_MAGIC)
	    return(VPSetError(vpc, VPERROR_BAD_FILE));
	swab = 1;
    }

    /* read parameter size/offset information */
    size = 3 * header.num_clsfy_params * sizeof(unsigned);
    if (vpc->read_func(fd, field_data, size) != size)
	return(VPSetError(vpc, VPERROR_IO));
    if (swab)
	SwapWords(field_data, size);

    /* check for consistency with current volume data */
    if ((c = VPCheckRawVolume(vpc)) != VP_OK)
	return(c);
    if (header.xlen != vpc->xlen || header.ylen != vpc->ylen ||
	header.zlen != vpc->zlen ||
	header.num_clsfy_params != vpc->num_clsfy_params)
	return(VPSetError(vpc, VPERROR_BAD_VOLUME));
    ncp = vpc->num_clsfy_params;
    for (c = 0; c < ncp; c++) {
	if (field_data[c] != vpc->field_size[vpc->param_field[c]] ||
	    field_data[ncp + c] != vpc->field_max[vpc->param_field[c]])
	    return(VPSetError(vpc, VPERROR_BAD_VOXEL));
    }

    /* clear old octree */
    vpDestroyMinMaxOctree(vpc);

    /* initialize new octree */
    Alloc(vpc, vpc->mm_octree, MinMaxOctree *, sizeof(MinMaxOctree),
	  "MinMaxOctree");
    bzero(vpc->mm_octree, sizeof(MinMaxOctree));
    vpc->mm_octree->levels = header.levels;
    vpc->mm_octree->root_node_size = header.root_node_size;
    vpc->mm_octree->base_node_size = header.base_node_size;
    vpc->mm_octree->range_bytes_per_node = header.range_bytes_per_node;
    vpc->mm_octree->base_bytes_per_node = header.base_bytes_per_node;
    vpc->mm_octree->nonbase_bytes_per_node = header.nonbase_bytes_per_node;
    vpc->mm_octree->status_offset = header.status_offset;
    vpc->mm_octree->child_offset = header.child_offset;
    vpc->mm_octree->octree_bytes = header.octree_bytes;
    ncp = header.num_clsfy_params;
    for (c = 0; c < ncp; c++)
	vpc->mm_octree->node_offset[c] = field_data[2*ncp + c];

    /* load octree data */
    size = header.octree_bytes;
    Alloc(vpc, vpc->mm_octree->root, void *, size, "mm_octree");
    if (vpc->read_func(fd, vpc->mm_octree->root, size) != size)
	return(VPSetError(vpc, VPERROR_IO));
    if (swab)
	SwapOctreeNode(vpc, 0, vpc->mm_octree->root);

    return(VP_OK);
}

/*
 * SwapOctreeNode
 *
 * Recursive depth-first traversal of an octree to byte-swap each node's
 * data (in order to switch the endianess).
 */

static void
SwapOctreeNode(vpc, level, node)
vpContext *vpc;
int level;
void *node;
{
    int p, field, size, offset, tmp1, tmp2;
    int child_bytes_per_node;
    char *node_ptr = (char*)node;

    /* byte swap min-max data */
    for (p = 0; p < vpc->num_clsfy_params; p++) {
	field = vpc->param_field[p];
	size = vpc->field_size[field];
	if (size != 1) {
	    ASSERT(size == 2);
	    offset = vpc->mm_octree->node_offset[p];
	    tmp1 = node_ptr[offset];
	    node_ptr[offset] = node_ptr[offset+1];
	    node_ptr[offset+1] = tmp1;
	    tmp2 = node_ptr[offset+2];
	    node_ptr[offset+2] = node_ptr[offset+3];
	    node_ptr[offset+3] = tmp2;
	}
    }

    /* byte swap child pointer and recurse */
    if (level != vpc->mm_octree->levels-1) {
	offset = vpc->mm_octree->child_offset;
	tmp1 = node_ptr[offset];
	node_ptr[offset] = node_ptr[offset+3];
	node_ptr[offset+3] = tmp1;
	tmp2 = node_ptr[offset+1];
	node_ptr[offset+1] = node_ptr[offset+2];
	node_ptr[offset+2] = tmp2;

	ASSERT(IntField(node, offset) != 0);
	node_ptr = (char *)vpc->mm_octree->root + IntField(node, offset);
	if (level == vpc->mm_octree->levels-2)
	    child_bytes_per_node = vpc->mm_octree->base_bytes_per_node;
	else
	    child_bytes_per_node = vpc->mm_octree->nonbase_bytes_per_node;
	SwapOctreeNode(vpc, level+1, node_ptr);
	node_ptr += child_bytes_per_node;
	SwapOctreeNode(vpc, level+1, node_ptr);
	node_ptr += child_bytes_per_node;
	SwapOctreeNode(vpc, level+1, node_ptr);
	node_ptr += child_bytes_per_node;
	SwapOctreeNode(vpc, level+1, node_ptr);
	node_ptr += child_bytes_per_node;
	SwapOctreeNode(vpc, level+1, node_ptr);
	node_ptr += child_bytes_per_node;
	SwapOctreeNode(vpc, level+1, node_ptr);
	node_ptr += child_bytes_per_node;
	SwapOctreeNode(vpc, level+1, node_ptr);
	node_ptr += child_bytes_per_node;
	SwapOctreeNode(vpc, level+1, node_ptr);
    }
}

/*******************************************************************
 * Raw Volume Files.                                               *
 *******************************************************************/

/* file header structure */
typedef struct {
    unsigned magic;		/* magic number for identification */
    unsigned xlen;		/* voxels in each dimension */
    unsigned ylen;
    unsigned zlen;
    unsigned bytes_per_voxel;	/* size of a raw voxel */
    unsigned num_voxel_fields;	/* number of fields in a voxel */
    unsigned num_shade_fields;	/* number of fields for shading */
    unsigned num_clsfy_fields;	/* number of fields for classification */
    int xstride;		/* strides for voxel data */
    int ystride;
    int zstride;
} RawVoxelHdr;

/*
 * File layout:
 *   RawVoxelHdr hdr;
 *   unsigned field_size[hdr.num_shade_fields];	   (size of each voxel field)
 *   unsigned field_offset[hdr.num_shade_fields];  (offset for each field)
 *   unsigned field_max[hdr.num_shade_fields];     (max. value of each field)
 *   char data[hdr.xlen*hdr.ylen*hdr.zlen*hdr.bytes_per_voxel]; (volume data)
 */

/*
 * vpStoreRawVolume
 *
 * Store an unclassified volume to a file.
 */

vpResult
vpStoreRawVolume(vpc, fd)
vpContext *vpc;	/* context containing the volume */
int fd;		/* UNIX file descriptor open for writing */
{
    RawVoxelHdr header;
    unsigned field_data[3*VP_MAX_FIELDS];
    int nvf, c;
    unsigned size;
    int retcode;

    /* check for errors */
    if ((retcode = VPCheckRawVolume(vpc)) != VP_OK)
	return(retcode);

    /* write header */
    header.magic = VP_RVFILE_MAGIC;
    header.xlen = vpc->xlen;
    header.ylen = vpc->ylen;
    header.zlen = vpc->zlen;
    header.bytes_per_voxel = vpc->raw_bytes_per_voxel;
    header.num_voxel_fields = vpc->num_voxel_fields;
    header.num_shade_fields = vpc->num_shade_fields;
    header.num_clsfy_fields = vpc->num_clsfy_params;
    header.xstride = vpc->xstride;
    header.ystride = vpc->ystride;
    header.zstride = vpc->zstride;
    if (vpc->write_func(fd, &header, sizeof(header)) != sizeof(header))
	return(VPSetError(vpc, VPERROR_IO));

    /* write voxel layout information */
    nvf = vpc->num_voxel_fields;
    for (c = 0; c < nvf; c++) {
	field_data[c] = vpc->field_size[c];
	field_data[nvf + c] = vpc->field_offset[c];
	field_data[2*nvf + c] = vpc->field_max[c];
    }
    size = 3*nvf*sizeof(unsigned);
    if (vpc->write_func(fd, field_data, size) != size)
	return(VPSetError(vpc, VPERROR_IO));

    /* write data */
    if (vpc->write_func(fd, vpc->raw_voxels, vpc->raw_voxels_size) !=
	vpc->raw_voxels_size)
	return(VPSetError(vpc, VPERROR_IO));

    return(VP_OK);
}

/*
 * vpLoadRawVolume
 *
 * Load an unclassified volume from a file.
 */

vpResult
vpLoadRawVolume(vpc, fd)
vpContext *vpc;	/* context to store the volume into */
int fd;		/* UNIX file descriptor open for reading */
{
    RawVoxelHdr header;
    unsigned field_data[3*VP_MAX_FIELDS];
    int nvf, c, swab;
    unsigned size;
    unsigned voxel_offset;

    /* read header */
    if (vpc->read_func(fd, &header, sizeof(header)) != sizeof(header))
	return(VPSetError(vpc, VPERROR_IO));
    swab = 0;
    if (header.magic != VP_RVFILE_MAGIC) {
	SwapWords(&header, sizeof(header));
	if (header.magic != VP_RVFILE_MAGIC)
	    return(VPSetError(vpc, VPERROR_BAD_FILE));
	swab = 1;
    }

    /* read voxel layout information */
    size = 3 * header.num_voxel_fields * sizeof(unsigned);
    if (vpc->read_func(fd, field_data, size) != size)
	return(VPSetError(vpc, VPERROR_IO));
    if (swab)
	SwapWords(field_data, size);
    voxel_offset = sizeof(header) + size;

    /* destroy old volume data */
    vpDestroyClassifiedVolume(vpc);
    vpDestroyMinMaxOctree(vpc);

    /* load new volume size */
    vpc->xlen = header.xlen;
    vpc->ylen = header.ylen;
    vpc->zlen = header.zlen;
    vpc->raw_bytes_per_voxel = header.bytes_per_voxel;
    vpc->num_voxel_fields = header.num_voxel_fields;
    vpc->num_shade_fields = header.num_shade_fields;
    vpc->num_clsfy_params = header.num_clsfy_fields;
    vpc->xstride = header.xstride;
    vpc->ystride = header.ystride;
    vpc->zstride = header.zstride;
    nvf = header.num_voxel_fields;
    for (c = 0; c < nvf; c++) {
	vpc->field_size[c] = field_data[c];
	vpc->field_offset[c] = field_data[nvf + c];
	vpc->field_max[c] = field_data[2*nvf + c];
    }

    /* load new volume data */
    size = vpc->xlen*vpc->ylen*vpc->zlen*vpc->raw_bytes_per_voxel;
    vpc->raw_voxels_size = size;
    if (vpc->mmap_func != NULL && !swab) {
	if ((vpc->raw_voxels = vpc->mmap_func(fd, voxel_offset,
					      vpc->client_data)) == NULL)
	    return(VPSetError(vpc, VPERROR_IO));
    } else {
	Alloc(vpc, vpc->raw_voxels, void *, size, "raw_voxels");
	if (vpc->read_func(fd, vpc->raw_voxels, size) != size)
	    return(VPSetError(vpc, VPERROR_IO));
	if (swab) {
	    SwapVoxels(vpc, vpc->raw_voxels, vpc->xlen*vpc->ylen*vpc->zlen,
		       vpc->num_voxel_fields, vpc->raw_bytes_per_voxel);
	}
    }

    return(VP_OK);
}

/*******************************************************************
 * Rendering Context Dump Files.                                   *
 *******************************************************************/

/* file header structure */
typedef struct {
    unsigned magic;		/* magic number for identification */
    unsigned major_version;	/* major version number */
    unsigned minor_version;	/* minor version number */
    unsigned max_fields;	/* value of VP_MAX_FIELDS */
    unsigned max_material;	/* value of VP_MAX_MATERIAL */
    unsigned max_lights;	/* value of VP_MAX_LIGHTS */
} VpcHdr;

/*
 * File layout:
 *   VpcHdr hdr;
 *   vpContext vpc; --> truncated just before "end_of_parameters" field
 *   unsigned shade_color_table_size;
 *   float shade_color_table[shade_color_table_size];
 *   unsigned shade_weight_table_size;
 *   float shade_weight_table[shade_weight_table_size];
 *   for i = 1 to vpc.num_clsfy_params:
 *       int clsfy_table_size;
 *       float clsfy_table[clsfy_table_size];
 */

/*
 * vpStoreContext
 *
 * Store the contents of a volpack context to a file.  All state parameters
 * stored directly in the vpContext structure are stored.  User-supplied
 * lookup tables are also stored.  Volume data and octrees are not stored
 * (use the routines specifically for storing those data structures), and
 * internal tables that can be computed from other state variables
 * (e.g. depth cueing lookup table) are not stored.
 */

vpResult
vpStoreContext(vpc, fd)
vpContext *vpc;
int fd;
{
    VpcHdr header;
    int i;
    unsigned vpc_size;

    header.magic = VP_VPCFILE_MAGIC;
    header.major_version = VP_MAJOR_VERSION;
    header.minor_version = VP_MINOR_VERSION;
    header.max_fields = VP_MAX_FIELDS;
    header.max_material = VP_MAX_MATERIAL;
    header.max_lights = VP_MAX_LIGHTS;
    vpc_size = vpFieldOffset(vpc, end_of_parameters);
    if (vpc->write_func(fd, &header, sizeof(header)) != sizeof(header))
	return(VPSetError(vpc, VPERROR_IO));
    if (vpc->write_func(fd, vpc, vpc_size) != vpc_size)
	return(VPSetError(vpc, VPERROR_IO));
    if (!StoreTable(vpc, fd, vpc->shade_color_table,
		    vpc->shade_color_table_size))
	return(VPSetError(vpc, VPERROR_IO));
    if (!StoreTable(vpc, fd, vpc->shade_weight_table,
		    vpc->shade_weight_table_size))
	return(VPSetError(vpc, VPERROR_IO));
    for (i = 0; i < vpc->num_clsfy_params; i++) {
	if (!StoreTable(vpc, fd, vpc->clsfy_table[i],
			vpc->clsfy_table_size[i]))
	    return(VPSetError(vpc, VPERROR_IO));
    }
    return(VP_OK);
}

/*
 * StoreTable
 *
 * Store a table to a file and check for errors.  Return value is 1 for
 * success, 0 for failure.
 */

static int
StoreTable(vpc, fd, ptr, size)
vpContext *vpc;
int fd;
float *ptr;
unsigned size;
{
    if (size == 0 || ptr == NULL) {
	size = 0;
	if (vpc->write_func(fd, &size, sizeof(size)) != sizeof(size))
	    return(0);
    } else {
	if (vpc->write_func(fd, &size, sizeof(size)) != sizeof(size))
	    return(0);
	if (vpc->write_func(fd, ptr, size) != size)
	    return(0);
    }
    return(1);
}

/*
 * vpLoadContext
 *
 * Load a volpack context from a file.  The old contents of the context are
 * destroyed, including any volume data.  Lookup tables for shading and
 * classification that are loaded from the file are stored in newly-allocated
 * memory, but the application is responsible for freeing the tables;
 * existing tables in the context are not overwritten (since there is no
 * way for the application to predict the right table sizes), and the new
 * tables are not freed when vpDestroyContext is called (since volpack
 * normally does not manage the tables).  Byte swapping is not performed.
 */

vpResult
vpLoadContext(vpc, fd)
vpContext *vpc;
int fd;
{
    VpcHdr header;
    int swab, i;
    unsigned vpc_size;

    /* read header */
    if (vpc->read_func(fd, &header, sizeof(header)) != sizeof(header))
	return(VPSetError(vpc, VPERROR_IO));
    swab = 0;
    if (header.magic != VP_VPCFILE_MAGIC)
	return(VPSetError(vpc, VPERROR_BAD_FILE));
    if (header.major_version != VP_MAJOR_VERSION || 
	header.minor_version != VP_MINOR_VERSION ||
	header.max_fields != VP_MAX_FIELDS ||
	header.max_material != VP_MAX_MATERIAL ||
	header.max_lights != VP_MAX_LIGHTS) {
	return(VPSetError(vpc, VPERROR_BAD_VALUE));
    }

    /* destroy old data structures */
    vpDestroyMinMaxOctree(vpc);
    vpDestroyClassifiedVolume(vpc);

    /* load new context */
    vpc_size = vpFieldOffset(vpc, end_of_parameters);
    if (vpc->read_func(fd, vpc, vpc_size) != vpc_size)
	return(VPSetError(vpc, VPERROR_IO));
    vpc->raw_voxels = NULL;
    for (i = 0; i < VP_MAX_FIELDS; i++)
	vpc->clsfy_table[i] = NULL;
    vpc->shade_color_table = NULL;
    vpc->shade_weight_table = NULL;
    vpc->image = NULL;
    if (vpc->shade_func == NULL)
	vpc->shading_mode = LOOKUP_SHADER;
    if (!LoadTable(vpc, fd, &vpc->shade_color_table,
		   (unsigned *)&vpc->shade_color_table_size))
	goto failed;
    if (!LoadTable(vpc, fd, &vpc->shade_weight_table,
		   (unsigned *)&vpc->shade_weight_table_size))
	goto failed;
    for (i = 0; i < vpc->num_clsfy_params; i++) {
	if (!LoadTable(vpc, fd, &vpc->clsfy_table[i],
		       (unsigned *)&vpc->clsfy_table_size[i]))
	    goto failed;
    }
    return(VP_OK);

 failed:
    if (vpc->shade_color_table != NULL) {
	Dealloc(vpc, vpc->shade_color_table);
	vpc->shade_color_table = NULL;
    }
    if (vpc->shade_weight_table != NULL) {
	Dealloc(vpc, vpc->shade_weight_table);
	vpc->shade_weight_table = NULL;
    }
    for (i = 0; i < vpc->num_clsfy_params; i++) {
	if (vpc->clsfy_table[i] != NULL) {
	    Dealloc(vpc, vpc->clsfy_table[i]);
	    vpc->clsfy_table[i] = NULL;
	}
    }
    return(VPSetError(vpc, VPERROR_IO));
}

/*
 * LoadTable
 *
 * Load a table from a file and check for errors.  Return value is 1 for
 * success, 0 for failure.
 */

static int
LoadTable(vpc, fd, ptr_ptr, size_ptr)
vpContext *vpc;
int fd;
float **ptr_ptr;
unsigned *size_ptr;
{
    if (vpc->read_func(fd, size_ptr, sizeof(unsigned)) != sizeof(unsigned))
	return(0);
    if (*size_ptr != 0) {
	Alloc(vpc, *ptr_ptr, float*, *size_ptr, "lookup table");
	if (vpc->read_func(fd, *ptr_ptr, *size_ptr) != *size_ptr)
	    return(0);
    }
    return(1);
}
