goat3d

diff exporters/maxgoat/src/maxgoat.cc @ 73:9862541fdcf5

- build qt goatview on linux - fixed line endings in a bunch of files
author John Tsiombikas <nuclear@member.fsf.org>
date Tue, 06 May 2014 03:57:11 +0300
parents 36e39632db75
children 8b156bc5205b
line diff
     1.1 --- a/exporters/maxgoat/src/maxgoat.cc	Tue May 06 03:31:35 2014 +0300
     1.2 +++ b/exporters/maxgoat/src/maxgoat.cc	Tue May 06 03:57:11 2014 +0300
     1.3 @@ -1,892 +1,892 @@
     1.4 -#include <stdio.h>
     1.5 -#include <string.h>
     1.6 -#include <stdlib.h>
     1.7 -#include <ctype.h>
     1.8 -#include <errno.h>
     1.9 -#include <map>
    1.10 -#include <vector>
    1.11 -#include <windows.h>
    1.12 -#include <shlobj.h>
    1.13 -#include "max.h"
    1.14 -#include "impexp.h"		// SceneExport
    1.15 -#include "iparamb2.h"	// ClassDesc2
    1.16 -#include "plugapi.h"
    1.17 -#include "IGame.h"
    1.18 -#include "IGameExport.h"
    1.19 -#include "IGameControl.h"
    1.20 -#include "IConversionmanager.h"
    1.21 -#include "goat3d.h"
    1.22 -#include "config.h"
    1.23 -#include "logger.h"
    1.24 -#include "resource.h"
    1.25 -
    1.26 -
    1.27 -#pragma comment (lib, "core.lib")
    1.28 -#pragma comment (lib, "geom.lib")
    1.29 -#pragma comment (lib, "gfx.lib")
    1.30 -#pragma comment (lib, "mesh.lib")
    1.31 -#pragma comment (lib, "maxutil.lib")
    1.32 -#pragma comment (lib, "maxscrpt.lib")
    1.33 -#pragma comment (lib, "paramblk2.lib")
    1.34 -#pragma comment (lib, "msxml2.lib")
    1.35 -#pragma comment (lib, "igame.lib")
    1.36 -#pragma comment (lib, "comctl32.lib")
    1.37 -
    1.38 -
    1.39 -#define COPYRIGHT	\
    1.40 -	L"Copyright 2014 (C) John Tsiombikas - GNU General Public License v3, see COPYING for details."
    1.41 -#define VERSION(major, minor) \
    1.42 -	((major) * 100 + ((minor) < 10 ? (minor) * 10 : (minor)))
    1.43 -
    1.44 -static INT_PTR CALLBACK scene_gui_handler(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam);
    1.45 -static INT_PTR CALLBACK anim_gui_handler(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam);
    1.46 -static void get_position_keys(IGameControl *ctrl, goat3d_node *node);
    1.47 -static void get_xyz_position_keys(IGameControl *ctrl, goat3d_node *node);
    1.48 -static void get_rotation_keys(IGameControl *ctrl, goat3d_node *node);
    1.49 -static void get_euler_keys(IGameControl *ctrl, goat3d_node *node);
    1.50 -static void get_scaling_keys(IGameControl *ctrl, goat3d_node *node);
    1.51 -static const char *max_string(const MCHAR *wstr);
    1.52 -
    1.53 -HINSTANCE hinst;
    1.54 -
    1.55 -class GoatExporter : public SceneExport {
    1.56 -private:
    1.57 -	std::map<IGameMaterial*, goat3d_material*> mtlmap;
    1.58 -	std::map<IGameNode*, goat3d_node*> nodemap;
    1.59 -
    1.60 -public:
    1.61 -	IGameScene *igame;
    1.62 -
    1.63 -	int DoExport(const MCHAR *name, ExpInterface *eiface, Interface *iface, BOOL silent = FALSE, DWORD opt = 0);
    1.64 -
    1.65 -	void process_materials(goat3d *goat);
    1.66 -
    1.67 -	void process_node(goat3d *goat, goat3d_node *parent, IGameNode *maxnode);
    1.68 -
    1.69 -	void process_mesh(goat3d *goat, goat3d_mesh *mesh, IGameObject *maxobj);
    1.70 -	void process_light(goat3d *goat, goat3d_light *light, IGameObject *maxobj);
    1.71 -	void process_camera(goat3d *goat, goat3d_camera *cam, IGameObject *maxobj);
    1.72 -
    1.73 -
    1.74 -	int ExtCount() { return 1; }
    1.75 -	const TCHAR *Ext(int n) { return L"goatsce"; }
    1.76 -	const TCHAR *LongDesc() { return L"Goat3D scene file"; }
    1.77 -	const TCHAR *ShortDesc() { return L"Goat3D"; }
    1.78 -	const TCHAR *AuthorName() { return L"John Tsiombikas"; }
    1.79 -	const TCHAR *CopyrightMessage() { return COPYRIGHT; }
    1.80 -	const TCHAR *OtherMessage1() { return L"other1"; }
    1.81 -	const TCHAR *OtherMessage2() { return L"other2"; }
    1.82 -	unsigned int Version() { return VERSION(VER_MAJOR, VER_MINOR); }
    1.83 -	void ShowAbout(HWND win) { MessageBoxA(win, "Goat3D exporter plugin", "About this plugin", 0); }
    1.84 -};
    1.85 -
    1.86 -class GoatAnimExporter : public GoatExporter {
    1.87 -private:
    1.88 -public:
    1.89 -	int DoExport(const MCHAR *name, ExpInterface *eiface, Interface *iface, BOOL silent = FALSE, DWORD opt = 0);
    1.90 -
    1.91 -	const TCHAR *Ext(int n) { return L"goatanm"; }
    1.92 -	const TCHAR *LongDesc() { return L"Goat3D animation file"; }
    1.93 -};
    1.94 -
    1.95 -
    1.96 -// ---- GoatExporter implementation ----
    1.97 -
    1.98 -int GoatExporter::DoExport(const MCHAR *name, ExpInterface *eiface, Interface *iface,
    1.99 -		BOOL non_interactive, DWORD opt)
   1.100 -{
   1.101 -	if(!DialogBox(hinst, MAKEINTRESOURCE(IDD_GOAT_SCE), 0, scene_gui_handler)) {
   1.102 -		return IMPEXP_CANCEL;
   1.103 -	}
   1.104 -
   1.105 -	mtlmap.clear();
   1.106 -	nodemap.clear();
   1.107 -
   1.108 -	char fname[512];
   1.109 -	wcstombs(fname, name, sizeof fname - 1);
   1.110 -	for(int i=0; fname[i]; i++) {
   1.111 -		fname[i] = tolower(fname[i]);
   1.112 -	}
   1.113 -	char *basename = (char*)alloca(strlen(fname) + 1);
   1.114 -	strcpy(basename, fname);
   1.115 -	char *suffix = strrchr(basename, '.');
   1.116 -	if(suffix) *suffix = 0;
   1.117 -
   1.118 -	maxlog("Exporting Goat3D Scene (text) file: %s\n", fname);
   1.119 -	if(!(igame = GetIGameInterface())) {
   1.120 -		maxlog("failed to get the igame interface\n");
   1.121 -		return IMPEXP_FAIL;
   1.122 -	}
   1.123 -	IGameConversionManager *cm = GetConversionManager();
   1.124 -	cm->SetCoordSystem(IGameConversionManager::IGAME_OGL);
   1.125 -	igame->InitialiseIGame();
   1.126 -
   1.127 -	goat3d *goat = goat3d_create();
   1.128 -	goat3d_set_name(goat, basename);
   1.129 -
   1.130 -	process_materials(goat);
   1.131 -
   1.132 -	// process all nodes
   1.133 -	for(int i=0; i<igame->GetTopLevelNodeCount(); i++) {
   1.134 -		IGameNode *node = igame->GetTopLevelNode(i);
   1.135 -		process_node(goat, 0, node);
   1.136 -	}
   1.137 -
   1.138 -	if(goat3d_save(goat, fname) == -1) {
   1.139 -		goat3d_free(goat);
   1.140 -		return IMPEXP_FAIL;
   1.141 -	}
   1.142 -
   1.143 -	goat3d_free(goat);
   1.144 -	return IMPEXP_SUCCESS;
   1.145 -}
   1.146 -
   1.147 -void GoatExporter::process_materials(goat3d *goat)
   1.148 -{
   1.149 -	IGameProperty *prop;
   1.150 -
   1.151 -	int num_mtl = igame->GetRootMaterialCount();
   1.152 -	for(int i=0; i<num_mtl; i++) {
   1.153 -		IGameMaterial *maxmtl = igame->GetRootMaterial(i);
   1.154 -		if(maxmtl) {
   1.155 -			goat3d_material *mtl = goat3d_create_mtl();
   1.156 -
   1.157 -			const char *name = max_string(maxmtl->GetMaterialName());
   1.158 -			if(name) {
   1.159 -				goat3d_set_mtl_name(mtl, name);
   1.160 -			}
   1.161 -
   1.162 -			// diffuse
   1.163 -			if((prop = maxmtl->GetDiffuseData())) {
   1.164 -				Point3 diffuse(1, 1, 1);
   1.165 -				prop->GetPropertyValue(diffuse);
   1.166 -				goat3d_set_mtl_attrib3f(mtl, GOAT3D_MAT_ATTR_DIFFUSE, diffuse[0],
   1.167 -					diffuse[1], diffuse[2]);
   1.168 -			}
   1.169 -			// specular
   1.170 -			if((prop = maxmtl->GetSpecularData())) {
   1.171 -				Point3 specular(0, 0, 0);
   1.172 -				prop->GetPropertyValue(specular);
   1.173 -
   1.174 -				float sstr = 1.0;
   1.175 -				if((prop = maxmtl->GetSpecularLevelData())) {
   1.176 -					prop->GetPropertyValue(sstr);
   1.177 -				}
   1.178 -				goat3d_set_mtl_attrib3f(mtl, GOAT3D_MAT_ATTR_SPECULAR, specular[0] * sstr,
   1.179 -					specular[1] * sstr, specular[2] * sstr);
   1.180 -			}
   1.181 -			// shininess
   1.182 -			if((prop = maxmtl->GetGlossinessData())) {
   1.183 -				float shin;
   1.184 -				prop->GetPropertyValue(shin);
   1.185 -				goat3d_set_mtl_attrib1f(mtl, GOAT3D_MAT_ATTR_SHININESS, shin * 100.0);
   1.186 -			}
   1.187 -
   1.188 -			// textures
   1.189 -			for(int j=0; j<maxmtl->GetNumberOfTextureMaps(); j++) {
   1.190 -				IGameTextureMap *tex = maxmtl->GetIGameTextureMap(j);
   1.191 -
   1.192 -				const char *fname = max_string(tex->GetBitmapFileName());
   1.193 -				if(!fname) {
   1.194 -					continue;
   1.195 -				}
   1.196 -
   1.197 -				int slot = tex->GetStdMapSlot();
   1.198 -				switch(slot) {
   1.199 -				case ID_DI:	// diffuse
   1.200 -					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_DIFFUSE, fname);
   1.201 -					break;
   1.202 -
   1.203 -				case ID_SP:
   1.204 -				case ID_SS:
   1.205 -					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_SPECULAR, fname);
   1.206 -					break;
   1.207 -
   1.208 -				case ID_SH:
   1.209 -					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_SHININESS, fname);
   1.210 -					break;
   1.211 -
   1.212 -				case ID_BU:
   1.213 -					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_NORMAL, fname);
   1.214 -					break;
   1.215 -
   1.216 -				case ID_RL:
   1.217 -					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_REFLECTION, fname);
   1.218 -					break;
   1.219 -
   1.220 -				case ID_RR:
   1.221 -					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_TRANSMISSION, fname);
   1.222 -					break;
   1.223 -
   1.224 -				default:
   1.225 -					break;
   1.226 -				}
   1.227 -			}
   1.228 -
   1.229 -			goat3d_add_mtl(goat, mtl);
   1.230 -			mtlmap[maxmtl] = mtl;
   1.231 -		}
   1.232 -	}
   1.233 -}
   1.234 -
   1.235 -void GoatExporter::process_node(goat3d *goat, goat3d_node *parent, IGameNode *maxnode)
   1.236 -{
   1.237 -	goat3d_node *node = goat3d_create_node();
   1.238 -	goat3d_add_node(goat, node);
   1.239 -
   1.240 -	if(parent) {
   1.241 -		goat3d_add_node_child(parent, node);
   1.242 -	}
   1.243 -
   1.244 -	const char *name = max_string(maxnode->GetName());
   1.245 -	if(name) {
   1.246 -		goat3d_set_node_name(node, name);
   1.247 -	}
   1.248 -
   1.249 -	IGameObject *maxobj = maxnode->GetIGameObject();
   1.250 -	IGameObject::ObjectTypes type = maxobj->GetIGameType();
   1.251 -
   1.252 -	switch(type) {
   1.253 -	case IGameObject::IGAME_MESH:
   1.254 -		{
   1.255 -			goat3d_mesh *mesh = goat3d_create_mesh();
   1.256 -			if(name) goat3d_set_mesh_name(mesh, name);
   1.257 -			goat3d_set_node_object(node, GOAT3D_NODE_MESH, mesh);
   1.258 -
   1.259 -			// get the node material and assign it to the mesh
   1.260 -			IGameMaterial *maxmtl = maxnode->GetNodeMaterial();
   1.261 -			goat3d_material *mtl = mtlmap[maxmtl];
   1.262 -			if(mtl) {
   1.263 -				goat3d_set_mesh_mtl(mesh, mtl);
   1.264 -			}
   1.265 -
   1.266 -			process_mesh(goat, mesh, maxobj);
   1.267 -			goat3d_add_mesh(goat, mesh);
   1.268 -		}
   1.269 -		break;
   1.270 -
   1.271 -	case IGameObject::IGAME_LIGHT:
   1.272 -		{
   1.273 -			goat3d_light *light = goat3d_create_light();
   1.274 -			//if(name) goat3d_set_light_name(light, name);
   1.275 -			goat3d_set_node_object(node, GOAT3D_NODE_LIGHT, light);
   1.276 -
   1.277 -			process_light(goat, light, maxobj);
   1.278 -			goat3d_add_light(goat, light);
   1.279 -		}
   1.280 -		break;
   1.281 -
   1.282 -	case IGameObject::IGAME_CAMERA:
   1.283 -		{
   1.284 -			goat3d_camera *cam = goat3d_create_camera();
   1.285 -			//if(name) goat3d_set_camera_name(camera, name);
   1.286 -			goat3d_set_node_object(node, GOAT3D_NODE_CAMERA, cam);
   1.287 -
   1.288 -			process_camera(goat, cam, maxobj);
   1.289 -			goat3d_add_camera(goat, cam);
   1.290 -		}
   1.291 -		break;
   1.292 -
   1.293 -	default:
   1.294 -		// otherwise don't assign an object, essentially treating it as a null node
   1.295 -		break;
   1.296 -	}
   1.297 -
   1.298 -	// grab the animation data
   1.299 -	if(!dynamic_cast<GoatAnimExporter*>(this)) {
   1.300 -		// no animation, just get the static PRS
   1.301 -		GMatrix maxmatrix = maxnode->GetObjectTM();
   1.302 -		Point3 trans = maxmatrix.Translation();
   1.303 -		Quat rot = maxmatrix.Rotation();
   1.304 -		Point3 scale = maxmatrix.Scaling();
   1.305 -
   1.306 -		goat3d_set_node_position(node, trans.x, trans.y, trans.z, 0);
   1.307 -		goat3d_set_node_rotation(node, rot.x, rot.y, rot.z, rot.w, 0);
   1.308 -		goat3d_set_node_scaling(node, scale.x, scale.y, scale.z, 0);
   1.309 -
   1.310 -	} else {
   1.311 -		// exporting animations (if available)
   1.312 -		// TODO sample keys if requested
   1.313 -		IGameControl *ctrl = maxnode->GetIGameControl();
   1.314 -		if(ctrl) {
   1.315 -			if(ctrl->IsAnimated(IGAME_POS) || ctrl->IsAnimated(IGAME_POS_X) ||
   1.316 -					ctrl->IsAnimated(IGAME_POS_Y) || ctrl->IsAnimated(IGAME_POS_Z)) {
   1.317 -				get_position_keys(ctrl, node);
   1.318 -			}
   1.319 -			if(ctrl->IsAnimated(IGAME_ROT) || ctrl->IsAnimated(IGAME_EULER_X) ||
   1.320 -					ctrl->IsAnimated(IGAME_EULER_Y) || ctrl->IsAnimated(IGAME_EULER_Z)) {
   1.321 -				get_rotation_keys(ctrl, node);
   1.322 -			}
   1.323 -			if(ctrl->IsAnimated(IGAME_SCALE)) {
   1.324 -				get_scaling_keys(ctrl, node);
   1.325 -			}
   1.326 -		} else {
   1.327 -			maxlog("%s: failed to get IGameControl for node: %s\n", __FUNCTION__, name);
   1.328 -		}
   1.329 -	}
   1.330 -
   1.331 -	for(int i=0; i<maxnode->GetChildCount(); i++) {
   1.332 -		process_node(goat, node, maxnode->GetNodeChild(i));
   1.333 -	}
   1.334 -}
   1.335 -
   1.336 -#define KEY_TIME(key)	((long)(TicksToSec(key.t) * 1000.0))
   1.337 -
   1.338 -static void get_position_keys(IGameControl *ctrl, goat3d_node *node)
   1.339 -{
   1.340 -	const char *nodename = goat3d_get_node_name(node);
   1.341 -	IGameKeyTab keys;
   1.342 -
   1.343 -	if(ctrl->GetLinearKeys(keys, IGAME_POS)) {
   1.344 -		maxlog("node %s: getting %d linear position keys\n", nodename, keys.Count());
   1.345 -		for(int i=0; i<keys.Count(); i++) {
   1.346 -			Point3 p = keys[i].linearKey.pval;
   1.347 -			goat3d_set_node_position(node, p.x, p.y, p.z, KEY_TIME(keys[i]));
   1.348 -		}
   1.349 -	} else if(ctrl->GetBezierKeys(keys, IGAME_POS)) {
   1.350 -		maxlog("node %s: getting %d bezier position keys\n", nodename, keys.Count());
   1.351 -		for(int i=0; i<keys.Count(); i++) {
   1.352 -			Point3 p = keys[i].bezierKey.pval;
   1.353 -			goat3d_set_node_position(node, p.x, p.y, p.z, KEY_TIME(keys[i]));
   1.354 -		}
   1.355 -	} else if(ctrl->GetTCBKeys(keys, IGAME_POS)) {
   1.356 -		maxlog("node %s: getting %d tcb position keys\n", nodename, keys.Count());
   1.357 -		for(int i=0; i<keys.Count(); i++) {
   1.358 -			Point3 p = keys[i].tcbKey.pval;
   1.359 -			goat3d_set_node_position(node, p.x, p.y, p.z, KEY_TIME(keys[i]));
   1.360 -		}
   1.361 -	} else {
   1.362 -		get_xyz_position_keys(ctrl, node);
   1.363 -	}
   1.364 -}
   1.365 -
   1.366 -static void get_xyz_position_keys(IGameControl *ctrl, goat3d_node *node)
   1.367 -{
   1.368 -	const char *nodename = goat3d_get_node_name(node);
   1.369 -	IGameKeyTab keys;
   1.370 -	IGameControlType postype[] = {IGAME_POS_X, IGAME_POS_Y, IGAME_POS_Z};
   1.371 -	std::map<long, Point3> pos;
   1.372 -
   1.373 -	for(int i=0; i<3; i++) {
   1.374 -		if(ctrl->GetLinearKeys(keys, postype[i])) {
   1.375 -			maxlog("node %s: getting %d linear position %c keys\n", nodename, keys.Count(), "xyz"[i]);
   1.376 -			for(int j=0; j<keys.Count(); j++) {
   1.377 -				long tm = KEY_TIME(keys[j]);
   1.378 -				Point3 v = pos[tm];
   1.379 -				v[i] = keys[j].linearKey.fval;
   1.380 -				pos[tm] = v;
   1.381 -			}
   1.382 -		} else if(ctrl->GetBezierKeys(keys, postype[i])) {
   1.383 -			maxlog("node %s: getting %d bezier position %c keys\n", nodename, keys.Count(), "xyz"[i]);
   1.384 -			for(int j=0; j<keys.Count(); j++) {
   1.385 -				long tm = KEY_TIME(keys[j]);
   1.386 -				Point3 v = pos[tm];
   1.387 -				v[i] = keys[j].bezierKey.fval;
   1.388 -				pos[tm] = v;
   1.389 -			}
   1.390 -		} else if(ctrl->GetTCBKeys(keys, postype[i])) {
   1.391 -			maxlog("node %s: getting %d tcb position %c keys\n", nodename, keys.Count(), "xyz"[i]);
   1.392 -			for(int j=0; j<keys.Count(); j++) {
   1.393 -				long tm = KEY_TIME(keys[j]);
   1.394 -				Point3 v = pos[tm];
   1.395 -				v[i] = keys[j].tcbKey.fval;
   1.396 -				pos[tm] = v;
   1.397 -			}
   1.398 -		}
   1.399 -	}
   1.400 -
   1.401 -	std::map<long, Point3>::iterator it = pos.begin();
   1.402 -	while(it != pos.end()) {
   1.403 -		Point3 p = it->second;
   1.404 -		goat3d_set_node_position(node, p.x, p.y, p.z, it->first);
   1.405 -		++it;
   1.406 -	}
   1.407 -}
   1.408 -
   1.409 -static void get_rotation_keys(IGameControl *ctrl, goat3d_node *node)
   1.410 -{
   1.411 -	const char *nodename = goat3d_get_node_name(node);
   1.412 -	IGameKeyTab rkeys;
   1.413 -
   1.414 -	if(ctrl->GetLinearKeys(rkeys, IGAME_ROT)) {
   1.415 -		maxlog("node %s: getting %d linear rotation keys\n", nodename, rkeys.Count());
   1.416 -		for(int i=0; i<rkeys.Count(); i++) {
   1.417 -			Quat q = rkeys[i].linearKey.qval;
   1.418 -			goat3d_set_node_rotation(node, q.x, q.y, q.z, q.w, KEY_TIME(rkeys[i]));
   1.419 -		}
   1.420 -	} else if(ctrl->GetBezierKeys(rkeys, IGAME_ROT)) {
   1.421 -		maxlog("node %s: getting %d bezier rotation keys\n", nodename, rkeys.Count());
   1.422 -		for(int i=0; i<rkeys.Count(); i++) {
   1.423 -			Quat q = rkeys[i].bezierKey.qval;
   1.424 -			goat3d_set_node_rotation(node, q.x, q.y, q.z, q.w, KEY_TIME(rkeys[i]));
   1.425 -		}
   1.426 -	} else if(ctrl->GetTCBKeys(rkeys, IGAME_ROT)) {
   1.427 -		maxlog("node %s: getting %d TCB rotation keys\n", nodename, rkeys.Count());
   1.428 -		for(int i=0; i<rkeys.Count(); i++) {
   1.429 -			Quat q(rkeys[i].tcbKey.aval);
   1.430 -			goat3d_set_node_rotation(node, q.x, q.y, q.z, q.w, KEY_TIME(rkeys[i]));
   1.431 -		}
   1.432 -	} else {
   1.433 -		get_euler_keys(ctrl, node);
   1.434 -	}
   1.435 -}
   1.436 -
   1.437 -static void get_euler_keys(IGameControl *ctrl, goat3d_node *node)
   1.438 -{
   1.439 -	const char *nodename = goat3d_get_node_name(node);
   1.440 -	IGameKeyTab keys;
   1.441 -	IGameControlType eulertype[] = {IGAME_EULER_X, IGAME_EULER_Y, IGAME_EULER_Z};
   1.442 -	std::map<long, Point3> euler;
   1.443 -
   1.444 -	for(int i=0; i<3; i++) {
   1.445 -		if(ctrl->GetLinearKeys(keys, eulertype[i])) {
   1.446 -			maxlog("node %s: getting %d linear euler %c keys\n", nodename, keys.Count(), "xyz"[i]);
   1.447 -			for(int j=0; j<keys.Count(); j++) {
   1.448 -				long tm = KEY_TIME(keys[j]);
   1.449 -				Point3 v = euler[tm];
   1.450 -				v[i] = keys[j].linearKey.fval;
   1.451 -				euler[tm] = v;
   1.452 -			}
   1.453 -		} else if(ctrl->GetBezierKeys(keys, eulertype[i])) {
   1.454 -			maxlog("node %s: getting %d bezier euler %c keys\n", nodename, keys.Count(), "xyz"[i]);
   1.455 -			for(int j=0; j<keys.Count(); j++) {
   1.456 -				long tm = KEY_TIME(keys[j]);
   1.457 -				Point3 v = euler[tm];
   1.458 -				v[i] = keys[j].bezierKey.fval;
   1.459 -				euler[tm] = v;
   1.460 -			}
   1.461 -		} else if(ctrl->GetTCBKeys(keys, eulertype[i])) {
   1.462 -			maxlog("node %s: getting %d tcb euler %c keys\n", nodename, keys.Count(), "xyz"[i]);
   1.463 -			for(int j=0; j<keys.Count(); j++) {
   1.464 -				long tm = KEY_TIME(keys[j]);
   1.465 -				Point3 v = euler[tm];
   1.466 -				v[i] = keys[j].tcbKey.fval;
   1.467 -				euler[tm] = v;
   1.468 -			}
   1.469 -		}
   1.470 -	}
   1.471 -
   1.472 -	int order = ctrl->GetEulerOrder();
   1.473 -	std::map<long, Point3>::iterator it = euler.begin();
   1.474 -	while(it != euler.end()) {
   1.475 -		Quat q;
   1.476 -		EulerToQuat(it->second, q, order);
   1.477 -		goat3d_set_node_rotation(node, q.x, q.y, q.z, q.w, it->first);
   1.478 -		++it;
   1.479 -	}
   1.480 -}
   1.481 -
   1.482 -static void get_scaling_keys(IGameControl *ctrl, goat3d_node *node)
   1.483 -{
   1.484 -	const char *nodename = goat3d_get_node_name(node);
   1.485 -	IGameKeyTab keys;
   1.486 -
   1.487 -	// XXX the way I'm using the ScaleValue is wrong, but fuck it...
   1.488 -
   1.489 -	if(ctrl->GetLinearKeys(keys, IGAME_SCALE)) {
   1.490 -		maxlog("node %s: getting %d linear scaling keys\n", nodename, keys.Count());
   1.491 -		for(int i=0; i<keys.Count(); i++) {
   1.492 -			ScaleValue s = keys[i].linearKey.sval;
   1.493 -			goat3d_set_node_scaling(node, s.s.x, s.s.y, s.s.z, KEY_TIME(keys[i]));
   1.494 -		}
   1.495 -	} else if(ctrl->GetBezierKeys(keys, IGAME_SCALE)) {
   1.496 -		maxlog("node %s: getting %d bezier scaling keys\n", nodename, keys.Count());
   1.497 -		for(int i=0; i<keys.Count(); i++) {
   1.498 -			ScaleValue s = keys[i].bezierKey.sval;
   1.499 -			goat3d_set_node_scaling(node, s.s.x, s.s.y, s.s.z, KEY_TIME(keys[i]));
   1.500 -		}
   1.501 -	} else if(ctrl->GetTCBKeys(keys, IGAME_SCALE)) {
   1.502 -		maxlog("node %s: getting %d tcb scaling keys\n", nodename, keys.Count());
   1.503 -		for(int i=0; i<keys.Count(); i++) {
   1.504 -			ScaleValue s = keys[i].tcbKey.sval;
   1.505 -			goat3d_set_node_scaling(node, s.s.x, s.s.y, s.s.z, KEY_TIME(keys[i]));
   1.506 -		}
   1.507 -	}
   1.508 -}
   1.509 -
   1.510 -static bool get_anim_bounds(IGameNode *node, long *tstart, long *tend);
   1.511 -static bool get_node_anim_bounds(IGameNode *node, long *tstart, long *tend);
   1.512 -
   1.513 -static bool get_anim_bounds(IGameScene *igame, long *tstart, long *tend)
   1.514 -{
   1.515 -	long tmin = LONG_MAX;
   1.516 -	long tmax = LONG_MIN;
   1.517 -
   1.518 -	int num_nodes = igame->GetTopLevelNodeCount();
   1.519 -	for(int i=0; i<num_nodes; i++) {
   1.520 -		long t0, t1;
   1.521 -		if(get_anim_bounds(igame->GetTopLevelNode(i), &t0, &t1)) {
   1.522 -			if(t0 < tmin) tmin = t0;
   1.523 -			if(t1 > tmax) tmax = t1;
   1.524 -		}
   1.525 -	}
   1.526 -
   1.527 -	if(tmin != LONG_MAX) {
   1.528 -		*tstart = tmin;
   1.529 -		*tend = tmax;
   1.530 -		return true;
   1.531 -	}
   1.532 -	return false;
   1.533 -}
   1.534 -
   1.535 -static bool get_anim_bounds(IGameNode *node, long *tstart, long *tend)
   1.536 -{
   1.537 -	long tmin = LONG_MAX;
   1.538 -	long tmax = LONG_MIN;
   1.539 -
   1.540 -	get_node_anim_bounds(node, &tmin, &tmax);
   1.541 -
   1.542 -	int num_children = node->GetChildCount();
   1.543 -	for(int i=0; i<num_children; i++) {
   1.544 -		long t0, t1;
   1.545 -		if(get_anim_bounds(node->GetNodeChild(i), &t0, &t1)) {
   1.546 -			if(t0 < tmin) tmin = t0;
   1.547 -			if(t1 > tmax) tmax = t1;
   1.548 -		}
   1.549 -	}
   1.550 -
   1.551 -	if(tmin != LONG_MAX) {
   1.552 -		*tstart = tmin;
   1.553 -		*tend = tmax;
   1.554 -		return true;
   1.555 -	}
   1.556 -	return false;
   1.557 -}
   1.558 -
   1.559 -static bool get_node_anim_bounds(IGameNode *node, long *tstart, long *tend)
   1.560 -{
   1.561 -	static const IGameControlType ctypes[] = {
   1.562 -		IGAME_POS, IGAME_POS_X, IGAME_POS_Y, IGAME_POS_Z,
   1.563 -		IGAME_ROT, IGAME_EULER_X, IGAME_EULER_Y, IGAME_EULER_Z,
   1.564 -		IGAME_SCALE
   1.565 -	};
   1.566 -
   1.567 -	// NOTE: apparently if I don't call GetIGameObject, then GetIGameControl always returns null...
   1.568 -	node->GetIGameObject();
   1.569 -	IGameControl *ctrl = node->GetIGameControl();
   1.570 -	if(!ctrl) {
   1.571 -		maxlog("%s: failed to get IGameControl for node: %s\n", __FUNCTION__, max_string(node->GetName()));
   1.572 -		return false;
   1.573 -	}
   1.574 -
   1.575 -	IGameKeyTab keys;
   1.576 -	long t0, t1;
   1.577 -	long tmin = LONG_MAX;
   1.578 -	long tmax = LONG_MIN;
   1.579 -
   1.580 -	for(int i=0; i<sizeof ctypes / sizeof *ctypes; i++) {
   1.581 -		if(ctrl->GetBezierKeys(keys, ctypes[i]) && keys.Count()) {
   1.582 -			t0 = KEY_TIME(keys[0]);
   1.583 -			t1 = KEY_TIME(keys[keys.Count() - 1]);
   1.584 -			if(t0 < tmin) tmin = t0;
   1.585 -			if(t1 > tmax) tmax = t1;
   1.586 -		}
   1.587 -		if(ctrl->GetLinearKeys(keys, ctypes[i]) && keys.Count()) {
   1.588 -			t0 = KEY_TIME(keys[0]);
   1.589 -			t1 = KEY_TIME(keys[keys.Count() - 1]);
   1.590 -			if(t0 < tmin) tmin = t0;
   1.591 -			if(t1 > tmax) tmax = t1;
   1.592 -		}
   1.593 -		if(ctrl->GetTCBKeys(keys, ctypes[i]) && keys.Count()) {
   1.594 -			t0 = KEY_TIME(keys[0]);
   1.595 -			t1 = KEY_TIME(keys[keys.Count() - 1]);
   1.596 -			if(t0 < tmin) tmin = t0;
   1.597 -			if(t1 > tmax) tmax = t1;
   1.598 -		}
   1.599 -	}
   1.600 -
   1.601 -	if(tmin != LONG_MAX) {
   1.602 -		*tstart = tmin;
   1.603 -		*tend = tmax;
   1.604 -		return true;
   1.605 -	}
   1.606 -	return false;
   1.607 -}
   1.608 -
   1.609 -void GoatExporter::process_mesh(goat3d *goat, goat3d_mesh *mesh, IGameObject *maxobj)
   1.610 -{
   1.611 -	IGameMesh *maxmesh = (IGameMesh*)maxobj;
   1.612 -
   1.613 -	maxmesh->SetCreateOptimizedNormalList();	// not needed any more according to docs
   1.614 -	maxobj->InitializeData();
   1.615 -
   1.616 -	int num_verts = maxmesh->GetNumberOfVerts();
   1.617 -	int num_faces = maxmesh->GetNumberOfFaces();
   1.618 -	//assert(maxmesh->GetNumberOfTexVerts() == num_verts);
   1.619 -
   1.620 -	float *vertices = new float[num_verts * 3];
   1.621 -	float *normals = new float[num_verts * 3];
   1.622 -	//float *texcoords = new float[num_verts * 2];
   1.623 -	int *indices = new int[num_faces * 3];
   1.624 -
   1.625 -	for(int i=0; i<num_verts; i++) {
   1.626 -		Point3 v = maxmesh->GetVertex(i, true);
   1.627 -		vertices[i * 3] = v.x;
   1.628 -		vertices[i * 3 + 1] = v.y;
   1.629 -		vertices[i * 3 + 2] = v.z;
   1.630 -	}
   1.631 -
   1.632 -	for(int i=0; i<maxmesh->GetNumberOfNormals(); i++) {
   1.633 -		Point3 norm = maxmesh->GetNormal(i);
   1.634 -
   1.635 -		int vidx = maxmesh->GetNormalVertexIndex(i);
   1.636 -		normals[vidx * 3] = norm.x;
   1.637 -		normals[vidx * 3 + 1] = norm.y;
   1.638 -		normals[vidx * 3 + 2] = norm.z;
   1.639 -	}
   1.640 -
   1.641 -	/*for(int i=0; i<maxmesh->GetNumberOfTexVerts(); i++) {
   1.642 -		Point3 tex = maxmesh->GetTexVertex(i);
   1.643 -
   1.644 -		texcoords[i * 2] = tex.x;
   1.645 -		texcoords[i * 2 + 1] = tex.y;
   1.646 -	}*/
   1.647 -
   1.648 -	// get the faces
   1.649 -	for(int i=0; i<num_faces; i++) {
   1.650 -		FaceEx *face = maxmesh->GetFace(i);
   1.651 -		indices[i * 3] = face->vert[0];
   1.652 -		indices[i * 3 + 1] = face->vert[1];
   1.653 -		indices[i * 3 + 2] = face->vert[2];
   1.654 -		// TODO at some point I'll have to split based on normal/texcoord indices
   1.655 -	}
   1.656 -
   1.657 -	goat3d_set_mesh_attribs(mesh, GOAT3D_MESH_ATTR_VERTEX, vertices, num_verts);
   1.658 -	goat3d_set_mesh_attribs(mesh, GOAT3D_MESH_ATTR_NORMAL, normals, num_verts);
   1.659 -	//goat3d_set_mesh_attribs(mesh, GOAT3D_MESH_ATTR_TEXCOORD, texcoords, num_verts);
   1.660 -	goat3d_set_mesh_faces(mesh, indices, num_faces);
   1.661 -
   1.662 -	delete [] vertices;
   1.663 -	delete [] normals;
   1.664 -	//delete [] texcoords;
   1.665 -	delete [] indices;
   1.666 -}
   1.667 -
   1.668 -void GoatExporter::process_light(goat3d *goat, goat3d_light *light, IGameObject *maxobj)
   1.669 -{
   1.670 -	// TODO
   1.671 -}
   1.672 -
   1.673 -void GoatExporter::process_camera(goat3d *goat, goat3d_camera *cam, IGameObject *maxobj)
   1.674 -{
   1.675 -	// TODO
   1.676 -}
   1.677 -
   1.678 -static INT_PTR CALLBACK scene_gui_handler(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam)
   1.679 -{
   1.680 -	switch(msg) {
   1.681 -	case WM_INITDIALOG:
   1.682 -		CheckDlgButton(win, IDC_GOAT_NODES, 1);
   1.683 -		CheckDlgButton(win, IDC_GOAT_MESHES, 1);
   1.684 -		CheckDlgButton(win, IDC_GOAT_LIGHTS, 1);
   1.685 -		CheckDlgButton(win, IDC_GOAT_CAMERAS, 1);
   1.686 -		break;
   1.687 -
   1.688 -	case WM_COMMAND:
   1.689 -		switch(LOWORD(wparam)) {
   1.690 -		case IDOK:
   1.691 -			EndDialog(win, 1);
   1.692 -			break;
   1.693 -
   1.694 -		case IDCANCEL:
   1.695 -			EndDialog(win, 0);
   1.696 -			break;
   1.697 -
   1.698 -		default:
   1.699 -			return 0;
   1.700 -		}
   1.701 -		break;
   1.702 -
   1.703 -	default:
   1.704 -		return 0;
   1.705 -	}
   1.706 -
   1.707 -	return 1;
   1.708 -}
   1.709 -
   1.710 -
   1.711 -
   1.712 -// ---- GoatAnimExporter implementation ----
   1.713 -static long tstart, tend;
   1.714 -
   1.715 -int GoatAnimExporter::DoExport(const MCHAR *name, ExpInterface *eiface, Interface *iface, BOOL silent, DWORD opt)
   1.716 -{
   1.717 -	if(!(igame = GetIGameInterface())) {
   1.718 -		maxlog("failed to get the igame interface\n");
   1.719 -		return IMPEXP_FAIL;
   1.720 -	}
   1.721 -	IGameConversionManager *cm = GetConversionManager();
   1.722 -	cm->SetCoordSystem(IGameConversionManager::IGAME_OGL);
   1.723 -	igame->InitialiseIGame();
   1.724 -	igame->SetStaticFrame(0);
   1.725 -
   1.726 -	tstart = tend = 0;
   1.727 -	get_anim_bounds(igame, &tstart, &tend);
   1.728 -
   1.729 -	if(!DialogBox(hinst, MAKEINTRESOURCE(IDD_GOAT_ANM), 0, anim_gui_handler)) {
   1.730 -		igame->ReleaseIGame();
   1.731 -		return IMPEXP_CANCEL;
   1.732 -	}
   1.733 -
   1.734 -	char fname[512];
   1.735 -	wcstombs(fname, name, sizeof fname - 1);
   1.736 -	for(int i=0; fname[i]; i++) {
   1.737 -		fname[i] = tolower(fname[i]);
   1.738 -	}
   1.739 -
   1.740 -	maxlog("Exporting Goat3D Animation (text) file: %s\n", fname);
   1.741 -
   1.742 -	goat3d *goat = goat3d_create();
   1.743 -
   1.744 -	// process all nodes
   1.745 -	for(int i=0; i<igame->GetTopLevelNodeCount(); i++) {
   1.746 -		IGameNode *node = igame->GetTopLevelNode(i);
   1.747 -		process_node(goat, 0, node);
   1.748 -	}
   1.749 -
   1.750 -	if(goat3d_save_anim(goat, fname) == -1) {
   1.751 -		goat3d_free(goat);
   1.752 -		igame->ReleaseIGame();
   1.753 -		return IMPEXP_FAIL;
   1.754 -	}
   1.755 -
   1.756 -	goat3d_free(goat);
   1.757 -	igame->ReleaseIGame();
   1.758 -	return IMPEXP_SUCCESS;
   1.759 -}
   1.760 -
   1.761 -static INT_PTR CALLBACK anim_gui_handler(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam)
   1.762 -{
   1.763 -	switch(msg) {
   1.764 -	case WM_INITDIALOG:
   1.765 -		{
   1.766 -			wchar_t buf[128];
   1.767 -			CheckDlgButton(win, IDC_GOAT_ANM_FULL, BST_CHECKED);
   1.768 -			CheckDlgButton(win, IDC_RAD_KEYS_ORIG, BST_CHECKED);
   1.769 -			wsprintf(buf, L"%ld", tstart);
   1.770 -			SetDlgItemText(win, IDC_EDIT_TSTART, buf);
   1.771 -			wsprintf(buf, L"%ld", tend);
   1.772 -			SetDlgItemText(win, IDC_EDIT_TEND, buf);
   1.773 -		}
   1.774 -		break;
   1.775 -
   1.776 -	case WM_COMMAND:
   1.777 -		switch(LOWORD(wparam)) {
   1.778 -		case IDOK:
   1.779 -			EndDialog(win, 1);
   1.780 -			break;
   1.781 -
   1.782 -		case IDCANCEL:
   1.783 -			EndDialog(win, 0);
   1.784 -			break;
   1.785 -
   1.786 -		default:
   1.787 -			return 0;
   1.788 -		}
   1.789 -		break;
   1.790 -
   1.791 -	default:
   1.792 -		return 0;
   1.793 -	}
   1.794 -
   1.795 -	return 1;
   1.796 -}
   1.797 -
   1.798 -// ------------------------------------------
   1.799 -
   1.800 -class GoatClassDesc : public ClassDesc2 {
   1.801 -public:
   1.802 -	int IsPublic() { return TRUE; }
   1.803 -	void *Create(BOOL loading = FALSE) { return new GoatExporter; }
   1.804 -	const TCHAR *ClassName() { return L"GoatExporter"; }
   1.805 -	SClass_ID SuperClassID() { return SCENE_EXPORT_CLASS_ID; }
   1.806 -	Class_ID ClassID() { return Class_ID(0x77050f0d, 0x7d4c5ab5); }
   1.807 -	const TCHAR *Category() { return L"Mutant Stargoat"; }
   1.808 -
   1.809 -	const TCHAR *InternalName() { return L"GoatExporter"; }
   1.810 -	HINSTANCE HInstance() { return hinst; }
   1.811 -};
   1.812 -
   1.813 -class GoatAnimClassDesc : public ClassDesc2 {
   1.814 -public:
   1.815 -	int IsPublic() { return TRUE; }
   1.816 -	void *Create(BOOL loading = FALSE) { return new GoatAnimExporter; }
   1.817 -	const TCHAR *ClassName() { return L"GoatAnimExporter"; }
   1.818 -	SClass_ID SuperClassID() { return SCENE_EXPORT_CLASS_ID; }
   1.819 -	Class_ID ClassID() { return Class_ID(0x51b94924, 0x2e0332f3); }
   1.820 -	const TCHAR *Category() { return L"Mutant Stargoat"; }
   1.821 -
   1.822 -	const TCHAR *InternalName() { return L"GoatAnimExporter"; }
   1.823 -	HINSTANCE HInstance() { return hinst; }
   1.824 -};
   1.825 -
   1.826 -// TODO: make 2 class descriptors, one for goat3d, one for goat3danim
   1.827 -static GoatClassDesc class_desc;
   1.828 -static GoatAnimClassDesc anim_class_desc;
   1.829 -
   1.830 -BOOL WINAPI DllMain(HINSTANCE inst_handle, ULONG reason, void *reserved)
   1.831 -{
   1.832 -	if(reason == DLL_PROCESS_ATTACH) {
   1.833 -		hinst = inst_handle;
   1.834 -		DisableThreadLibraryCalls(hinst);
   1.835 -	}
   1.836 -	return TRUE;
   1.837 -}
   1.838 -
   1.839 -extern "C" {
   1.840 -
   1.841 -__declspec(dllexport) const TCHAR *LibDescription()
   1.842 -{
   1.843 -	return L"test exporter";
   1.844 -}
   1.845 -
   1.846 -__declspec(dllexport) int LibNumberClasses()
   1.847 -{
   1.848 -	return 1;
   1.849 -}
   1.850 -
   1.851 -__declspec(dllexport) ClassDesc *LibClassDesc(int i)
   1.852 -{
   1.853 -	switch(i) {
   1.854 -	case 0:
   1.855 -		return &class_desc;
   1.856 -	case 1:
   1.857 -		return &anim_class_desc;
   1.858 -	default:
   1.859 -		break;
   1.860 -	}
   1.861 -	return 0;
   1.862 -}
   1.863 -
   1.864 -__declspec(dllexport) ULONG LibVersion()
   1.865 -{
   1.866 -	return Get3DSMAXVersion();
   1.867 -}
   1.868 -
   1.869 -__declspec(dllexport) int LibInitialize()
   1.870 -{
   1.871 -	static char path[1024];
   1.872 -
   1.873 -	SHGetFolderPathA(0, CSIDL_PERSONAL, 0, 0, path);
   1.874 -	strcat(path, "/testexp.log");
   1.875 -
   1.876 -	maxlog_open(path);
   1.877 -	return TRUE;
   1.878 -}
   1.879 -
   1.880 -__declspec(dllexport) int LibShutdown()
   1.881 -{
   1.882 -	maxlog_close();
   1.883 -	return TRUE;
   1.884 -}
   1.885 -
   1.886 -}	// extern "C"
   1.887 -
   1.888 -
   1.889 -static const char *max_string(const MCHAR *wstr)
   1.890 -{
   1.891 -	if(!wstr) return 0;
   1.892 -	static char str[512];
   1.893 -	wcstombs(str, wstr, sizeof str - 1);
   1.894 -	return str;
   1.895 -}
   1.896 \ No newline at end of file
   1.897 +#include <stdio.h>
   1.898 +#include <string.h>
   1.899 +#include <stdlib.h>
   1.900 +#include <ctype.h>
   1.901 +#include <errno.h>
   1.902 +#include <map>
   1.903 +#include <vector>
   1.904 +#include <windows.h>
   1.905 +#include <shlobj.h>
   1.906 +#include "max.h"
   1.907 +#include "impexp.h"		// SceneExport
   1.908 +#include "iparamb2.h"	// ClassDesc2
   1.909 +#include "plugapi.h"
   1.910 +#include "IGame.h"
   1.911 +#include "IGameExport.h"
   1.912 +#include "IGameControl.h"
   1.913 +#include "IConversionmanager.h"
   1.914 +#include "goat3d.h"
   1.915 +#include "config.h"
   1.916 +#include "logger.h"
   1.917 +#include "resource.h"
   1.918 +
   1.919 +
   1.920 +#pragma comment (lib, "core.lib")
   1.921 +#pragma comment (lib, "geom.lib")
   1.922 +#pragma comment (lib, "gfx.lib")
   1.923 +#pragma comment (lib, "mesh.lib")
   1.924 +#pragma comment (lib, "maxutil.lib")
   1.925 +#pragma comment (lib, "maxscrpt.lib")
   1.926 +#pragma comment (lib, "paramblk2.lib")
   1.927 +#pragma comment (lib, "msxml2.lib")
   1.928 +#pragma comment (lib, "igame.lib")
   1.929 +#pragma comment (lib, "comctl32.lib")
   1.930 +
   1.931 +
   1.932 +#define COPYRIGHT	\
   1.933 +	L"Copyright 2014 (C) John Tsiombikas - GNU General Public License v3, see COPYING for details."
   1.934 +#define VERSION(major, minor) \
   1.935 +	((major) * 100 + ((minor) < 10 ? (minor) * 10 : (minor)))
   1.936 +
   1.937 +static INT_PTR CALLBACK scene_gui_handler(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam);
   1.938 +static INT_PTR CALLBACK anim_gui_handler(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam);
   1.939 +static void get_position_keys(IGameControl *ctrl, goat3d_node *node);
   1.940 +static void get_xyz_position_keys(IGameControl *ctrl, goat3d_node *node);
   1.941 +static void get_rotation_keys(IGameControl *ctrl, goat3d_node *node);
   1.942 +static void get_euler_keys(IGameControl *ctrl, goat3d_node *node);
   1.943 +static void get_scaling_keys(IGameControl *ctrl, goat3d_node *node);
   1.944 +static const char *max_string(const MCHAR *wstr);
   1.945 +
   1.946 +HINSTANCE hinst;
   1.947 +
   1.948 +class GoatExporter : public SceneExport {
   1.949 +private:
   1.950 +	std::map<IGameMaterial*, goat3d_material*> mtlmap;
   1.951 +	std::map<IGameNode*, goat3d_node*> nodemap;
   1.952 +
   1.953 +public:
   1.954 +	IGameScene *igame;
   1.955 +
   1.956 +	int DoExport(const MCHAR *name, ExpInterface *eiface, Interface *iface, BOOL silent = FALSE, DWORD opt = 0);
   1.957 +
   1.958 +	void process_materials(goat3d *goat);
   1.959 +
   1.960 +	void process_node(goat3d *goat, goat3d_node *parent, IGameNode *maxnode);
   1.961 +
   1.962 +	void process_mesh(goat3d *goat, goat3d_mesh *mesh, IGameObject *maxobj);
   1.963 +	void process_light(goat3d *goat, goat3d_light *light, IGameObject *maxobj);
   1.964 +	void process_camera(goat3d *goat, goat3d_camera *cam, IGameObject *maxobj);
   1.965 +
   1.966 +
   1.967 +	int ExtCount() { return 1; }
   1.968 +	const TCHAR *Ext(int n) { return L"goatsce"; }
   1.969 +	const TCHAR *LongDesc() { return L"Goat3D scene file"; }
   1.970 +	const TCHAR *ShortDesc() { return L"Goat3D"; }
   1.971 +	const TCHAR *AuthorName() { return L"John Tsiombikas"; }
   1.972 +	const TCHAR *CopyrightMessage() { return COPYRIGHT; }
   1.973 +	const TCHAR *OtherMessage1() { return L"other1"; }
   1.974 +	const TCHAR *OtherMessage2() { return L"other2"; }
   1.975 +	unsigned int Version() { return VERSION(VER_MAJOR, VER_MINOR); }
   1.976 +	void ShowAbout(HWND win) { MessageBoxA(win, "Goat3D exporter plugin", "About this plugin", 0); }
   1.977 +};
   1.978 +
   1.979 +class GoatAnimExporter : public GoatExporter {
   1.980 +private:
   1.981 +public:
   1.982 +	int DoExport(const MCHAR *name, ExpInterface *eiface, Interface *iface, BOOL silent = FALSE, DWORD opt = 0);
   1.983 +
   1.984 +	const TCHAR *Ext(int n) { return L"goatanm"; }
   1.985 +	const TCHAR *LongDesc() { return L"Goat3D animation file"; }
   1.986 +};
   1.987 +
   1.988 +
   1.989 +// ---- GoatExporter implementation ----
   1.990 +
   1.991 +int GoatExporter::DoExport(const MCHAR *name, ExpInterface *eiface, Interface *iface,
   1.992 +		BOOL non_interactive, DWORD opt)
   1.993 +{
   1.994 +	if(!DialogBox(hinst, MAKEINTRESOURCE(IDD_GOAT_SCE), 0, scene_gui_handler)) {
   1.995 +		return IMPEXP_CANCEL;
   1.996 +	}
   1.997 +
   1.998 +	mtlmap.clear();
   1.999 +	nodemap.clear();
  1.1000 +
  1.1001 +	char fname[512];
  1.1002 +	wcstombs(fname, name, sizeof fname - 1);
  1.1003 +	for(int i=0; fname[i]; i++) {
  1.1004 +		fname[i] = tolower(fname[i]);
  1.1005 +	}
  1.1006 +	char *basename = (char*)alloca(strlen(fname) + 1);
  1.1007 +	strcpy(basename, fname);
  1.1008 +	char *suffix = strrchr(basename, '.');
  1.1009 +	if(suffix) *suffix = 0;
  1.1010 +
  1.1011 +	maxlog("Exporting Goat3D Scene (text) file: %s\n", fname);
  1.1012 +	if(!(igame = GetIGameInterface())) {
  1.1013 +		maxlog("failed to get the igame interface\n");
  1.1014 +		return IMPEXP_FAIL;
  1.1015 +	}
  1.1016 +	IGameConversionManager *cm = GetConversionManager();
  1.1017 +	cm->SetCoordSystem(IGameConversionManager::IGAME_OGL);
  1.1018 +	igame->InitialiseIGame();
  1.1019 +
  1.1020 +	goat3d *goat = goat3d_create();
  1.1021 +	goat3d_set_name(goat, basename);
  1.1022 +
  1.1023 +	process_materials(goat);
  1.1024 +
  1.1025 +	// process all nodes
  1.1026 +	for(int i=0; i<igame->GetTopLevelNodeCount(); i++) {
  1.1027 +		IGameNode *node = igame->GetTopLevelNode(i);
  1.1028 +		process_node(goat, 0, node);
  1.1029 +	}
  1.1030 +
  1.1031 +	if(goat3d_save(goat, fname) == -1) {
  1.1032 +		goat3d_free(goat);
  1.1033 +		return IMPEXP_FAIL;
  1.1034 +	}
  1.1035 +
  1.1036 +	goat3d_free(goat);
  1.1037 +	return IMPEXP_SUCCESS;
  1.1038 +}
  1.1039 +
  1.1040 +void GoatExporter::process_materials(goat3d *goat)
  1.1041 +{
  1.1042 +	IGameProperty *prop;
  1.1043 +
  1.1044 +	int num_mtl = igame->GetRootMaterialCount();
  1.1045 +	for(int i=0; i<num_mtl; i++) {
  1.1046 +		IGameMaterial *maxmtl = igame->GetRootMaterial(i);
  1.1047 +		if(maxmtl) {
  1.1048 +			goat3d_material *mtl = goat3d_create_mtl();
  1.1049 +
  1.1050 +			const char *name = max_string(maxmtl->GetMaterialName());
  1.1051 +			if(name) {
  1.1052 +				goat3d_set_mtl_name(mtl, name);
  1.1053 +			}
  1.1054 +
  1.1055 +			// diffuse
  1.1056 +			if((prop = maxmtl->GetDiffuseData())) {
  1.1057 +				Point3 diffuse(1, 1, 1);
  1.1058 +				prop->GetPropertyValue(diffuse);
  1.1059 +				goat3d_set_mtl_attrib3f(mtl, GOAT3D_MAT_ATTR_DIFFUSE, diffuse[0],
  1.1060 +					diffuse[1], diffuse[2]);
  1.1061 +			}
  1.1062 +			// specular
  1.1063 +			if((prop = maxmtl->GetSpecularData())) {
  1.1064 +				Point3 specular(0, 0, 0);
  1.1065 +				prop->GetPropertyValue(specular);
  1.1066 +
  1.1067 +				float sstr = 1.0;
  1.1068 +				if((prop = maxmtl->GetSpecularLevelData())) {
  1.1069 +					prop->GetPropertyValue(sstr);
  1.1070 +				}
  1.1071 +				goat3d_set_mtl_attrib3f(mtl, GOAT3D_MAT_ATTR_SPECULAR, specular[0] * sstr,
  1.1072 +					specular[1] * sstr, specular[2] * sstr);
  1.1073 +			}
  1.1074 +			// shininess
  1.1075 +			if((prop = maxmtl->GetGlossinessData())) {
  1.1076 +				float shin;
  1.1077 +				prop->GetPropertyValue(shin);
  1.1078 +				goat3d_set_mtl_attrib1f(mtl, GOAT3D_MAT_ATTR_SHININESS, shin * 100.0);
  1.1079 +			}
  1.1080 +
  1.1081 +			// textures
  1.1082 +			for(int j=0; j<maxmtl->GetNumberOfTextureMaps(); j++) {
  1.1083 +				IGameTextureMap *tex = maxmtl->GetIGameTextureMap(j);
  1.1084 +
  1.1085 +				const char *fname = max_string(tex->GetBitmapFileName());
  1.1086 +				if(!fname) {
  1.1087 +					continue;
  1.1088 +				}
  1.1089 +
  1.1090 +				int slot = tex->GetStdMapSlot();
  1.1091 +				switch(slot) {
  1.1092 +				case ID_DI:	// diffuse
  1.1093 +					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_DIFFUSE, fname);
  1.1094 +					break;
  1.1095 +
  1.1096 +				case ID_SP:
  1.1097 +				case ID_SS:
  1.1098 +					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_SPECULAR, fname);
  1.1099 +					break;
  1.1100 +
  1.1101 +				case ID_SH:
  1.1102 +					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_SHININESS, fname);
  1.1103 +					break;
  1.1104 +
  1.1105 +				case ID_BU:
  1.1106 +					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_NORMAL, fname);
  1.1107 +					break;
  1.1108 +
  1.1109 +				case ID_RL:
  1.1110 +					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_REFLECTION, fname);
  1.1111 +					break;
  1.1112 +
  1.1113 +				case ID_RR:
  1.1114 +					goat3d_set_mtl_attrib_map(mtl, GOAT3D_MAT_ATTR_TRANSMISSION, fname);
  1.1115 +					break;
  1.1116 +
  1.1117 +				default:
  1.1118 +					break;
  1.1119 +				}
  1.1120 +			}
  1.1121 +
  1.1122 +			goat3d_add_mtl(goat, mtl);
  1.1123 +			mtlmap[maxmtl] = mtl;
  1.1124 +		}
  1.1125 +	}
  1.1126 +}
  1.1127 +
  1.1128 +void GoatExporter::process_node(goat3d *goat, goat3d_node *parent, IGameNode *maxnode)
  1.1129 +{
  1.1130 +	goat3d_node *node = goat3d_create_node();
  1.1131 +	goat3d_add_node(goat, node);
  1.1132 +
  1.1133 +	if(parent) {
  1.1134 +		goat3d_add_node_child(parent, node);
  1.1135 +	}
  1.1136 +
  1.1137 +	const char *name = max_string(maxnode->GetName());
  1.1138 +	if(name) {
  1.1139 +		goat3d_set_node_name(node, name);
  1.1140 +	}
  1.1141 +
  1.1142 +	IGameObject *maxobj = maxnode->GetIGameObject();
  1.1143 +	IGameObject::ObjectTypes type = maxobj->GetIGameType();
  1.1144 +
  1.1145 +	switch(type) {
  1.1146 +	case IGameObject::IGAME_MESH:
  1.1147 +		{
  1.1148 +			goat3d_mesh *mesh = goat3d_create_mesh();
  1.1149 +			if(name) goat3d_set_mesh_name(mesh, name);
  1.1150 +			goat3d_set_node_object(node, GOAT3D_NODE_MESH, mesh);
  1.1151 +
  1.1152 +			// get the node material and assign it to the mesh
  1.1153 +			IGameMaterial *maxmtl = maxnode->GetNodeMaterial();
  1.1154 +			goat3d_material *mtl = mtlmap[maxmtl];
  1.1155 +			if(mtl) {
  1.1156 +				goat3d_set_mesh_mtl(mesh, mtl);
  1.1157 +			}
  1.1158 +
  1.1159 +			process_mesh(goat, mesh, maxobj);
  1.1160 +			goat3d_add_mesh(goat, mesh);
  1.1161 +		}
  1.1162 +		break;
  1.1163 +
  1.1164 +	case IGameObject::IGAME_LIGHT:
  1.1165 +		{
  1.1166 +			goat3d_light *light = goat3d_create_light();
  1.1167 +			//if(name) goat3d_set_light_name(light, name);
  1.1168 +			goat3d_set_node_object(node, GOAT3D_NODE_LIGHT, light);
  1.1169 +
  1.1170 +			process_light(goat, light, maxobj);
  1.1171 +			goat3d_add_light(goat, light);
  1.1172 +		}
  1.1173 +		break;
  1.1174 +
  1.1175 +	case IGameObject::IGAME_CAMERA:
  1.1176 +		{
  1.1177 +			goat3d_camera *cam = goat3d_create_camera();
  1.1178 +			//if(name) goat3d_set_camera_name(camera, name);
  1.1179 +			goat3d_set_node_object(node, GOAT3D_NODE_CAMERA, cam);
  1.1180 +
  1.1181 +			process_camera(goat, cam, maxobj);
  1.1182 +			goat3d_add_camera(goat, cam);
  1.1183 +		}
  1.1184 +		break;
  1.1185 +
  1.1186 +	default:
  1.1187 +		// otherwise don't assign an object, essentially treating it as a null node
  1.1188 +		break;
  1.1189 +	}
  1.1190 +
  1.1191 +	// grab the animation data
  1.1192 +	if(!dynamic_cast<GoatAnimExporter*>(this)) {
  1.1193 +		// no animation, just get the static PRS
  1.1194 +		GMatrix maxmatrix = maxnode->GetObjectTM();
  1.1195 +		Point3 trans = maxmatrix.Translation();
  1.1196 +		Quat rot = maxmatrix.Rotation();
  1.1197 +		Point3 scale = maxmatrix.Scaling();
  1.1198 +
  1.1199 +		goat3d_set_node_position(node, trans.x, trans.y, trans.z, 0);
  1.1200 +		goat3d_set_node_rotation(node, rot.x, rot.y, rot.z, rot.w, 0);
  1.1201 +		goat3d_set_node_scaling(node, scale.x, scale.y, scale.z, 0);
  1.1202 +
  1.1203 +	} else {
  1.1204 +		// exporting animations (if available)
  1.1205 +		// TODO sample keys if requested
  1.1206 +		IGameControl *ctrl = maxnode->GetIGameControl();
  1.1207 +		if(ctrl) {
  1.1208 +			if(ctrl->IsAnimated(IGAME_POS) || ctrl->IsAnimated(IGAME_POS_X) ||
  1.1209 +					ctrl->IsAnimated(IGAME_POS_Y) || ctrl->IsAnimated(IGAME_POS_Z)) {
  1.1210 +				get_position_keys(ctrl, node);
  1.1211 +			}
  1.1212 +			if(ctrl->IsAnimated(IGAME_ROT) || ctrl->IsAnimated(IGAME_EULER_X) ||
  1.1213 +					ctrl->IsAnimated(IGAME_EULER_Y) || ctrl->IsAnimated(IGAME_EULER_Z)) {
  1.1214 +				get_rotation_keys(ctrl, node);
  1.1215 +			}
  1.1216 +			if(ctrl->IsAnimated(IGAME_SCALE)) {
  1.1217 +				get_scaling_keys(ctrl, node);
  1.1218 +			}
  1.1219 +		} else {
  1.1220 +			maxlog("%s: failed to get IGameControl for node: %s\n", __FUNCTION__, name);
  1.1221 +		}
  1.1222 +	}
  1.1223 +
  1.1224 +	for(int i=0; i<maxnode->GetChildCount(); i++) {
  1.1225 +		process_node(goat, node, maxnode->GetNodeChild(i));
  1.1226 +	}
  1.1227 +}
  1.1228 +
  1.1229 +#define KEY_TIME(key)	((long)(TicksToSec(key.t) * 1000.0))
  1.1230 +
  1.1231 +static void get_position_keys(IGameControl *ctrl, goat3d_node *node)
  1.1232 +{
  1.1233 +	const char *nodename = goat3d_get_node_name(node);
  1.1234 +	IGameKeyTab keys;
  1.1235 +
  1.1236 +	if(ctrl->GetLinearKeys(keys, IGAME_POS)) {
  1.1237 +		maxlog("node %s: getting %d linear position keys\n", nodename, keys.Count());
  1.1238 +		for(int i=0; i<keys.Count(); i++) {
  1.1239 +			Point3 p = keys[i].linearKey.pval;
  1.1240 +			goat3d_set_node_position(node, p.x, p.y, p.z, KEY_TIME(keys[i]));
  1.1241 +		}
  1.1242 +	} else if(ctrl->GetBezierKeys(keys, IGAME_POS)) {
  1.1243 +		maxlog("node %s: getting %d bezier position keys\n", nodename, keys.Count());
  1.1244 +		for(int i=0; i<keys.Count(); i++) {
  1.1245 +			Point3 p = keys[i].bezierKey.pval;
  1.1246 +			goat3d_set_node_position(node, p.x, p.y, p.z, KEY_TIME(keys[i]));
  1.1247 +		}
  1.1248 +	} else if(ctrl->GetTCBKeys(keys, IGAME_POS)) {
  1.1249 +		maxlog("node %s: getting %d tcb position keys\n", nodename, keys.Count());
  1.1250 +		for(int i=0; i<keys.Count(); i++) {
  1.1251 +			Point3 p = keys[i].tcbKey.pval;
  1.1252 +			goat3d_set_node_position(node, p.x, p.y, p.z, KEY_TIME(keys[i]));
  1.1253 +		}
  1.1254 +	} else {
  1.1255 +		get_xyz_position_keys(ctrl, node);
  1.1256 +	}
  1.1257 +}
  1.1258 +
  1.1259 +static void get_xyz_position_keys(IGameControl *ctrl, goat3d_node *node)
  1.1260 +{
  1.1261 +	const char *nodename = goat3d_get_node_name(node);
  1.1262 +	IGameKeyTab keys;
  1.1263 +	IGameControlType postype[] = {IGAME_POS_X, IGAME_POS_Y, IGAME_POS_Z};
  1.1264 +	std::map<long, Point3> pos;
  1.1265 +
  1.1266 +	for(int i=0; i<3; i++) {
  1.1267 +		if(ctrl->GetLinearKeys(keys, postype[i])) {
  1.1268 +			maxlog("node %s: getting %d linear position %c keys\n", nodename, keys.Count(), "xyz"[i]);
  1.1269 +			for(int j=0; j<keys.Count(); j++) {
  1.1270 +				long tm = KEY_TIME(keys[j]);
  1.1271 +				Point3 v = pos[tm];
  1.1272 +				v[i] = keys[j].linearKey.fval;
  1.1273 +				pos[tm] = v;
  1.1274 +			}
  1.1275 +		} else if(ctrl->GetBezierKeys(keys, postype[i])) {
  1.1276 +			maxlog("node %s: getting %d bezier position %c keys\n", nodename, keys.Count(), "xyz"[i]);
  1.1277 +			for(int j=0; j<keys.Count(); j++) {
  1.1278 +				long tm = KEY_TIME(keys[j]);
  1.1279 +				Point3 v = pos[tm];
  1.1280 +				v[i] = keys[j].bezierKey.fval;
  1.1281 +				pos[tm] = v;
  1.1282 +			}
  1.1283 +		} else if(ctrl->GetTCBKeys(keys, postype[i])) {
  1.1284 +			maxlog("node %s: getting %d tcb position %c keys\n", nodename, keys.Count(), "xyz"[i]);
  1.1285 +			for(int j=0; j<keys.Count(); j++) {
  1.1286 +				long tm = KEY_TIME(keys[j]);
  1.1287 +				Point3 v = pos[tm];
  1.1288 +				v[i] = keys[j].tcbKey.fval;
  1.1289 +				pos[tm] = v;
  1.1290 +			}
  1.1291 +		}
  1.1292 +	}
  1.1293 +
  1.1294 +	std::map<long, Point3>::iterator it = pos.begin();
  1.1295 +	while(it != pos.end()) {
  1.1296 +		Point3 p = it->second;
  1.1297 +		goat3d_set_node_position(node, p.x, p.y, p.z, it->first);
  1.1298 +		++it;
  1.1299 +	}
  1.1300 +}
  1.1301 +
  1.1302 +static void get_rotation_keys(IGameControl *ctrl, goat3d_node *node)
  1.1303 +{
  1.1304 +	const char *nodename = goat3d_get_node_name(node);
  1.1305 +	IGameKeyTab rkeys;
  1.1306 +
  1.1307 +	if(ctrl->GetLinearKeys(rkeys, IGAME_ROT)) {
  1.1308 +		maxlog("node %s: getting %d linear rotation keys\n", nodename, rkeys.Count());
  1.1309 +		for(int i=0; i<rkeys.Count(); i++) {
  1.1310 +			Quat q = rkeys[i].linearKey.qval;
  1.1311 +			goat3d_set_node_rotation(node, q.x, q.y, q.z, q.w, KEY_TIME(rkeys[i]));
  1.1312 +		}
  1.1313 +	} else if(ctrl->GetBezierKeys(rkeys, IGAME_ROT)) {
  1.1314 +		maxlog("node %s: getting %d bezier rotation keys\n", nodename, rkeys.Count());
  1.1315 +		for(int i=0; i<rkeys.Count(); i++) {
  1.1316 +			Quat q = rkeys[i].bezierKey.qval;
  1.1317 +			goat3d_set_node_rotation(node, q.x, q.y, q.z, q.w, KEY_TIME(rkeys[i]));
  1.1318 +		}
  1.1319 +	} else if(ctrl->GetTCBKeys(rkeys, IGAME_ROT)) {
  1.1320 +		maxlog("node %s: getting %d TCB rotation keys\n", nodename, rkeys.Count());
  1.1321 +		for(int i=0; i<rkeys.Count(); i++) {
  1.1322 +			Quat q(rkeys[i].tcbKey.aval);
  1.1323 +			goat3d_set_node_rotation(node, q.x, q.y, q.z, q.w, KEY_TIME(rkeys[i]));
  1.1324 +		}
  1.1325 +	} else {
  1.1326 +		get_euler_keys(ctrl, node);
  1.1327 +	}
  1.1328 +}
  1.1329 +
  1.1330 +static void get_euler_keys(IGameControl *ctrl, goat3d_node *node)
  1.1331 +{
  1.1332 +	const char *nodename = goat3d_get_node_name(node);
  1.1333 +	IGameKeyTab keys;
  1.1334 +	IGameControlType eulertype[] = {IGAME_EULER_X, IGAME_EULER_Y, IGAME_EULER_Z};
  1.1335 +	std::map<long, Point3> euler;
  1.1336 +
  1.1337 +	for(int i=0; i<3; i++) {
  1.1338 +		if(ctrl->GetLinearKeys(keys, eulertype[i])) {
  1.1339 +			maxlog("node %s: getting %d linear euler %c keys\n", nodename, keys.Count(), "xyz"[i]);
  1.1340 +			for(int j=0; j<keys.Count(); j++) {
  1.1341 +				long tm = KEY_TIME(keys[j]);
  1.1342 +				Point3 v = euler[tm];
  1.1343 +				v[i] = keys[j].linearKey.fval;
  1.1344 +				euler[tm] = v;
  1.1345 +			}
  1.1346 +		} else if(ctrl->GetBezierKeys(keys, eulertype[i])) {
  1.1347 +			maxlog("node %s: getting %d bezier euler %c keys\n", nodename, keys.Count(), "xyz"[i]);
  1.1348 +			for(int j=0; j<keys.Count(); j++) {
  1.1349 +				long tm = KEY_TIME(keys[j]);
  1.1350 +				Point3 v = euler[tm];
  1.1351 +				v[i] = keys[j].bezierKey.fval;
  1.1352 +				euler[tm] = v;
  1.1353 +			}
  1.1354 +		} else if(ctrl->GetTCBKeys(keys, eulertype[i])) {
  1.1355 +			maxlog("node %s: getting %d tcb euler %c keys\n", nodename, keys.Count(), "xyz"[i]);
  1.1356 +			for(int j=0; j<keys.Count(); j++) {
  1.1357 +				long tm = KEY_TIME(keys[j]);
  1.1358 +				Point3 v = euler[tm];
  1.1359 +				v[i] = keys[j].tcbKey.fval;
  1.1360 +				euler[tm] = v;
  1.1361 +			}
  1.1362 +		}
  1.1363 +	}
  1.1364 +
  1.1365 +	int order = ctrl->GetEulerOrder();
  1.1366 +	std::map<long, Point3>::iterator it = euler.begin();
  1.1367 +	while(it != euler.end()) {
  1.1368 +		Quat q;
  1.1369 +		EulerToQuat(it->second, q, order);
  1.1370 +		goat3d_set_node_rotation(node, q.x, q.y, q.z, q.w, it->first);
  1.1371 +		++it;
  1.1372 +	}
  1.1373 +}
  1.1374 +
  1.1375 +static void get_scaling_keys(IGameControl *ctrl, goat3d_node *node)
  1.1376 +{
  1.1377 +	const char *nodename = goat3d_get_node_name(node);
  1.1378 +	IGameKeyTab keys;
  1.1379 +
  1.1380 +	// XXX the way I'm using the ScaleValue is wrong, but fuck it...
  1.1381 +
  1.1382 +	if(ctrl->GetLinearKeys(keys, IGAME_SCALE)) {
  1.1383 +		maxlog("node %s: getting %d linear scaling keys\n", nodename, keys.Count());
  1.1384 +		for(int i=0; i<keys.Count(); i++) {
  1.1385 +			ScaleValue s = keys[i].linearKey.sval;
  1.1386 +			goat3d_set_node_scaling(node, s.s.x, s.s.y, s.s.z, KEY_TIME(keys[i]));
  1.1387 +		}
  1.1388 +	} else if(ctrl->GetBezierKeys(keys, IGAME_SCALE)) {
  1.1389 +		maxlog("node %s: getting %d bezier scaling keys\n", nodename, keys.Count());
  1.1390 +		for(int i=0; i<keys.Count(); i++) {
  1.1391 +			ScaleValue s = keys[i].bezierKey.sval;
  1.1392 +			goat3d_set_node_scaling(node, s.s.x, s.s.y, s.s.z, KEY_TIME(keys[i]));
  1.1393 +		}
  1.1394 +	} else if(ctrl->GetTCBKeys(keys, IGAME_SCALE)) {
  1.1395 +		maxlog("node %s: getting %d tcb scaling keys\n", nodename, keys.Count());
  1.1396 +		for(int i=0; i<keys.Count(); i++) {
  1.1397 +			ScaleValue s = keys[i].tcbKey.sval;
  1.1398 +			goat3d_set_node_scaling(node, s.s.x, s.s.y, s.s.z, KEY_TIME(keys[i]));
  1.1399 +		}
  1.1400 +	}
  1.1401 +}
  1.1402 +
  1.1403 +static bool get_anim_bounds(IGameNode *node, long *tstart, long *tend);
  1.1404 +static bool get_node_anim_bounds(IGameNode *node, long *tstart, long *tend);
  1.1405 +
  1.1406 +static bool get_anim_bounds(IGameScene *igame, long *tstart, long *tend)
  1.1407 +{
  1.1408 +	long tmin = LONG_MAX;
  1.1409 +	long tmax = LONG_MIN;
  1.1410 +
  1.1411 +	int num_nodes = igame->GetTopLevelNodeCount();
  1.1412 +	for(int i=0; i<num_nodes; i++) {
  1.1413 +		long t0, t1;
  1.1414 +		if(get_anim_bounds(igame->GetTopLevelNode(i), &t0, &t1)) {
  1.1415 +			if(t0 < tmin) tmin = t0;
  1.1416 +			if(t1 > tmax) tmax = t1;
  1.1417 +		}
  1.1418 +	}
  1.1419 +
  1.1420 +	if(tmin != LONG_MAX) {
  1.1421 +		*tstart = tmin;
  1.1422 +		*tend = tmax;
  1.1423 +		return true;
  1.1424 +	}
  1.1425 +	return false;
  1.1426 +}
  1.1427 +
  1.1428 +static bool get_anim_bounds(IGameNode *node, long *tstart, long *tend)
  1.1429 +{
  1.1430 +	long tmin = LONG_MAX;
  1.1431 +	long tmax = LONG_MIN;
  1.1432 +
  1.1433 +	get_node_anim_bounds(node, &tmin, &tmax);
  1.1434 +
  1.1435 +	int num_children = node->GetChildCount();
  1.1436 +	for(int i=0; i<num_children; i++) {
  1.1437 +		long t0, t1;
  1.1438 +		if(get_anim_bounds(node->GetNodeChild(i), &t0, &t1)) {
  1.1439 +			if(t0 < tmin) tmin = t0;
  1.1440 +			if(t1 > tmax) tmax = t1;
  1.1441 +		}
  1.1442 +	}
  1.1443 +
  1.1444 +	if(tmin != LONG_MAX) {
  1.1445 +		*tstart = tmin;
  1.1446 +		*tend = tmax;
  1.1447 +		return true;
  1.1448 +	}
  1.1449 +	return false;
  1.1450 +}
  1.1451 +
  1.1452 +static bool get_node_anim_bounds(IGameNode *node, long *tstart, long *tend)
  1.1453 +{
  1.1454 +	static const IGameControlType ctypes[] = {
  1.1455 +		IGAME_POS, IGAME_POS_X, IGAME_POS_Y, IGAME_POS_Z,
  1.1456 +		IGAME_ROT, IGAME_EULER_X, IGAME_EULER_Y, IGAME_EULER_Z,
  1.1457 +		IGAME_SCALE
  1.1458 +	};
  1.1459 +
  1.1460 +	// NOTE: apparently if I don't call GetIGameObject, then GetIGameControl always returns null...
  1.1461 +	node->GetIGameObject();
  1.1462 +	IGameControl *ctrl = node->GetIGameControl();
  1.1463 +	if(!ctrl) {
  1.1464 +		maxlog("%s: failed to get IGameControl for node: %s\n", __FUNCTION__, max_string(node->GetName()));
  1.1465 +		return false;
  1.1466 +	}
  1.1467 +
  1.1468 +	IGameKeyTab keys;
  1.1469 +	long t0, t1;
  1.1470 +	long tmin = LONG_MAX;
  1.1471 +	long tmax = LONG_MIN;
  1.1472 +
  1.1473 +	for(int i=0; i<sizeof ctypes / sizeof *ctypes; i++) {
  1.1474 +		if(ctrl->GetBezierKeys(keys, ctypes[i]) && keys.Count()) {
  1.1475 +			t0 = KEY_TIME(keys[0]);
  1.1476 +			t1 = KEY_TIME(keys[keys.Count() - 1]);
  1.1477 +			if(t0 < tmin) tmin = t0;
  1.1478 +			if(t1 > tmax) tmax = t1;
  1.1479 +		}
  1.1480 +		if(ctrl->GetLinearKeys(keys, ctypes[i]) && keys.Count()) {
  1.1481 +			t0 = KEY_TIME(keys[0]);
  1.1482 +			t1 = KEY_TIME(keys[keys.Count() - 1]);
  1.1483 +			if(t0 < tmin) tmin = t0;
  1.1484 +			if(t1 > tmax) tmax = t1;
  1.1485 +		}
  1.1486 +		if(ctrl->GetTCBKeys(keys, ctypes[i]) && keys.Count()) {
  1.1487 +			t0 = KEY_TIME(keys[0]);
  1.1488 +			t1 = KEY_TIME(keys[keys.Count() - 1]);
  1.1489 +			if(t0 < tmin) tmin = t0;
  1.1490 +			if(t1 > tmax) tmax = t1;
  1.1491 +		}
  1.1492 +	}
  1.1493 +
  1.1494 +	if(tmin != LONG_MAX) {
  1.1495 +		*tstart = tmin;
  1.1496 +		*tend = tmax;
  1.1497 +		return true;
  1.1498 +	}
  1.1499 +	return false;
  1.1500 +}
  1.1501 +
  1.1502 +void GoatExporter::process_mesh(goat3d *goat, goat3d_mesh *mesh, IGameObject *maxobj)
  1.1503 +{
  1.1504 +	IGameMesh *maxmesh = (IGameMesh*)maxobj;
  1.1505 +
  1.1506 +	maxmesh->SetCreateOptimizedNormalList();	// not needed any more according to docs
  1.1507 +	maxobj->InitializeData();
  1.1508 +
  1.1509 +	int num_verts = maxmesh->GetNumberOfVerts();
  1.1510 +	int num_faces = maxmesh->GetNumberOfFaces();
  1.1511 +	//assert(maxmesh->GetNumberOfTexVerts() == num_verts);
  1.1512 +
  1.1513 +	float *vertices = new float[num_verts * 3];
  1.1514 +	float *normals = new float[num_verts * 3];
  1.1515 +	//float *texcoords = new float[num_verts * 2];
  1.1516 +	int *indices = new int[num_faces * 3];
  1.1517 +
  1.1518 +	for(int i=0; i<num_verts; i++) {
  1.1519 +		Point3 v = maxmesh->GetVertex(i, true);
  1.1520 +		vertices[i * 3] = v.x;
  1.1521 +		vertices[i * 3 + 1] = v.y;
  1.1522 +		vertices[i * 3 + 2] = v.z;
  1.1523 +	}
  1.1524 +
  1.1525 +	for(int i=0; i<maxmesh->GetNumberOfNormals(); i++) {
  1.1526 +		Point3 norm = maxmesh->GetNormal(i);
  1.1527 +
  1.1528 +		int vidx = maxmesh->GetNormalVertexIndex(i);
  1.1529 +		normals[vidx * 3] = norm.x;
  1.1530 +		normals[vidx * 3 + 1] = norm.y;
  1.1531 +		normals[vidx * 3 + 2] = norm.z;
  1.1532 +	}
  1.1533 +
  1.1534 +	/*for(int i=0; i<maxmesh->GetNumberOfTexVerts(); i++) {
  1.1535 +		Point3 tex = maxmesh->GetTexVertex(i);
  1.1536 +
  1.1537 +		texcoords[i * 2] = tex.x;
  1.1538 +		texcoords[i * 2 + 1] = tex.y;
  1.1539 +	}*/
  1.1540 +
  1.1541 +	// get the faces
  1.1542 +	for(int i=0; i<num_faces; i++) {
  1.1543 +		FaceEx *face = maxmesh->GetFace(i);
  1.1544 +		indices[i * 3] = face->vert[0];
  1.1545 +		indices[i * 3 + 1] = face->vert[1];
  1.1546 +		indices[i * 3 + 2] = face->vert[2];
  1.1547 +		// TODO at some point I'll have to split based on normal/texcoord indices
  1.1548 +	}
  1.1549 +
  1.1550 +	goat3d_set_mesh_attribs(mesh, GOAT3D_MESH_ATTR_VERTEX, vertices, num_verts);
  1.1551 +	goat3d_set_mesh_attribs(mesh, GOAT3D_MESH_ATTR_NORMAL, normals, num_verts);
  1.1552 +	//goat3d_set_mesh_attribs(mesh, GOAT3D_MESH_ATTR_TEXCOORD, texcoords, num_verts);
  1.1553 +	goat3d_set_mesh_faces(mesh, indices, num_faces);
  1.1554 +
  1.1555 +	delete [] vertices;
  1.1556 +	delete [] normals;
  1.1557 +	//delete [] texcoords;
  1.1558 +	delete [] indices;
  1.1559 +}
  1.1560 +
  1.1561 +void GoatExporter::process_light(goat3d *goat, goat3d_light *light, IGameObject *maxobj)
  1.1562 +{
  1.1563 +	// TODO
  1.1564 +}
  1.1565 +
  1.1566 +void GoatExporter::process_camera(goat3d *goat, goat3d_camera *cam, IGameObject *maxobj)
  1.1567 +{
  1.1568 +	// TODO
  1.1569 +}
  1.1570 +
  1.1571 +static INT_PTR CALLBACK scene_gui_handler(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam)
  1.1572 +{
  1.1573 +	switch(msg) {
  1.1574 +	case WM_INITDIALOG:
  1.1575 +		CheckDlgButton(win, IDC_GOAT_NODES, 1);
  1.1576 +		CheckDlgButton(win, IDC_GOAT_MESHES, 1);
  1.1577 +		CheckDlgButton(win, IDC_GOAT_LIGHTS, 1);
  1.1578 +		CheckDlgButton(win, IDC_GOAT_CAMERAS, 1);
  1.1579 +		break;
  1.1580 +
  1.1581 +	case WM_COMMAND:
  1.1582 +		switch(LOWORD(wparam)) {
  1.1583 +		case IDOK:
  1.1584 +			EndDialog(win, 1);
  1.1585 +			break;
  1.1586 +
  1.1587 +		case IDCANCEL:
  1.1588 +			EndDialog(win, 0);
  1.1589 +			break;
  1.1590 +
  1.1591 +		default:
  1.1592 +			return 0;
  1.1593 +		}
  1.1594 +		break;
  1.1595 +
  1.1596 +	default:
  1.1597 +		return 0;
  1.1598 +	}
  1.1599 +
  1.1600 +	return 1;
  1.1601 +}
  1.1602 +
  1.1603 +
  1.1604 +
  1.1605 +// ---- GoatAnimExporter implementation ----
  1.1606 +static long tstart, tend;
  1.1607 +
  1.1608 +int GoatAnimExporter::DoExport(const MCHAR *name, ExpInterface *eiface, Interface *iface, BOOL silent, DWORD opt)
  1.1609 +{
  1.1610 +	if(!(igame = GetIGameInterface())) {
  1.1611 +		maxlog("failed to get the igame interface\n");
  1.1612 +		return IMPEXP_FAIL;
  1.1613 +	}
  1.1614 +	IGameConversionManager *cm = GetConversionManager();
  1.1615 +	cm->SetCoordSystem(IGameConversionManager::IGAME_OGL);
  1.1616 +	igame->InitialiseIGame();
  1.1617 +	igame->SetStaticFrame(0);
  1.1618 +
  1.1619 +	tstart = tend = 0;
  1.1620 +	get_anim_bounds(igame, &tstart, &tend);
  1.1621 +
  1.1622 +	if(!DialogBox(hinst, MAKEINTRESOURCE(IDD_GOAT_ANM), 0, anim_gui_handler)) {
  1.1623 +		igame->ReleaseIGame();
  1.1624 +		return IMPEXP_CANCEL;
  1.1625 +	}
  1.1626 +
  1.1627 +	char fname[512];
  1.1628 +	wcstombs(fname, name, sizeof fname - 1);
  1.1629 +	for(int i=0; fname[i]; i++) {
  1.1630 +		fname[i] = tolower(fname[i]);
  1.1631 +	}
  1.1632 +
  1.1633 +	maxlog("Exporting Goat3D Animation (text) file: %s\n", fname);
  1.1634 +
  1.1635 +	goat3d *goat = goat3d_create();
  1.1636 +
  1.1637 +	// process all nodes
  1.1638 +	for(int i=0; i<igame->GetTopLevelNodeCount(); i++) {
  1.1639 +		IGameNode *node = igame->GetTopLevelNode(i);
  1.1640 +		process_node(goat, 0, node);
  1.1641 +	}
  1.1642 +
  1.1643 +	if(goat3d_save_anim(goat, fname) == -1) {
  1.1644 +		goat3d_free(goat);
  1.1645 +		igame->ReleaseIGame();
  1.1646 +		return IMPEXP_FAIL;
  1.1647 +	}
  1.1648 +
  1.1649 +	goat3d_free(goat);
  1.1650 +	igame->ReleaseIGame();
  1.1651 +	return IMPEXP_SUCCESS;
  1.1652 +}
  1.1653 +
  1.1654 +static INT_PTR CALLBACK anim_gui_handler(HWND win, unsigned int msg, WPARAM wparam, LPARAM lparam)
  1.1655 +{
  1.1656 +	switch(msg) {
  1.1657 +	case WM_INITDIALOG:
  1.1658 +		{
  1.1659 +			wchar_t buf[128];
  1.1660 +			CheckDlgButton(win, IDC_GOAT_ANM_FULL, BST_CHECKED);
  1.1661 +			CheckDlgButton(win, IDC_RAD_KEYS_ORIG, BST_CHECKED);
  1.1662 +			wsprintf(buf, L"%ld", tstart);
  1.1663 +			SetDlgItemText(win, IDC_EDIT_TSTART, buf);
  1.1664 +			wsprintf(buf, L"%ld", tend);
  1.1665 +			SetDlgItemText(win, IDC_EDIT_TEND, buf);
  1.1666 +		}
  1.1667 +		break;
  1.1668 +
  1.1669 +	case WM_COMMAND:
  1.1670 +		switch(LOWORD(wparam)) {
  1.1671 +		case IDOK:
  1.1672 +			EndDialog(win, 1);
  1.1673 +			break;
  1.1674 +
  1.1675 +		case IDCANCEL:
  1.1676 +			EndDialog(win, 0);
  1.1677 +			break;
  1.1678 +
  1.1679 +		default:
  1.1680 +			return 0;
  1.1681 +		}
  1.1682 +		break;
  1.1683 +
  1.1684 +	default:
  1.1685 +		return 0;
  1.1686 +	}
  1.1687 +
  1.1688 +	return 1;
  1.1689 +}
  1.1690 +
  1.1691 +// ------------------------------------------
  1.1692 +
  1.1693 +class GoatClassDesc : public ClassDesc2 {
  1.1694 +public:
  1.1695 +	int IsPublic() { return TRUE; }
  1.1696 +	void *Create(BOOL loading = FALSE) { return new GoatExporter; }
  1.1697 +	const TCHAR *ClassName() { return L"GoatExporter"; }
  1.1698 +	SClass_ID SuperClassID() { return SCENE_EXPORT_CLASS_ID; }
  1.1699 +	Class_ID ClassID() { return Class_ID(0x77050f0d, 0x7d4c5ab5); }
  1.1700 +	const TCHAR *Category() { return L"Mutant Stargoat"; }
  1.1701 +
  1.1702 +	const TCHAR *InternalName() { return L"GoatExporter"; }
  1.1703 +	HINSTANCE HInstance() { return hinst; }
  1.1704 +};
  1.1705 +
  1.1706 +class GoatAnimClassDesc : public ClassDesc2 {
  1.1707 +public:
  1.1708 +	int IsPublic() { return TRUE; }
  1.1709 +	void *Create(BOOL loading = FALSE) { return new GoatAnimExporter; }
  1.1710 +	const TCHAR *ClassName() { return L"GoatAnimExporter"; }
  1.1711 +	SClass_ID SuperClassID() { return SCENE_EXPORT_CLASS_ID; }
  1.1712 +	Class_ID ClassID() { return Class_ID(0x51b94924, 0x2e0332f3); }
  1.1713 +	const TCHAR *Category() { return L"Mutant Stargoat"; }
  1.1714 +
  1.1715 +	const TCHAR *InternalName() { return L"GoatAnimExporter"; }
  1.1716 +	HINSTANCE HInstance() { return hinst; }
  1.1717 +};
  1.1718 +
  1.1719 +// TODO: make 2 class descriptors, one for goat3d, one for goat3danim
  1.1720 +static GoatClassDesc class_desc;
  1.1721 +static GoatAnimClassDesc anim_class_desc;
  1.1722 +
  1.1723 +BOOL WINAPI DllMain(HINSTANCE inst_handle, ULONG reason, void *reserved)
  1.1724 +{
  1.1725 +	if(reason == DLL_PROCESS_ATTACH) {
  1.1726 +		hinst = inst_handle;
  1.1727 +		DisableThreadLibraryCalls(hinst);
  1.1728 +	}
  1.1729 +	return TRUE;
  1.1730 +}
  1.1731 +
  1.1732 +extern "C" {
  1.1733 +
  1.1734 +__declspec(dllexport) const TCHAR *LibDescription()
  1.1735 +{
  1.1736 +	return L"test exporter";
  1.1737 +}
  1.1738 +
  1.1739 +__declspec(dllexport) int LibNumberClasses()
  1.1740 +{
  1.1741 +	return 1;
  1.1742 +}
  1.1743 +
  1.1744 +__declspec(dllexport) ClassDesc *LibClassDesc(int i)
  1.1745 +{
  1.1746 +	switch(i) {
  1.1747 +	case 0:
  1.1748 +		return &class_desc;
  1.1749 +	case 1:
  1.1750 +		return &anim_class_desc;
  1.1751 +	default:
  1.1752 +		break;
  1.1753 +	}
  1.1754 +	return 0;
  1.1755 +}
  1.1756 +
  1.1757 +__declspec(dllexport) ULONG LibVersion()
  1.1758 +{
  1.1759 +	return Get3DSMAXVersion();
  1.1760 +}
  1.1761 +
  1.1762 +__declspec(dllexport) int LibInitialize()
  1.1763 +{
  1.1764 +	static char path[1024];
  1.1765 +
  1.1766 +	SHGetFolderPathA(0, CSIDL_PERSONAL, 0, 0, path);
  1.1767 +	strcat(path, "/testexp.log");
  1.1768 +
  1.1769 +	maxlog_open(path);
  1.1770 +	return TRUE;
  1.1771 +}
  1.1772 +
  1.1773 +__declspec(dllexport) int LibShutdown()
  1.1774 +{
  1.1775 +	maxlog_close();
  1.1776 +	return TRUE;
  1.1777 +}
  1.1778 +
  1.1779 +}	// extern "C"
  1.1780 +
  1.1781 +
  1.1782 +static const char *max_string(const MCHAR *wstr)
  1.1783 +{
  1.1784 +	if(!wstr) return 0;
  1.1785 +	static char str[512];
  1.1786 +	wcstombs(str, wstr, sizeof str - 1);
  1.1787 +	return str;
  1.1788 +}