示例#1
0
void ObjLoader::generateTangents( const QVector<QVector3D>& points,
                                  const QVector<QVector3D>& normals,
                                  const QVector<unsigned  int>& faces,
                                  const QVector<QVector2D>& texCoords,
                                  QVector<QVector4D>& tangents ) const
{
    tangents.clear();
    QVector<QVector3D> tan1Accum;
    QVector<QVector3D> tan2Accum;

    for ( int i = 0; i < points.size(); i++ )
    {
        tan1Accum.append( QVector3D() );
        tan2Accum.append( QVector3D() );
        tangents.append( QVector4D() );
    }

    // Compute the tangent vector
    for ( int i = 0; i < faces.size(); i += 3 )
    {
        const QVector3D& p1 = points[ faces[i] ];
        const QVector3D& p2 = points[ faces[i+1] ];
        const QVector3D& p3 = points[ faces[i+2] ];

        const QVector2D& tc1 = texCoords[ faces[i] ];
        const QVector2D& tc2 = texCoords[ faces[i+1] ];
        const QVector2D& tc3 = texCoords[ faces[i+2] ];

        QVector3D q1 = p2 - p1;
        QVector3D q2 = p3 - p1;
        float s1 = tc2.x() - tc1.x(), s2 = tc3.x() - tc1.x();
        float t1 = tc2.y() - tc1.y(), t2 = tc3.y() - tc1.y();
        float r = 1.0f / ( s1 * t2 - s2 * t1 );
        QVector3D tan1( ( t2 * q1.x() - t1 * q2.x() ) * r,
                        ( t2 * q1.y() - t1 * q2.y() ) * r,
                        ( t2 * q1.z() - t1 * q2.z() ) * r );
        QVector3D tan2( ( s1 * q2.x() - s2 * q1.x() ) * r,
                        ( s1 * q2.y() - s2 * q1.y() ) * r,
                        ( s1 * q2.z() - s2 * q1.z() ) * r );
        tan1Accum[ faces[i]   ] += tan1;
        tan1Accum[ faces[i+1] ] += tan1;
        tan1Accum[ faces[i+2] ] += tan1;
        tan2Accum[ faces[i]   ] += tan2;
        tan2Accum[ faces[i+1] ] += tan2;
        tan2Accum[ faces[i+2] ] += tan2;
    }

    for ( int i = 0; i < points.size(); ++i )
    {
        const QVector3D& n = normals[i];
        QVector3D& t1 = tan1Accum[i];
        QVector3D& t2 = tan2Accum[i];

        // Gram-Schmidt orthogonalize
        tangents[i] = QVector4D( QVector3D( t1 - QVector3D::dotProduct( n, t1 ) * n ).normalized(), 0.0f );

        // Store handedness in w
        tangents[i].setW( ( QVector3D::dotProduct( QVector3D::crossProduct( n, t1 ), t2 ) < 0.0f ) ? -1.0f : 1.0f );
    }
}
示例#2
0
			void DrawVBOMesh::generateTangents(
				const std::vector< vrGLMVec3 > & points,
				const std::vector< vrGLMVec3 > & normals,
				const std::vector< vrInt > & faces,
				const std::vector< vrGLMVec2 > & texCoords,
				std::vector< vrGLMVec4 > & tangents)
			{
				std::vector< vrGLMVec3 > tan1Accum;
				std::vector< vrGLMVec3 > tan2Accum;

				for (vrUnsigned i = 0; i < points.size(); i++) {
					tan1Accum.push_back(vrGLMVec3(0.0));
					tan2Accum.push_back(vrGLMVec3(0.0));
					tangents.push_back(vrGLMVec4(0.0));
				}

				// Compute the tangent vector
				for (vrUnsigned i = 0; i < faces.size(); i += 3)
				{
					const vrGLMVec3 &p1 = points[faces[i]];
					const vrGLMVec3 &p2 = points[faces[i + 1]];
					const vrGLMVec3 &p3 = points[faces[i + 2]];

					const vrGLMVec2 &tc1 = texCoords[faces[i]];
					const vrGLMVec2 &tc2 = texCoords[faces[i + 1]];
					const vrGLMVec2 &tc3 = texCoords[faces[i + 2]];

					vrGLMVec3 q1 = p2 - p1;
					vrGLMVec3 q2 = p3 - p1;
					vrFloat s1 = tc2.x - tc1.x, s2 = tc3.x - tc1.x;
					vrFloat t1 = tc2.y - tc1.y, t2 = tc3.y - tc1.y;
					vrFloat r = 1.0f / (s1 * t2 - s2 * t1);
					vrGLMVec3 tan1((t2*q1.x - t1*q2.x) * r,
						(t2*q1.y - t1*q2.y) * r,
						(t2*q1.z - t1*q2.z) * r);
					vrGLMVec3 tan2((s1*q2.x - s2*q1.x) * r,
						(s1*q2.y - s2*q1.y) * r,
						(s1*q2.z - s2*q1.z) * r);
					tan1Accum[faces[i]] += tan1;
					tan1Accum[faces[i + 1]] += tan1;
					tan1Accum[faces[i + 2]] += tan1;
					tan2Accum[faces[i]] += tan2;
					tan2Accum[faces[i + 1]] += tan2;
					tan2Accum[faces[i + 2]] += tan2;
				}

				for (vrUnsigned i = 0; i < points.size(); ++i)
				{
					const vrGLMVec3 &n = normals[i];
					vrGLMVec3 &t1 = tan1Accum[i];
					vrGLMVec3 &t2 = tan2Accum[i];

					// Gram-Schmidt orthogonalize
					tangents[i] = vrGLMVec4(glm::normalize(t1 - (glm::dot(n, t1) * n)), 0.0);
					// Store handedness in w
					tangents[i].w = (glm::dot(glm::cross(n, t1), t2) < 0.0) ? -1.0 : 1.0;
				}
				tan1Accum.clear();
				tan2Accum.clear();
			}
示例#3
0
//Deprecated does not work properly
void ModelLoader::CalculateTangents(VertexPosNormTanTex& vpntx1, VertexPosNormTanTex& vpntx2, VertexPosNormTanTex& vpntx3)
{
	Vector3& v1 = vpntx1.position;
	Vector3& v2 = vpntx2.position;
	Vector3& v3 = vpntx2.position;

	Vector3& n1 = vpntx1.normal;
	//Vector3& n2 = vpntx2.normal;
	//Vector3& n3 = vpntx3.normal;

	Vector2& tx1 = vpntx1.tex;
	Vector2& tx2 = vpntx2.tex;
	Vector2& tx3 = vpntx3.tex;

	Vector3 t1;
	Vector3 t2;
	Vector3 t3;

	float x1 = v2.X - v1.X;
	float x2 = v3.X - v1.X;
	float y1 = v2.Y - v1.Y;
	float y2 = v3.Y - v1.Y;
	float z1 = v2.Z - v1.Z;
	float z2 = v3.Z - v1.Z;

	float s1 = tx2.X - tx1.X;
	float s2 = tx3.X - tx1.X;
	float st1 = tx2.Y - tx1.Y;
	float st2 = tx3.Y - tx1.Y;

	float r = 1.0f / (s1 * st2 - s2 * st1);

	Vector3 tan1((st2 * x1 - st1 * x2) * r, (st2 * y1 - st1 * y2) * r, (st2 * z1 - st1 * z2) * r );
	Vector3 tan2((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r );

	float n1tan1dot = n1.Dot(tan1);
	t1 = n1 * n1tan1dot;
	t1 = tan1 - t1;
	t1.Normalize();
	
	t2 = n1 * n1tan1dot;
	t2 = tan1 - t1;
	t2.Normalize();

	t3 = n1 * n1tan1dot;
	t3 = tan1 - t1;
	t3.Normalize();

	vpntx1.tangent += t1;
	vpntx2.tangent += t2;
	vpntx3.tangent += t3;
}
示例#4
0
文件: main.cpp 项目: aaanX11/hydro_c
void flux(double* par_ax, int ix, int iy, int iz, double* B, int ax, double& ddeltat){
	int alarm;
	double *par, rho1, u1, p1, ltan1, ltan2, rho2, u2, p2, rtan1, rtan2;
	par = new double[12];

	//forward
	riemparam2(par, ix, iy, iz, ax);	riemi(par, &alarm);
	if(alarm){
		std::cerr<<"function 'riemi' sends an alarm at ("<<ix<<" "<<iy<<" "<<iz<<'\n';}
	tanvel(par[10], rtan1, rtan2, ax, ix, iy, iz);
	updatetimestep(ax, ix, iy, iz, par[9], par[11], ddeltat);
	rho2 = par[6];		u2 = par[7];		p2 = par[8];

	//backward
	if(obstacle(ix, iy, iz, -ax) == 1){
		//tangential velosity components conserve near an obstacle
		riemparam2(par, ix, iy, iz, -ax);	riemi(par, &alarm);
		if(alarm){
			std::cerr<<"function 'riemi' sends an alarm at ("<<ix<<" "<<iy<<" "<<iz<<'\n';}
		updatetimestep(-ax, ix, iy, iz, par[9], par[11], ddeltat);
		par_ax[0] = par[6];		par_ax[1] = par[7];		par_ax[2] = par[8];		par_ax[3] = p[tan1(ax)][ix][iy][iz];		par_ax[4] = p[tan2(ax)][ix][iy][iz];
	}
	rho1 = par_ax[0];			u1 = par_ax[1];			p1 = par_ax[2];		ltan1 = par_ax[3];		ltan2 = par_ax[4];
	
	//fluxes
	B[0] = u2*rho2 - rho1*u1;
	switch(ax){
		case 'X':				
			B[1] = rho2*u2*u2 + p2 - (rho1*u1*u1 + p1);
			B[2] = rho2*u2*rtan1 -	(rho1*u1*ltan1);
			B[3] = rho2*u2*rtan2 -	(rho1*u1*ltan2);
			break;
		case 'Y':
			B[1] = rho2*u2*rtan1 - (rho1*u1*ltan1);
			B[2] = rho2*u2*u2 + p2 - (rho1*u1*u1 + p1);
			B[3] = rho2*u2*rtan2 - (rho1*u1*ltan2);
			break;
		case 'Z':
			B[1] = rho2*u2*rtan1 - rho1*u1*ltan1;
			B[2] = rho2*u2*rtan2 - rho1*u1*ltan2;
			B[3] = rho2*u2*u2 + p2 - (rho1*u1*u1 + p1);
			break;
	}
	B[4] = u2*(rho2*(inenergy(rho2,p2) + 0.5*(u2*u2+rtan1*rtan1+rtan2*rtan2)) + p2) - u1*(rho1*(inenergy(rho1,p1) + 0.5*(u1*u1+ltan1*ltan1+ltan2*ltan2)) + p1);
	
	delete[] par;
	
	//parameters' update
	par_ax[0] = rho2;		par_ax[1] = u2;		par_ax[2] = p2;		par_ax[3] = rtan1;		par_ax[4] = rtan2;
	return;
}
示例#5
0
void ObjGPUData::loadObject(const char* fileName)
{
    std::string folderName = fileName;

    for(int i = folderName.size() - 1; i >= 0; i--)
    {
        if(folderName[i] == '/' || folderName[i] == '\\')
        {
            folderName = folderName.substr(0, i+1);
            break;
        }
        if(i == 0)
            folderName = "";
    }

    std::string objFileName = fileName;
    std::string mtlFileName = fileName;
    objFileName += ".obj";
    mtlFileName += ".mtl";

    std::ifstream mtlFile(mtlFileName.c_str(), std::ios::in);

    if(!mtlFile.is_open())
    {
        printf("Failed to open object %s\n", objFileName.c_str());
        exit(1);
    }

    std::string dataTypeString;
    float xVal, yVal, zVal;
    std::string valString;
    std::stringstream valStream;
    int iVal;

    while(mtlFile >> dataTypeString)
    {

        mtlDataType mtlDataTypeVal = getMtlDataType(dataTypeString);

        switch(mtlDataTypeVal)
        {
        case mtlDataType::mtlDataNEWMTL:
            mtlFile >> valString;
            materials.push_back(Material(valString));
            valString.clear();
            break;

        case mtlDataType::mtlDataNS:
            mtlFile >> xVal;
            materials.back().shine = xVal;
            break;

        case mtlDataType::mtlDataKA:
            mtlFile >> xVal;
            mtlFile >> yVal;
            mtlFile >> zVal;

            materials.back().Ka = glm::vec3(xVal, yVal, zVal);

            break;

        case mtlDataType::mtlDataKD:
            mtlFile >> xVal;
            mtlFile >> yVal;
            mtlFile >> zVal;

            materials.back().Kd = glm::vec3(xVal, yVal, zVal);

            break;

        case mtlDataType::mtlDataKS:
            mtlFile >> xVal;
            mtlFile >> yVal;
            mtlFile >> zVal;

            materials.back().Ks = glm::vec3(xVal, yVal, zVal);

            break;

        case mtlDataType::mtlDataMAP:
        {
            if(materials.back().textureSet)
                break;
            getline(mtlFile, valString);
            int fileNameLocation = valString.find_last_of("/\\");
            if(fileNameLocation != -1)
                valString = valString.substr(fileNameLocation + 1);
            while(valString[0] == ' ' || valString[0] == '\t')
                valString = valString.substr(1);
            if(valString.size() < 4 || valString.substr(valString.size() - 4) != ".dds")
            {
                valString = valString.substr(0, valString.find_last_of('.') + 1);
                valString += "dds";
            }
            materials.back().textureName = valString;
            bool textureExists = false;
            for(int i = 0; i < materials.size() - 1; i++)
            {
                if(materials[i].textureName == valString)
                {
                    textureExists = true;
                    materials.back().texture = materials[i].texture;
                    break;
                }
            }
            if(textureExists)
            {
                valString.clear();
                break;
            }
            materials.back().texture = loadImage((folderName + valString).c_str());
            materials.back().textureSet = true;
            valString.clear();
            break;
        }

        case mtlDataType::mtlDataBUMP:
        {
            if(materials.back().bumpSet)
                break;
            getline(mtlFile, valString);
            int fileNameLocation = valString.find_last_of("/\\");
            if(fileNameLocation != -1)
                valString = valString.substr(fileNameLocation + 1);
            while(valString[0] == ' ' || valString[0] == '\t')
                valString = valString.substr(1);
            if(valString.size() < 4 || valString.substr(valString.size() - 4) != ".dds")
            {
                valString = valString.substr(0, valString.find_last_of('.') + 1);
                valString += "dds";
            }
            materials.back().bumpName = valString;
            bool bumpExists = false;
            for(int i = 0; i < materials.size() - 1; i++)
            {
                if(materials[i].bumpName == valString)
                {
                    bumpExists = true;
                    materials.back().bump = materials[i].bump;
                    break;
                }
            }
            if(bumpExists)
            {
                valString.clear();
                break;
            }
            materials.back().bump = loadImage((folderName + valString).c_str());
            materials.back().bumpSet = true;
            valString.clear();
            break;
        }

        default:
            break;
        }
    }

    mtlFile.close();

    printf("Loading object %s... ", objFileName.c_str());

    std::ifstream objFile(objFileName.c_str(), std::ios::in);

    if(!objFile.is_open())
    {
        printf("Failed to open object %s\n", objFileName.c_str());
        exit(1);
    }

    std::vector<glm::vec3> vList_in;
    std::vector<glm::vec2> vTextureList_in;
    std::vector<glm::vec3> vNormalList_in;
    std::vector<GLuint> iVertex;
    std::vector<GLuint> iTexture;
    std::vector<GLuint> iNormal;

    std::vector<unsigned int> materialIndices_in;

    while(objFile >> dataTypeString)
    {

        dataType dataTypeVal = getDataType(dataTypeString);

        switch(dataTypeVal)
        {
        case dataType::dataV:
            objFile >> xVal;
            objFile >> yVal;
            objFile >> zVal;

            vList_in.push_back(glm::vec3(xVal, yVal, zVal));

            break;

        case dataType::dataVT:
            objFile >> xVal;
            objFile >> yVal;

            vTextureList_in.push_back(glm::vec2(xVal, yVal));

            break;

        case dataType::dataVN:
            objFile >> xVal;
            objFile >> yVal;
            objFile >> zVal;

            vNormalList_in.push_back(glm::vec3(xVal, yVal, zVal));

            break;

        case dataType::dataUSEMTL:
        {
            int index = -1;
            objFile >> valString;

            for(int i = 0; i < materials.size(); i++)
            {
                if(materials[i].materialName == valString)
                {
                    index = i;
                    break;
                }
            }

            if(index == -1)
                exit(1);

            valString.clear();

            materialIndices_in.push_back(iVertex.size());
            materialIndices_in.push_back(index);

            break;
        }


        case dataType::dataF:
        {
            char lastChar;

            for(int i = 0; i < 3; i++)
            {
                getline(objFile, valString, '/');
                valStream << valString;
                valString.clear();
                valStream >> iVal;
                valStream.str(std::string());
                valStream.clear();
                iVertex.push_back(iVal);

                getline(objFile, valString, '/');
                valStream << valString;
                valString.clear();
                valStream >> iVal;
                valStream.str(std::string());
                valStream.clear();
                iTexture.push_back(iVal);

                objFile >> iVal;
                iNormal.push_back(iVal);

                lastChar = objFile.get();
            }

            if(lastChar != '\n')
            {
                while(objFile.peek() == ' ' || objFile.peek() == '\t')
                {
                    objFile.get();
                }

                if(objFile.peek() == '\n')
                    break;

                iVertex.push_back(iVertex[iVertex.size() - 3]);
                iTexture.push_back(iTexture[iTexture.size() - 3]);
                iNormal.push_back(iNormal[iNormal.size() - 3]);

                iVertex.push_back(iVertex[iVertex.size() - 2]);
                iTexture.push_back(iTexture[iTexture.size() - 2]);
                iNormal.push_back(iNormal[iNormal.size() - 2]);

                getline(objFile, valString, '/');
                valStream << valString;
                valString.clear();
                valStream >> iVal;
                valStream.str(std::string());
                valStream.clear();
                iVertex.push_back(iVal);

                getline(objFile, valString, '/');
                valStream << valString;
                valString.clear();
                valStream >> iVal;
                valStream.str(std::string());
                valStream.clear();
                iTexture.push_back(iVal);

                objFile >> iVal;
                iNormal.push_back(iVal);
            }

            break;
        }

        default:
            break;
        }
    }

    objFile.close();

    int first, last;

    std::map<FullVertex,unsigned int> vertexToOutIndex;

    for(int k = 0; k < materialIndices_in.size()/2; ++k)
    {

        first = materialIndices_in[2*k];
        if((2*k + 2) > (materialIndices_in.size() - 1))
            last = iVertex.size();
        else
            last = materialIndices_in[2*k + 2];

        materialIndices.push_back(fList.size());
        materialIndices.push_back(materialIndices_in[2*k + 1]);

        for(int i = first; i < last; i++)
        {

            FullVertex nextVertex = {vList_in[iVertex[i] - 1], vTextureList_in[iTexture[i] - 1], vNormalList_in[iNormal[i] - 1]};

            std::map<FullVertex,unsigned int>::iterator vLocation = vertexToOutIndex.find(nextVertex);

            if(vLocation == vertexToOutIndex.end())
            {
                vList.push_back(vList_in[iVertex[i]-1]);
                vTextureList.push_back(vTextureList_in[iTexture[i]-1]);
                vNormalList.push_back(vNormalList_in[iNormal[i]-1]);

                vertexToOutIndex[nextVertex] = vList.size() - 1;
                fList.push_back(vList.size() - 1);
            }
            else
            {
                fList.push_back(vLocation->second);
            }
        }
    }


    //  Invert all texture v-coordinates for use with DXT compression textures

    for(int i = 0; i < vTextureList.size(); i++)
    {
        vTextureList[i][1] = 1 - vTextureList[i][1];
    }


    //  Generate tangent vectors for normals
    //  Sourced from http://www.terathon.com/code/tangent.html

    std::vector<glm::vec3> tan1(vList.size());
    std::vector<glm::vec3> tan2(vList.size());

    for (unsigned int a = 0; a < fList.size(); a+=3)
    {
        GLuint i1 = fList[a + 0];
        GLuint i2 = fList[a + 1];
        GLuint i3 = fList[a + 2];

        glm::vec3 v1 = vList[i1];
        glm::vec3 v2 = vList[i2];
        glm::vec3 v3 = vList[i3];

        glm::vec2 w1 = vTextureList[i1];
        glm::vec2 w2 = vTextureList[i2];
        glm::vec2 w3 = vTextureList[i3];

        float x1 = v2.x - v1.x;
        float x2 = v3.x - v1.x;
        float y1 = v2.y - v1.y;
        float y2 = v3.y - v1.y;
        float z1 = v2.z - v1.z;
        float z2 = v3.z - v1.z;

        float s1 = w2.x - w1.x;
        float s2 = w3.x - w1.x;
        float t1 = w2.y - w1.y;
        float t2 = w3.y - w1.y;

        float r = 1.0f / (s1 * t2 - s2 * t1);

        glm::vec3 sdir((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
        glm::vec3 tdir((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);

        tan1[i1] += sdir;
        tan1[i2] += sdir;
        tan1[i3] += sdir;

        tan2[i1] += tdir;
        tan2[i2] += tdir;
        tan2[i3] += tdir;
    }

    for (unsigned int a = 0; a < vList.size(); a++)
    {
        glm::vec3 n = vNormalList[a];
        glm::vec3 t = tan1[a];

        // Gram-Schmidt orthogonalize
        glm::vec4 tangent = glm::vec4(glm::normalize(t - n * glm::dot(n, t)), 0.0f);

        // Calculate handedness
        tangent.w = (glm::dot(glm::cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f;

        vTangentList.push_back(tangent);
    }

    printf("DONE\n");

    return;

}
void BufferGeometry::computeTangents() {

  // based on http://www.terathon.com/code/tangent.html
  // (per vertex tangents)

  if ( !attributes.contains( AttributeKey::index() ) ||
       !attributes.contains( AttributeKey::position() ) ||
       !attributes.contains( AttributeKey::normal() ) ||
       !attributes.contains( AttributeKey::uv() ) ) {

    console().warn( "Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()" );
    return;

  }

  const auto& indices   = attributes[ AttributeKey::index() ].array;
  const auto& positions = attributes[ AttributeKey::position() ].array;
  const auto& normals   = attributes[ AttributeKey::normal() ].array;
  const auto& uvs       = attributes[ AttributeKey::uv() ].array;

  const auto nVertices = ( int )positions.size() / 3;

  if ( !attributes.contains( AttributeKey::tangent() ) ) {
    const auto nTangentElements = 4 * nVertices;
    attributes[ AttributeKey::tangent() ] = Attribute( THREE::v4, nTangentElements );
  }

  auto& tangents = attributes[ AttributeKey::tangent() ].array;

  std::vector<Vector3> tan1( nVertices ), tan2( nVertices );

  float xA, yA, zA,
        xB, yB, zB,
        xC, yC, zC,

        uA, vA,
        uB, vB,
        uC, vC,

        x1, x2, y1, y2, z1, z2,
        s1, s2, t1, t2, r;

  Vector3 sdir, tdir;

  auto handleTriangle = [&]( size_t a, size_t b, size_t c ) {

    xA = positions[ a * 3 ];
    yA = positions[ a * 3 + 1 ];
    zA = positions[ a * 3 + 2 ];

    xB = positions[ b * 3 ];
    yB = positions[ b * 3 + 1 ];
    zB = positions[ b * 3 + 2 ];

    xC = positions[ c * 3 ];
    yC = positions[ c * 3 + 1 ];
    zC = positions[ c * 3 + 2 ];

    uA = uvs[ a * 2 ];
    vA = uvs[ a * 2 + 1 ];

    uB = uvs[ b * 2 ];
    vB = uvs[ b * 2 + 1 ];

    uC = uvs[ c * 2 ];
    vC = uvs[ c * 2 + 1 ];

    x1 = xB - xA;
    x2 = xC - xA;

    y1 = yB - yA;
    y2 = yC - yA;

    z1 = zB - zA;
    z2 = zC - zA;

    s1 = uB - uA;
    s2 = uC - uA;

    t1 = vB - vA;
    t2 = vC - vA;

    r = 1.0f / ( s1 * t2 - s2 * t1 );

    sdir.set(
      ( t2 * x1 - t1 * x2 ) * r,
      ( t2 * y1 - t1 * y2 ) * r,
      ( t2 * z1 - t1 * z2 ) * r
    );

    tdir.set(
      ( s1 * x2 - s2 * x1 ) * r,
      ( s1 * y2 - s2 * y1 ) * r,
      ( s1 * z2 - s2 * z1 ) * r
    );

    tan1[ a ].add( sdir );
    tan1[ b ].add( sdir );
    tan1[ c ].add( sdir );

    tan2[ a ].add( tdir );
    tan2[ b ].add( tdir );
    tan2[ c ].add( tdir );

  };

  for ( size_t j = 0, jl = offsets.size(); j < jl; ++ j ) {

    const auto start = offsets[ j ].start;
    const auto count = offsets[ j ].count;
    const auto index = offsets[ j ].index;

    for ( auto i = start, il = start + count; i < il; i += 3 ) {

      const auto iA = index + ( int )indices[ i ];
      const auto iB = index + ( int )indices[ i + 1 ];
      const auto iC = index + ( int )indices[ i + 2 ];

      handleTriangle( iA, iB, iC );

    }

  }

  Vector3 tmp, tmp2;
  Vector3 n, n2;

  auto handleVertex = [&]( size_t v ) {

    n.x = normals[ v * 3 ];
    n.y = normals[ v * 3 + 1 ];
    n.z = normals[ v * 3 + 2 ];

    n2.copy( n );

    const auto& t = tan1[ v ];

    // Gram-Schmidt orthogonalize

    tmp.copy( t );
    tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize();

    // Calculate handedness

    tmp2.crossVectors( n2, t );
    const auto test = tmp2.dot( tan2[ v ] );
    const auto w = ( test < 0.0f ) ? -1.0f : 1.0f;

    tangents[ v * 4 ]     = tmp.x;
    tangents[ v * 4 + 1 ] = tmp.y;
    tangents[ v * 4 + 2 ] = tmp.z;
    tangents[ v * 4 + 3 ] = w;

  };

  for ( size_t j = 0, jl = offsets.size(); j < jl; ++ j ) {

    const auto start = offsets[ j ].start;
    const auto count = offsets[ j ].count;
    const auto index = offsets[ j ].index;

    for ( auto i = start, il = start + count; i < il; i += 3 ) {

      const auto iA = index + ( int )indices[ i ];
      const auto iB = index + ( int )indices[ i + 1 ];
      const auto iC = index + ( int )indices[ i + 2 ];

      handleVertex( iA );
      handleVertex( iB );
      handleVertex( iC );

    }

  }

  hasTangents = true;
  tangentsNeedUpdate = true;

}
void calculateTangents( const void* pVertices, size_t numVertices,
                        uint32 posOff, uint32 texOff, uint32 normOff, uint32 vertStride,
                        const void* pIndices, size_t numIndices, gfx::IndexStride indexStride,
                        he::PrimitiveList<vec3>& outTangents)
{
    he::PrimitiveList<vec3> tan1(numVertices);
    tan1.resize(numVertices);

    const char* pCharVertices(static_cast<const char*>(pVertices));

    const uint16* indicesUShort(nullptr); 
    const uint32* indicesUInt(nullptr); 
    
    if (indexStride == gfx::IndexStride_UShort)
        indicesUShort = static_cast<const uint16*>(pIndices);
    else if (indexStride == gfx::IndexStride_UInt)
        indicesUInt = static_cast<const uint32*>(pIndices);
    else
        LOG(LogType_ProgrammerAssert, "unkown index stride: %d", indexStride);
    
    for (uint32 i = 0; i < numIndices; i += 3) //per triangle
    {
        uint32 i1(0), i2(0), i3(0);
        if (indexStride == gfx::IndexStride_UShort)
        {
            i1 = indicesUShort[i];
            i2 = indicesUShort[i + 1];
            i3 = indicesUShort[i + 2];
        }
        else if (indexStride == gfx::IndexStride_UInt)
        {
            i1 = indicesUInt[i];
            i2 = indicesUInt[i + 1];
            i3 = indicesUInt[i + 2];
        }

        const vec3& v1 = *reinterpret_cast<const vec3*>(pCharVertices + (i1 * vertStride + posOff));
        const vec3& v2 = *reinterpret_cast<const vec3*>(pCharVertices + (i2 * vertStride + posOff));
        const vec3& v3 = *reinterpret_cast<const vec3*>(pCharVertices + (i3 * vertStride + posOff));

        const vec2& tx1 = *reinterpret_cast<const vec2*>(pCharVertices + (i1 * vertStride + texOff));
        const vec2& tx2 = *reinterpret_cast<const vec2*>(pCharVertices + (i2 * vertStride + texOff));
        const vec2& tx3 = *reinterpret_cast<const vec2*>(pCharVertices + (i3 * vertStride + texOff));

        float x1 = v2.x - v1.x;
        float x2 = v3.x - v1.x;
        float y1 = v2.y - v1.y;
        float y2 = v3.y - v1.y;
        float z1 = v2.z - v1.z;
        float z2 = v3.z - v1.z;

        float s1 = tx2.x - tx1.x;
        float s2 = tx3.x - tx1.x;
        float t1 = tx2.y - tx1.y;
        float t2 = tx3.y - tx1.y;

        float r = 1.0f / (s1 * t2 - s2 * t1);

        vec3 sdir(
            (t2 * x1 - t1 * x2) * r, 
            (t2 * y1 - t1 * y2) * r, 
            (t2 * z1 - t1 * z2) * r );
        vec3 tdir(
            (s1 * x2 - s2 * x1) * r, 
            (s1 * y2 - s2 * y1) * r, 
            (s1 * z2 - s2 * z1) * r );

        tan1[i1] += sdir;
        tan1[i2] += sdir;
        tan1[i3] += sdir;
    }

    for (uint32 i = 0; i < numVertices; ++i)
    {
        const vec3& n = *reinterpret_cast<const vec3*>(pCharVertices + (i * vertStride + normOff));
        const vec3& t = tan1[i];

        outTangents.add(normalize(t - n * dot(n, t)));
    }
}