as you see the only using of meshop.getvertsusingedge kills the performance
Absolutely! I wonder how these functions are internally implemented because it should be literally impossible to make MaxScript faster than Max itself. What a shame.
The good thing is that due to these issues we can have a fun time coding.
Here is an optimized version of my previous code.
It’s almost as fast as yours up to 1K edges, but uses less memory.
Over 1K edges it is about 1.5 times faster (exponentially up to some point) and uses about 1.5 less memory.
(
fn getReverseEdge mesh edge =
(
edgeFace = ceil (edge/3.0)
faceVerts = getface mesh edgeFace
if edge == (edgeFace*3-2) do edgeVerts = [faceVerts.y, faceVerts.x]
if edge == (edgeFace*3-1) do edgeVerts = [faceVerts.z, faceVerts.y]
if edge == (edgeFace*3 ) do edgeVerts = [faceVerts.x, faceVerts.z]
validFaces = meshop.getfacesusingvert mesh edgeVerts[1]
result = 0
for f in validFaces do (
local faceVerts = getface mesh f
if edgeVerts == [faceVerts.x, faceVerts.y] do result = f*3-2
if edgeVerts == [faceVerts.y, faceVerts.z] do result = f*3-1
if edgeVerts == [faceVerts.z, faceVerts.x] do result = f*3
)
result
)
)
Yet, another optimization makes it faster in almost all scenarios.
(
fn getReverseEdge mesh edge r:0 =
(
edgeFace = ceil (edge/3.0)
faceVerts = getface mesh edgeFace
case edge of
(
(edgeFace*3-2):(va = faceVerts[2]; vb = faceVerts[1])
(edgeFace*3-1):(va = faceVerts[3]; vb = faceVerts[2])
(edgeFace*3 ):(va = faceVerts[1]; vb = faceVerts[3])
)
validFaces = meshop.getfacesusingvert mesh vb
for f in validFaces do (
local faceVerts = getface mesh f
case va of
(
(faceVerts[1]): if vb == faceVerts[2] do r = f*3-2
(faceVerts[2]): if vb == faceVerts[3] do r = f*3-1
(faceVerts[3]): if vb == faceVerts[1] do r = f*3
)
)
return r
)
)
in general case for optimization purpose you have to add ‘loop break’ condition if a reverse edge was found
so the line has to be:
for f in validFaces while r == 0 do...
another from me :)
fn getReverseEdge4 mesh e =
(
local
i = (e - 1)/3 + 1,
k = e + (1 - i)*3
local vv = getface mesh i
vv = [vv[1],vv[2],vv[3],vv[1]]
vv = [vv[k+1],vv[k]]
k = 0
for f in (meshop.getfacesusingvert mesh vv[1]) while k == 0 where f != i do
(
local v = getface mesh f
if vv == [v[1],v[2]] then k = f*3 - 2 else if vv == [v[2],v[3]] then k = f*3 - 1 else if vv == [v[3],v[1]] do k = f*3
)
k
)
btw… try to forget about the ‘return’ existence in mxs. i couldn’t find any situations where ‘return’ gives any advantage of using it vs not-using.
i’ve showed several times on this forum how ‘smart’ algorithm can beat ‘low level programming’. just understand that 90% of max was written at the beginning of 2000s by young programmers.
not at all! in ±2000s it was a big deal to save a memory. the MESH structure holds only info about geo face verts and map face verts… all about an other is on-fly. that’s why most of meshop functions are slow.
the polyop is much faster because editable-poly stores most of topology data inside the poly structure instead of getting it on-fly (like the mesh does it do). but… it makes POLY update tons slower than a MESH update.
Sorry, I was talking about what I said. This comment was meant to be an irony.
“…I wonder how these functions are internally implemented because it should be literally impossible to make MaxScript faster than Max itself…”
i’ve showed several times on this forum how ‘smart’ algorithm can beat ‘low level programming’. just understand that 90% of max was written at the beginning of 2000s by young programmers.
which is pretty academic as it’s a very simple process to port the ‘smart’ algorithm to the ‘low level programming’ giving on average 5-6 times the performance gains and a very low memory foot print. On the poorly written max code, some is, but that is mostly because it’s designed to work in the most general broad cases.
I have a different perspective,
When was edit_mesh implemented? I guess, 1995.?
Editable_poly in 2000 year. (3dmax4)
Both mesh/poly, share functions that are very inefficient, back then and still now.
epoly is extremely slow, if not used correctly (what happens in all mxs tools I’ve seen), despite the new mesh data structure.
And if the SDK is the same, or it’s not used correctly(and I bet most, if not all 3rd party/devs use it similarly as mxs), compiling it, will make it faster, but it will still be a joke in optimization.
Using this code I get these results:
edges:60 time:113 memory:1860696L
edges:240 time:137 memory:1860336L
edges:540 time:147 memory:1861824L
edges:1500 time:185 memory:1864608L
edges:6000 time:378 memory:1866752L
edges:24000 time:1062 memory:1859600L
edges:54000 time:2288 memory:1864424L
While using my last version (with your suggestion for braking the case statement) I get these:
edges:60 time:89 memory:1906840L
edges:240 time:107 memory:1906904L
edges:540 time:118 memory:1904072L
edges:1500 time:155 memory:1916000L
edges:6000 time:344 memory:1909992L
edges:24000 time:1021 memory:1905688L
edges:54000 time:2265 memory:1913096L
(
fn getReverseEdge mesh edge r:0 =
(
edgeFace = ceil (edge/3.0)
faceVerts = getface mesh edgeFace
case edge of
(
(edgeFace*3-2):(va = faceVerts[2]; vb = faceVerts[1])
(edgeFace*3-1):(va = faceVerts[3]; vb = faceVerts[2])
(edgeFace*3 ):(va = faceVerts[1]; vb = faceVerts[3])
)
validFaces = meshop.getfacesusingvert mesh vb
for f in validFaces while r == 0 do (
local faceVerts = getface mesh f
case va of
(
(faceVerts[1]): if vb == faceVerts[2] do r = f*3-2
(faceVerts[2]): if vb == faceVerts[3] do r = f*3-1
(faceVerts[3]): if vb == faceVerts[1] do r = f*3
)
)
return r
)
)
re = for x = 1 to 10000 collect random 1 (s.edges.count)
I have checked your versions, mine and meshop, and for degenerated faces all seems to return different values.