// ivpoints.cxx - read Open Inventor "iv" file format for 3-D models
// and print out points with known depth in the following format
//	X Y Z R G B
//
// This understands only a small subset of the file format, namely
//	Texture2 { image WIDTH HEIGHT 3 COLOR_IN_HEXADECIMAL ... }
//	TextureCoordinate2 { point[ X Y, ... X Y ] }
//	Coordinate3 { point [ X Y Z, ... X Y Z ] }
// where all capitals means a numerical or string variable,
// and it ignores comments
//	# EVERYTHING_UP_TO_NEWLINE
// and any other tokens it doesn't recognize.
// Currently, comments within the Texture2, TextureCoordinate2, and Coordinate3
// commands are not tolerated (this is a bug).
//
// Written specifically to parse Inventor files written by the
// Minolta Vivid 700 laser scanner,
// for 15-869 assignment 2: View Transformation
//
// Paul Heckbert	23 Sept 1999

#include <stdlib.h>
#include <string.h>	// for strcmp
#include <fstream.h>	// for file I/O, includes iostream.h
#include <iomanip.h>	// for ios::

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>

#include <pic.h>	// for color image, from libpicio
typedef struct {float r, g, b;}	Pixelf_rgb;


#define TOKEN_SIZE 20


struct Point {
    float x, y, z;		// 3-D position
    float s, t;			// texture coordinates
    unsigned char r, g, b;	// color
};

#define NMAX 40000 // because Vivid scans 200*200 3-D points, max

class Object {			// a 3-D object with shape and texture
  public:
    Pic *pic;			// texture
    Point pt[NMAX];		// points
    int npt;			// number of points
    double xmin, xmax, ymin, ymax, zmin, zmax;	// bounding box for xyz
    
    Object();
    ~Object();

    void get_color_bilinear(float s, float t,
	unsigned char *r, unsigned char *g, unsigned char *b);
	    // do bilinear interpolation from texture

    // to read Open Inventor file format
    void inventor_read(istream &s, const char *streamname);
    void inventor_read(const char *file);

    void print();		// print the points

  private:
    // to read Open Inventor file format, internals
    void inventor_read_texture2(istream &s, const char *streamname);
    void inventor_read_texturecoordinate2(istream &s, const char *streamname);
    void inventor_read_coordinate3(istream &s, const char *streamname);
};

Object::Object() {
    pic = NULL;
    npt = 0;
    xmin = HUGE;
    xmax = -HUGE;
    ymin = HUGE;
    ymax = -HUGE;
    zmin = HUGE;
    zmax = -HUGE;
}

Object::~Object() {
    if (pic) pic_free(pic);
}

void Object::get_color_bilinear(float s, float t,
unsigned char *r, unsigned char *g, unsigned char *b) {
    // use bilinear interpolation to extract the color of the
    // texture pixel at (s,t) from pic

    float x = s*pic->nx;
    float y = t*pic->ny;

    // in case some of the (s,t)'s are outside [0,1] range (some are!)
    if (x<0) x = 0;
    if (x>=pic->nx-1) x = pic->nx-1.001;
    if (y<0) y = 0;
    if (y>=pic->ny-1) y = pic->ny-1.001;

    int xi = floor(x);	// integer part
    int yi = floor(y);
    float xf = x-xi;	// fractional part
    float yf = y-yi;

    Pixel1_rgb *p00, *p10, *p01, *p11;
    Pixelf_rgb px0, px1, pxy;

    p00 = (Pixel1_rgb *)&PIC_PIXEL(pic, xi, yi, 0);
    p10 = p00+1;
    // note that we're reading unsigned chars below, but writing floats
    // linearly interpolate (lerp) between colors 00 and 10
    px0.r = p00->r + xf*(p10->r - p00->r);
    px0.g = p00->g + xf*(p10->g - p00->g);
    px0.b = p00->b + xf*(p10->b - p00->b);

    p01 = (Pixel1_rgb *)&PIC_PIXEL(pic, xi, yi+1, 0);
    p11 = p01+1;
    // lerp between colors 01 and 11
    px1.r = p01->r + xf*(p11->r - p01->r);
    px1.g = p01->g + xf*(p11->g - p01->g);
    px1.b = p01->b + xf*(p11->b - p01->b);

    // lerp between colors x0 and x1
    pxy.r = px0.r + yf*(px1.r - px0.r);
    pxy.g = px0.g + yf*(px1.g - px0.g);
    pxy.b = px0.b + yf*(px1.b - px0.b);

    assert(pxy.r>=0 && pxy.r<256);
    assert(pxy.g>=0 && pxy.g<256);
    assert(pxy.b>=0 && pxy.b<256);

    // convert back to unsigned char
    *r = pxy.r;
    *g = pxy.g;
    *b = pxy.b;
}

// Open Inventor parsing

static int next_token_is_not(istream &s, const char *str) {
    // read a token from stream s, return whether token is not str
    static char tok[TOKEN_SIZE];
    s >> setw(sizeof tok) >> tok;
    return strcmp(tok, str);
}

static void expect_token(istream &s, const char *command, const char *str) {
    // read a token from stream s, and print an error and exit if it's not str
    char tok[TOKEN_SIZE];
    s >> setw(sizeof tok) >> tok;
    if (strcmp(tok, str)) {
	cerr << "Error reading '" << command << "' command, got '"
	    << tok << "', not '" << str << "', as expected" << endl;
	// would be  neat to print filename and line number of input file here
	exit(1);
    }
}

static void exit_if_trouble(istream &s, const char *command) {
    if (s.fail()) {
	cerr << "Unknown error reading '" << command << "' command" << endl;
	exit(1);
    }
}

// warning: the following routines do a lousy job of checking for errors
// (e.g. braces not preceded by space)

void Object::inventor_read_texture2(istream &s, const char *streamname) {
    // Texture2 { image WIDTH HEIGHT 3 COLOR_IN_HEXADECIMAL ... }
    // read a color image

    expect_token(s, "Texture2", "{");
    expect_token(s, "Texture2", "image");
    int nx, ny, x, y, color24;
    s >> nx;	// read width
    s >> ny;	// read height
    cout << nx << "x" << ny << " texture array" << endl;
    expect_token(s, "Texture2", "3");
    pic = pic_alloc(nx, ny, 3, NULL);	// allocate a picture array

    s.setf(ios::hex);		// use hexadecimal notation, not decimal
    for (y=0; y<ny; y++)
	for (x=0; x<nx; x++) {
	    s >> color24;	// read in hexadecimal 24-bit color
	    exit_if_trouble(s, "Texture2");
	    // cout << "(" << x << "," << y << ") ";

	    // get the address of pixel (x,y) in pic
	    Pixel1_rgb *p = (Pixel1_rgb *)&PIC_PIXEL(pic, x, y, 0);

	    p->r = color24>>16;	// save the bytes
	    p->g = color24>>8;
	    p->b = color24;
	}
    s.setf(ios::dec);		// back to decimal
    expect_token(s, "Texture2", "}");
    exit_if_trouble(s, "Texture2");	// check that no stream error so far
}

void Object::inventor_read_texturecoordinate2
(istream &s, const char *streamname) {
    // TextureCoordinate2 { point[ X Y, ... X Y ] }
    // read a list of texture coordinates

    expect_token(s, "TextureCoordinate2", "{");
    expect_token(s, "TextureCoordinate2", "point[");
	// note! we're relying on the quirks of Minolta's VIVID software,
	// expecting no space between "point" and "[" here!

    int n;
    char tok[TOKEN_SIZE];

    for (n=0;;) {
	double x, y;
	s >> x >> y;	// read texture x and y (a.k.a. s and t)

	assert(n<NMAX);
	assert(pic);

	pt[n].s = x;
	pt[n].t = y;
	get_color_bilinear(pt[n].s, pt[n].t, &pt[n].r, &pt[n].g, &pt[n].b);

	// cout << "tex" << n << " ("
	    // << x << ","
	    // << y << ")" << endl;
	n++;

	s >> setw(sizeof tok) >> tok;
	if (strcmp(tok, ","))			// usually, token is a comma
	    if (!strcmp(tok, "]")) break;	// "]" means end of list
	    else {
		cerr << "Didn't find end of TextureCoordinate2 list in "
		    << streamname << endl;
		exit(1);
	    }
    }
    expect_token(s, "TextureCoordinate2", "}");
    exit_if_trouble(s, "TextureCoordinate2"); // check that no error so far

    cout << "read " << n << " texture coordinates" << endl;
    npt = n;
}

void Object::inventor_read_coordinate3(istream &s, const char *streamname) {
    // Coordinate3 { point [ X Y Z, ... X Y Z ] }
    // read a list of xyz coordinates

    expect_token(s, "Coordinate3", "{");
    expect_token(s, "Coordinate3", "point");
    expect_token(s, "Coordinate3", "[");
    int n;
    char tok[TOKEN_SIZE];
    for (n=0;;) {
	double x, y, z;
	s >> x >> y >> z;	// read x, y, and z
	if (x<xmin) xmin = x;
	if (x>xmax) xmax = x;
	if (y<ymin) ymin = y;
	if (y>ymax) ymax = y;
	if (z<zmin) zmin = z;
	if (z>zmax) zmax = z;

	assert(n<NMAX);
	pt[n].x = x;
	pt[n].y = y;
	pt[n].z = z;

	// cout << "pt" << n << " ("
	    // << x << ","
	    // << y << ","
	    // << z << ")" << endl;
	n++;

	s >> setw(sizeof tok) >> tok;
	if (strcmp(tok, ","))			// usually, token is a comma
	    if (!strcmp(tok, "]")) break;	// "]" means end of list
	    else {
		cerr << "Didn't find end of Coordinate3 list in "
		    << streamname << endl;
		exit(1);
	    }
    }
    expect_token(s, "Coordinate3", "}");
    exit_if_trouble(s, "Coordinate3"); // check that no stream error so far

    cout << "read " << n << " 3-D points" << endl;
    cout << "    bounding box: "
	<< "x [" << xmin << ":" << xmax << "] "
	<< "y [" << ymin << ":" << ymax << "] "
	<< "z [" << zmin << ":" << zmax << "]" << endl;

    // check that the number of texture points and 3-D points are equal
    assert(n==npt);
}

void Object::inventor_read(istream &s, const char *streamname) {
    // read an Inventor file, returning a texture and a set of points

    char tok[TOKEN_SIZE];

    cout << "reading " << streamname << endl;
    while (s >> setw(sizeof tok) >> tok) {
        // cout << "(" << tok << ")" << endl;
	if (!strcmp(tok, "#")) {
	    // gobble comment
	    s.ignore(1000, '\n');
	}
	else if (!strcmp(tok, "Texture2"))
	    inventor_read_texture2(s, streamname);
	else if (!strcmp(tok, "TextureCoordinate2"))
	    inventor_read_texturecoordinate2(s, streamname);
	else if (!strcmp(tok, "Coordinate3"))
	    inventor_read_coordinate3(s, streamname);
    }
    cout << "done with " << streamname << endl;
}

void Object::inventor_read(const char *file) {
    ifstream s(file, ios::in);
    if (!s) {
	cerr << "inventor_read: can't read " << file << endl;
	exit(1);
    }
    inventor_read(s, file);
    s.close();
}

void Object::print() {
    int i;
    printf("\n%d points\n# X Y Z R G B\n", npt);
    for (i=0; i<npt; i++)
	printf("%g %g %g %d %d %d\n",
	    pt[i].x, pt[i].y, pt[i].z,
	    pt[i].r, pt[i].g, pt[i].b);
}

int main (int argc, char **argv) {
    if (argc!=2) {
	fprintf(stderr, "Usage: ivpoints IVFILE\n");
	exit(1);
    }
    Object ob;

    ob.inventor_read(argv[1]);
    ob.print();
}
