[Closed] How to make it faster?
Hi,
I’ve been trying to make my script much faster. I’ve read everything in How to make it faster? help chapter and tried to apply as much as I can.
My code is now 103 lines only (with GUI), mostly while loops, eventually for obj in, ofcourse I’ve disabled viewport redraw and I’m using bitarrays instead arrays wherever I can.
But still my code is pretty slow. It’s a voronoi fracturing script. I haven’t implemented any voronoi point generation pattern yet, I’m using mesh vertices as clusters. As for now that it’s working properly I want to boost up the speed.
As I said it’s pretty slow right now. 92 pieces are beeing cut in about 5-8 seconds. And I would like to make it almost realtime.
I know it can be done, just look here: https://vimeo.com/121768524 or at least take a look for mir vadims voronoi modifier: https://www.youtube.com/watch?v=2Fro0wLw86g
This is my first script that I’m trying to make it better, faster. All the scripts I’ve written out so far were pretty simple tools that didn’t need improvements like this one. I treat this as a great exercise.
So what can I do exept applying all from How to make it faster? chapter? Can I somehow track the times of my loops to detect where is the most computation? Do you have any suggestions for me?
1st example is using .Net+3rdparty lib for calculating and im sure Rayfire
s Voronoi modifier is written as a native C++ plugin.
Not sure if you can get realtime fracturing by using Maxscript only.
IShutter is using Qhull for cluster generation only, the fracturing is done in maxscript. I’m not generating any points for now, I’m using mesh vertices as clusters. However rayfire is probably developed in c++
the first link shows the thing that can be easily reproduced. technically it can be done by pure mxs with similar performance.
the second one is big. many options, big and smart user interface.
but… hmm… performance is not really impressive. all shown is not more than 50K verts, but we can see lugging.
To “profile” your code you can use timestamp().
(
st = timestamp(); sh = heapfree
for j = 1 to 1000000 do ()
format "time:% ram:%
" (timestamp()-st) (sh-heapfree)
)
Using BitArrays is not always faster than using Arrays.
Using Return() does not always have a huge impact in performance.
Using Exit or Continue does usually have a big impact in performance.
If you are creating nodes on the fly you can:
Avoid selecting-deselecting them
Disable Undo
Move to other panel than Modify
I haven’t found a fixed rule for optimization. In my experience it all depends on the code.
Undo off can really speed up things. Predefining arrays and their sizes also can gain you a few performance. (Mapped functions are really good performance boosters but their usage are very limited. ) If you can convert your editable poly to editable mesh by using snapshot(), it can also speed up things.
I’m sure you can! The only thing that may ruin the performance is the bottleneck of maxscript. So at a certain point it will be real-time, and after that point mxs will lose it’s performance. I have written for example a conform tool that can handle thousands of polys almost immediately (based on built in rayIntersect method, that uses brute force intersect search engine) or a splineFFD that can control ten-thousands of polygons with soft selection, all done with pure mxs. So I think you can do a lot of things, and I don’t think that voronoi fractureing is so compute heavy, as a conform tool for example. (but I’m not sure about that. I didn’t make a tool like that before. :))
Would you mind sharing the piece of code you think is slow and a practical test?
Sure
fn getMiddlePos A B = ( -- getting the middle point position
(A + B) * 0.5
)
fn setSlicePlane A B = ( -- function to get the rotation of the slice plane
global rot = (inverse (matrixFromNormal (A - B))).rotationpart
)
fn getVertPos obj num = ( -- this is a function I need to remove
meshop.getVert obj num
)
fn fracture p1 p2 obj frag = ( -- slicing the object
collapseStack frag
Slicer = SliceModifier()
Slicer.Slice_Type = 3
addModifier frag Slicer
setSlicePlane (getVertPos obj p1) (getVertPos obj p2)
frag.modifiers[1].Slice_Plane.rotation = rot
frag.modifiers[1].Slice_Plane.position = getMiddlePos (getVertPos obj p1) (getVertPos obj p2)
addModifier frag (Cap_Holes ())
)
fn getArrNum bitArr = (
i = 1
while bitArr[i]==true do (
i += 1
)
i
)
fn CalculateVolume obj = ( -- calculate volumes
local Volume= 0.0
local theMesh = snapshotasmesh obj
local numFaces = theMesh.numfaces
for i = 1 to numFaces do (
local Face= getFace theMesh i
local vert2 = getVert theMesh Face.z
local vert1 = getVert theMesh Face.y
local vert0 = getVert theMesh Face.x
local dV = Dot (Cross (vert1 - vert0) (vert2 - vert0)) vert0
Volume+= dV
)
delete theMesh
Volume /= 6
Volume
)
fn colorizeGroup Objectz Colors = ( -- actual colouring
i=1
while i<=Objectz.count do (
Objectz[i].wirecolor = Colors[i]
i+=1
)
)
fn setColorByVolume Objectz = ( -- collect object by volume, set colors
local colors = #()
j=1
while j<=Objectz.count do (
volume = CalculateVolume Objectz[j]
append colors volume
j+=1
)
scaleV = (amax colors) - (amin colors)
scaleR = maxCol.x - minCol.x
scaleG = maxCol.y - minCol.y
scaleB = maxCol.z - minCol.z
global outColors=#()
i=1
while i<=Objectz.count do (
R = ((colors[i] - (amin colors))/(amax colors))*scaleR
G = ((colors[i] - (amin colors))/(amax colors))*scaleR
B = ((colors[i] - (amin colors))/(amax colors))*scaleR
outColor = [R,G,B]
append outColors outColor
i+=1
)
)
maxCol = [220,220,220]
minCol = [5,5,5]
try(destroyDialog NightmareFracture)catch()
rollout NightmareFracture "NightmareFracture" width:200 height:200 (
-- maxCol = [220,220,220]
-- minCol = [5,5,5]
button __testScene "Create Test Objects"
button __fracture "Fracture"
colorpicker __minCol "Low Color:" color:[5,5,5] pos:[20, 150] width: 120 height:15
-- on __minCol changed arg do __minCol.color=minCol
colorpicker __maxCol "High Color:" color:[220,220,220] pos:[20, 175] width: 120 height:15
-- on __maxCol changed arg do __maxCol.color=minCol
on __testScene pressed do (
with redraw off (
for obj in geometry do (
if obj==theGeo do delete obj
)
global theGeo = Box lengthsegs:1 widthsegs:1 heightsegs:1 length:150 width:150 height:150 mapcoords:on pos:[0,0,0]
centerPivot theGeo
theGeo.position = [0,0,0]
global theVor = Geosphere radius:25 pos:[0,0,0] segs:3 boxmode:on
theNoise = Noisemodifier()
addModifier theVor theNoise
theNoise.scale = 10
theNoise.strength = [100,100,100]
theNoise.seed = random 0 999999
)
)
on __fracture pressed do (
local startF = timeStamp()
Undo off()
suspendEditing()
with redraw off (
VertexCloud = getNumVerts theVor
clusters=#{}
fragmens=#()
for i=1 to VertexCloud do (
append clusters i
)
cluster=1
while cluster <= clusters.count do ( -- cutting each piece
local start = timeStamp()
resetXform theGeo
KeepCopy = snapshot theGeo
-- format "KeepCopy is classof %
" (classof KeepCopy)
clusters[cluster]=false
i=1
for i in clusters do ( -- cutting each slice
num = getArrNum clusters
if num==undefined do num=1
fracture num i theVor theGeo
)
clusters[cluster]=true
centerPivot theGeo
theGeo.name = uniquename "frag_001"
-- theGeo.wirecolor = [random 0 255, 255, 255]
collapseStack theGeo
append fragmens theGeo
theGeo = KeepCopy
-- format "theGeo is classof %
" (classof theGeo)
cluster+=1
local end = timeStamp()
-- format "fracured piece no % within % seconds
" cluster ((end - start) / 1000.0)
)
delete KeepCopy
)
setColorByVolume fragmens
colorizeGroup fragmens outColors
Undo on()
resumeEditing()
local endF = timeStamp()
format "fractured % fragments within: % seconds
" VertexCloud ((endF - startF) / 1000.0)
)
)createDialog NightmareFracture
All you need is just execute, it will create test objects.
I just added colouring by volume size.
Sorry about poor descriptions in the code, but I’m writing it “on the fly” so I don’t need to comment this right now. But I’ll probably do it soon because it’s starting to be more complicated
Thank you for sharing it.
At first glance, without testing the code yet, I can see you are using modifiers, collapsing the stack, etc.
There is no way you can use all these actions and get a good performance. You may get a decent speed, but if you really want to make it the fastest way possible in MXS, I would suggest building all the meshes from the ground.
Perhaps you could have a set of predefined blocks and modify the vertices, UVs and SG to speed it up. Don’t know how many different meshes does Voronoi generate, but if they are just a few, it may work.
I will try it and see how it goes.
I’ve looked at the code and what slows it down are indeed the modifiers and collapsing the stack.
Other things could be improved but they wouldn’t have as much impact as building the geometry mesh instead of using modifiers.
For 93 blocks the code makes 8372 calls to:
collapseStack node
addModifier node (SliceModifier())
addModifier node (Cap_Holes ())
All inside the Fracture() function. I don’t see a way to considerably speed it up other than building the meshes yourself.
I have never implemented a Voronoi algorithm, so I don’t know how much it would cost to build the cells, but once you have that data building the meshes shouldn’t take too long. I think that’s what we see in the first video you posted. All the Voronoi calculations are done in C# and then the meshes are built in Max.
Also the video doesn’t show if the mapping, materials IDs, are set. If they are not, it would be a little faster.
slice and cap can be done on editable_poly level. it doesn’t need modifiers
I have just run through the code you shared and I think the proboolean or procutter might be a better solution. I would give them a chance if it works worse or better. (Using modifiers are slow because they always have to go through the modifiers stack pipeline which is slower than using base class object methods.)
You shold take a look at the methods that proboolean and procutter offer. (Just an idea… :))
Oh and I noticed that you don’t use polyop methods (that’s what denisT mentioned). Search them in mxs help. They are a lot more faster.
no… it’s too old. i’m sure you can do it better today
the bottle neck of any modifier using is ‘a lot of notifications’ are forced to be taken by system. usually the first one makes all job, but every next does do it again anyway.
so… try to use editable poly methods. it’s what i would do
(btw… i usually prototype my every tool by the mxs first. and if the idea works i rewrote it on c++ sdk and get 10+ times speedup with almost zero memory use)