I ran all code at 50^3 and some interesting results, the original code expected time would be 351sec took more like 2h scales really really bad, and at n 50 pArray took 21.6sec while pflow only takes 2.47sec.
The C# version takes 52ms (0.052 sec.) for 50^3 and 400ms (0.4 sec.) for 100^3 (12 million face).
yeah the c# are for sure the fastest, that’s kind of expected, tho for me its 243ms 50^3, don’t know why its like 5x slower for me.
ok… finally i’ve found a time to look at this problem deeper…
first is a numbers i have for “true mesh clone” written on c++ SDK
50^3 boxes => verts:1000000 time:14 heap:272L
50^3 teapots => verts:66250000 time:1140 heap:272L
(50^3 == 125000)
but it seems like Jorge’s machine is much faster than mine
How would someone set uv coordinates with the C# version, example so every box have one unique UV / “solid color” ?
With pflow I just do:
pCont.setParticleMapping i [u,v,w]
also another problem with pflow version other then its slower then C# is that mesher() only support ~500k boxes and I want to do > 1M, so pflow with mesher() doesn’t really even work for what I am trying to do.
PFlow version with UV:
fn fn_createBoxSpeedTest = (
gc()
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
pCont.useMapping = true
)
on Proceed pCont do
(
n = ((pCont.NumParticles())^(1/3.0))
i = [0,0]
rng
rngUV
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]
pCont.setParticleMapping i.x 1 [rngUV,rngUV,0]
)
)
)
)
")
local pmat = StandardMaterial name:"pMat"
local ramp = Gradient_Ramp() --(bitmaptexture filename:imgToVoxelDB.importedColorMap)
ramp.Gradient_Ramp.Flag__1.color = [255,0,0]
ramp.Gradient_Ramp.Flag__2.color = [0,255,0]
ramp.Gradient_Ramp.Flag__3.color = [0,0,255]
pmat.diffuseMap = ramp
showTextureMap pMat on
local pf = PF_Source Quantity_Viewport:100 Particle_Amount_Limit:1000000
ParticleFlow.BeginEdit()
pf.AppendAction (RenderParticles())
local e1 = Event name:"genBoxesEvent"
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)
e1.AppendAction (Material_Static Assigned_Material:pMat)
e1.AppendAction (DisplayParticles type:6)
ParticleFlow.EndEdit()
pf.appendInitialActionList e1
local m = Mesher()
m.pick = pf
sm = (snapshot m)
sm.material = pmat
particleFlow.BeginEdit()
particleFlow.delete $'genBoxesEvent'
particleFlow.EndEdit()
delete m
delete pf
)
)
local timeStop = timestamp()
print ("Time: " + (((timeStop - timeStart)/1000.0)as string) + " sec Mem: " + ((hf-heapfree) as string))
)
fn_createBoxSpeedTest()
Didn’t know that, it brings down the original n 15 time of 0.073sec to 0.045sec but looks like that also limited to ~500k boxes
(n 100)
I would think that this should work
(snapshotasmesh (pf.getParticleGroup 1))
but only works if I do this as print before
print ((getnodebyname(pf.name + "->" + e1.name))as string)
its a bit strange :S
delete objects
gc()
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 name:"testEvent"
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()
print ((getnodebyname(pf.name + "->" + e1.name))as string)
mm = mesh name:"result" mesh:(snapshotasmesh (pf.getParticleGroup 1))
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()
this snippet shows that you don’t need a Mesher node at all, and also shows how to wipe (delete) the particle’s stuff after at the end
delete objects
gc()
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
pCont.useMapping = true
)
on Proceed pCont do
(
n = ((pCont.NumParticles())^(1/3.0))
i = [0,0]
rng
rngUV
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]
pCont.setParticleMapping i.x 1 [rngUV,rngUV,0]
)
)
)
)
")
local pmat = StandardMaterial name:"pMat"
local ramp = Gradient_Ramp() --(bitmaptexture filename:imgToVoxelDB.importedColorMap)
ramp.Gradient_Ramp.Flag__1.color = [255,0,0]
ramp.Gradient_Ramp.Flag__2.color = [0,255,0]
ramp.Gradient_Ramp.Flag__3.color = [0,0,255]
pmat.diffuseMap = ramp
showTextureMap pMat on
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)
e1.AppendAction (Material_Static Assigned_Material:pMat)
pf.appendInitialActionList e1
ParticleFlow.EndEdit()
mm = mesh name:"result" mesh:(snapshotasmesh (getnodebyname (pf.name + "->" + e1.name))) material:pmat
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()
getnodebyname (pf.name + "->" + e1.name)
is not a good way to find a ‘mesh container’.
to find a better one will be your homework
Maxscript :UV single mesh
delete objects
gc()
b = box width:10 height:10 lenght:10 mapcoords:true
convertToMesh b
pmat = StandardMaterial name:"pMat"
ramp = Gradient_Ramp()
ramp.Gradient_Ramp.Flag__1.color = [255,0,0]
ramp.Gradient_Ramp.Flag__2.color = [0,255,0]
ramp.Gradient_Ramp.Flag__3.color = [0,0,255]
pmat.diffuseMap = ramp
showTextureMap pMat on
b.material = pMat
fn fn_uvSingleMesh inMesh = (
local rng = (random 0.0 1.0)
for i = 1 to (getNumTVerts inMesh) do (
setTVert inMesh i [rng,rng,0]
)
update inMesh
)
fn_uvSingleMesh b
C# what am I doing wrong/missing here?
TMESH.SetNumTVerts(8 * nTotal, false);
TMESH.SetTVert(v + actualVert, rng, rng, rng);
C# version with UV (not working)
(
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);
TMESH.SetNumTVerts(8 * nTotal, 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);
TMESH.SetTVert(v + actualVert, rng, rng, 0);
}
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
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)
)
i guess it’s a limitation of pflow’s or particlesys’s number of particles per emitter
but anyway i don’t think that the using of a particle systems is the right way to clone meshes
maybe MCG can help somehow.
I’m actually very interested to see any example of do this kind of job with MCG help
more likely it’s only particle display limitation, but we are using in view render mesh to get the original mesh which is not right