new version that saves a little system memory and gives an option to return elements of verts instead of faces:
def_visible_primitive(getMeshAllElements, "getMeshAllElements");
Value* getMeshAllElements_cf(Value** arg_list, int count)
{
check_arg_count_with_keys(getMeshAllElements, 1, count);
BOOL useVerts = key_arg_or_default(level, n_faces) == n_verts;
Mesh* mesh;
BOOL deleteIt = FALSE;
TriObject *pTri;
if (is_node(arg_list[0]))
{
INode* node = arg_list[0]->to_node();
TimeValue t = MAXScript_time();
pTri = GetTriObjectFromNode(node, t, deleteIt);
if (pTri)
{
mesh = &pTri->GetMesh();
}
}
else if (is_mesh(arg_list[0]))
{
mesh = arg_list[0]->to_mesh();
}
if (mesh)
{
int numf = mesh->numFaces;
BitArray faces(numf);
int numv = mesh->numVerts;
Tab<Tab verts;
verts.SetCount(numv);
Array* elements = new Array(0);
for (int i = 0; i < numf; i++)
{
Face& f = mesh->faces[i];
for (int k = 0; k < 3; k++) verts[f.v[k]].Append(1, &i);
}
BitArray fbits(numf);
BitArray vbits(numv);
for (int i = 0; i < numf; i++) if (!faces[i])
{
Tab<int> element;
element.Append(1, &i);
fbits.ClearAll();
vbits.ClearAll();
for (int j = 0; j < element.Count(); j++)
{
int fi = element[j];
if (!faces[fi])
{
Face& f = mesh->faces[fi];
faces.Set(fi);
for (int k = 0; k < 3; k++)
{
int v = f.v[k];
if (!vbits[v])
{
element.Append(verts[v].Count(), &verts[v][0]);
vbits.Set(v);
}
}
fbits.Set(fi);
}
}
elements->append(new BitArrayValue(useVerts ? vbits : fbits));
}
if (pTri && deleteIt) pTri->AutoDelete();
return elements;
}
return &undefined;
}
the problem lies in Tab < Tab < int > >
Tabs may be used on the stack, i.e. they may be declared as a local variable of a function or method. You can set the number of items in the table, work with them, and then when the function returns, the destructor of the Tab is called, and the memory will be deallocated.
Tabs are only appropriate for use with classes that don’t allocate memory. For example, Tab<float> is fine while Tab<MSTR> is problematic (MSTR is the class used for strings in 3ds max). In this case, the MSTR class itself allocates memory for the string. It relies on its constructor or destructor to allocate and free the memory. The problem is the Tab class will not call the constructors and destructors for all the items in the table, nor will it call the copy operator. As an example of this, when you assign a string to another string, the MSTR class does not just copy the pointer to the string buffer (which would result in two items pointing to the same block of memory). Rather it will allocate new memory and copy the contents of the source buffer. In this way you have two individual pointers pointing at two individual buffers. When each of the MSTR destructors is called it will free each piece of memory. So, the problem with using a Tab<MSTR> is that when you assign a Tab to another Tab, the Tab copy constructor will copy all the items in the table, but it will not call the copy operator on the individual items. Thus, if you had a Tab<MSTR> and you assigned it to another Tab, you’d have two TSTRs pointing to the same memory. Then when the second one gets deleted it will be trying to double free that memory.
So again, you should only put things in a Tab that don’t allocate and deallocate memory in their destructors. Thus, this class should not be used with classes that implement an assignment operator and or destructor because neither are guaranteed to be called. The way around this is to use a table of pointers to the items. For example, instead of Tab<MSTR> use Tab <MSTR *>. As another example, Tab<int> is OK, while Tab<BitArray> would be no good. In the BitArray case one should use class pointers, i.e. Tab<BitArray *>.
calling
verts[v].SetCount(0);
should release most, but if Tab allocates other memory in it’s constructor you’ll have a leak.
the correct usage would be
Tab < Tab < int > * > verts;
but in this case we have fill this tab with vert tabs:
for ....
{
verts[v] = new Tab<int>();
}
typedef std::vector < int, MaxAlloc < int > > ivector;
std::vector < ivector, MaxAlloc < ivector > > verts;
should work correctly
the MaxAlloc allocator code can be found in the mxs extension thread.
works as expected though 20% slower than the Tab version
this is the fastest so far for me…
#define __USE_STL__
#ifdef __USE_STL__
typedef std::vector < int, MaxAlloc < int > > ivector;
#endif
def_visible_primitive(getMeshAllElements2, "getMeshAllElements2");
Value* getMeshAllElements2_cf(Value** arg_list, int count)
{
check_arg_count(getMeshAllElements2, 1, count);
Mesh* mesh = get_meshForValue(arg_list[0], MESH_READ_ACCESS, NULL, getMeshAllElements2);
int numf = mesh->numFaces;
BitArray faces(numf);
int numv = mesh->numVerts;
#ifdef __USE_STL__
std::vector < ivector, MaxAlloc < ivector > > verts(numv);
for(int i = 0; i < numv; ++i) verts[i].reserve(16);
#else
Tab< Tab < int > > verts;
verts.SetCount(numv);
#endif
one_typed_value_local(Array* result);
vl.result = new Array(0);
for(int i = 0; i < numf; ++i)
{
Face& f = mesh->faces[i];
for(int k = 0; k < 3; ++k)
{
#ifdef __USE_STL__
verts[f.v[k]].push_back(i);
#else
verts[f.v[k]].Append(1, &i);
#endif
}
}
for(int i = 0; i < numf; ++i)
{
if(faces[i]) continue;
#ifdef __USE_STL__
ivector element;
element.push_back(i);
#else
Tab<int> element;
element.Append(1, &i);
#endif
#ifdef __USE_STL__
for(int j = 0; j < (int)element.size(); ++j)
#else
for(int j = 0; j < element.Count(); ++j)
#endif
{
int fi = element[j];
if(faces[fi]) continue;
Face& f = mesh->faces[fi];
for(int k = 0; k < 3; ++k)
{
int v = f.v[k];
#ifdef __USE_STL__
if(!verts[v].size()) continue;
element.insert(element.end(), verts[v].begin(), verts[v].end());
verts[v].clear();
#else
if(!verts[v].Count()) continue;
element.Append(verts[v].Count(), &verts[v][0]);
verts[v].SetCount(0);
#endif
}
faces.Set(fi);
}
BitArray fbits(numf);
#ifdef __USE_STL__
for(int i = 0;i < (int)element.size(); ++i) fbits.Set(element[i]);
#else
for(int i = 0;i < element.Count(); ++i) fbits.Set(element[i]);
#endif
vl.result->append(new BitArrayValue(fbits));
}
return_value(vl.result);
}
time:2396 ram:75200L faces:3551232 elements:1734
adding something like…
element.reserve(4096);
shaves a bit off too
time:2126 ram:75200L faces:3551232 elements:1734
Hi Denis, Jorge and Klunk, thank you for sharing your code!
I’m now trying to get the SDK up and running, is all very new to me.
I guess I can copy-paste your code into one of the example projects?
Well I’ll first try by myself.
interesting story. i’ve looked at my old script and found that i search elements by edges (not vertices). i’ve check max (editable_poly and editable_mesh). they both ‘think’ that an element is a set of faces isolated by edges!!!
delete objects
m = converttomesh (plane widthsegs:2 lenghtsegs:2)
meshop.deletefaces m #{3..6}
update m
meshop.getelementsusingface m 1
in my understanding if faces are connected by a vert they are members of the same element. in all my tools i use this theory.
I use the same logic. Basically, if by moving one face you alter another, they must belong to the same element.
If you convert to poly the model from your example, it will be broken in two elements, and you have to manually weld the vertices to preserve the topology of the mesh.