bool OctreeProjectedPolygon::matches(const OctreeProjectedPolygon& testee) const {
    if (testee.getVertexCount() != getVertexCount()) {
        return false;
    }
    int vertextCount = getVertexCount();
    // find which testee vertex matches our first polygon vertex.
    glm::vec2 polygonVertex = getVertex(0);
    int originIndex = 0;
    for(int i = 0; i < vertextCount; i++) {
        glm::vec2 testeeVertex = testee.getVertex(i);

        // if they match, we found our origin.
        if (testeeVertex == polygonVertex) {
            originIndex = i;
            break;
        }
    }
    // Now, starting at the originIndex, walk the vertices of both the testee and ourselves

    for(int i = 0; i < vertextCount; i++) {
        glm::vec2 testeeVertex  = testee.getVertex((i + originIndex) % vertextCount);
        glm::vec2 polygonVertex = getVertex(i);
        if (testeeVertex != polygonVertex) {
            return false; // we don't match, therefore we're not the same
        }
    }
    return true; // all of our vertices match, therefore we're the same
}
bool OctreeProjectedPolygon::pointInside(const glm::vec2& point, bool* matchesVertex) const {

    OctreeProjectedPolygon::pointInside_calls++;

    // first check the bounding boxes, the point must be fully within the boounding box of this polygon
    if ((point.x > getMaxX()) ||
            (point.y > getMaxY()) ||
            (point.x < getMinX()) ||
            (point.y < getMinY())) {
        return false;
    }

    // consider each edge of this polygon as a potential separating axis
    // check the point against each edge
    for (int i = 0; i < getVertexCount(); i++) {
        glm::vec2 start = getVertex(i);
        glm::vec2 end   = getVertex((i + 1) % getVertexCount());
        float a = start.y - end.y;
        float b = end.x - start.x;
        float c = a * start.x + b * start.y;
        if (a * point.x + b * point.y < c) {
            return false;
        }
    }

    return true;
}
Exemple #3
0
    //-----------------------------------------------------------------------
    bool Polygon::operator == (const Polygon& rhs) const
    {
        if ( getVertexCount() != rhs.getVertexCount() )
            return false;

        // Compare vertices. They may differ in its starting position.
        // find start
        size_t start = 0;
        bool foundStart = false;
        for (size_t i = 0; i < getVertexCount(); ++i )
        {   
            if (getVertex(0).positionEquals(rhs.getVertex(i)))
            {
                start = i;
                foundStart = true;
                break;
            }
        }

        if (!foundStart)
            return false;

        for (size_t i = 0; i < getVertexCount(); ++i )
        {
            const Vector3& vA = getVertex( i );
            const Vector3& vB = rhs.getVertex( ( i + start) % getVertexCount() );

            if (!vA.positionEquals(vB))
                return false;
        }

        return true;
    }
//
// Tests the edges of this polygon as potential separating axes for this polygon and the
// specified other.
//
// @return false if the polygons are disjoint on any of this polygon's axes, true if they
// intersect on all axes.
//
// Note: this only works on convex polygons
//
//
bool OctreeProjectedPolygon::intersectsOnAxes(const OctreeProjectedPolygon& testee) const {

    // consider each edge of this polygon as a potential separating axis
    for (int i = 0; i < getVertexCount(); i++) {
        glm::vec2 start = getVertex(i);
        glm::vec2 end   = getVertex((i + 1) % getVertexCount());
        float a = start.y - end.y;
        float b = end.x - start.x;
        float c = a * start.x + b * start.y;

        // if all vertices fall outside the edge, the polygons are disjoint
        // points that are ON the edge, are considered to be "outside"
        for (int j = 0; j < testee.getVertexCount(); j++) {
            glm::vec2 testeeVertex = testee.getVertex(j);

            // in comparison below:
            //      >= will cause points on edge to be considered inside
            //      >  will cause points on edge to be considered outside

            float c2 = a * testeeVertex.x + b * testeeVertex.y;
            if (c2 >= c) {
                goto CONTINUE_OUTER;
            }
        }
        return false;
CONTINUE_OUTER:
        ;
    }
    return true;
}
bool VoxelProjectedPolygon::pointInside(const glm::vec2& point) const {
    // first check the bounding boxes, the point must be fully within the boounding box of this shadow
    if ((point.x > getMaxX()) ||
        (point.y > getMaxY()) ||
        (point.x < getMinX()) ||
        (point.y < getMinY())) {
        return false;
    }

    float e = (getMaxX() - getMinX()) / 100.0f; // some epsilon
    
    // We need to have one ray that goes from a known outside position to the point in question. We'll pick a
    // start point just outside of our min X
    glm::vec2 r1p1(getMinX() - e, point.y);
    glm::vec2 r1p2(point);

    glm::vec2 r2p1(getVertex(getVertexCount()-1)); // start with last vertex to first vertex
    glm::vec2 r2p2;
    
    // Test the ray against all sides
    int intersections = 0;
    for (int i = 0; i < getVertexCount(); i++) {
        r2p2 = getVertex(i);
        if (doLineSegmentsIntersect(r1p1, r1p2, r2p1, r2p2)) {
            intersections++;
        }
        r2p1 = r2p2; // set up for next side
    }

    // If odd number of intersections, we're inside    
    return ((intersections & 1) == 1);
}
void VoxelProjectedPolygon::printDebugDetails() const {
    printf("VoxelProjectedPolygon...");
    printf("    minX=%f maxX=%f minY=%f maxY=%f\n", getMinX(), getMaxX(), getMinY(), getMaxY());
    printf("    vertex count=%d distance=%f\n", getVertexCount(), getDistance());
    for (int i = 0; i < getVertexCount(); i++) {
        glm::vec2 point = getVertex(i);
        printf("    vertex[%d] = %f, %f \n", i, point.x, point.y);
    }
}
void CubeProjectedPolygon::printDebugDetails() const {
    qCDebug(shared, "CubeProjectedPolygon..."
            "    minX=%f maxX=%f minY=%f maxY=%f", (double)getMinX(), (double)getMaxX(), (double)getMinY(), (double)getMaxY());
    qCDebug(shared, "    vertex count=%d distance=%f", getVertexCount(), (double)getDistance());
    for (int i = 0; i < getVertexCount(); i++) {
        glm::vec2 point = getVertex(i);
        qCDebug(shared, "    vertex[%d] = %f, %f ", i, (double)point.x, (double)point.y);
    }
}
Exemple #8
0
    //-----------------------------------------------------------------------
    void Polygon::removeDuplicates( void )
    {
        for ( size_t i = 0; i < getVertexCount(); ++i )
        {
            const Vector3& a = getVertex( i );
            const Vector3& b = getVertex( (i + 1)%getVertexCount() );

            if (a.positionEquals(b))
            {
                deleteVertex(i);
                --i;
            }
        }
    }
void ApexVertexBuffer::applyTransformation(const PxMat44& transformation)
{
	RenderDataFormat::Enum format;
	void* buf;
	uint32_t index;

	// Positions
	index = (uint32_t)getFormat().getBufferIndexFromID(getFormat().getSemanticID(RenderVertexSemantic::POSITION));
	buf = getBuffer(index);
	if (buf)
	{
		format = getFormat().getBufferFormat(index);
		transformRenderBuffer(buf, buf, format, getVertexCount(), transformation);
	}

	// Normals
	index = (uint32_t)getFormat().getBufferIndexFromID(getFormat().getSemanticID(RenderVertexSemantic::NORMAL));
	buf = getBuffer(index);
	if (buf)
	{
		// PH: the Cofactor matrix now also handles negative determinants, so it does the same as multiplying with the inverse transpose of transformation.M.
		const Cof44 cof(transformation);
		format = getFormat().getBufferFormat(index);
		transformRenderBuffer(buf, buf, format, getVertexCount(), cof.getBlock33());
	}

	// Tangents
	index = (uint32_t)getFormat().getBufferIndexFromID(getFormat().getSemanticID(RenderVertexSemantic::TANGENT));
	buf = getBuffer(index);
	if (buf)
	{
		format = getFormat().getBufferFormat(index);
		const PxMat33 tm(transformation.column0.getXYZ(),
								transformation.column1.getXYZ(),
								transformation.column2.getXYZ());
		transformRenderBuffer(buf, buf, format, getVertexCount(), tm);
	}

	// Binormals
	index = (uint32_t)getFormat().getBufferIndexFromID(getFormat().getSemanticID(RenderVertexSemantic::BINORMAL));
	buf = getBuffer(index);
	if (buf)
	{
		format = getFormat().getBufferFormat(index);
		const PxMat33 tm(transformation.column0.getXYZ(),
								transformation.column1.getXYZ(),
								transformation.column2.getXYZ());
		transformRenderBuffer(buf, buf, format, getVertexCount(), tm);
	}
}
Exemple #10
0
void cGraph::getVertexLocation( double& x, double& y, int i )
{
	if( 0 > i || i >= getVertexCount() )
		return;
	x = myGraph[ *vertices(myGraph).first + i ].getLocationX();
	y = myGraph[ *vertices(myGraph).first + i ].getLocationY();
}
BoundingVolume* AbstractMesh::computeBoundingVolume() const {
  const int vertexCount = getVertexCount();

  if (vertexCount <= 0) {
    return NULL;
  }

  double minX = 1e12;
  double minY = 1e12;
  double minZ = 1e12;

  double maxX = -1e12;
  double maxY = -1e12;
  double maxZ = -1e12;

  for (int i=0; i < vertexCount; i++) {
    const int i3 = i * 3;

    const double x = _vertices->get(i3    ) + _center._x;
    const double y = _vertices->get(i3 + 1) + _center._y;
    const double z = _vertices->get(i3 + 2) + _center._z;

    if (x < minX) minX = x;
    if (x > maxX) maxX = x;

    if (y < minY) minY = y;
    if (y > maxY) maxY = y;

    if (z < minZ) minZ = z;
    if (z > maxZ) maxZ = z;
  }

  return new Box(Vector3D(minX, minY, minZ),
                 Vector3D(maxX, maxY, maxZ));
}
Exemple #12
0
unsigned int Mesh::addVertex(const Vertex& vertex)
{
    unsigned int index = getVertexCount();
    vertices.push_back(vertex);
    vertexLookupTable[vertex] = index;
    return index;
}
Exemple #13
0
    //-----------------------------------------------------------------------
    bool Polygon::isPointInside(const Vector3& point) const
    {
        // sum the angles 
        Real anglesum = 0;
        size_t n = getVertexCount();
        for (size_t i = 0; i < n; i++) 
        {
            const Vector3& p1 = getVertex(i);
            const Vector3& p2 = getVertex((i + 1) % n);

            Vector3 v1 = p1 - point;
            Vector3 v2 = p2 - point;

            Real len1 = v1.length();
            Real len2 = v2.length();

            if (Math::RealEqual(len1 * len2, 0.0f, 1e-4f))
            {
                // We are on a vertex so consider this inside
                return true; 
            }
            else
            {
                Real costheta = v1.dotProduct(v2) / (len1 * len2);
                anglesum += acos(costheta);
            }
        }

        // result should be 2*PI if point is inside poly
        return Math::RealEqual(anglesum, Math::TWO_PI, 1e-4f);

    }
Exemple #14
0
    //-----------------------------------------------------------------------
    void Polygon::updateNormal( void ) const
    {
        OgreAssertDbg( getVertexCount() >= 3, "Insufficient vertex count!" );

        if (mIsNormalSet)
            return;

        // vertex order is ccw
        const Vector3& a = getVertex( 0 );
        const Vector3& b = getVertex( 1 );
        const Vector3& c = getVertex( 2 );

        // used method: Newell
        mNormal.x = 0.5f * ( (a.y - b.y) * (a.z + b.z) +
                               (b.y - c.y) * (b.z + c.z) + 
                               (c.y - a.y) * (c.z + a.z));

        mNormal.y = 0.5f * ( (a.z - b.z) * (a.x + b.x) +
                               (b.z - c.z) * (b.x + c.x) + 
                               (c.z - a.z) * (c.x + a.x));

        mNormal.z = 0.5f * ( (a.x - b.x) * (a.y + b.y) +
                               (b.x - c.x) * (b.y + c.y) + 
                               (c.x - a.x) * (c.y + a.y));

        mNormal.normalise();

        mIsNormalSet = true;

    }
Exemple #15
0
	//------------------------------------------------------------------------------------------------
	Ogre::Vector3 VertexIndexToShape::getSize()
	{
		const unsigned int vCount = getVertexCount();
		if (mBounds == Ogre::Vector3(-1,-1,-1) && vCount > 0)
		{

			const Ogre::Vector3 * const v = getVertices();

			Ogre::Vector3 vmin(v[0]);
			Ogre::Vector3 vmax(v[0]);

			for(unsigned int j = 1; j < vCount; j++)
			{
				vmin.x = std::min(vmin.x, v[j].x);
				vmin.y = std::min(vmin.y, v[j].y);
				vmin.z = std::min(vmin.z, v[j].z);

				vmax.x = std::max(vmax.x, v[j].x);
				vmax.y = std::max(vmax.y, v[j].y);
				vmax.z = std::max(vmax.z, v[j].z);
			}

			mBounds.x = vmax.x - vmin.x;
			mBounds.y = vmax.y - vmin.y;
			mBounds.z = vmax.z - vmin.z;
		}

		return mBounds;
	}
    void StaticGeometryBuffer::build( DrawHint drawHint )
	{
		if(getVertexProperties() & TANGENTS)
			_generateTangents();

		_generateIndices();
		
		
        if( _vertexBufferHandle == 0 )
            glGenBuffers( 1, &_vertexBufferHandle );

        if( _indexBufferHandle == 0 )
            glGenBuffers( 1, &_indexBufferHandle );
		
        int vCount = getVertexCount();
        int iCount = getIndexCount();

        glBindBuffer( GL_ARRAY_BUFFER,          _vertexBufferHandle );
        glBufferData( GL_ARRAY_BUFFER,          sizeof( Vertex ) * _vertexList.size(), &_vertexList[ 0 ], drawHint );
        //glBufferSubData( GL_ARRAY_BUFFER, 0,    sizeof( Vertex ) * _vertexList.size(), &_vertexList[ 0 ] );


        glBindBuffer( GL_ELEMENT_ARRAY_BUFFER,          _indexBufferHandle );
        glBufferData( GL_ELEMENT_ARRAY_BUFFER,          sizeof( GLuint ) * _indexList.size(), &_indexList[ 0 ] , drawHint );
       // glBufferSubData( GL_ELEMENT_ARRAY_BUFFER, 0,    sizeof( GLuint ) * _indexList.size(), &_indexList[ 0 ] );
	}
void VertexGenerator::Mesh2D::dumpInfo()
{
    LOG_INFO("<-Dumpping Mesh2D information->\n");
    LOG_INFO("Vertex Point Count: %d, Vertex Size: %d, TexCoords Size: %d, Byte Stride: %d\n",
             getVertexCount(), getVertexSize(), getTexCoordsSize(), getByteStride());
    for(size_t i = 0; i < getVertexCount(); ++i)
        {
            float* position = getPositions() + getStride() * i;
            LOG_INFO("VertexPosition[%d]: %f, %f\n", i, *position, *(position + 1));
        }
    for(size_t i = 0; i < getVertexCount(); ++i)
        {
            float* texCoords = getTexCoords() + getStride() * i;
            LOG_INFO("TexCoords[%d]: %f, %f\n", i, *texCoords, *(texCoords + 1));
        }
}
void CC3MeshParticleEmitter::initializeParticle( CC3Particle* aParticle )
{
	// The vertex offsets depend on particleCount, which has not yet been incremented.
	((CC3MeshParticle*)aParticle)->setFirstVertexOffset( getVertexCount() );
	((CC3MeshParticle*)aParticle)->setFirstVertexIndexOffset( getVertexIndexCount() );
	copyTemplateContentToParticle( ((CC3MeshParticle*)aParticle) );
}
Exemple #19
0
 void Mesh::forAllIndices(std::function<void(int iter, int index)> func)
 {
     if (!mIndices)
     {
         for (int i = 0; i < getVertexCount(); ++i)
         {
             func(i, i);
         }
     }
     else if (mIndices->getIndexSize() == 2)
     {
         const unsigned short* indexData = reinterpret_cast<const unsigned short *>(mIndices->getIndexData());
         for (int i = 0; i < mIndices->getIndexCount(); ++i)
         {
             int v = *(indexData + i);
             func(i, v);
         }
     }
     else
     {
         const unsigned int* indexData = reinterpret_cast<const unsigned int *>(mIndices->getIndexData());
         for (int i = 0; i < mIndices->getIndexCount(); ++i)
         {
             int v = *(indexData + i);
             func(i, v);
         }
     }
 }
Exemple #20
0
void Spline::addVertex(const unsigned int index, const sf::Vector2f position)
{
	if (index < getVertexCount())
		m_vertices.insert(m_vertices.begin() + index, Vertex(position));
	else
		addVertex(position);
}
void ApexVertexBuffer::applyScale(float scale)
{
	uint32_t index = (uint32_t)getFormat().getBufferIndexFromID(getFormat().getSemanticID(RenderVertexSemantic::POSITION));
	void* buf = getBuffer(index);
	RenderDataFormat::Enum format = getFormat().getBufferFormat(index);
	transformRenderBuffer(buf, buf, format, getVertexCount(), scale);
}
void Scene::onPolyhedronChanged()
{
    // Update Polyhedron
    auto symbol = "#" + wild::conversion_cast<std::string>(wild::mod(_index, 80) + 1);

    _pgeometry->setSymbol(symbol);
    _pgeometry->setFaceMask(static_cast<osgKaleido::PolyhedronGeometry::FaceMask>(_faces));

    auto polyhedron = _pgeometry->getOrCreatePolyhedron();

    OSG_INFO << polyhedron->getName() << " (" << polyhedron->getDualName() << "*)" << std::endl;
    OSG_INFO << polyhedron->getWythoffSymbol() << std::endl;
    OSG_INFO << polyhedron->getVertexConfiguration() << std::endl;
    OSG_INFO << polyhedron->getVertexCount() << std::endl;
    OSG_INFO << polyhedron->getFaceCount() << std::endl;

    // Update Vertices
    osg::ref_ptr<osg::Vec3Array> vertices = osgKaleido::createVertexArray(*polyhedron);

    auto stateSet = _vgeode->getOrCreateStateSet();
    auto offsets = stateSet->getUniform("offsets");

    copy(offsets, vertices, osg::Vec3());

    // Update Text
    _text->setText(polyhedron->getName() + "\n" + polyhedron->getWythoffSymbol());
}
Exemple #23
0
cVertex& cGraph::getVertex( int i )
{
	if( 0 > i || i >= getVertexCount() ) {
		static cVertex vertex_null;
		return vertex_null;
	}
	return  myGraph[ *vertices(myGraph).first + i ];
}
Exemple #24
0
    //-----------------------------------------------------------------------
    void Polygon::setVertex(const Vector3& vdata, size_t vertex )
    {
        // TODO: optional: check planarity
        OgreAssertDbg(vertex < getVertexCount(), "Search position out of range" );

        // set new vertex
        mVertexList[ vertex ] = vdata;
    }
Exemple #25
0
void cGraph::setFreeLocation( int i )
{
	if( 0 > i || i >= getVertexCount() )
		return;
	graph_t::vertex_descriptor v = *vertices(myGraph).first;
	myGraph[v+i].myFixedLocation = false;
	SaveToDB();
}
	//-----------------------------------------------------------------------
	const Vector3& Polygon::getNormal( void ) const
	{
		OgreAssert( getVertexCount() >= 3, "Insufficient vertex count!" );

		updateNormal();

		return mNormal;
	}
// can be optimized with new pointInside()
bool OctreeProjectedPolygon::occludes(const OctreeProjectedPolygon& occludee, bool checkAllInView) const {

    OctreeProjectedPolygon::occludes_calls++;

    // if we are completely out of view, then we definitely don't occlude!
    // if the occludee is completely out of view, then we also don't occlude it
    //
    // this is true, but unfortunately, we're not quite handling projects in the
    // case when SOME points are in view and others are not. So, we will not consider
    // occlusion for any shadows that are partially in view.
    if (checkAllInView && (!getAllInView() || !occludee.getAllInView())) {
        return false;
    }

    // first check the bounding boxes, the occludee must be fully within the boounding box of this shadow
    if ((occludee.getMaxX() > getMaxX()) ||
            (occludee.getMaxY() > getMaxY()) ||
            (occludee.getMinX() < getMinX()) ||
            (occludee.getMinY() < getMinY())) {
        return false;
    }

    // we need to test for identity as well, because in the case of identity, none of the points
    // will be "inside" but we don't want to bail early on the first non-inside point
    bool potentialIdenity = false;
    if ((occludee.getVertexCount() == getVertexCount()) && (getBoundingBox().contains(occludee.getBoundingBox())) ) {
        potentialIdenity = true;
    }
    // if we got this far, then check each vertex of the occludee, if all those points
    // are inside our polygon, then the tested occludee is fully occluded
    int pointsInside = 0;
    for(int i = 0; i < occludee.getVertexCount(); i++) {
        bool vertexMatched = false;
        if (!pointInside(occludee.getVertex(i), &vertexMatched)) {

            // so the point we just tested isn't inside, but it might have matched a vertex
            // if it didn't match a vertext, then we bail because we can't be an identity
            // or if we're not expecting identity, then we also bail early, no matter what
            if (!potentialIdenity || !vertexMatched) {
                return false;
            }
        } else {
            pointsInside++;
        }
    }

    // we're only here if all points are inside matched and/or we had a potentialIdentity we need to check
    if (pointsInside == occludee.getVertexCount()) {
        return true;
    }

    // If we have the potential for identity, then test to see if we match, if we match, we occlude
    if (potentialIdenity) {
        return matches(occludee);
    }

    return false; // if we got this far, then we're not occluded
}
Exemple #28
0
    //-----------------------------------------------------------------------
    void Polygon::deleteVertex( size_t vertex )
    {
        OgreAssertDbg( vertex < getVertexCount(), "Search position out of range" );

        VertexList::iterator it = mVertexList.begin();
        std::advance(it, vertex);

        mVertexList.erase( it );
    }
Exemple #29
0
void cGraph::setFixedLocation( int i,  double x, double y )
{
	if( 0 > i || i >= getVertexCount() )
		return ;
	graph_t::vertex_descriptor v = *vertices(myGraph).first;
	myGraph[v+i].myFixedLocation = true;
	myGraph[v+i].setFixedLocation( x, y );
	SaveToDB();
}
Exemple #30
0
void MeshData::generateTangents() {
  int   pos_attr, uv_attr, len, n;
  float r, *pos, *uv, *tan, *bitan;
  vec3  pos_d1, pos_d2, uv_d1, uv_d2;

  pos_attr = getAttributeIndex("position");
  uv_attr  = getAttributeIndex("texcoord");

  if (pos_attr < 0) {
#ifndef PLG_RELEASE
    dprintf(2, "error: can't generateTangents() without position attribute\n");
#endif
    return;
  }

  if (uv_attr < 0) {
#ifndef PLG_RELEASE
    dprintf(2, "error: can't generateTangents() without texcoord attribute\n");
#endif
    return;
  }

  pos     = getVertex(pos_attr, 0);
  uv      = getVertex(uv_attr, 0);
  len     = getVertexCount() / 3;
  tan     = (float *) malloc(len * 9 * sizeof(float));
  bitan   = (float *) malloc(len * 9 * sizeof(float));

  for (n = 0; n < len; n ++) {
    vec3_sub(&pos_d1, (vec3 *) (pos + n * 9 + 3), (vec3 *) (pos + n * 9));
    vec3_sub(&pos_d2, (vec3 *) (pos + n * 9 + 6), (vec3 *) (pos + n * 9));

    uv_d1.d[0] = uv[n * 6 + 2] - uv[n * 6 + 0];
    uv_d1.d[1] = uv[n * 6 + 3] - uv[n * 6 + 1];
    uv_d2.d[0] = uv[n * 6 + 4] - uv[n * 6 + 0];
    uv_d2.d[1] = uv[n * 6 + 5] - uv[n * 6 + 1];

    r = 1.0f / (uv_d1.d[0] * uv_d2.d[1] - uv_d1.d[1] * uv_d2.d[0]);

    tan[n * 9 + 0] = (pos_d1.d[0] * uv_d2.d[1] - pos_d2.d[0] * uv_d1.d[1]) * r;
    tan[n * 9 + 1] = (pos_d1.d[1] * uv_d2.d[1] - pos_d2.d[1] * uv_d1.d[1]) * r;
    tan[n * 9 + 2] = (pos_d1.d[2] * uv_d2.d[1] - pos_d2.d[2] * uv_d1.d[1]) * r;
    tan[n * 9 + 6] = tan[n * 9 + 3] = tan[n * 9 + 0];
    tan[n * 9 + 7] = tan[n * 9 + 4] = tan[n * 9 + 1];
    tan[n * 9 + 8] = tan[n * 9 + 5] = tan[n * 9 + 2];

    bitan[n * 9 + 0] = (pos_d2.d[0] * uv_d1.d[0] - pos_d1.d[0] * uv_d2.d[0]) * r;
    bitan[n * 9 + 1] = (pos_d2.d[1] * uv_d1.d[0] - pos_d1.d[1] * uv_d2.d[0]) * r;
    bitan[n * 9 + 2] = (pos_d2.d[2] * uv_d1.d[0] - pos_d1.d[2] * uv_d2.d[0]) * r;
    bitan[n * 9 + 6] = bitan[n * 9 + 3] = bitan[n * 9 + 0];
    bitan[n * 9 + 7] = bitan[n * 9 + 4] = bitan[n * 9 + 1];
    bitan[n * 9 + 8] = bitan[n * 9 + 5] = bitan[n * 9 + 2];
  }

  addTangents(tan, len * 9);
  addBitangents(bitan, len * 9);
}