void CCharShape::CreateMaterial (const string& line) {
	TVector3d diff = SPVector3d(line, "diff");
	TVector3d spec = SPVector3d(line, "spec");
	float exp = SPFloatN (line, "exp", 50);
	std::string mat = SPStrN(line, "mat");

	Materials.push_back(TCharMaterial());
	Materials.back().diffuse.r = diff.x;
	Materials.back().diffuse.g = diff.y;
	Materials.back().diffuse.b = diff.z;
	Materials.back().diffuse.a = 1.0;
	Materials.back().specular.r = spec.x;
	Materials.back().specular.g = spec.y;
	Materials.back().specular.b = spec.z;
	Materials.back().specular.a = 1.0;
	Materials.back().exp = exp;
	if (useActions)
		Materials.back().matline = line;

	MaterialIndex[mat] = Materials.size()-1;
}
bool CKeyframe::Load(const std::string& dir, const std::string& filename) {
	if (loaded && loadedfile == filename) return true;
	CSPList list;

	if (list.Load(dir, filename)) {
		frames.resize(list.size());
		std::size_t i = 0;
		for (CSPList::const_iterator line = list.cbegin(); line != list.cend(); ++line, i++) {
			frames[i].val[0] = SPFloatN(*line, "time", 0);
			TVector3d posit = SPVector3d(*line, "pos");
			frames[i].val[1] = posit.x;
			frames[i].val[2] = posit.y;
			frames[i].val[3] = posit.z;
			frames[i].val[4] = SPFloatN(*line, "yaw", 0);
			frames[i].val[5] = SPFloatN(*line, "pitch", 0);
			frames[i].val[6] = SPFloatN(*line, "roll", 0);
			frames[i].val[7] = SPFloatN(*line, "neck", 0);
			frames[i].val[8] = SPFloatN(*line, "head", 0);
			TVector2d pp = SPVector2d(*line, "sh");
			frames[i].val[9] = pp.x;
			frames[i].val[10] = pp.y;
			pp = SPVector2d(*line, "arm");
			frames[i].val[11] = pp.x;
			frames[i].val[12] = pp.y;
			pp = SPVector2d(*line, "hip");
			frames[i].val[13] = pp.x;
			frames[i].val[14] = pp.y;
			pp = SPVector2d(*line, "knee");
			frames[i].val[15] = pp.x;
			frames[i].val[16] = pp.y;
			pp = SPVector2d(*line, "ankle");
			frames[i].val[17] = pp.x;
			frames[i].val[18] = pp.y;
		}
		loaded = true;
		loadedfile = filename;
		return true;
	} else {
		Message("keyframe not found:", filename);
		loaded = false;
		return false;
	}
}
bool CCharShape::Load (const string& dir, const string& filename, bool with_actions) {
	CSPList list (500);

	useActions = with_actions;
	CreateRootNode ();
	newActions = true;

	if (!list.Load (dir, filename)) {
		Message ("could not load character", filename);
		return false;
	}

	for (size_t i=0; i<list.Count(); i++) {
		const string& line = list.Line(i);
		int node_name = SPIntN (line, "node", -1);
		int parent_name = SPIntN (line, "par", -1);
		string mat_name = SPStrN (line, "mat");
		string name = SPStrN (line, "joint");
		string fullname = SPStrN (line, "name");

		if (SPIntN (line, "material", 0) > 0) {
			CreateMaterial (line);
		} else {
			float visible = SPFloatN (line, "vis", -1.0);
			bool shadow = SPBoolN (line, "shad", false);
			string order = SPStrN (line, "order");
			CreateCharNode (parent_name, node_name, name, fullname, order, shadow);
			TVector3d rot = SPVector3d(line, "rot");
			MaterialNode (node_name, mat_name);
			for (size_t ii = 0; ii < order.size(); ii++) {
				int act = order[ii]-48;
				switch (act) {
					case 0: {
						TVector3d trans = SPVector3d(line, "trans");
						TranslateNode (node_name, trans);
						break;
					}
					case 1:
						RotateNode (node_name, 1, rot.x);
						break;
					case 2:
						RotateNode (node_name, 2, rot.y);
						break;
					case 3:
						RotateNode (node_name, 3, rot.z);
						break;
					case 4: {
						TVector3d scale = SPVector3(line, "scale", TVector3d(1, 1, 1));
						ScaleNode (node_name, scale);
						break;
					}
					case 5:
						VisibleNode (node_name, visible);
						break;
					case 9:
						RotateNode (node_name, 2, rot.z);
						break;
					default:
						break;
				}
			}
		}
	}
	newActions = false;
	return true;
}
bool CEvents::LoadEventList () {
	CSPList list(256);

	if (!list.Load (param.common_course_dir, "events.lst")) {
		Message ("could not load events.lst");
		return false;
	}

	// pass 1: races
	for (size_t i=0; i<list.Count(); i++) {
		const string& line = list.Line(i);
		int type = SPIntN (line, "struct", -1);
		if (type == 0) {
			RaceList.push_back(TRace(
			                       Course.GetCourse(SPStrN(line, "course")),
			                       Env.GetLightIdx(SPStrN(line, "light")),
			                       SPIntN(line, "snow", 0),
			                       SPIntN(line, "wind", 0),
			                       SPVector3i(line, "herring"),
			                       SPVector3d(line, "time"),
			                       Music.GetThemeIdx(SPStrN(line, "theme", "normal"))));
		}
	}
	list.MakeIndex (RaceIndex, "race");

	// pass 2: cups
	for (size_t i=0; i<list.Count(); i++) {
		const string& line = list.Line(i);
		int type = SPIntN (line, "struct", -1);
		if (type == 1) {
			CupList.push_back(TCup(
			                      SPStrN(line, "cup", errorString),
			                      SPStrN(line, "name", "unknown"),
			                      SPStrN(line, "desc", "unknown")));
			int num = SPIntN (line, "num", 0);
			CupList.back().races.resize(num);
			for (int ii=0; ii<num; ii++) {
				string race = SPStrN (line, Int_StrN (ii+1));
				CupList.back().races[ii] = &RaceList[GetRaceIdx(race)];
			}
		}
	}
	list.MakeIndex (CupIndex, "cup");

	// pass 3: events
	for (size_t i=0; i<list.Count(); i++) {
		const string& line = list.Line(i);
		int type = SPIntN (line, "struct", -1);
		if (type == 2) {
			EventList.push_back(TEvent(SPStrN(line, "name", "unknown")));
			int num = SPIntN (line, "num", 0);
			EventList.back().cups.resize(num);
			for (int ii=0; ii<num; ii++) {
				string cup = SPStrN (line, Int_StrN (ii+1));
				EventList.back().cups[ii] = &CupList[GetCupIdx(cup)];
			}
		}
	}
	list.MakeIndex (EventIndex, "event");

	return true;
}