[Closed] Improve speed of "Create box()"
Hi, is there anyway to improve the speed of this script:
Currently it takes ~10sec with n at 15 and I would like to use way larger n but at the current performance its a not really practical.
EDIT: (Added 3dsMax 2014 results)
[ORIGINAL] (UPDATED)
3dsMax 2015:
: (n15 / 3375boxes) [Time: 9.5 sec] [Mem: 904296L]
: (n50 / 125000 boxes) [Time: ~ 2h ] [Mem: 2882104L]
3dsMax 2014:
: (n15 / 3375boxes) [Time: 9.503 sec]
[I]– > : Patan77 [/I]
fn fn_createBoxSpeedTest = (
delete objects
gc()
local timeStart = timestamp()
with undo false
with redraw off
--delete $polyobj
local polyobj = editable_mesh name:"polyObj"
local n = 15
convertTo polyobj Editable_Poly
for z = 0 to (n-1) do (
for y = 0 to (n-1) do (
for x = 0 to (n-1) do (
local b = box()
rng = (random 0.1 1)
b.length = rng
b.width = rng
b.height = rng
b.center = [x,y,z]
polyop.attach polyobj b
)
)
)
polyobj.wirecolor = [255,0,0]
gc()
print ("Time: " + (((timestamp() - timeStart)/1000.0)as string) + " sec")
)
fn_createBoxSpeedTest()
thanks in advance
Currently Fastest Version :
[ multi-threaded ]
3dsMax 2014
n15: Time: 0.005sec Mem: 416L
n50: Time: 0.027sec Mem: 416L
n100: Time: 0.181sec Mem: 416L
(UPDATED)
[I]– > : patan77 [/I] [I]– > : aaandres [/I]
[View Code]( http://www.patan77.com/fastest_attach/genBoxesMultiThreaded.txt)
3dsMax 2015:
: (n15 / 3375boxes) [Time: 0.006 sec] [Mem: 440L]
: (n50 / 125000 boxes) [Time: 0.243 sec] [Mem: 432L]
3dsMax 2014:
: (n15 / 3375boxes) [Time: 0.005 sec] [Mem: 416L]
: (n50 / 125000 boxes) [Time: 0.068 sec] [Mem: 416L]
[I]– > : aaandres [/I] [I]– > : polytools3d [/I]
(
fn compileCString =
(
SuperCubeClassName = "PathScripts.SuperCube"
classStr = (
"
using System;
using Autodesk.Max;
using System.Collections.Generic;
namespace PathScripts
{
class SuperCube
{
static public IGlobal global = GlobalInterface.Instance;
static public IInterface14 ip = global.COREInterface14;
static public void createSuperCube(int numCubes)
{
int[][] Faces = new int[12][] { new int[3] { 0, 2, 3 }, new int[3] { 3, 1, 0 }, new int[3] { 4, 5, 7 }, new int[3] { 7, 6, 4 },
new int[3] { 0, 1, 5 }, new int[3] { 5, 4, 0 }, new int[3] { 1, 3, 7 }, new int[3] { 7, 5, 1 },
new int[3] { 3, 2, 6 }, new int[3] { 6, 7, 3 }, new int[3] { 2, 0, 4 }, new int[3] { 4, 6, 2 }};
float[][] Verts = new float[8][] { new float[3] { -0.5f, -0.5f, -0.5f }, new float[3] { 0.5f, -0.5f, -0.5f }, new float[3] { -0.5f, 0.5f, -0.5f }, new float[3] { 0.5f, 0.5f, -0.5f },
new float[3] { -0.5f, -0.5f, 0.5f }, new float[3] { 0.5f, -0.5f, 0.5f }, new float[3] { -0.5f, 0.5f, 0.5f }, new float[3] { 0.5f, 0.5f, 0.5f }};
uint[] theElementSmooth = new uint[12] { 2, 2, 4, 4, 8, 8, 16, 16, 32, 32, 64, 64 };
IClass_ID cid = global.Class_ID.Create( (uint) BuiltInClassIDA.EDITTRIOBJ_CLASS_ID , 0 );
// Create a new TriObject.
object objectEditMesh = ip.CreateInstance(SClass_ID.Geomobject, cid as IClass_ID);
// Create a new node to hold it in the scene.
IObject objBaseObject = (IObject)objectEditMesh;
IINode node = global.COREInterface.CreateObjectNode( objBaseObject );
// Name it unique.
string newName = \"SuperCube\";
//ip.MakeNameUnique(ref newName);
node.Name = newName;
// Cast to TriObject
ITriObject triNew = objBaseObject as ITriObject;
int nTotal = numCubes * numCubes * numCubes;
Random r = new Random();
int countVerts = 0;
int actualVert = 0;
int actualFace = 0;
double range = 1.0 - 0.1;
IMesh TMESH = triNew.Mesh;
// Setup the new TriObject with number of faces and verts
TMESH.SetNumFaces(12 * nTotal, false, false);
TMESH.SetNumVerts(8 * nTotal, false, false);
IList<IFace> MESH_FACES = TMESH.Faces;
for (int z = 0; z < numCubes; z++)
{
for (int y = 0; y < numCubes; y++)
{
for (int x = 0; x < numCubes; x++)
{
actualVert = (8 * countVerts);
actualFace = (12 * countVerts);
float rng = (float)(r.NextDouble() * range + 0.1);
for (int v = 0; v < 8; v++)
{
TMESH.SetVert(v + actualVert, Verts[v][0] * rng + x, Verts[v][1] * rng + y, Verts[v][2] * rng + z);
}
for (int f = 0; f < 12; f++)
{
IFace FACE = MESH_FACES[f + actualFace];
FACE.SetVerts(Faces[f][0] + actualVert, Faces[f][1] + actualVert, Faces[f][2] + actualVert);
FACE.SetEdgeVisFlags(EdgeVisibility.Vis, EdgeVisibility.Vis, EdgeVisibility.Invis);
FACE.SmGroup = theElementSmooth[f];
}
countVerts++;
}
}
}
// Assign identity transform, position and center pivot.
//IMatrix3 tm = global.Matrix3.Create(); tm.IdentityMatrix();
//IPoint3 pt0 = global.Point3.Create(0, 0, 0);
//node.SetNodeTM(0, tm);
//node.ObjOffsetPos = pt0;
//node.CenterPivot(0, false);
// make it drawable.
TMESH.InvalidateGeomCache();
//ip.RedrawViews(0, RedrawFlags.Normal, null);
}
}
}
"
)
compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
dotnet.setlifetimecontrol compilerParams #dotnet
compilerParams.ReferencedAssemblies.Add("System.dll");
compilerParams.ReferencedAssemblies.Add( getdir #maxroot + "Autodesk.Max.dll");
compilerParams.GenerateInMemory = on
csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(classStr)
dotnet.setlifetimecontrol compilerResults #dotnet
---- CHECK COMPILE
if (compilerResults.Errors.Count > 0 ) then
(
local errs = stringstream ""
for i = 0 to (compilerResults.Errors.Count-1) do
(
local err = compilerResults.Errors.Item[i]
format "Error:% Line:% Column:% %
" err.ErrorNumber err.Line err.Column err.ErrorText to:errs
)
format "%
" errs
return undefined
)
----
else
(
compilerResults.CompiledAssembly
)
)
global assembly = compileCString()
SuperCube = assembly.CreateInstance "PathScripts.SuperCube"
/* TEST */
delete objects
gc()
st=timestamp(); sh=heapfree
SuperCube.createSuperCube 15
format "time:% heap:%
" (timestamp()-st) (sh-heapfree)
)
(UPDATED 3)
3dsMax 2015:
: (n15 / 3375boxes) [Time: 0.045 sec] [Mem: 16208L] [/I]
: (n50 / 125000 boxes) [Time: 1.492 sec] [Mem: 16584L]
3dsMax 2014:
Not working at the moment
[I]– > : Patan77 [/I]
fn fn_createBoxSpeedTest = (
hf = heapfree
local timeStart = timestamp()
with redraw off(
undo off(
sliderTime = 0
local n = 15
local bScript =
("
on ChannelsUsed pCont do
(
pCont.useScale = true
pCont.usePosition = true
)
on Proceed pCont do
(
n = ((pCont.NumParticles())^(1/3.0))
i = [0,0]
for z = 0 to (n - 1) do (
for y = 0 to (n - 1) do (
for x = 0 to (n - 1) do (
rng = (random 0.1 1)
rngUV = (random 0.0 1.0)
i.x += 1
pCont.particleIndex = i.x
pCont.particleScale = rng
pCont.particlePosition = [x,y,z]
)
)
)
)
")
local pf = PF_Source Quantity_Viewport:100 Particle_Amount_Limit:1000000
ParticleFlow.BeginEdit()
pf.AppendAction (RenderParticles())
local e1 = Event()
e1.AppendAction (Birth Amount: (n^3) Emit_Start:0 Emit_Stop:0)
e1.AppendAction (Script_Operator Proceed_Script: bScript)
e1.AppendAction (ShapeLibrary '3D_Type':0 size:1)
pf.appendInitialActionList e1
ParticleFlow.EndEdit()
mm = mesh name:"result" mesh:(snapshotasmesh (getnodebyname (pf.name + "->" + e1.name)))
particleFlow.BeginEdit()
particleFlow.delete pf
particleFlow.EndEdit()
)
)
local timeStop = timestamp()
print ("Time: " + (((timeStop - timeStart)/1000.0)as string) + " sec Mem: " + ((hf-heapfree) as string))
)
fn_createBoxSpeedTest()
3dsMax 2015:
: (n15 / 3375boxes) [Time: 0.033 sec] [Mem: 368L]
: (n50 / 125000 boxes) [Time: 2.37 sec] [Mem: 368L]
3dsMax 2014:
: (n15 / 3375boxes) [Time: 0.006 sec] [Mem: 360L]
: (n50 / 125000 boxes) [Time: 0.205 sec] [Mem: 368L]
[I]– > : aaandres [/I]
[View Code]( http://www.patan77.com/download/CODE_fn_createBoxSpeedTest_C01.txt)
I[/I]
[I]– > : Serejah [/I]
3dsMax 2015:
: (n15 / 3375boxes) [Time: 0.04 sec] [Mem: 1432L]
: (n50 / 125000 boxes) [Time: 21.62 sec] [Mem: 1432L]
3dsMax 2014:
: (n15 / 3375boxes) [Time: 0.037 sec] [Mem: 1384L]
: (n50 / 125000 boxes) [Time: 21.555 sec] [Mem: 1384L]
fn fn_createBoxSpeedTest = (
delete objects
(
gc()
with redraw off (
with undo off (
t1=timestamp()
hf = heapfree
size = 15
w = 10
h = 10
l = 10
ps = (w/2.0)
m = mesh numverts:(size^3) numfaces:(size^3)
i = [0,0]
for x=0 to size-1 do(
for y=0 to size-1 do(
for z=0 to size-1 do(
setvert m (i.x += 1) [ x*w, y*l, z*h ]
)
)
)
p = PArray()
p.seed = (random 0 99999)
p.Emitter_Stop = 0f
p.quantityMethod = 1
p.Total_Number = size^3
p.formation = 2
p.Growth_Time = 0f
p.Fade_Time = 0f
p.size = ps
p.standardParticle = 1
p.viewPercent = 100
p.viewType = 2
p.Size_Variation = 100
p.emitter = m
mm = Mesher()
mm.pick = p
converttomesh mm
delete p
delete m
format "Time: %sec. Mem: %
" ((timestamp()-t1)/1000 as float) (hf-heapfree)
)
)
)
)
fn_createBoxSpeedTest()
I[/I]
3dsMax 2015:
: (n15 / 3375boxes) [Time: 0.150 sec] [Mem: 4969600L]
: (n50 / 125000 boxes) [Time: 11.98 sec] [Mem: 184982226L]
3dsMax 2014:
: (n15 / 3375boxes) [Time: 0.12 sec]
: (n50 / 125000 boxes) [Time: 12.338 ]
[I]– > : aaandres [/I]
fn fn_createBoxSpeedTest = (
timeStart = timestamp()
-- Verts:
theElementVerts = #([-0.5,-0.5,-0.5], [0.5,-0.5,-0.5], [-0.5,0.5,-0.5], [0.5,0.5,-0.5], [-0.5,-0.5,0.5], [0.5,-0.5,0.5], [-0.5,0.5,0.5], [0.5,0.5,0.5])
-- Faces:
theElementFaces = #([1,3,4], [4,2,1], [5,6,8], [8,7,5], [1,2,6], [6,5,1], [2,4,8], [8,6,2], [4,3,7], [7,8,4], [3,1,5], [5,7,3])
-- Smooth
theElementSmooth = #(2, 2, 4, 4, 8, 8, 16, 16, 32, 32, 64, 64)
colorb = (color (random 0 255) (random 0 255) (random 0 255))
countFaces = 0
vertsArray = #()
facesArray = #()
n = 15
for z = 0 to (n-1) do (
for y = 0 to (n-1) do (
for x = 0 to (n-1) do (
rng = (random 0.1 1)
VertPos = for v in theElementVerts collect (posLocal = v*rng; [posLocal.x + x, posLocal.y + y, posLocal.z + z])
join vertsArray VertPos
Faces = for f in theElementFaces collect (f + (8 * countFaces))
join facesArray Faces
countFaces += 1
)
)
)
oo = mesh vertices:vertsArray faces:facesArray
oo.wirecolor = colorb
oo.name = "SuperCube"
for i = 1 to facesArray.count by 12 do
(
for j = 0 to 11 do
(
setFaceSmoothGroup oo (i+j) theElementSmooth[j+1]
)
)
update oo
print ("Time: " + (((timestamp() - timeStart)/1000.0)as string) + " sec")
)
fn_createBoxSpeedTest()
Note: All codes are a collaboration between:
Patan77
aaandres
denisT
polytools3d
Serejah
Search for ‘Fast attach’ in this forum. Possibly you have a performance problem in the attach command.
also it would be faster to make a poly-box template, and make an instances of this template instead of making a new box object.
delete objects
gc()
(
delete objects
gc()
t = timestamp()
h = heapfree
b = box width:2 length:2 height:2
converttopoly b
for k=1 to 1000 do
(
instance b
)
format "instance >> time:% heap:%
" (timestamp() - t) (h - heapfree)
)
(
delete objects
gc()
t = timestamp()
h = heapfree
for k=1 to 1000 do
(
b = box width:2 length:2 height:2
converttopoly b
)
format "predefined >> time:% heap:%
" (timestamp() - t) (h - heapfree)
)
(
delete objects
gc()
t = timestamp()
h = heapfree
for k=1 to 1000 do
(
b = box()
b.width = 2
b.length = 2
b.height = 2
converttopoly b
)
format "make and set >> time:% heap:%
" (timestamp() - t) (h - heapfree)
)
Thx,
Yeah, but for my real script every box need to be a different size so wont work with instances.
you can scale it with instancing (and position as well):
instance b scale:<scale> pos:<pos>
or do full transform if you need
Didn’t think about that, but for me instance is slower then creating box(), instance: 0.592sec vs box() 0.444sec, but still of the 10sec 9.5 is the attach process
instance: (0.592sec)
fn fn_createBoxSpeedTest = (
delete objects
gc()
local timeStart = timestamp()
with undo false
with redraw off
local polyobj = editable_mesh name:"polyObj"
local n = 15
convertTo polyobj Editable_Poly
local b = (box length:1 width:1 height:1)
local bi
local rng
converttopoly b
for z = 0 to (n-1) do (
for y = 0 to (n-1) do (
for x = 0 to (n-1) do (
rng = (random 0.1 1)
bi = (instance b scale:[rng,rng,rng])
bi.center = [x,y,z]
--polyop.attach polyobj bi
)
)
)
polyobj.wirecolor = [255,0,0]
delete b
gc()
print ("Time: " + (((timestamp() - timeStart)/1000.0)as string) + " sec")
)
fn_createBoxSpeedTest()
box(): (0.444sec)
fn fn_createBoxSpeedTest = (
delete objects
gc()
local timeStart = timestamp()
with undo false
with redraw off
local polyobj = editable_mesh name:"polyObj"
local n = 15
convertTo polyobj Editable_Poly
local b
local rng
for z = 0 to (n-1) do (
for y = 0 to (n-1) do (
for x = 0 to (n-1) do (
rng = (random 0.1 1)
b = (box length:rng width:rng height:rng)
b.center = [x,y,z]
-- polyop.attach polyobj b
)
)
)
polyobj.wirecolor = [255,0,0]
gc()
print ("Time: " + (((timestamp() - timeStart)/1000.0)as string) + " sec")
)
fn_createBoxSpeedTest()
your performance test is not correct… you have to add to ‘just create box’ method conversion to poly…
sure if I add converttopoly after box() its 0.7sec so then its slower then instance, but if I have polyop.attach enabled and do just box() then attach and skip converttopoly its faster then instance and attach:
instance + attach: (“Time: 9.709 )
box() + attach: (“Time: 9.381 sec”)
Ok, this is what I came up with (recursive attach script) :
3375 objects 0.45sec instead of 9.5sec so that’s much faster
fn quickAttachRec inArr = (
if inArr.count == 1 do (
return inArr[1]
)
for i = 1 to (inArr.count - 1) by 2 do (
polyop.attach inArr[ i ] inArr[i+1]
)
quickAttachRec (objects as array)
)
fn fn_Timer = (
gc()
local timeStart = timestamp()
quickAttachRec (objects as array)
local timeStop = timestamp()
print ("Time: " + (((timeStop - timeStart)/1000.0)as string) + " sec")
)
fn_Timer()
And “complete” script: n at 15 (~2sec) (would like it to be way faster still[/I])
fn quickAttachRec inArr = (
if inArr.count == 1 do (
return inArr[1]
)
for i = 1 to (inArr.count - 1) by 2 do (
meshop.attach inArr[ i ] inArr[i+1]
)
quickAttachRec (objects as array)
)
fn fn_createBoxSpeedTest = (
delete objects
gc()
local timeStart = timestamp()
with undo false
with redraw off
local n = 15
local b = (box length:1 width:1 height:1)
local bi
local rng
converttomesh b
for z = 0 to (n-1) do (
for y = 0 to (n-1) do (
for x = 0 to (n-1) do (
rng = (random 0.1 1)
bi = (instance b scale:[rng,rng,rng])
bi.center = [x,y,z]
)
)
)
delete b
InstanceMgr.MakeObjectsUnique (objects as array) #individual
quickAttachRec (objects as array)
gc()
print ("Time: " + (((timestamp() - timeStart)/1000.0)as string) + " sec")
)
fn_createBoxSpeedTest()
this is not the ‘canonical’ FastAttach method. I’m not sure that yours is better.
I’ve tryed the ‘canonical’ and the result is the same, possibly because all meshes are boxes with the same number of verts.
The only improvements I’ve found are:
- Go back to copy instead of instances (InstanceMgr takes half a second or more)
- delete the end gc() (that is another half second)
- scale after the creation (it’s 0,1 seconds better)
So, I get a total of 1,23 seconds.
fn quickAttachRec inArr = (
if inArr.count == 1 do (
return inArr[1]
)
for i = 1 to (inArr.count - 1) by 2 do (
meshop.attach inArr[i] inArr[i+1]
)
quickAttachRec (objects as array)
)
fn fn_createBoxSpeedTest = (
delete objects
gc()
local timeStart = timestamp()
with undo false
with redraw off
local n = 15
local b = (box length:1 width:1 height:1)
local bi
local rng
converttomesh b
for z = 0 to (n-1) do (
for y = 0 to (n-1) do (
for x = 0 to (n-1) do (
rng = (random 0.1 1)
bi = copy b
bi.center = [x,y,z]
bi.scale = [rng,rng,rng]
)
)
)
delete b
quickAttachRec (objects as array)
print ("Time: " + (((timestamp() - timeStart)/1000.0)as string) + " sec")
)
fn_createBoxSpeedTest()
recursive method needs better way to work with list of original nodes. (it uses now (objects as array) which not correct in general).
‘canonical’ version makes array operations (deleteitem) what takes a time.
Well I didn’t spend much time writing that script, its just a test not final code, here is another version without the (objects as array, )its still a bit flawed tho, need to use object count of power of 2 like 4096objects etc. or run it multiple times
n 16 (4096)objects 0.464sec (n15 3375objects est: 0.365sec)
fn quickAttachRec inArr = (
tmpAry = #()
if inArr.count == 1 do (
return inArr[1]
)
for i = 1 to (inArr.count - 1) by 2 do (
meshop.attach inArr[i] inArr[i+1]
append tmpAry inArr[i]
)
quickAttachRec tmpAry
)
my thought is that recursive version has to be slower and has to use much more memory.
try it for bigger numbers.
If Patrik is interested, here’s the function I’ve used to compare:
fn customAttach nodes =
(
fn qsfn v1 v2 = v1.numVerts - v2.numVerts
qSort nodes qsFn
local k = 1
local att = meshop.attach
while nodes.count > 1 do
(
local nk = nodes[k]
att nk nodes[k+1]
deleteItem nodes (k+1)
if nodes[k+2]!=undefined and nk.numVerts >= nodes[k+2].numVerts do k += 1
if k >= nodes.count do k = 1
)
nodes[1]
)
that can be simplified for this special case to: (no need to sort, no need to check later nodes with greater number of verts)
fn customAttach nodes =
(
--fn qsfn v1 v2 = v1.numVerts - v2.numVerts
--qSort nodes qsFn
local k = 1
local att = meshop.attach
while nodes.count > 1 do
(
local nk = nodes[k]
att nk nodes[k+1]
deleteItem nodes (k+1)
--if nodes[k+2]!=undefined and nk.numVerts >= nodes[k+2].numVerts do k += 1
if nodes[k+2]!=undefined do k += 1
if k >= nodes.count do k = 1
)
nodes[1]
)
Trying going about it from another angle by using pflow scripting instead with n 15 3375boxes random size I got it down to (0.062 sec) Time: 0.056 sec generate boxes then 0.004sec for snapshot
Code is just temporary prof of concept to test the speed, need clean it up alot, but yeah this is for sure they way to do it:
on ChannelsUsed pCont do
(
pCont.useScale = true
)
on Init pCont do
(
)
on Proceed pCont do
(
--if pflowRun == false then (
pflowSource = $ --select Pflow obj before ctrl+e
gc()
timeStart = timeStamp()
n = 15
i = 1
rng
for z = 0 to (n - 1) do (
for y = 0 to (n - 1) do (
for x = 0 to (n - 1) do (
rng = (random 0.1 1)
pCont.AddParticle()
pCont.particleIndex = i
pCont.particleScale = rng
pCont.particlePosition = [x,y,z]
i += 1
)
)
)
--m = Mesher()
--m.pick = pflowSource
--snapshot m
--flowSource.Enable_Particles = false
--delete m
--pflowRun = false
timeStop = timeStamp()
print ("Time: " + (((timestamp() - timeStart)/1000.0)as string) + " sec")
--)
)
on Release pCont do
(
)