Skip to content

Instantly share code, notes, and snippets.

@Farfarer
Last active May 13, 2022 03:33
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Farfarer/6e1051db62a675fc99fb to your computer and use it in GitHub Desktop.
Save Farfarer/6e1051db62a675fc99fb to your computer and use it in GitHub Desktop.
Modo C++ SDK UV code sample
// A couple of things to make life easier.
#define MIN_UV_GAP 0.0000005f
#define LXx_V2NEARLYEQ(a,b) ((abs((a)[0]-(b)[0]) < MIN_UV_GAP) && (abs((a)[1]-(b)[1]) < MIN_UV_GAP))
typedef std::vector <LXtPolygonID> PolygonList;
// The visitors we'll use.
// Visitors allow us to iterate over elements and filter that iteration by flags set upon them, known as mark modes.
// Simple one to set the supplied mark mode on polygons.
class MarkPolys : public CLxImpl_AbstractVisitor
{
public:
CLxUser_Polygon &polygon;
LXtMarkMode &markMode;
MarkPolys (CLxUser_Polygon &polygon_in,
LXtMarkMode &markMode_in
)
: polygon (polygon_in),
markMode (markMode_in)
{}
LxResult Evaluate () LXx_OVERRIDE
{
polygon.SetMarks (markMode);
return LXe_OK;
}
};
// And the same on points.
class MarkPoints : public CLxImpl_AbstractVisitor
{
public:
CLxUser_Point &point;
LXtMarkMode &markMode;
MarkPoints (CLxUser_Point &point_in,
LXtMarkMode &markMode_in
)
: point (point_in),
markMode (markMode_in)
{}
LxResult Evaluate () LXx_OVERRIDE
{
point.SetMarks (markMode);
return LXe_OK;
}
};
// Big one. This will iterate over a UV island at a time and move it +1 unit in the U axis.
class MoveUVIslands : public CLxImpl_AbstractVisitor
{
public:
CLxUser_Polygon &polygon;
CLxUser_Point &point;
LXtMeshMapID &uvmapID;
LXtMarkMode &markModeChecked;
LXtMarkMode &markModeToCheck;
MoveUVIslands (
CLxUser_Polygon &polygon_in,
CLxUser_Point &point_in,
LXtMeshMapID &uvmapID_in,
LXtMarkMode &markModeChecked_in,
LXtMarkMode &markModeToCheck_in,
)
:
polygon (polygon_in),
point (point_in),
uvmapID (uvmapID_in),
targetTexelDensity (targetTexelDensity_in),
markModeChecked (markModeChecked_in),
markModeToCheck (markModeToCheck_in)
{}
LxResult Evaluate () LXx_OVERRIDE
{
PolygonList innerSet; // Polygons whose connected polygons have been added to the outer set.
PolygonList outerSet; // Polygons whose connected polygons are yet to be checked for connectivity.
// Start with the current polygon and add it to the outerSet to be checked.
outerSet.push_back (polygon.ID ());
unsigned outerSetSize = (unsigned)outerSet.size ();
while (outerSetSize > 0) {
LXtPolygonID polygonID = outerSet.front ();
polygon.Select (polygonID);
// Add polygon to inner list.
// Remove from outer list.
// Mark as checked.
innerSet.push_back (polygonID);
outerSet.erase (std::remove (outerSet.begin (), outerSet.end (), polygonID), outerSet.end ());
outerSetSize--;
polygon.SetMarks (markModeChecked);
// Get the number of vertices for this polygon.
unsigned polygonVertexCount;
polygon.VertexCount (&polygonVertexCount);
// Now find the polygons connected to this polygon via UVs.
// We do this by iterating over each vertex, getting the vertices' polygons and seeing if the UV value for that vertex matches that of the vertex for this polygon.
for (unsigned v = 0; v < polygonVertexCount; v++) {
// Get the pointID of this point.
LXtPointID pointID;
polygon.VertexByIndex (v, &pointID);
// Get it's UV value, polygon.MapEvaluate will return either the discontinuous value stored on this polygon for this point.
// Or it will return the continuous value stored on the point if no discontinuous value exists.
LXtFVector2 uvValue;
polygon.MapEvaluate (uvmapID, pointID, uvValue);
// Now select the point and find out how many polygons it's connected to.
point.Select (pointID);
unsigned pointPolygonCount;
point.PolygonCount (&pointPolygonCount);
// Iterate over the polygons it's connected to and find out their value for this point.
// But only if they're not in the outerSet or innerSet (i.e. we've already checked them).
for (unsigned p = 0; p < pointPolygonCount; p++) {
LXtPolygonID pointPolygon;
point.PolygonByIndex (p, &pointPolygon);
if ((std::find (outerSet.begin (), outerSet.end (), pointPolygon) == outerSet.end ()) && (std::find (innerSet.begin (), innerSet.end (), pointPolygon) == innerSet.end ())) {
// The polygon hasn't already been checked.
// So select it then grab it's UV value for this point and we'll see if they match.
LXtFVector2 uvValueCheckAgainst;
polygon.Select (pointPolygon);
polygon.MapEvaluate (uvmapID, pointID, uvValueCheckAgainst);
if (LXx_V2NEARLYEQ (uvValue, uvValueCheckAgainst)) {
// The values are close enough to be considered a match - this polygon is connected to the original one - so add this polygon to the outerSet.
outerSet.push_back (pointPolygon);
outerSetSize++;
}
}
}
polygon.Select (polygonID);
}
}
// Now we're here, innerSet contians a list of all of the polygons that are connected to the original polygon via UVs.
// Essentially, it contains the polygonIDs of the entire UV island.
// I'll just move the UVs by 1 unit in U for the sake of demonstration.
for (PolygonList::iterator poly_it = innerSet.begin(); poly_it != innerSet.end(); poly_it++) {
polygon.Select (*poly_it);
unsigned polygonVertexCount;
polygon.VertexCount (&polygonVertexCount);
for (unsigned v = 0; v < polygonVertexCount; v++) {
LXtPointID pointID;
polygon.VertexByIndex (v, &pointID);
LXtFVector2 uvValue;
bool updateContinuousValue = false;
// polygon.MapValue is like MapEvaluate except it will return False if the polygon doesn't hold a discontinuous UV value for this point.
if (polygon.MapValue (uvmapID, pointID, uvValue) == LXe_FALSE) {
// If we get here, the polygon has no discontinuous value, so we need to update the continuous value directly on the point.
// We'll use mark modes here to ensure that we don't alter the continuous value more than once.
point.Select (pointID);
if (point.TestMarks (markModeToCheck) == LXe_FALSE) {
if (!(point.MapValue (uvmapID, uvValue) == LXe_FALSE)) {
// Add 1 to the U value.
uvValue[0] += 1.0f;
// Apply the new discontinuous value for this point.
point.SetMapValue (uvmapID, uvValue);
// Mark the point has having been moved.
point.SetMarks (markModeToCheck);
}
}
}
else {
// Add 1 to the U value.
uvValue[0] += 1.0f;
// Apply the new discontinuous value for this point on this polygon.
polygon.SetMapValue (pointID, uvmapID, uvValue);
}
}
}
};
// The below goes inside the cmd_Execute method.
// Grab a layer scan object from the layer service.
// Scan through active layers.
CLxUser_LayerService layer_svc;
CLxUser_LayerScan scan;
layer_svc.BeginScan (LXf_LAYERSCAN_EDIT, scan);
if (!scan.test ()) {
return;
}
// Get the number of active layers.
unsigned layer_count = 0;
scan.Count (&layer_count);
if (layer_count == 0) {
// Early out if there are no active layers found.
// I have some custom error messages set up in my kit config, which is what SetMessage is referring to.
// They basically give user friendly text to explain the failure.
CLxUser_Message &msg = basic_Message ();
msg.SetCode (LXe_FAILED);
msg.SetMessage ("ffr_uv_msg", "nolayers", 0);
return;
}
// Get the mark modes we'll use from the mesh service.
// This is what I'll use to be able to iterate over each UV island.
CLxUser_MeshService mesh_svc;
LXtMarkMode markModeSelected, markModeToCheck, markModeChecked;
mesh_svc.ModeCompose (LXsMARK_SELECT, NULL, &markModeSelected); // Standard "selected" mark mode.
mesh_svc.ModeCompose (LXsMARK_USER_4, NULL, &markModeToCheck); // Mark mode to apply to selected polygons, so I know I still need to check these ones.
mesh_svc.ModeCompose (NULL, LXsMARK_USER_4, &markModeChecked); // Mark mode to clear that mark I need to ensure that I know I don't need to process them again.
// Get the name of the first selected UV map.
// We will need this name later for selecting the map per-layer.
const char* uvMapName = "";
// Initiate the selection service and iterate over the selected vertex maps.
CLxUser_SelectionService sel_svc;
void *pkt;
CLxUser_VMapPacketTranslation vmap_pkt_trans;
vmap_pkt_trans.autoInit ();
LXtID4 vmapSelType = sel_svc.LookupType (LXsSELTYP_VERTEXMAP);
LXtID4 uvMapType = LXi_VMAP_TEXTUREUV;
unsigned vmapSelCount = sel_svc.Count (vmapSelType);
for (unsigned i = 0; i < vmapSelCount; i++) {
pkt = sel_svc.ByIndex (vmapSelType, i);
LXtID4 selVmapType;
vmap_pkt_trans.Type (pkt, &selVmapType);
if (selVmapType == uvMapType) {
// If it's of the type TEXTREUV, then we've got a selected UV map.
// So grab it's name.
// Then break out of this loop.
vmap_pkt_trans.Name (pkt, &uvMapName);
break;
}
}
if (strlen(uvMapName) == 0) {
// Early out if no UV map is selected.
// I have some custom error messages set up in my kit config, which is what SetMessage is referring to.
// They basically give user friendly text to explain the failure.
CLxUser_Message &msg = basic_Message ();
msg.SetCode (LXe_FAILED);
msg.SetMessage ("ffr_uv_msg", "nouvs", 0);
return;
}
// Iterate over the layers that the layerscan found.
for (unsigned i = 0; i < layer_count; i++) {
// Declare the mesh and the mesh accessors we'll need - a meshmap, point and polygon accessor.
CLxUser_Mesh mesh;
CLxUser_MeshMap meshmap;
CLxUser_Point point;
CLxUser_Polygon polygon;
// Get the mesh for this layer.
scan.MeshEdit (i, mesh);
if (!mesh.test ()) {
continue;
}
// Skip this layer if it's mesh has no polygons.
unsigned polygonCount;
mesh.PolygonCount (&polygonCount);
if (polygonCount == 0) {
continue;
}
// Get the meshmap accessor.
mesh.GetMaps (meshmap);
if (!meshmap.test ()) {
continue;
}
// Now we have the meshmap accessor, we can try and get the ID of the meshmap for the selected UV map.
LXtMeshMapID uvMapID = 0;
if (meshmap.SelectByName (LXi_VMAP_TEXTUREUV, uvMapName) == LXe_OK) {
// We've managed to select a UV map on this layer that matches the name of the selected UV map, so grab it's ID.
// That's what we'll use to access it's values.
uvMapID = meshmap.ID ();
}
else {
// We've failed to select the UV map, that basically means this layer doesn't have the UV map.
// So skip this layer.
continue;
}
// Get the remainder of the accessors.
mesh.GetPoints (point);
mesh.GetPolygons (polygon);
if ((!point.test ()) || (!polygon.test ()))
{
continue;
}
// Clear mark modes before all else. Just to be sure.
// Someone might have set these before and not cleared them and that could screw things up.
MarkPoints markPoints (point, markModeChecked);
point.Enum (&markPoints, markModeToCheck);
MarkPolys markPolys (polygon, markModeChecked);
polygon.Enum (&markPolys, markModeToCheck);
// Here we set the "to check" mark mode on all selected polygons.
// By iterating a visitor that sets the mark mode "to check" over the polygons of the mark mode "selected".
MarkPolys markPolys (polygon, markModeToCheck);
polygon.Enum (&markPolys, markModeSelected);
// Now iterate over the polygons we've just set as "to check".
// By iterating over them using another visitor - the visitor contains code to do things for the UV island belonging to those polygons.
// It will also unset the "to check" mark mode as it goes, so that the visitor doesn't iterate over any polygon that we may have already touched upon.
MoveUVIslands moveUVIslands (polygon, point, uvMapID, markModeChecked, markModeToCheck);
polygon.Enum (&moveUVIslands, markModeToCheck);
// Clear mark modes after we're done.
// I don't want to leave them set and screw up someone else's plugin who might use this mark mode.
// I'm not actually certain they remain set after this is done, anyway, but it doesn't hurt to be sure.
MarkPoints markPoints (point, markModeChecked);
point.Enum (&markPoints, markModeToCheck);
MarkPolys markPolys (polygon, markModeChecked);
polygon.Enum (&markPolys, markModeToCheck);
// Apply changes to this layer's mesh, telling Modo we've changed it's UV map values and the continuity of that UV map.
scan.SetMeshChange (i, LXf_MESHEDIT_MAP_UV | LXf_MESHEDIT_MAP_CONTINUITY);
}
// Apply changes from the layer scan - this tells Modo to implement the changes to the affected layers.
scan.Apply ();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment