#define _USE_MATH_DEFINES
#define _CRT_SECURE_NO_WARNINGS
#define _CRT_NON_CONFORMING_SWPRINTFS

#include <initguid.h>
#include <windows.h>
#include <gl/glu.h>
#include <gl/gl.h>
#include <math.h>
#include <string>
#include <vector>
#include <list>
#include <set>

#pragma comment(lib,"glu32.lib")
#pragma comment(lib,"opengl32.lib")

// Location is only important during the build process, otherwise these DLLs simply need to be in the search path (or registered)
#ifdef WIN64
#define READERS_DIRECTORY "R:/Program64/SDK/Readers/"
#import "R:/Program64/SDK/Readers/SEFacet.dll" no_namespace
#import "R:/Program64/SDK/Readers/SEReader.dll" no_namespace
#else
#define READERS_DIRECTORY "R:/Program32/SDK/Readers/"
#import "R:/Program32/SDK/Readers/SEFacet.dll" no_namespace
#import "R:/Program32/SDK/Readers/SEReader.dll" no_namespace
#endif
//#import "libid:0F1764FC-C6CD-11D4-A3B0-00C04FA154DE" no_namespace
//#import "libid:C9165401-2A39-11D3-9273-00C04F79BE98" no_namespace

// Solid Edge CLSID's for GetClassFile support
DEFINE_GUID( CLSID_PartDocument,				0x23C52E80, 0x4698, 0x11CE, 0xB3, 0x07, 0x08, 0x00, 0x36, 0x3A, 0x1E, 0x02 );
DEFINE_GUID( CLSID_WeldmentDocument,			0x98CCDF9C, 0x213B, 0x11D4, 0xB6, 0x4C, 0x00, 0xC0, 0x4F, 0x79, 0xB2, 0xBF );
DEFINE_GUID( CLSID_AssemblyDocument,			0x00C6BF00, 0x483B, 0x11CE, 0x95, 0x1A, 0x08, 0x00, 0x36, 0x01, 0xBE, 0x52 );
DEFINE_GUID( CLSID_SheetMetalDocument,			0xDD8522E0, 0x2375, 0x11D0, 0xAC, 0x05, 0x08, 0x00, 0x36, 0xFD, 0x18, 0x02 );
DEFINE_GUID( CLSID_FamilyOfAssembliesDocument,	0x04d613a0, 0xa322, 0x40b5, 0xa2, 0xa4, 0x36, 0xca, 0x0d, 0xe6, 0xf5, 0xd9 ); 
DEFINE_GUID( CLSID_DMPartDocument,				0x5854e4b9, 0xda16, 0x415b, 0xae, 0xd5, 0xf6, 0xba, 0x2e, 0xc0, 0x8c, 0x9b );
DEFINE_GUID( CLSID_DMAssemblyDocument,			0x213a04b0, 0x815b, 0x4ade, 0x97, 0xd6, 0x85, 0x9b, 0xda, 0x68, 0x34, 0x13 );
DEFINE_GUID( CLSID_DMSheetMetalDocument,		0xf779edbf, 0xec73, 0x4d27, 0x9c, 0xa9, 0xf9, 0x6f, 0x58, 0x26, 0x7a, 0xd1 );

// Extracted CLSID's for the primary class factory objects 
DEFINE_GUID( CLSID_FacetReader,					0x0B70E783, 0xC796, 0x11D4, 0xA3, 0xB1, 0x00, 0xC0, 0x4F, 0xA1, 0x54, 0xDE );
DEFINE_GUID( CLSID_StyleLibrary,				0x27DC08BA, 0x3E4A, 0x11D5, 0xA4, 0x04, 0x00, 0xC0, 0x4F, 0xA1, 0x54, 0xDE );
DEFINE_GUID( CLSID_SEFileReader,				0xc9165411, 0x2a39, 0x11d3, 0x92, 0x73, 0x00, 0xC0, 0x4F, 0x79, 0xbe, 0x98 );

// Check to see if a file is a Solid Edge document
BOOL IsSolidEdgeAssemblyDocument(const wchar_t *filename)
{
	CLSID FileCLSID = IID_IUnknown;
	if (filename && SUCCEEDED(GetClassFile(filename, &FileCLSID)) && (IID_IUnknown != FileCLSID))
	{
		return (	CLSID_AssemblyDocument == FileCLSID || 		
					CLSID_FamilyOfAssembliesDocument == FileCLSID || 	
					CLSID_DMAssemblyDocument == FileCLSID || 			
					0
				);
	}

	return FALSE;
}

BOOL IsSolidEdgePartDocument(const wchar_t *filename)
{
	CLSID FileCLSID = IID_IUnknown;
	if (filename && SUCCEEDED(GetClassFile(filename, &FileCLSID)) && (IID_IUnknown != FileCLSID))
	{
		return (	CLSID_PartDocument == FileCLSID || 				
					CLSID_WeldmentDocument == FileCLSID || 			
					CLSID_SheetMetalDocument == FileCLSID || 			
					CLSID_DMPartDocument == FileCLSID || 				
					CLSID_DMSheetMetalDocument == FileCLSID || 		
					0
				);
	}

	return FALSE;
}

BOOL IsSolidEdgeDocument(const wchar_t *filename)
{
	return (IsSolidEdgeAssemblyDocument(filename) || IsSolidEdgePartDocument(filename));
}

// Function prototype for class factory method
typedef HRESULT STDAPICALLTYPE DLLGETCLASSOBJECT(REFCLSID rclsid, REFIID riid, LPVOID* ppv);

// Local function to simulate a class factory object creation sequence. This function can be used in lieu of creating an 
// instance via XXXReader.CreateInstance("SolidEdge.XXXReader"). This can be used to avoid registration conflicts with 
// existing Solid Edge installations (which we want to avoid). This example calls this function first with the fallback
// being an attempt to construct the object via COM services. An alternative is to switch the order and use the registered
// readers although Solid Edge ST2 readers do not provice all the necessary style information
HRESULT GetClassObject(wchar_t *szModuleName,REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
	HMODULE hModule = ::GetModuleHandle(szModuleName);
	if (!hModule) hModule = ::LoadLibraryW(szModuleName);
	if (!hModule) hModule = ::LoadLibraryW(bstr_t(READERS_DIRECTORY) + bstr_t(szModuleName));
	if (!hModule) return E_FAIL;

	if (DLLGETCLASSOBJECT *DllGetClassObject = (DLLGETCLASSOBJECT*)GetProcAddress(hModule,"DllGetClassObject"))
	{
		IClassFactoryPtr icf;
		if (SUCCEEDED(DllGetClassObject(rclsid,IID_IClassFactory,(LPVOID*)&icf)) && icf)
		{
			return icf->CreateInstance(NULL,riid,ppv);
		}
	}

	return E_FAIL;
}


double _range[6] = {1e30,1e30,1e30,-1e30,-1e30,-1e30};
double _identity[16] = {1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1};

class safearray_t : public variant_t
{
	void *p;
public:
	safearray_t(VARTYPE t) : p(nullptr)
	{
		vt = (t|VT_ARRAY);
		SAFEARRAYBOUND bounds = {1,0};
		parray = SafeArrayCreate(t,1,&bounds);
	}
	~safearray_t()
	{
		if (p && parray) SafeArrayUnaccessData(parray);
		if (parray) SafeArrayDestroy(parray);
		parray = nullptr;
		vt = VT_EMPTY;
	}

	operator void*() { if (p && parray) SafeArrayUnaccessData(parray); p = nullptr; return SUCCEEDED(SafeArrayAccessData(parray,&p)) ? p : nullptr; }
	operator unsigned long*() { return (unsigned long*)(void*)*this; }
	operator double*() { return (double*)(void*)*this; }
};

void copy(int count,double *fixed,LPSAFEARRAY safe)
{
	if (safe)
	{
		for (long index=0;index<count;index++)
		{
			SafeArrayGetElement(safe, &index, &fixed[index]);
		}
	}
}
void multiply(double o[16],double i[16],double w[16])
{
	for (int c=0;c<4;c++)
	{
		for (int r=0;r<4;r++)
		{
			o[c*4+r] = ((w[r]*i[c*4]) + ((w[r+4]*i[c*4+1])) + ((w[r+8]*i[c*4+2])) + ((w[r+12]*i[c*4+3])));
		}
	}
}
void multiply(double io[6],double t[16])
{
	double o[6];
	o[0] = io[0]*t[0] + io[1]*t[4] + io[2]*t[ 8] + t[12];
	o[1] = io[0]*t[1] + io[1]*t[5] + io[2]*t[ 9] + t[13];
	o[2] = io[0]*t[2] + io[1]*t[6] + io[2]*t[10] + t[14];
	o[3] = io[3]*t[0] + io[4]*t[4] + io[5]*t[ 8] + t[12];
	o[4] = io[3]*t[1] + io[4]*t[5] + io[5]*t[ 9] + t[13];
	o[5] = io[3]*t[2] + io[4]*t[6] + io[5]*t[10] + t[14];
	memcpy(io,o,sizeof(o));
}
void merge(double o[6],double i[6])
{
	o[0] = min(o[0],i[0]);
	o[1] = min(o[1],i[1]);
	o[2] = min(o[2],i[2]);
	o[3] = max(o[3],i[3]);
	o[4] = max(o[4],i[4]);
	o[5] = max(o[5],i[5]);
}

struct Color
{
	union
	{
		long color;
		COLORREF colorref;
		struct
		{
			unsigned char red;
			unsigned char green;
			unsigned char blue;
			unsigned char alpha;
		};
	};
	float c[4];
	void populate() 
	{
		c[0] = (float)red/255.f;
		c[1] = (float)green/255.f;
		c[2] = (float)blue/255.f;
		c[3] = (float)alpha/255.f;
	}
	operator long() { return color; }
	operator long&() { return color; }
	operator long*() { return &color; }
	operator COLORREF() { return COLORREF(color); }
	operator const float*()	{ return c;	}
};

struct Image
{
	wchar_t			filename[MAX_PATH];
	int				width;
	int				height;
	LPDWORD			bitmap;

	Image()
	{
		memset(this,0,sizeof(*this));
	}
	static Image *Load(const wchar_t *Filename)
	{
		Image *image = 0;
		if (HBITMAP hImage = (HBITMAP)LoadImage(NULL,Filename,IMAGE_BITMAP,0,0,LR_CREATEDIBSECTION|LR_LOADFROMFILE))
		{
			DIBSECTION dibsection = {0};
			if (GetObject(hImage,sizeof(DIBSECTION),&dibsection))
			{
				if (image = new Image)
				{
					wcscpy(image->filename,Filename);

					LPDWORD bits = 0;
					BITMAPINFO bmi = {0};
					bmi.bmiHeader.biPlanes = 1;
					bmi.bmiHeader.biBitCount = 32;
					bmi.bmiHeader.biCompression = BI_RGB;    
					bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
					bmi.bmiHeader.biWidth = image->width = 64;//dibsection.dsBmih.biWidth;
					bmi.bmiHeader.biHeight = image->height = 64;//dibsection.dsBmih.biHeight;
					HDC hdcSrc = CreateCompatibleDC(GetDC(GetDesktopWindow()));
					HDC hdcDest = CreateCompatibleDC(GetDC(GetDesktopWindow()));
					SetStretchBltMode(hdcSrc,HALFTONE);
					SetStretchBltMode(hdcDest,HALFTONE);
					HBITMAP hBitmap = CreateDIBSection(hdcDest, &bmi, DIB_RGB_COLORS, (LPVOID*)&bits, NULL, 0);
					HGDIOBJ hBitmapOrig = SelectObject(hdcDest,hBitmap);
					HGDIOBJ hImageOrig = SelectObject(hdcSrc,hImage);
					StretchBlt(hdcDest,0,0,bmi.bmiHeader.biWidth,bmi.bmiHeader.biHeight,hdcSrc,0,0,dibsection.dsBmih.biWidth,dibsection.dsBmih.biHeight,SRCCOPY);
					if (image->bitmap = (LPDWORD)malloc(image->width*image->height*sizeof(DWORD)))
						memcpy(image->bitmap,bits,(image->width*image->height*sizeof(DWORD)));
					SelectObject(hdcDest,hBitmapOrig);
					SelectObject(hdcSrc,hImageOrig);
					DeleteObject(hBitmap);
					DeleteDC(hdcDest);
					DeleteDC(hdcSrc);
				}
			}
			DeleteObject(hImage);
		}
		return image;
	}
};

std::list<Image*> imageList;

struct Texture
{
	wchar_t			filename[MAX_PATH];
	int				units;
	float			rotation;
	float			scaleX;
	float			scaleY;
	float			offsetX;
	float			offsetY;
	float			weight;
	int				mirror;
	VARIANT_BOOL	transparentBackground;
	long			transparentColor;
	Image			*image;

	Texture()
	{
		memset(this,0,sizeof(*this));
		scaleX = 1;
		scaleY = 1;
		weight = 1;
		transparentBackground = VARIANT_FALSE;
		transparentColor = RGB(255,255,255);
	}
	void Load(IStyleLibraryPtr Library,unsigned long Id)
	{
		BSTR Filename = NULL;
		Library->StyleTexture(Id,&Filename,&units,&rotation,&scaleX,&scaleY,&offsetX,&offsetY,&weight,&mirror,&transparentBackground,&transparentColor);
		if (Filename)
		{
			wcscpy(filename,bstr_t(Filename));
			SysFreeString(Filename);
			imageList.push_back(image = Image::Load(filename));
		}
	}
};

struct Style
{
	unsigned long	id;
	wchar_t			name[MAX_PATH];
	Color			diffuse;
	Color			ambient;
	Color			specular;
	Color			emission;
	float			alpha;
	float			shininess;
	float			refraction;
	float			reflectivity;
	int				shadows;
	Texture			texture;

	Style() 
	{
		memset(this,0,sizeof(*this)); 
	}
	Style(IStyleLibraryPtr Library,unsigned long Id)
	{
		memset(this,0,sizeof(*this));
		if (Library && Id)
		{
			id = Id;
			if (wchar_t *Name = Library->GetStyleName(id)) wcscpy(name,Name);
			Library->StyleSurface(id,diffuse,specular,ambient,emission,&shininess,&alpha);
			Library->StyleSurfaceEx(id,&reflectivity,&refraction,&shadows);
			diffuse.populate();
			ambient.populate();
			specular.populate();
			emission.populate();

			texture.Load(Library,Id);
			//bumpmap.Load(Library,Id);
			//environment.Load(Library,Id);
		}
	}
	void display()
	{
		glColor4ub(diffuse.red,diffuse.green,diffuse.blue,diffuse.alpha);
		glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse);
		glMaterialfv(GL_FRONT, GL_AMBIENT, ambient);
		glMaterialfv(GL_FRONT, GL_SPECULAR, specular);
		glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, emission);
		glMaterialf (GL_FRONT, GL_SHININESS, shininess * 128.f);

		if (texture.image && texture.image->bitmap)
		{
			// GL_MODULATE does not apply weight in the same way as Solid Edge but the idea is the same (i.e., blend to white)
			if (texture.weight<1.0f)
			{
				float w = texture.weight;
				#define ub(d,s,w) (unsigned char)(float(s)-((float(s)-float(d))*float(w)))
				glColor3ub(ub(255,diffuse.red,w),ub(255,diffuse.green,w),ub(255,diffuse.blue,w));
			}
			else
			{
				glColor3ub(255,255,255);
			}

			//if (texture)
			{
				double tm[16]; 
				memcpy(tm,_identity,sizeof(tm));

				bool mirrorx = false;
				bool mirrory = false;
				bool reverse = !((mirrorx || mirrory) && !(mirrorx && mirrory));

				double angle = (reverse) ? texture.rotation : -texture.rotation;
				double scalex = (mirrory) ? -texture.scaleX : texture.scaleX;
				double scaley = (mirrorx) ? -texture.scaleY : texture.scaleY;
				double rsin = sin(angle * M_PI / 180.0);
				double rcos = cos(angle * M_PI / 180.0);

				tm[0]  = rcos / scalex;
				tm[1]  = rsin / scalex;
				tm[4]  = -rsin / scaley;
				tm[5]  =  rcos / scaley;
				tm[12] = texture.offsetX / scalex;
				tm[13] = texture.offsetY / scaley;

				glMatrixMode(GL_TEXTURE);
				glLoadMatrixd(tm);
			}

			glTexEnvi(GL_TEXTURE_ENV,GL_TEXTURE_ENV_MODE,GL_MODULATE);
			glTexImage2D(GL_TEXTURE_2D, 0, 4, texture.image->width, texture.image->height, 0, GL_BGRA_EXT, GL_UNSIGNED_BYTE, texture.image->bitmap);
			glEnable(GL_TEXTURE_2D);
		}
		else
		{
			glDisable(GL_TEXTURE_2D);
		}
	}
	bool operator<(const Style &r) const 
	{
		return id<r.id; 
	}
};

struct Vertex
{
	struct	{ double x,y,z;	}	position;
	struct	{ double x,y,z;	}	normal;
	struct	{ double s,t;	}	texture;
};

class Strip
{
public:
	std::vector<Vertex>	vertices;
};

class Face
{
public:
	Style				style;
	std::vector<Strip>	strips;
};

class Body
{
public:
	Style				style;
	std::vector<Face>	faces;
};

class PartSite
{
public:
	wchar_t	filename[MAX_PATH];
	wchar_t	filepath[MAX_PATH];

	struct
	{
		Style			body;
		Style			thread;
		Style			construction;
		std::set<Style>	library;
	} style;
	double				range[6];
	std::vector<Body>	bodies;

	PartSite(const wchar_t *name=0)
	{
		if (name)
		{
			wcscpy(filename,name);
			wcscpy(filepath,name);
			if (wchar_t *filepart = wcsrchr(filename,L'\\'))
				wcscpy(filename,++filepart);
			else 
				wcscpy(filename,filepath);
		}
		else
		{
			memset(filename,0,sizeof(name));
			memset(filepath,0,sizeof(filepath));
		}

		memcpy(range,_range,sizeof(_range));

		IFacetReaderPtr FacetReader;
		if (SUCCEEDED(GetClassObject(L"SEFACET.DLL", CLSID_FacetReader, FacetReader.GetIID(), (LPVOID*)&FacetReader)) || SUCCEEDED(FacetReader.CreateInstance("SEFacet.FacetReader")))
		{
			// Force retessellation ...
			FacetReader->ChordTolerance = 0; // Added in ST4

			// Open root document ...
			if (SUCCEEDED(FacetReader->Open(filename)))
			{
				IStyleLibraryPtr StyleLibrary;
				if (SUCCEEDED(GetClassObject(L"SEFACET.DLL", CLSID_StyleLibrary, StyleLibrary.GetIID(), (LPVOID*)&StyleLibrary)) || SUCCEEDED(StyleLibrary.CreateInstance("SEFacet.StyleLibrary")))
					StyleLibrary->Open(filename);

				if (long nBodies = FacetReader->GetBodyCount())
				{
					unsigned long body = FacetReader->GetBodyStyle(1);
					unsigned long thread = FacetReader->GetBodyThreadStyle(1); // Added in ST3
					unsigned long construction = FacetReader->GetBodyConstructionStyle(1);

					if (StyleLibrary)
					{
						if (body) style.library.insert(style.body = Style(StyleLibrary,body));
						if (thread) style.library.insert(style.thread = Style(StyleLibrary,thread));
						if (construction) style.library.insert(style.construction = Style(StyleLibrary,construction));
					}

					bodies.resize(nBodies);
					for (long nBody=0; nBody<nBodies; nBody++)
					{
						if (long nFaces = FacetReader->GetBodyFaceCount(nBody+1))
						{
							bodies[nBody].faces.resize(nFaces);
							for (long nFace=0; nFace<nFaces; nFace++)
							{
								unsigned long face = FacetReader->GetBodyFaceStyle(nBody+1,nFace+1);
								if (face && StyleLibrary)
									style.library.insert(bodies[nBody].faces[nFace].style = Style(StyleLibrary,face));

								if (long nStrips = FacetReader->GetBodyFaceStripCount(nBody+1,nFace+1))
								{
									bodies[nBody].faces[nFace].strips.resize(nStrips);
									for (long nStrip=0; nStrip<nStrips; nStrip++)
									{
										if (long nVerts = FacetReader->GetBodyFaceStripVertCount(nBody+1,nFace+1,nStrip+1))
										{
											bodies[nBody].faces[nFace].strips[nStrip].vertices.resize(nVerts);
											for (long nVert=0; nVert<nVerts; nVert++)
											{
												Vertex &vertex = bodies[nBody].faces[nFace].strips[nStrip].vertices[nVert];
												FacetReader->GetBodyFaceStripVertex(nBody+1,nFace+1,nStrip+1,nVert+1,
													&vertex.position.x,&vertex.position.y,&vertex.position.z,
													&vertex.normal.x,&vertex.normal.y,&vertex.normal.z,
													&vertex.texture.s,&vertex.texture.t);

												range[0] = min(range[0],vertex.position.x);
												range[1] = min(range[1],vertex.position.y);
												range[2] = min(range[2],vertex.position.z);
												range[3] = max(range[3],vertex.position.x);
												range[4] = max(range[4],vertex.position.y);
												range[5] = max(range[5],vertex.position.z);
											}
										}
									}
								}
							}
						}
					}
				}
				StyleLibrary = NULL;
			}
			FacetReader = NULL;
		}
	}
	~PartSite()
	{
		bodies.clear();
		style.library.clear();
	}
	void display(bool allowcolors=true)
	{
		glPushAttrib(GL_ALL_ATTRIB_BITS);
		if (allowcolors && style.body.id)
			style.body.display();
		for (std::vector<Body>::iterator bit=bodies.begin(); bodies.end()!= bit; bit++)
		{
			for (std::vector<Face>::iterator fit=bit->faces.begin(); bit->faces.end()!= fit; fit++)
			{
				glPushAttrib(GL_ALL_ATTRIB_BITS);
				if (allowcolors && fit->style.id)
				{
					fit->style.display();
				}
				for (std::vector<Strip>::iterator sit=fit->strips.begin(); fit->strips.end()!= sit; sit++)
				{
					glBegin(GL_TRIANGLE_STRIP);
					for (std::vector<Vertex>::iterator vit=sit->vertices.begin(); sit->vertices.end()!= vit; vit++)
					{
						glNormal3dv(&vit->normal.x);
						glTexCoord2dv(&vit->texture.s);
						glVertex3dv(&vit->position.x);
					}
					glEnd();
				}
				glPopAttrib();
			}
		}
		glPopAttrib();
	}
	bool operator<(const PartSite &r) const 
	{
		return filepath<r.filepath; 
	}
};

class Occurrence
{
public:
	PartSite		*part;

	wchar_t			filename[MAX_PATH];
	wchar_t			filepath[MAX_PATH];
	wchar_t			occurrencename[MAX_PATH];

	Style			style;
	long			props;
	long			propsex;
	double			range[6];
	double			transform[16];

	std::list<Occurrence*> children;

	Occurrence() : part(0), props(0), propsex(0)
	{
		initialize(0);
	}
	Occurrence(const wchar_t *name) : part(0), props(0), propsex(0)
	{
		initialize(name);
	}
	Occurrence(IAttachmentPtr Attachment) : part(0), props(0), propsex(0) 
	{
		if (Attachment)
		{
			initialize(Attachment->Filename);
			wcscpy(occurrencename,Attachment->OccurrenceName);
			props = Attachment->GetOccurrenceProps();
			propsex = Attachment->GetOccurrenceExtendedProps();
			style.id = Attachment->GetStyle(); // Added in ST3
			copy(sizeof(range)/sizeof(double),range,Attachment->Range);
			copy(sizeof(transform)/sizeof(double),transform,Attachment->Matrix);
		}
		else
		{
			memset(filename,0,sizeof(filename));
			memset(filepath,0,sizeof(filepath));
			memset(occurrencename,0,sizeof(occurrencename));
			memset(transform,0,sizeof(transform));
			memcpy(range,_range,sizeof(_range));
		}
	}
	~Occurrence()
	{
		for (std::list<Occurrence*>::iterator it=children.begin(); children.end()!= it; it++)
		{
			delete (*it);
			(*it) = 0;
		}
		children.clear();
	}
	void initialize(const wchar_t *name)
	{
		setname(name);
		memcpy(range,_range,sizeof(_range));
		memcpy(transform,_identity,sizeof(_identity));
	}
	void setname(const wchar_t *name)
	{
		if (name)
		{
			wcscpy(filename,name);
			wcscpy(filepath,filename);
			if (wchar_t *filepart = wcsrchr(filename,L'\\'))
				wcscpy(filename,++filepart);
			else 
				wcscpy(filename,filepath);
			wcscpy(occurrencename,filename);
		}
		else
		{
			memset(filename,0,sizeof(filename));
			memset(filepath,0,sizeof(filepath));
			memset(occurrencename,0,sizeof(occurrencename));
		}
	}
	void accumulate(double irange[6],double ctw[16])
	{
		double ltw[16];
		double orange[6]; memcpy(orange,range,sizeof(orange));
		multiply(ltw,transform,ctw);
		for (std::list<Occurrence*>::iterator it=children.begin(); children.end()!= it; it++)
		{
			(*it)->accumulate(orange,ltw);
			merge(irange,orange);
		}
		if (part)
		{
			double orange[6]; memcpy(orange,range,sizeof(orange));
			merge(irange,orange);
		}
	}
	void display(bool allowcolors=true)
	{
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glMultMatrixd(transform);
		glPushAttrib(GL_ALL_ATTRIB_BITS);

		if (allowcolors && style.id)
		{
			allowcolors = false;
			style.display();
		}
		if (part)
		{
			part->display(allowcolors);
		}
		for (std::list<Occurrence*>::iterator it=children.begin(); children.end()!= it; it++)
		{
			(*it)->display(allowcolors);
		}

		glPopAttrib();
		glPopMatrix();
	}
	bool operator<(const Occurrence &r) const 
	{
		return occurrencename<r.occurrencename; 
	}
};

#include <sstream>
#include <iomanip>

class Model
{
public:
	Occurrence root;
	std::list<PartSite*> parts;
	Model(const wchar_t *filename)
	{
		load(root,filename);
		root.accumulate(root.range,root.transform);
	}
	~Model()
	{
		for (std::list<Occurrence*>::iterator it=root.children.begin(); root.children.end()!= it; it++)
		{
			delete (*it);
			(*it) = 0;
		}
		root.children.clear();
		for (std::list<PartSite*>::iterator it=parts.begin(); parts.end()!= it; it++)
		{
			delete (*it);
			(*it) = 0;
		}
		parts.clear();
	}
	PartSite *find(const wchar_t *filename)
	{
		for (std::list<PartSite*>::iterator it=parts.begin(); parts.end()!= it; it++)
		{
			if (!_wcsicmp((*it)->filepath,filename))
				return *it;
		}
		return NULL;
	}
	bool load(Occurrence &node, const wchar_t *filename)
	{
		node.setname(filename);
		if (IsSolidEdgePartDocument(filename))
		{
			if (Occurrence *occurrence = new Occurrence(filename))
			{
				node.children.push_back(occurrence);
				PartSite *part = find(occurrence->filename);
				if (!part && (part = new PartSite(occurrence->filename)))
				{
					memcpy(occurrence->range,part->range,sizeof(part->range));
					parts.push_back(part);
				}
				occurrence->part = part;
			}
		}
		else if (IsSolidEdgeAssemblyDocument(filename))
		{
			CLSID FileCLSID = IID_IUnknown;
			bool bIsFamilyOfAssemblies = (filename && SUCCEEDED(GetClassFile(filename, &FileCLSID)) && (CLSID_FamilyOfAssembliesDocument == FileCLSID)) ? true : false;


			ISEFileReaderPtr SEFileReader;
			if (SUCCEEDED(GetClassObject(L"SEREADER.DLL", CLSID_SEFileReader, SEFileReader.GetIID(), (LPVOID*)&SEFileReader)) || SUCCEEDED(SEFileReader.CreateInstance("SEreader.SEFileReader")))
			{
				wchar_t member[MAX_PATH] = {0};
				wcscpy(member,filename);

				if (SUCCEEDED(SEFileReader->OpenEx(bstr_t(filename),SEFileReaderOpenProperties_Shared)))
				{
					// If this is a FamilyOfAssemblies object, then get the list of members and print them out to the debugger output window ...
					if (SEFileReader->FamilyOfAssemblies)
					{
						SAFEARRAY *MemberNames = NULL;
						if (SUCCEEDED(SEFileReader->GetFamilyMemberNames(&MemberNames)) && MemberNames)
						{

							long lLBound = 0;
							long lUBound = 0;

							SafeArrayGetLBound(MemberNames, 1, &lLBound);
							SafeArrayGetUBound(MemberNames, 1, &lUBound); //ubound == lbound :-(

							for (long index=lLBound; index<=lUBound; index++)
							{
								BSTR MemberName = 0;
								if (SUCCEEDED(SafeArrayGetElement(MemberNames, &index, &MemberName)) && MemberName)
								{
									if (!index) wcscat(member,L"!Member1");
									OutputDebugStringW(MemberName);
									OutputDebugStringW(L"\n");
								}
							}
							SafeArrayDestroy(MemberNames);

							// Reopen with the first member
							SEFileReader->OpenEx(bstr_t(member),SEFileReaderOpenProperties_Shared);
						}

					}
					if (IAttachmentsPtr Attachments = SEFileReader->GetAttachments())
					{
						if (long nAttachments = Attachments->GetCount())
						{
							for (long nAttachment=1; nAttachment<(nAttachments+1); nAttachment++)
							{
								if (IAttachmentPtr Attachment = Attachments->Item(nAttachment))
								{
									if (Occurrence *occurrence = new Occurrence(Attachment))
									{
										if (occurrence->style.id)
										{
											IStyleLibraryPtr StyleLibrary;
											if (SUCCEEDED(GetClassObject(L"SEFACET.DLL", CLSID_StyleLibrary, StyleLibrary.GetIID(), (LPVOID*)&StyleLibrary)) || SUCCEEDED(StyleLibrary.CreateInstance("SEFacet.StyleLibrary")))
												StyleLibrary->Open(filename);
											if (StyleLibrary)
												occurrence->style = Style(StyleLibrary,occurrence->style.id);
											StyleLibrary = NULL;
										}
										node.children.push_back(occurrence);
										wchar_t *extension = &occurrence->filename[wcslen(occurrence->filename)-3];
										if (!_wcsicmp(extension,L"asm"))
										{
											load(*occurrence,occurrence->filename);
										}
										else
										{
											PartSite *part = find(occurrence->filename);
											if (!part && (part = new PartSite(occurrence->filename)))
												parts.push_back(part);
											occurrence->part = part;
										}
									}
								}
							}
						}
					}
				}
				SEFileReader = NULL;
				return true;
			}
		}
		return false;
	}
	bool validate()
	{
		return true;
	}
	void display(bool allowcolors=true)
	{
		// Center of model
		double *r = root.range;
		double offset[16] = {1,0,0,0,0,1,0,0,0,0,1,0,-(r[3]+r[0])/2., -(r[4]+r[1])/2., -(r[5]+r[2])/2.,1};

		// Apply centering transform
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glMultMatrixd(offset);

		// Display model occurrences
		for (std::list<Occurrence*>::iterator it=root.children.begin(); root.children.end()!= it; it++)
		{
			(*it)->display(allowcolors);
		}

		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();
	}
};

HDC hDC = NULL;
HGLRC hRC = NULL;
Model *model = NULL;
GLfloat zoom = 1.0;
GLfloat pan[2] = { 0,0 };
GLfloat rotate[3] = { -60, 0, -45 };
POINT lastpoint = {0xFFFF,0xFFFF};
POINT firstpoint = {0xFFFF,0xFFFF};

LRESULT CALLBACK UserWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	RECT sRect = { 0 };
	if (hWnd) GetClientRect(hWnd, &sRect);

	switch (uMsg)
	{
	case WM_CREATE:
		if (hDC = GetDC(hWnd))
		{
			if (!hRC)
			{
				PIXELFORMATDESCRIPTOR pfd = {sizeof(PIXELFORMATDESCRIPTOR),0,(PFD_DOUBLEBUFFER|PFD_DRAW_TO_WINDOW|PFD_SUPPORT_OPENGL),PFD_TYPE_RGBA,(BYTE)GetDeviceCaps(hDC,BITSPIXEL)};
				if (SetPixelFormat(hDC,ChoosePixelFormat(hDC,&pfd),&pfd))
					if (hRC = wglCreateContext(hDC))
						wglMakeCurrent(hDC,hRC);
			}
		}
		break;

	case WM_PAINT:
		if (hDC)
		{
			if (hRC)
			{
				glViewport(0,0,sRect.right,sRect.bottom);

				glMatrixMode(GL_PROJECTION);
				glLoadIdentity();

				glMatrixMode(GL_MODELVIEW);
				glLoadIdentity();

				glClearColor(1,1,1,1);
				glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

				if (model && model->validate())
				{
					// Range diagonal
					GLfloat dx = GLfloat(model->root.range[3]-model->root.range[0]);
					GLfloat dy = GLfloat(model->root.range[4]-model->root.range[1]);
					GLfloat dz = GLfloat(model->root.range[5]-model->root.range[2]);
					GLfloat diagonal = sqrt(dx*dx + dy*dy + dz*dz);

					// Projection
					GLfloat az = GLfloat(2.0f / diagonal);
					GLfloat ay = (sRect.right<sRect.bottom) ? GLfloat(sRect.right) / GLfloat(sRect.bottom) : 1.0f;
					GLfloat ax = (sRect.right>sRect.bottom) ? GLfloat(sRect.bottom) / GLfloat(sRect.right) : 1.0f;
					GLfloat p[16] = { az*ax*zoom,0,0,0, 0,az*ay*zoom,0,0, 0,0,-az / 2.0f,0, GLfloat(pan[0]),GLfloat(pan[1]),0,1 };
					glMatrixMode(GL_PROJECTION);
					glLoadMatrixf(p);

					// Modelview
					glMatrixMode(GL_MODELVIEW);
					glLoadIdentity();

					// Apply user rotations
					glRotated(rotate[0], 1.0, 0.0, 0.0);
					glRotated(rotate[1], 0.0, 1.0, 0.0);
					glRotated(rotate[2], 0.0, 0.0, 1.0);

					// Lighting and depth
					glEnable(GL_LIGHT0);
					glEnable(GL_LIGHTING);
					glDepthFunc(GL_LEQUAL);
					glDepthMask(GL_TRUE);
					glEnable(GL_DEPTH_TEST);

					// Display the model
					model->display(true);
				}
				else
				{
					// GDI-like projection
					double Projection[16] = {(2.0/(double)sRect.right),0,0,0,0,(2.0/-(double)sRect.bottom),0,0,0,0,1,0,-1,1,1,1};
					glMatrixMode(GL_PROJECTION);
					glLoadMatrixd(Projection);

					glBegin(GL_TRIANGLES);
						glColor3f(1,0,0);
						glVertex2f(100,50);
						glColor3f(0,1,0);
						glVertex2f(150,150);
						glColor3f(1,1,0);
						glVertex2f(50,150);
					glEnd();
				}

				SwapBuffers(hDC);
			}
			ValidateRect(hWnd,NULL);
		}
		break;

	case WM_MOUSEMOVE:
		if (GetCapture() == hWnd)
		{
			MSG msg = {0};
			bool bShift = (wParam & MK_SHIFT)?true:false;
			bool bControl = (wParam & MK_CONTROL)?true:false;
			POINT point = {(short)(LOWORD(lParam)), (short)(HIWORD(lParam))};
			while(PeekMessage(&msg,hWnd,WM_MOUSEMOVE,WM_MOUSEMOVE,PM_REMOVE));

			if (lastpoint.x != point.x || lastpoint.y != point.y)
			{
				switch (wParam & (MK_LBUTTON|MK_MBUTTON|MK_RBUTTON))
				{
				case MK_LBUTTON:
				case MK_MBUTTON:
					rotate[0] -= ((lastpoint.y - point.y) / 2);
					rotate[2] -= ((lastpoint.x - point.x) / 2);
					break;
				case MK_RBUTTON:
					if (bControl && !bShift)
					{
						GLfloat ratio = (GLfloat)pow(2.0, (((lastpoint.y - point.y)*8.0) / (double)sRect.bottom));
						if (FLT_EPSILON < fabs(ratio)) { if (0 > ratio) zoom *= ratio; else zoom /= ratio; }
					}
					else if (bShift && !bControl)
					{
						rotate[0] -= ((lastpoint.y - point.y) / 2);
						rotate[2] -= ((lastpoint.x - point.x) / 2);
					}
					else
					{
						pan[0] -= (GLfloat)((lastpoint.x - point.x) << 1) / (GLfloat)sRect.right;
						pan[1] -= (GLfloat)((lastpoint.y - point.y) << 1) / (GLfloat)-sRect.bottom;
					}
					break;
				}
				InvalidateRect(hWnd,NULL,FALSE);
				lastpoint = point;
			}
		}
		break;
	case WM_RBUTTONDBLCLK:
		zoom = 1.0;
		pan[0] = pan[1] = 0;
		InvalidateRect(hWnd, 0, TRUE);
		break;
	case WM_MOUSEWHEEL:
		{
			GLfloat ratio = GLfloat(GET_WHEEL_DELTA_WPARAM(wParam)) / 80.f;
			if (FLT_EPSILON < fabs(ratio)) { if (0 > ratio) zoom *= fabs(ratio); else zoom /= fabs(ratio); }
		}
		InvalidateRect(hWnd, NULL, FALSE);
		break;
	case WM_RBUTTONUP:
	case WM_MBUTTONUP:
	case WM_LBUTTONUP:
		firstpoint.x = firstpoint.y = lastpoint.x = lastpoint.y = 0xFFFF;
		ReleaseCapture();
		break;
	case WM_RBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_LBUTTONDOWN:
		lastpoint.x = firstpoint.x = (short)(LOWORD(lParam));
		lastpoint.y = firstpoint.y = (short)(HIWORD(lParam));
		SetCapture(hWnd);
		break;
	case WM_XBUTTONUP:
	case WM_XBUTTONDOWN:
	case WM_CHAR:
	case WM_KEYUP:
	case WM_KEYDOWN:
	case WM_SYSKEYUP:
	case WM_SYSKEYDOWN:
		{
			if (WM_KEYUP == uMsg)
			{
				switch (wParam)
				{
				case VK_LEFT:
					break;
				case VK_RIGHT:
					break;
				}
			}
		}
		break;

	case WM_DESTROY:
		if (hRC)
		{
			wglMakeCurrent(NULL,NULL);
			wglDeleteContext(hRC);
		}
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}
	return 0;
}

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	//_CrtSetBreakAlloc(146);
	OPENFILENAMEW ofn = {0};
	wchar_t filename[MAX_PATH] = {0};

	ofn.nMaxFile = MAX_PATH;
	ofn.lpstrFile = filename;
	ofn.lStructSize = sizeof(OPENFILENAMEW);
	ofn.Flags = OFN_PATHMUSTEXIST|OFN_FILEMUSTEXIST;
	ofn.lpstrFilter = L"All Solid Edge Files (*.asm;*.par;*.psm;*.pwd)\0*.asm;*.par;*.psm;*.pwd\0Solid Edge Assembly Files (*.asm)\0*.asm\0Solid Edge Part Files (*.par)\0*.par\0All Files (*.*)\0*.*\0\0";
	if (GetOpenFileNameW(&ofn))
	{

		BOOL bSEDocument = IsSolidEdgeDocument(filename);

		if (SUCCEEDED(CoInitialize(0)))
		{
			if (model = new Model(filename))
			{
				WNDCLASSEX wcex = {sizeof(WNDCLASSEX)};
				wcex.hInstance = hInstance;
				wcex.lpszClassName = L"ImportSE";
				wcex.lpfnWndProc = UserWindowProc;
				wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
				if (RegisterClassEx(&wcex))
				{
					if (HWND hWnd = CreateWindow(L"ImportSE", L"ImportSE", (WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN), CW_USEDEFAULT, CW_USEDEFAULT, 400, 400, NULL, NULL, hInstance, NULL))
					{
						MSG msg = {0};
						while (GetMessage(&msg, NULL, 0, 0))
						{
							DispatchMessage(&msg);
						}
					}
				}
				delete model;
				model = 0;
			}
			CoUninitialize();
		}
	}
	return 0;
}

