Esempio n. 1
0
CShaderLibrary::CShaderLibrary()
{
	s_pShaderLibrary = this;

	m_bCompiled = false;
	m_iSamples = -1;

	FILE* f = tfopen("shaders/functions.si", "r");

	if (f)
	{
		tstring sLine;
		while (fgetts(sLine, f))
			m_sFunctions += sLine;

		fclose(f);
	}

	f = tfopen("shaders/header.si", "r");

	if (f)
	{
		tstring sLine;
		while (fgetts(sLine, f))
			m_sHeader += sLine;

		fclose(f);
	}
}
Esempio n. 2
0
// Silo ascii
void CModelConverter::ReadSIA(const tstring& sFilename)
{
	if (m_pWorkListener)
		m_pWorkListener->BeginProgress();

	CConversionSceneNode* pScene = m_pScene->GetScene(m_pScene->AddScene(GetFilename(sFilename).append(_T(".sia"))));

	if (m_pWorkListener)
		m_pWorkListener->SetAction(_T("Reading file into memory..."), 0);

	FILE* fp = tfopen(sFilename, _T("r"));

	if (!fp)
	{
		printf("No input file. Sorry!\n");
		return;
	}

	fseek(fp, 0L, SEEK_END);
	long iOBJSize = ftell(fp);
	fseek(fp, 0L, SEEK_SET);

	// Make sure we allocate more than we need just in case.
	size_t iFileSize = (iOBJSize+1) * (sizeof(tchar)+1);
	tchar* pszEntireFile = (tchar*)malloc(iFileSize);
	tchar* pszCurrent = pszEntireFile;

	// Read the entire file into an array first for faster processing.
	tstring sLine;
	while (fgetts(sLine, fp))
	{
		tstrncpy(pszCurrent, iFileSize-(pszCurrent-pszEntireFile), sLine.c_str(), sLine.length());
		size_t iLength = sLine.length();

		if (pszCurrent[iLength-1] == _T('\n'))
		{
			pszCurrent[iLength-1] = _T('\0');
			iLength--;
		}

		pszCurrent += iLength;
		pszCurrent++;

		if (m_pWorkListener)
			m_pWorkListener->WorkProgress(0);
	}

	pszCurrent[0] = _T('\0');

	fclose(fp);

	const tchar* pszLine = pszEntireFile;
	const tchar* pszNextLine = NULL;
	while (pszLine < pszCurrent)
	{
		if (pszNextLine)
			pszLine = pszNextLine;

		pszNextLine = pszLine + tstrlen(pszLine) + 1;

		// This code used to call StripWhitespace() but that's too slow for very large files w/ millions of lines.
		// Instead we'll just cut the whitespace off the front and deal with whitespace on the end when we come to it.
		while (*pszLine && IsWhitespace(*pszLine))
			pszLine++;

		if (tstrlen(pszLine) == 0)
			continue;

		eastl::vector<tstring> aTokens;
		tstrtok(pszLine, aTokens, _T(" "));
		const tchar* pszToken = aTokens[0].c_str();

		if (tstrncmp(pszToken, _T("-Version"), 8) == 0)
		{
			// Warning if version is later than 1.0, we may not support it
			int iMajor, iMinor;
			eastl::vector<tstring> asTokens;
			tstrtok(pszLine, asTokens, _T(" ."));

			if (asTokens.size() >= 3)
			{
				iMajor = stoi(asTokens[1]);
				iMinor = stoi(asTokens[2]);
				if (iMajor != 1 && iMinor != 0)
					printf("WARNING: I was programmed for version 1.0, this file is version %d.%d, so this might not work exactly right!\n", iMajor, iMinor);
			}
		}
		else if (tstrncmp(pszToken, _T("-Mat"), 4) == 0)
		{
			pszNextLine = ReadSIAMat(pszNextLine, pszCurrent, pScene, sFilename);
		}
		else if (tstrncmp(pszToken, _T("-Shape"), 6) == 0)
		{
			pszNextLine = ReadSIAShape(pszNextLine, pszCurrent, pScene);
		}
		else if (tstrncmp(pszToken, _T("-Texshape"), 9) == 0)
		{
			// This is the 3d UV space of the object, but we only care about its 2d UV space which is contained in rhw -Shape section, so meh.
			pszNextLine = ReadSIAShape(pszNextLine, pszCurrent, pScene, false);
		}
	}

	free(pszEntireFile);

	m_pScene->SetWorkListener(m_pWorkListener);

	for (size_t i = 0; i < m_pScene->GetNumMeshes(); i++)
	{
		m_pScene->GetMesh(i)->CalculateEdgeData();
		m_pScene->GetMesh(i)->CalculateVertexNormals();
		m_pScene->GetMesh(i)->CalculateVertexTangents();
	}

	m_pScene->CalculateExtends();

	if (m_pWorkListener)
		m_pWorkListener->EndProgress();
}
Esempio n. 3
0
void CModelConverter::ReadOBJ(const tstring& sFilename)
{
	if (m_pWorkListener)
		m_pWorkListener->BeginProgress();

	FILE* fp = tfopen(sFilename, _T("r"));

	if (!fp)
	{
		printf("No input file. Sorry!\n");
		return;
	}

	CConversionSceneNode* pScene = m_pScene->GetScene(m_pScene->AddScene(GetFilename(sFilename).append(_T(".obj"))));

	CConversionMesh* pMesh = m_pScene->GetMesh(m_pScene->AddMesh(GetFilename(sFilename)));
	// Make sure it exists.
	CConversionSceneNode* pMeshNode = m_pScene->GetDefaultSceneMeshInstance(pScene, pMesh);

	size_t iCurrentMaterial = ~0;
	size_t iSmoothingGroup = ~0;

	bool bSmoothingGroups = false;

	tstring sLastTask;

	int iTotalVertices = 0;
	int iTotalFaces = 0;
	int iVerticesComplete = 0;
	int iFacesComplete = 0;

	if (m_pWorkListener)
		m_pWorkListener->SetAction(_T("Reading file into memory..."), 0);

	fseek(fp, 0L, SEEK_END);
	long iOBJSize = ftell(fp);
	fseek(fp, 0L, SEEK_SET);

	// Make sure we allocate more than we need just in case.
	size_t iFileSize = (iOBJSize+1) * (sizeof(tchar)+1);
	tchar* pszEntireFile = (tchar*)malloc(iFileSize);
	tchar* pszCurrent = pszEntireFile;
	pszCurrent[0] = _T('\0');

	// Read the entire file into an array first for faster processing.
	tstring sLine;
	while (fgetts(sLine, fp))
	{
		tstrncpy(pszCurrent, iFileSize-(pszCurrent-pszEntireFile), sLine.c_str(), sLine.length());
		size_t iLength = sLine.length();

		tchar cLastChar = pszCurrent[iLength-1];
		while (cLastChar == _T('\n') || cLastChar == _T('\r'))
		{
			pszCurrent[iLength-1] = _T('\0');
			iLength--;
			cLastChar = pszCurrent[iLength-1];
		}

		pszCurrent += iLength;
		pszCurrent++;

		if (m_pWorkListener)
			m_pWorkListener->WorkProgress(0);
	}

	pszCurrent[0] = _T('\0');

	fclose(fp);

	const tchar* pszLine = pszEntireFile;
	const tchar* pszNextLine = NULL;
	while (pszLine < pszCurrent)
	{
		if (pszNextLine)
			pszLine = pszNextLine;

		pszNextLine = pszLine + tstrlen(pszLine) + 1;

		// This code used to call StripWhitespace() but that's too slow for very large files w/ millions of lines.
		// Instead we'll just cut the whitespace off the front and deal with whitespace on the end when we come to it.
		while (*pszLine && IsWhitespace(*pszLine))
			pszLine++;

		if (tstrlen(pszLine) == 0)
			continue;

		if (pszLine[0] == '#')
		{
			// ZBrush is kind enough to notate exactly how many vertices and faces we have in the comments at the top of the file.
			if (tstrncmp(pszLine, _T("#Vertex Count"), 13) == 0)
			{
				iTotalVertices = stoi(pszLine+13);
				pMesh->SetTotalVertices(iTotalVertices);
			}

			if (tstrncmp(pszLine, _T("#Face Count"), 11) == 0)
			{
				iTotalFaces = stoi(pszLine+11);
				pMesh->SetTotalFaces(iTotalFaces);

				// Don't kill the video card while we're loading the faces.
				if (iTotalFaces > 10000)
					pMeshNode->GetMeshInstance(0)->SetVisible(false);
			}

			continue;
		}

		tchar szToken[1024];
		tstrncpy(szToken, 1024, pszLine, 1024);
		tchar* pszState = NULL;
		tchar* pszToken = strtok<tchar>(szToken, " ", &pszState);

		if (tstrncmp(pszToken, _T("mtllib"), 6) == 0)
		{
			tstring sDirectory = GetDirectory(sFilename);
			tstring sMaterial = sprintf(tstring("%s/%s"), sDirectory.c_str(), pszLine + 7);
			ReadMTL(sMaterial);
		}
		else if (tstrncmp(pszToken, _T("o"), 1) == 0)
		{
			// Dunno what this does.
		}
		else if (tstrncmp(pszToken, _T("v"), 2) == 0)
		{
			if (m_pWorkListener)
			{
				if (tstrncmp(sLastTask.c_str(), pszToken, sLastTask.length()) == 0)
					m_pWorkListener->WorkProgress(iVerticesComplete++);
				else
				{
					m_pWorkListener->SetAction(_T("Reading vertex data"), iTotalVertices);
					sLastTask = tstring(pszToken);
				}
			}

			// A vertex.
			float v[3];
			// scanf is pretty slow even for such a short string due to lots of mallocs.
			const tchar* pszToken = pszLine+1;
			int iDimension = 0;
			while (*pszToken)
			{
				while (pszToken[0] == _T(' '))
					pszToken++;

				v[iDimension++] = (float)stof(pszToken);
				if (iDimension >= 3)
					break;

				while (pszToken[0] != _T(' '))
					pszToken++;
			}
			pMesh->AddVertex(v[0], v[1], v[2]);
		}
		else if (tstrncmp(pszToken, _T("vn"), 3) == 0)
		{
			if (m_pWorkListener)
			{
				if (tstrncmp(sLastTask.c_str(), pszToken, sLastTask.length()) == 0)
					m_pWorkListener->WorkProgress(0);
				else
					m_pWorkListener->SetAction(_T("Reading vertex normal data"), 0);
			}
			sLastTask = tstring(pszToken);

			// A vertex normal.
			float x, y, z;
			eastl::vector<tstring> asTokens;
			tstrtok(pszLine, asTokens, _T(" "));
			if (asTokens.size() == 4)
			{
				x = stof(asTokens[1]);
				y = stof(asTokens[2]);
				z = stof(asTokens[3]);
				pMesh->AddNormal(x, y, z);
			}
		}
		else if (tstrncmp(pszToken, _T("vt"), 3) == 0)
		{
			if (m_pWorkListener)
			{
				if (tstrncmp(sLastTask.c_str(), pszToken, sLastTask.length()) == 0)
					m_pWorkListener->WorkProgress(0);
				else
					m_pWorkListener->SetAction(_T("Reading texture coordinate data"), 0);
			}
			sLastTask = tstring(pszToken);

			// A UV coordinate for a vertex.
			float u, v;
			eastl::vector<tstring> asTokens;
			tstrtok(pszLine, asTokens, _T(" "));
			if (asTokens.size() == 3)
			{
				u = stof(asTokens[1]);
				v = stof(asTokens[2]);
				pMesh->AddUV(u, v);
			}
		}
		else if (tstrncmp(pszToken, _T("g"), 1) == 0)
		{
			// A group of faces.
			pMesh->AddBone(pszLine+2);
		}
		else if (tstrncmp(pszToken, _T("usemtl"), 6) == 0)
		{
			// All following faces should use this material.
			tstring sMaterial = tstring(pszLine+7);
			size_t iMaterial = pMesh->FindMaterialStub(sMaterial);
			if (iMaterial == ((size_t)~0))
			{
				size_t iSceneMaterial = m_pScene->FindMaterial(sMaterial);
				if (iSceneMaterial == ((size_t)~0))
					iCurrentMaterial = m_pScene->AddDefaultSceneMaterial(pScene, pMesh, sMaterial);
				else
				{
					size_t iMaterialStub = pMesh->AddMaterialStub(sMaterial);
					m_pScene->GetDefaultSceneMeshInstance(pScene, pMesh)->GetMeshInstance(0)->AddMappedMaterial(iMaterialStub, iSceneMaterial);
					iCurrentMaterial = iMaterialStub;
				}
			}
			else
				iCurrentMaterial = iMaterial;
		}
		else if (tstrncmp(pszToken, _T("s"), 1) == 0)
		{
			if (tstrncmp(pszLine, _T("s off"), 5) == 0)
			{
				iSmoothingGroup = ~0;
			}
			else
			{
				bSmoothingGroups = true;
				eastl::vector<tstring> asTokens;
				tstrtok(pszLine, asTokens, _T(" "));
				if (asTokens.size() == 2)
					iSmoothingGroup = stoi(asTokens[1]);
			}
		}
		else if (tstrncmp(pszToken, _T("f"), 1) == 0)
		{
			if (m_pWorkListener)
			{
				if (tstrncmp(sLastTask.c_str(), pszToken, sLastTask.length()) == 0)
					m_pWorkListener->WorkProgress(iFacesComplete++);
				else
				{
					m_pWorkListener->SetAction(_T("Reading polygon data"), iTotalFaces);
					sLastTask = tstring(pszToken);
				}
			}

			if (iCurrentMaterial == ~0)
				iCurrentMaterial = m_pScene->AddDefaultSceneMaterial(pScene, pMesh, pMesh->GetName());

			// A face.
			size_t iFace = pMesh->AddFace(iCurrentMaterial);

			// If we get to 10k faces force the mesh off so it doesn't kill the video card.
			if (iFace == 10000)
				pMeshNode->GetMeshInstance(0)->SetVisible(false);

			pMesh->GetFace(iFace)->m_iSmoothingGroup = iSmoothingGroup;

			while (pszToken = strtok<tchar>(NULL, _T(" "), &pszState))
			{
				if (tstrlen(pszToken) == 0)
					continue;

				// We don't use size_t because SOME EXPORTS put out negative numbers.
				long f[3];
				bool bValues[3];
				bValues[0] = false;
				bValues[1] = false;
				bValues[2] = false;

				// scanf is pretty slow even for such a short string due to lots of mallocs.
				const tchar* pszValues = pszToken;
				int iValue = 0;
				do
				{
					if (!pszValues)
						break;

					if (!bValues[0] || pszValues[0] == _T('/'))
					{
						if (pszValues[0] == _T('/'))
							pszValues++;

						bValues[iValue] = true;
						f[iValue++] = (long)stoi(pszValues);
						if (iValue >= 3)
							break;
					}

					// Don't advance if we're on a slash, because that means empty slashes. ie, 11//12 <-- the 12 would get skipped.
					if (pszValues[0] != _T('/'))
						pszValues++;
				}
				while (*pszValues);

				if (bValues[0])
				{
					if (f[0] < 0)
						f[0] = (long)pMesh->GetNumVertices()+f[0]+1;
					TAssert ( f[0] >= 1 && f[0] < (long)pMesh->GetNumVertices()+1 );
				}

				if (bValues[1] && pMesh->GetNumUVs())
				{
					if (f[1] < 0)
						f[1] = (long)pMesh->GetNumUVs()+f[1]+1;
					TAssert ( f[1] >= 1 && f[1] < (long)pMesh->GetNumUVs()+1 );
				}

				if (bValues[2] && pMesh->GetNumNormals())
				{
					if (f[2] < 0)
						f[2] = (long)pMesh->GetNumNormals()+f[2]+1;
					TAssert ( f[2] >= 1 && f[2] < (long)pMesh->GetNumNormals()+1 );
				}

				// OBJ uses 1-based indexing.
				// Convert to 0-based indexing.
				f[0]--;
				f[1]--;
				f[2]--;

				if (!pMesh->GetNumUVs())
					f[1] = ~0;
				if (bValues[2] == false || !pMesh->GetNumNormals())
					f[2] = ~0;

				pMesh->AddVertexToFace(iFace, f[0], f[1], f[2]);
			}
		}
	}

	free(pszEntireFile);

	m_pScene->SetWorkListener(m_pWorkListener);

	m_pScene->CalculateExtends();

	for (size_t i = 0; i < m_pScene->GetNumMeshes(); i++)
	{
		m_pScene->GetMesh(i)->CalculateEdgeData();

		if (bSmoothingGroups || m_pScene->GetMesh(i)->GetNumNormals() == 0)
			m_pScene->GetMesh(i)->CalculateVertexNormals();

		m_pScene->GetMesh(i)->CalculateVertexTangents();
	}

	if (m_pWorkListener)
		m_pWorkListener->EndProgress();
}
Esempio n. 4
0
void CModelConverter::ReadMTL(const tstring& sFilename)
{
	FILE* fp = tfopen(sFilename, _T("r"));

	if (!fp)
		return;

	if (m_pWorkListener)
		m_pWorkListener->SetAction(_T("Reading materials"), 0);

	size_t iCurrentMaterial = ~0;

	tstring sLine;
	while (fgetts(sLine, fp))
	{
		sLine = StripWhitespace(sLine);

		if (sLine.length() == 0)
			continue;

		if (sLine[0] == '#')
			continue;

		eastl::vector<tstring> asTokens;
		tstrtok(sLine, asTokens, _T(" "));
		const tchar* pszToken = NULL;
		pszToken = asTokens[0].c_str();

		CConversionMaterial* pMaterial = NULL;
		if (iCurrentMaterial != ~0)
			pMaterial = m_pScene->GetMaterial(iCurrentMaterial);

		if (tstrncmp(pszToken, _T("newmtl"), 6) == 0)
		{
			pszToken = asTokens[1].c_str();
			CConversionMaterial oMaterial(pszToken, Vector(0.2f,0.2f,0.2f), Vector(0.8f,0.8f,0.8f), Vector(1,1,1), Vector(0,0,0), 1.0, 0);
			iCurrentMaterial = m_pScene->AddMaterial(oMaterial);
		}
		else if (tstrncmp(pszToken, _T("Ka"), 2) == 0)
		{
			eastl::vector<tstring> asTokens;
			tstrtok(sLine, asTokens, _T(" "));
			if (asTokens.size() == 4)
			{
				pMaterial->m_vecAmbient.x = stof(asTokens[1]);
				pMaterial->m_vecAmbient.y = stof(asTokens[2]);
				pMaterial->m_vecAmbient.z = stof(asTokens[3]);
			}
		}
		else if (tstrncmp(pszToken, _T("Kd"), 2) == 0)
		{
			eastl::vector<tstring> asTokens;
			tstrtok(sLine, asTokens, _T(" "));
			if (asTokens.size() == 4)
			{
				pMaterial->m_vecDiffuse.x = stof(asTokens[1]);
				pMaterial->m_vecDiffuse.y = stof(asTokens[2]);
				pMaterial->m_vecDiffuse.z = stof(asTokens[3]);
			}
		}
		else if (tstrncmp(pszToken, _T("Ks"), 2) == 0)
		{
			eastl::vector<tstring> asTokens;
			tstrtok(sLine, asTokens, _T(" "));
			if (asTokens.size() == 4)
			{
				pMaterial->m_vecSpecular.x = stof(asTokens[1]);
				pMaterial->m_vecSpecular.y = stof(asTokens[2]);
				pMaterial->m_vecSpecular.z = stof(asTokens[3]);
			}
		}
		else if (tstrncmp(pszToken, _T("d"), 1) == 0 || tstrncmp(pszToken, _T("Tr"), 2) == 0)
		{
			pMaterial->m_flTransparency = (float)stof(asTokens[1]);
		}
		else if (tstrncmp(pszToken, _T("Ns"), 2) == 0)
		{
			pMaterial->m_flShininess = (float)stof(asTokens[1])*128/1000;
		}
		else if (tstrncmp(pszToken, _T("illum"), 5) == 0)
		{
			pMaterial->m_eIllumType = (IllumType_t)stoi(asTokens[1]);
		}
		else if (tstrncmp(pszToken, _T("map_Kd"), 6) == 0)
		{
			pszToken = asTokens[1].c_str();

			FILE* fpTest = tfopen(pszToken, _T("r"));

			if (fpTest)
			{
				fclose(fpTest);

				pMaterial->m_sDiffuseTexture = tstring(pszToken);
			}
			else
			{
				tstring sDirectory = GetDirectory(sFilename);

				pMaterial->m_sDiffuseTexture = sprintf(tstring("%s/%s"), sDirectory.c_str(), pszToken);
			}
		}
	}

	fclose(fp);
}
Esempio n. 5
0
bool CShader::Compile()
{
	tstring sShaderHeader = CShaderLibrary::GetShaderHeader();

	if (CShaderLibrary::Get()->m_iSamples)
		sShaderHeader += "#define USE_MULTISAMPLE_TEXTURES 1\n";

	sShaderHeader += CShaderLibrary::GetShaderFunctions();

	FILE* f = tfopen("shaders/" + m_sVertexFile + ".vs", "r");

	TAssert(f);
	if (!f)
		return false;

	tstring sVertexShader = sShaderHeader;
	sVertexShader += "uniform mat4x4 mProjection;\n";
	sVertexShader += "uniform mat4x4 mView;\n";
	sVertexShader += "uniform mat4x4 mGlobal;\n";

	tstring sLine;
	while (fgetts(sLine, f))
		sVertexShader += sLine;

	fclose(f);

	f = tfopen("shaders/" + m_sFragmentFile + ".fs", "r");

	TAssert(f);
	if (!f)
		return false;

	tstring sFragmentShader = sShaderHeader;
	sFragmentShader += "out vec4 vecOutputColor;\n";

	while (fgetts(sLine, f))
		sFragmentShader += sLine;

	fclose(f);

	size_t iVShader = glCreateShader(GL_VERTEX_SHADER);
	const char* pszStr = sVertexShader.c_str();
	glShaderSource((GLuint)iVShader, 1, &pszStr, NULL);
	glCompileShader((GLuint)iVShader);

	int iVertexCompiled;
	glGetShaderiv((GLuint)iVShader, GL_COMPILE_STATUS, &iVertexCompiled);

	if (iVertexCompiled != GL_TRUE || Application()->HasCommandLineSwitch("--debug-gl"))
	{
		int iLogLength = 0;
		char szLog[1024];
		glGetShaderInfoLog((GLuint)iVShader, 1024, &iLogLength, szLog);
		CShaderLibrary::Get()->WriteLog(m_sVertexFile + ".vs", szLog, pszStr);
	}

	size_t iFShader = glCreateShader(GL_FRAGMENT_SHADER);
	pszStr = sFragmentShader.c_str();
	glShaderSource((GLuint)iFShader, 1, &pszStr, NULL);
	glCompileShader((GLuint)iFShader);

	int iFragmentCompiled;
	glGetShaderiv((GLuint)iFShader, GL_COMPILE_STATUS, &iFragmentCompiled);

	if (iFragmentCompiled != GL_TRUE || Application()->HasCommandLineSwitch("--debug-gl"))
	{
		int iLogLength = 0;
		char szLog[1024];
		glGetShaderInfoLog((GLuint)iFShader, 1024, &iLogLength, szLog);
		CShaderLibrary::Get()->WriteLog(m_sFragmentFile + ".fs", szLog, pszStr);
	}

	size_t iProgram = glCreateProgram();

	glBindAttribLocation(iProgram, 0, "vecPosition");		// Force position at location 0. ATI cards won't work without this.

	glAttachShader((GLuint)iProgram, (GLuint)iVShader);
	glAttachShader((GLuint)iProgram, (GLuint)iFShader);
	glLinkProgram((GLuint)iProgram);

	int iProgramLinked;
	glGetProgramiv((GLuint)iProgram, GL_LINK_STATUS, &iProgramLinked);

	if (iProgramLinked != GL_TRUE || Application()->HasCommandLineSwitch("--debug-gl"))
	{
		int iLogLength = 0;
		char szLog[1024];
		glGetProgramInfoLog((GLuint)iProgram, 1024, &iLogLength, szLog);
		CShaderLibrary::Get()->WriteLog("link", szLog, "link");
	}

	if (iVertexCompiled != GL_TRUE || iFragmentCompiled != GL_TRUE || iProgramLinked != GL_TRUE)
	{
		TError("Shader compilation failed for shader " + m_sName + ". Check shaders.txt\n");

		Destroy();

		return false;
	}

	m_iProgram = iProgram;
	m_iVShader = iVShader;
	m_iFShader = iFShader;

	m_iPositionAttribute = glGetAttribLocation(m_iProgram, "vecPosition");
	m_iNormalAttribute = glGetAttribLocation(m_iProgram, "vecNormal");
	m_iTangentAttribute = glGetAttribLocation(m_iProgram, "vecTangent");
	m_iBitangentAttribute = glGetAttribLocation(m_iProgram, "vecBitangent");
	for (size_t i = 0; i < MAX_TEXTURE_CHANNELS; i++)
		m_aiTexCoordAttributes[i] = glGetAttribLocation(m_iProgram, sprintf("vecTexCoord%d", i).c_str());
	m_iColorAttribute = glGetAttribLocation(m_iProgram, "vecVertexColor");

	glBindFragDataLocation(m_iProgram, 0, "vecOutputColor");

	TAssert(m_iPositionAttribute != ~0);

	int iNumUniforms;
	glGetProgramiv(m_iProgram, GL_ACTIVE_UNIFORMS, &iNumUniforms);

	char szUniformName[256];
	GLsizei iLength;
	GLint iSize;
	GLenum iType;
	for (int i = 0; i < iNumUniforms; i++)
	{
		glGetActiveUniform(m_iProgram, i, sizeof(szUniformName), &iLength, &iSize, &iType, szUniformName);

		tstring sUniformName = szUniformName;
		if (sUniformName == "mProjection")
			continue;
		if (sUniformName == "mView")
			continue;
		if (sUniformName == "mGlobal")
			continue;

		CShader::CUniform& oUniform = m_asUniforms[sUniformName];
		oUniform.m_pDefault = nullptr;
		switch (iType)
		{
		case GL_FLOAT: oUniform.m_sUniformType = "float"; break;
		case GL_FLOAT_VEC2: oUniform.m_sUniformType = "vec2"; break;
		case GL_FLOAT_VEC3: oUniform.m_sUniformType = "vec3"; break;
		case GL_FLOAT_VEC4: oUniform.m_sUniformType = "vec4"; break;
		case GL_INT: oUniform.m_sUniformType = "int"; break;
		case GL_BOOL: oUniform.m_sUniformType = "bool"; break;
		case GL_FLOAT_MAT4: oUniform.m_sUniformType = "mat4"; break;
		case GL_SAMPLER_2D: oUniform.m_sUniformType = "sampler2D"; break;
		default: TUnimplemented();
		}
	}

	for (auto it = m_aParameters.begin(); it != m_aParameters.end(); it++)
	{
		for (size_t j = 0; j < it->second.m_aActions.size(); j++)
		{
			auto it2 = m_asUniforms.find(it->second.m_aActions[j].m_sName);
			TAssert(it2 != m_asUniforms.end());
			if (it2 == m_asUniforms.end())
			{
				TError("Shader '" + m_sName + "' specifies a uniform '" + it->second.m_aActions[j].m_sName + "' that is not in the linked program.\n");
				continue;
			}

			CShader::CUniform& oUniform = it2->second;

			// This is almost cheating
			CData d;
			d.SetValue(it->second.m_aActions[j].m_sValue);

			if (oUniform.m_sUniformType == "float")
				it->second.m_aActions[j].m_flValue = d.GetValueFloat();
			else if (oUniform.m_sUniformType == "vec2")
				it->second.m_aActions[j].m_vec2Value = d.GetValueVector2D();
			else if (oUniform.m_sUniformType == "vec3")
				it->second.m_aActions[j].m_vecValue = d.GetValueVector();
			else if (oUniform.m_sUniformType == "vec4")
				it->second.m_aActions[j].m_vec4Value = d.GetValueVector4D();
			else if (oUniform.m_sUniformType == "int")
				it->second.m_aActions[j].m_iValue = d.GetValueInt();
			else if (oUniform.m_sUniformType == "bool")
				it->second.m_aActions[j].m_bValue = d.GetValueBool();
			else if (oUniform.m_sUniformType == "mat4")
			{
				TUnimplemented();
			}
			else if (oUniform.m_sUniformType == "sampler2D")
			{
				// No op.
			}
			else
				TUnimplemented();
		}
	}

	for (auto it = m_aDefaults.begin(); it != m_aDefaults.end(); it++)
	{
		auto it2 = m_asUniforms.find(it->first);
		TAssert(it2 != m_asUniforms.end());
		if (it2 == m_asUniforms.end())
		{
			TError("Shader '" + m_sName + "' specifies a default for uniform '" + it->second.m_sName + "' that is not in the linked program.\n");
			continue;
		}

		CShader::CUniform& oUniform = it2->second;
		oUniform.m_pDefault = &it->second;

		// Again with the cheating.
		CData d;
		d.SetValue(it->second.m_sValue);

		if (oUniform.m_sUniformType == "float")
			it->second.m_flValue = d.GetValueFloat();
		else if (oUniform.m_sUniformType == "vec2")
			it->second.m_vec2Value = d.GetValueVector2D();
		else if (oUniform.m_sUniformType == "vec3")
			it->second.m_vecValue = d.GetValueVector();
		else if (oUniform.m_sUniformType == "vec4")
			it->second.m_vec4Value = d.GetValueVector4D();
		else if (oUniform.m_sUniformType == "int")
			it->second.m_iValue = d.GetValueInt();
		else if (oUniform.m_sUniformType == "bool")
			it->second.m_bValue = d.GetValueBool();
		else if (oUniform.m_sUniformType == "mat4")
		{
			TUnimplemented(); 
		}
		else if (oUniform.m_sUniformType == "sampler2D")
		{
			TUnimplemented(); // Can't set a default texture... yet.
		}
		else
			TUnimplemented();
	}

	return true;
}