Beispiel #1
0
void create_subdivided_face(int sdRes, int nV, double *uvs, double *itv, int Tidx, MFloatArray &uA, MFloatArray &vA, MIntArray &uvIdx)
{
    HDS hds;
    
    hds.V.setDims(2, nV); memcpy(&hds.V.v[0], uvs, 2*nV*sizeof(double));
    hds.nFV.setDims(1, 1); hds.nFV[0] = nV;

    hds.tip.setDims(1, nV); 
    for (size_t k=0; k<nV; k++) 
    {
        hds.tip[k] = k;
    }
    finalize_HDS(hds);
    
    size_t nHE = hds.nHE(), nIHE = hds.nIHE();⟵
    hds.T.setDims(1, nHE);
    hds.itv.setDims(1, nHE);

    memset(&hds.T.v[0], 0, nHE*sizeof(bool)); if (nV==5) hds.T[Tidx] = 1;
    memcpy(&hds.itv.v[0], itv, nV*sizeof(double));

    // border halfedge tags
    for (size_t k=nIHE; k<nHE; k++) 
    {
        hds.itv[k] = hds.itv[hds.twin[k]];
    }
    
    TCC_MAX::linear_subdivide(hds, sdRes);
    
    int sd_nV = hds.nV();
    int sd_nIHE = hds.nIHE();

    uA.setLength(sd_nV);
    vA.setLength(sd_nV);
    for (int k=0; k<sd_nV; k++)
    {
        uA[k] = hds.V[2*k+0];
        vA[k] = hds.V[2*k+1];
    }
    
    uvIdx.setLength(sd_nIHE);
    for (int k=0; k<sd_nIHE; k++)
    {
        uvIdx[k]=hds.tip[k];
    }
}
void peltOverlap::createBoundingCircle(const MStringArray &flattenFaces, MFloatArray &center, MFloatArray &radius)
//
// Description
//     Represent a face by a center and radius, i.e.
//     center = {center1u, center1v, center2u, center2v, ... }
//     radius = {radius1, radius2,  ... }
//
{
    center.setLength(2 * flattenFaces.length());
    radius.setLength(flattenFaces.length());
    for(unsigned int i = 0; i < flattenFaces.length(); i++) {
        MSelectionList selList;
        selList.add(flattenFaces[i]);
        MDagPath dagPath;
        MObject  comp;
        selList.getDagPath(0, dagPath, comp);
        MItMeshPolygon iter(dagPath, comp);

        MFloatArray uArray, vArray;
        iter.getUVs(uArray, vArray);
        // Loop through all vertices to construct edges/rays
        float cu = 0.f;
        float cv = 0.f;
        unsigned int j;
        for(j = 0; j < uArray.length(); j++) {
            cu += uArray[j];
            cv += vArray[j];
        }
        cu = cu / uArray.length();
        cv = cv / vArray.length();
        float rsqr = 0.f;
        for(j = 0; j < uArray.length(); j++) {
            float du = uArray[j] - cu;
            float dv = vArray[j] - cv;
            float dsqr = du*du + dv*dv;
            rsqr = dsqr > rsqr ? dsqr : rsqr;
        }
        center[2*i]   = cu;
        center[2*i+1] = cv;
        radius[i]  = sqrt(rsqr);
    }
}
bool peltOverlap::createRayGivenFace(const MString &face, MFloatArray &orig, MFloatArray &vec)
//
// Description
//     Represent a face by a series of edges(rays), i.e.
//     orig = {orig1u, orig1v, orig2u, orig2v, ... }
//     vec  = {vec1u,  vec1v,  vec2u,  vec2v,  ... }
//
//     return false if no valid uv's.
{
    MSelectionList selList;
    selList.add(face);
    MDagPath dagPath;
    MObject  comp;
    selList.getDagPath(0, dagPath, comp);
    MItMeshPolygon iter(dagPath, comp);

    MFloatArray uArray, vArray;
    iter.getUVs(uArray, vArray);

    if (uArray.length() == 0 || vArray.length() == 0) return false;

    orig.setLength(2 * uArray.length());
    vec.setLength( 2 * uArray.length());

    // Loop through all vertices to construct edges/rays
    float u = uArray[uArray.length() - 1];
    float v = vArray[vArray.length() - 1];
    for(unsigned int j = 0; j < uArray.length(); j++) {
        orig[2*j]   = uArray[j];
        orig[2*j+1] = vArray[j];
        vec[2*j]    = u - uArray[j];
        vec[2*j+1]  = v - vArray[j];
        u = uArray[j];
        v = vArray[j];
    }
    return true;
}
MStatus
XmlCacheFormat::readFloatArray( MFloatArray& array, unsigned arraySize )
{
	MStringArray value;
	readXmlTagValue(floatArrayTag, value);

	assert( value.length() == arraySize );
	array.setLength( arraySize );
	for ( unsigned int i = 0; i < value.length(); i++ )
	{
		array[i] = (float)strtod( value[i].asChar(), NULL );
	}
	
	return MS::kSuccess;
}
Beispiel #5
0
//
// Change the UVS for the given selection on this mesh object.
//
///////////////////////////////////////////////////////////////////////////////
MStatus flipUVCmd::getTweakedUVs(
    const MObject & meshObj,					// Object
    MIntArray & uvList,						// UVs to move
    MFloatArray & uPos,						// Moved UVs
    MFloatArray & vPos )					// Moved UVs
{
    MStatus status;
    unsigned int i;
    MFloatArray uArray;
    MFloatArray vArray;

    MFnMesh mesh( meshObj );

    // Read all UVs from the poly object
    status = mesh.getUVs(uArray, vArray);
    CHECK_MSTATUS_AND_RETURN_IT(status);

    unsigned int nbUvShells = 1;
    MIntArray uvShellIds;
    if ((!flipGlobal) || extendToShell)
    {
        // First, extract the UV shells.
        status = mesh.getUvShellsIds(uvShellIds, nbUvShells);
        CHECK_MSTATUS_AND_RETURN_IT(status);
    }

    if (extendToShell)
    {
        // Find all shells that have at least a selected UV.
        bool *selected = new bool[nbUvShells];
        for (i = 0 ; i<nbUvShells ; i++)
            selected[i] = false;

        for (i = 0 ; i<uvList.length() ; i++)
        {
            int indx = uvList[i];
            selected[uvShellIds[indx]] = true;
        }

        // Now recompute a new list of UVs to modify.

        unsigned int numUvs = mesh.numUVs();
        unsigned int numSelUvs = 0;

        // Preallocate a buffer, large enough to hold all Ids. This
        // prevents multiple reallocation from happening when growing
        // the array.
        uvList.setLength(numUvs);

        for (i = 0 ; i<numUvs ; i++)
        {
            if (selected[uvShellIds[i]])
                uvList[numSelUvs++] = i;
        }

        // clamp the array to the proper size.
        uvList.setLength(numSelUvs);

        delete [] selected;
    }

    // For global flips, just pretend there is only one shell
    if (flipGlobal) nbUvShells = 1;

    float *minMax = new float[nbUvShells*4];

    for (i = 0 ; i<nbUvShells ; i++)
    {
        minMax[4*i+0] =  1e30F;				// Min U
        minMax[4*i+1] =  1e30F;				// Min V
        minMax[4*i+2] = -1e30F;				// Max U
        minMax[4*i+3] = -1e30F;				// Max V
    }

    // Get the bounding box of the UVs, for each shell if flipGlobal
    // is true, or for the whole selection if false.
    for (i = 0 ; i<uvList.length() ; i++)
    {
        int indx = uvList[i];
        int shellId = 0;
        if (!flipGlobal) shellId = uvShellIds[indx];

        if (uArray[indx] < minMax[4*shellId+0])
            minMax[4*shellId+0] = uArray[indx];
        if (vArray[indx] < minMax[4*shellId+1])
            minMax[4*shellId+1] = vArray[indx];
        if (uArray[indx] > minMax[4*shellId+2])
            minMax[4*shellId+2] = uArray[indx];
        if (vArray[indx] > minMax[4*shellId+3])
            minMax[4*shellId+3] = vArray[indx];
    }

    // Adjust the size of the output arrays
    uPos.setLength(uvList.length());
    vPos.setLength(uvList.length());

    for (i = 0 ; i<uvList.length() ; i++)
    {
        int shellId = 0;
        int indx = uvList[i];
        if (!flipGlobal) shellId = uvShellIds[indx];

        // Flip U or V along the bounding box center.
        if (horizontal)
        {
            uPos[i] = minMax[4*shellId+0] + minMax[4*shellId+2] -
                      uArray[indx];
            vPos[i] = vArray[indx];
        }
        else
        {
            uPos[i] = uArray[indx];
            vPos[i] = minMax[4*shellId+1] + minMax[4*shellId+3] -
                      vArray[indx];
        }
    }

    delete [] minMax;

    return MS::kSuccess;
}
void ToMayaMeshConverter::addUVSet( MFnMesh &fnMesh, const MIntArray &polygonCounts, IECore::ConstMeshPrimitivePtr mesh, const std::string &sPrimVarName, const std::string &tPrimVarName, const std::string &stIdPrimVarName, MString *uvSetName ) const
{
	IECore::PrimitiveVariableMap::const_iterator sIt = mesh->variables.find( sPrimVarName );
	bool haveS = sIt != mesh->variables.end();
	IECore::PrimitiveVariableMap::const_iterator tIt = mesh->variables.find( tPrimVarName );
	bool haveT = tIt != mesh->variables.end();
	IECore::PrimitiveVariableMap::const_iterator stIdIt = mesh->variables.find( stIdPrimVarName );
	bool haveSTId = stIdIt != mesh->variables.end();

	if ( haveS && haveT )
	{
		if ( sIt->second.interpolation != IECore::PrimitiveVariable::FaceVarying )
		{
			IECore::msg( IECore::Msg::Warning,"ToMayaMeshConverter::doConversion",  boost::format(  "PrimitiveVariable \"%s\" has unsupported interpolation (expected FaceVarying).") % sPrimVarName );
			return;
		}

		if ( tIt->second.interpolation != IECore::PrimitiveVariable::FaceVarying )
		{
			IECore::msg( IECore::Msg::Warning, "ToMayaMeshConverter::doConversion", boost::format( "PrimitiveVariable \"%s\" has unsupported interpolation (expected FaceVarying).") % tPrimVarName);
			return;
		}

		if ( !sIt->second.data )
		{
			IECore::msg( IECore::Msg::Warning, "ToMayaMeshConverter::doConversion", boost::format( "PrimitiveVariable \"%s\" has no data." ) % sPrimVarName );
		}

		if ( !tIt->second.data )
		{
			IECore::msg( IECore::Msg::Warning, "ToMayaMeshConverter::doConversion", boost::format( "PrimitiveVariable \"%s\" has no data." ) % tPrimVarName );
		}

		/// \todo Employ some M*Array converters to simplify this
		int numUVs = mesh->variableSize( IECore::PrimitiveVariable::FaceVarying );

		IECore::ConstFloatVectorDataPtr u = IECore::runTimeCast<const IECore::FloatVectorData>(sIt->second.data);

		if ( !u )
		{
			IECore::msg( IECore::Msg::Warning, "ToMayaMeshConverter::doConversion", boost::format( "PrimitiveVariable \"%s\" has unsupported type \"%s\"." ) % sPrimVarName % sIt->second.data->typeName() );
			return;
		}

		assert( (int)u->readable().size() == numUVs );
		
		IECore::ConstFloatVectorDataPtr v = IECore::runTimeCast<const IECore::FloatVectorData>(tIt->second.data);
		if ( !v )
		{
			IECore::msg( IECore::Msg::Warning, "ToMayaMeshConverter::doConversion", boost::format( "PrimitiveVariable \"%s\" has unsupported type \"%s\"." ) % tPrimVarName % tIt->second.data->typeName() );
			return;
		}

		assert( (int)v->readable().size() == numUVs );
		
		const std::vector<float> &uAll = u->readable();
		const std::vector<float> &vAll = v->readable();

		if ( uvSetName )
		{
			bool setExists = false;
			MStringArray existingSets;
			fnMesh.getUVSetNames( existingSets );
			for ( unsigned i=0; i < existingSets.length(); ++i )
			{
				if ( *uvSetName == existingSets[i] )
				{
					fnMesh.clearUVs( uvSetName );
					setExists = true;
					break;
				}
			}
			
			if ( !setExists )
			{
				MDagPath dag;
				MStatus s = fnMesh.getPath( dag );
				if ( s )
				{
					fnMesh.createUVSetWithName( *uvSetName );
				}
				else
				{
					fnMesh.createUVSetDataMeshWithName( *uvSetName );
				}
			}
		}
		
		MIntArray uvIds;
		uvIds.setLength( numUVs );
		MFloatArray uArray;
		MFloatArray vArray;
		
		if( haveSTId )
		{
			// Get compressed uv values by matching them with their uvId.
			IECore::ConstIntVectorDataPtr uvId = IECore::runTimeCast<const IECore::IntVectorData>(stIdIt->second.data);
			if ( !uvId )
			{
				IECore::msg( IECore::Msg::Warning, "ToMayaMeshConverter::doConversion", boost::format( "PrimitiveVariable \"%s\" has unsupported type \"%s\"." ) % stIdPrimVarName % stIdIt->second.data->typeName() );
				return;
			}
			
			const std::vector<int> &uvIdData = uvId->readable();
	
			assert( (int)uvIdData.size() == numUVs );
			
			int highestId = 0;
			for ( int i = 0; i < numUVs; i++)
			{
				uvIds[i] = uvIdData[i];
				if( uvIdData[i] > highestId )
				{
					highestId = uvIdData[i];
				}
			}
			
			// u and v arrays need only be as long as the number of unique uvIds
			uArray.setLength( highestId + 1 );
			vArray.setLength( highestId + 1 );
			
			for ( int i = 0; i < numUVs; i++ )
			{
				uArray[ uvIds[i] ] = uAll[i];
				// FromMayaMeshConverter does the opposite of this
				vArray[ uvIds[i] ] = 1 - vAll[i];
			}
		}
		else
		{
			// If for some reason we cannot find the uv indices, set the UVs using the old way
			// the performances in maya won't be good (for weigth painting in particular)
			uArray.setLength( numUVs );
			vArray.setLength( numUVs );

			for ( int i = 0; i < numUVs; i++)
			{
				uArray[i] = u->readable()[i];
				// FromMayaMeshConverter does the opposite of this
				vArray[i] = 1 - v->readable()[i];
			}
			
			for ( int i = 0; i < numUVs; i++)
			{
				uvIds[i] = i;
			}
		}
		
		MStatus s = fnMesh.setUVs( uArray, vArray, uvSetName );
		if ( !s )
		{
			IECore::msg( IECore::Msg::Warning, "ToMayaMeshConverter::doConversion", "Failed to set UVs." );
			return;
		}

		s = fnMesh.assignUVs( polygonCounts, uvIds, uvSetName );
		if ( !s )
		{
			IECore::msg( IECore::Msg::Warning, "ToMayaMeshConverter::doConversion", "Failed to assign UVs." );
			return;
		}

	}
	else if ( haveS )
	{
		IECore::msg( IECore::Msg::Warning, "ToMayaMeshConverter::doConversion", boost::format( "Primitive variable \"%s\" found, but not \"%s\"." ) % sPrimVarName % tPrimVarName );
	}
	else if ( haveT )
	{
		IECore::msg( IECore::Msg::Warning, "ToMayaMeshConverter::doConversion", boost::format( "Primitive variable \"%s\" found, but not \"%s\"." ) % tPrimVarName % sPrimVarName );
	}
	else
	{
		assert( !uvSetName );
	}
}
Beispiel #7
0
MStatus sgBulgeDeformer::deform(MDataBlock& dataBlock, MItGeometry& iter, const MMatrix& mtx, unsigned int index)
{
	MStatus status;

	float bulgeWeight = dataBlock.inputValue(aBulgeWeight).asFloat();
	double bulgeRadius = dataBlock.inputValue(aBulgeRadius).asDouble();

	MArrayDataHandle hArrInputs = dataBlock.inputArrayValue(aBulgeInputs);

	MPointArray allPositions;
	iter.allPositions(allPositions);

	if (mem_resetElements)
	{
		unsigned int elementCount = hArrInputs.elementCount();
		mem_meshInfosInner.resize(mem_maxLogicalIndex);
		mem_meshInfosOuter.resize(mem_maxLogicalIndex);

		for (unsigned int i = 0; i < elementCount; i++, hArrInputs.next())
		{
			MDataHandle hInput = hArrInputs.inputValue();
			MDataHandle hMatrix = hInput.child(aMatrix);
			MDataHandle hMesh   = hInput.child(aMesh);

			MMatrix mtxMesh = hMatrix.asMatrix();
			MObject oMesh   = hMesh.asMesh();

			MFnMeshData meshDataInner, meshDataOuter;
			MObject oMeshInner = meshDataInner.create();
			MObject oMeshOuter = meshDataOuter.create();
			MFnMesh fnMesh;
			fnMesh.copy(oMesh, oMeshInner);
			fnMesh.copy(oMesh, oMeshOuter);

			sgMeshInfo* newMeshInfoInner = new sgMeshInfo(oMeshInner, hMatrix.asMatrix());
			sgMeshInfo* newMeshInfoOuter = new sgMeshInfo(oMeshOuter, hMatrix.asMatrix());

			mem_meshInfosInner[hArrInputs.elementIndex()] = newMeshInfoInner;
			mem_meshInfosOuter[hArrInputs.elementIndex()] = newMeshInfoOuter;
		}
	}
	
	for (unsigned int i = 0; i < elementCount; i++)
	{
		mem_meshInfosInner[i]->setBulge(bulgeWeight, MSpace::kWorld );
	}
	
	MFloatArray weightList;
	weightList.setLength(allPositions.length());
	for (unsigned int i = 0; i < weightList.length(); i++)
		weightList[i] = 0.0f;
	MMatrixArray inputMeshMatrixInverses;
	inputMeshMatrixInverses.setLength(elementCount);
	for (unsigned int i = 0; i < elementCount; i++)
	{
		inputMeshMatrixInverses[i] = mem_meshInfosInner[i]->matrix();
	}

	for (unsigned int i = 0; i < allPositions.length(); i++)
	{
		float resultWeight = 0;
		for (unsigned int infoIndex = 0; infoIndex < elementCount; infoIndex++)
		{
			MPoint localPoint = allPositions[i] * mtx* inputMeshMatrixInverses[infoIndex];
			MPoint innerPoint = mem_meshInfosInner[infoIndex]->getClosestPoint(localPoint);
			MPoint outerPoint = mem_meshInfosOuter[infoIndex]->getClosestPoint(localPoint);
			MVector innerVector = innerPoint - localPoint;
			MVector outerVector = outerPoint - localPoint;

			if (innerVector * outerVector < 0)
			{
				double innerLength = innerVector.length();
				double outerLength = outerVector.length();
				double allLength = innerLength + outerLength;

				float numerator = float( innerLength * outerLength );
				float denominator = float( pow(allLength / 2.0, 2) );

				resultWeight = numerator / denominator;
			}
		}
		weightList[i] = resultWeight;
	}

	for (unsigned int i = 0; i < allPositions.length(); i++)
	{
		allPositions[i] += weightList[i] * MVector(0, 1, 0);
	}
	
	iter.setAllPositions(allPositions);

	return MS::kSuccess;
}
Beispiel #8
0
MStatus TransferUV::redoIt()
{
    MStatus status;

    MFnMesh sourceFnMesh(sourceDagPath);
    MFnMesh targetFnMesh(targetDagPath);
    
    // Get current UV sets and keep them to switch back to them at the last
    MString currentSourceUVSet = sourceFnMesh.currentUVSetName();
    MString currentTargetUVSet = targetFnMesh.currentUVSetName();

    // Switch to uv sets specified in flags before starting transfer process
    // This is required because MFnMesh does not work properly with
    // "Non-current" UV sets
    sourceFnMesh.setCurrentUVSetName(sourceUvSet);
    targetFnMesh.setCurrentUVSetName(targetUvSet);
    
    MString* sourceUvSetPtr = &sourceUvSet;
    MString* targetUvSetPtr = &targetUvSet;

    MFloatArray sourceUarray;
    MFloatArray sourceVarray;
    MIntArray sourceUvCounts;
    MIntArray sourceUvIds;

    // Get mesh information for each
    status = sourceFnMesh.getUVs(sourceUarray, sourceVarray, sourceUvSetPtr);
    if (status != MS::kSuccess) {
        MGlobal::displayError("Failed to get source UVs");
        CHECK_MSTATUS_AND_RETURN_IT(status);
    }
    status = targetFnMesh.getUVs(originalUarray, originalVarray, targetUvSetPtr);
    if (status != MS::kSuccess) {
        MGlobal::displayError("Failed to get original UVs");
        CHECK_MSTATUS_AND_RETURN_IT(status);
    }
    status = sourceFnMesh.getAssignedUVs(sourceUvCounts, sourceUvIds, sourceUvSetPtr);
    if (status != MS::kSuccess) {
        MGlobal::displayError("Failed to get source assigned UVs");
        CHECK_MSTATUS_AND_RETURN_IT(status);
    }
    status = targetFnMesh.getAssignedUVs(originalUvCounts, originalUvIds, targetUvSetPtr);
    if (status != MS::kSuccess) {
        MGlobal::displayError("Failed to get original assigned UVs");
        CHECK_MSTATUS_AND_RETURN_IT(status);
    }

    // Resize source uv array so it becomes same size as number of targets uv
    unsigned int targetNumUVs = targetFnMesh.numUVs();
    if (targetNumUVs > sourceUarray.length()) {
        sourceUarray.setLength(targetNumUVs);
        sourceVarray.setLength(targetNumUVs);
    }

    status = targetFnMesh.setUVs(sourceUarray, sourceVarray, targetUvSetPtr);
    if (MS::kSuccess != status) {
        MGlobal::displayError("Failed to set source UVs");
        return status;
    }

    status = targetFnMesh.assignUVs(sourceUvCounts, sourceUvIds, targetUvSetPtr);
    if (MS::kSuccess != status) {
        MGlobal::displayError("Failed to assign source UVs");
        return status;
    }

    // Switch back to originals
    sourceFnMesh.setCurrentUVSetName(currentSourceUVSet);
    targetFnMesh.setCurrentUVSetName(currentTargetUVSet);
    
    return MS::kSuccess;
}
Beispiel #9
0
void createUVset(TCCData &tccData, int sdRes, MFloatArray &uArray, MFloatArray &vArray, MFloatArray &sc_uArray, MFloatArray &sc_vArray, MIntArray &uvIdx, float lineThickness)
{
    MFloatArray u4, v4, u5T0, v5T0, u5T1, v5T1, u5T2, v5T2, u5T3, v5T3, u5T4, v5T4;
    MIntArray id4, id5T0, id5T1, id5T2, id5T3, id5T4;
    
    double hds4_uvs[]   = { 0,1, 1,1, 1,0, 0,0 },       hds4_itv[] = {1,1,1,1};       create_subdivided_face(sdRes, 4, hds4_uvs, hds4_itv, -1, u4, v4, id4);
    double hds5T0_uvs[] = { 0,.5, 0,1, 1,1, 1,0, 0,0 }, hds5T0_itv[] = {.5,.5,1,1,1}; create_subdivided_face(sdRes, 5, hds5T0_uvs, hds5T0_itv, 0, u5T0, v5T0, id5T0);
    double hds5T1_uvs[] = { 0,0, 0,.5, 0,1, 1,1, 1,0 }, hds5T1_itv[] = {1,.5,.5,1,1}; create_subdivided_face(sdRes, 5, hds5T1_uvs, hds5T1_itv, 1, u5T1, v5T1, id5T1);
    double hds5T2_uvs[] = { 1,0, 0,0, 0,.5, 0,1, 1,1 }, hds5T2_itv[] = {1,1,.5,.5,1}; create_subdivided_face(sdRes, 5, hds5T2_uvs, hds5T2_itv, 2, u5T2, v5T2, id5T2);
    double hds5T3_uvs[] = { 1,1, 1,0, 0,0, 0,.5, 0,1 }, hds5T3_itv[] = {1,1,1,.5,.5}; create_subdivided_face(sdRes, 5, hds5T3_uvs, hds5T3_itv, 3, u5T3, v5T3, id5T3);
    double hds5T4_uvs[] = { 0,1, 1,1, 1,0, 0,0, 0,.5 }, hds5T4_itv[] = {.5,1,1,1,.5}; create_subdivided_face(sdRes, 5, hds5T4_uvs, hds5T4_itv, 4, u5T4, v5T4, id5T4);
    
    
    int nV4 = u4.length(), nHE4 = id4.length();
    int nV5 = u5T0.length(), nHE5 = id5T0.length();
    
    int nF = tccData.nFV.length();

    int nHE = 0, nV = 0;
    for (int kF = 0; kF<nF; kF++)
    {
        switch (tccData.nFV[kF])
        {
            case 4: nV+=nV4; nHE+=nHE4; break;
            case 5: nV+=nV5; nHE+=nHE5; break;
        }
    }
    
    uArray.setLength(nV); sc_uArray.setLength(nV);
    vArray.setLength(nV); sc_vArray.setLength(nV);
    uvIdx.setLength(nHE);
    
    int kHE = 0, sd_kV = 0, sd_kHE = 0;
    for (int kF = 0; kF<nF; kF++)
    {
        int faceConfig = tccData.nFV[kF];

        if (faceConfig==5)
        {
            for (int kT=0; kT<5; kT++)
            {
                if (tccData.T[kHE+kT]) break;
                faceConfig++;
            }
        }

        // uScale and vScale are chosen to not use T-edges, but match the U/V directions of the patch (convention: first edge gets vScale)
        switch (faceConfig)
        {
            case 4: copy_patch_uvs(sd_kV, sd_kHE, u4, v4, id4, uArray, vArray, tccData.itv[kHE], tccData.itv[kHE+1], sc_uArray, sc_vArray, uvIdx, lineThickness); break;
            case 5: copy_patch_uvs(sd_kV, sd_kHE, u5T0, v5T0, id5T0, uArray, vArray, tccData.itv[kHE+3], tccData.itv[kHE+4], sc_uArray, sc_vArray, uvIdx, lineThickness); break;
            case 6: copy_patch_uvs(sd_kV, sd_kHE, u5T1, v5T1, id5T1, uArray, vArray, tccData.itv[kHE+4], tccData.itv[kHE], sc_uArray, sc_vArray, uvIdx, lineThickness); break;
            case 7: copy_patch_uvs(sd_kV, sd_kHE, u5T2, v5T2, id5T2, uArray, vArray, tccData.itv[kHE], tccData.itv[kHE+1], sc_uArray, sc_vArray, uvIdx, lineThickness); break;
            case 8: copy_patch_uvs(sd_kV, sd_kHE, u5T3, v5T3, id5T3, uArray, vArray, tccData.itv[kHE+1], tccData.itv[kHE+2], sc_uArray, sc_vArray, uvIdx, lineThickness); break;
            case 9: copy_patch_uvs(sd_kV, sd_kHE, u5T4, v5T4, id5T4, uArray, vArray, tccData.itv[kHE+2], tccData.itv[kHE+3], sc_uArray, sc_vArray, uvIdx, lineThickness); break;
        }
        
        
        kHE += tccData.nFV[kF];
    }
}