http://www.scriptspot.com/3ds-max/scripts/fracture-voronoi
Despite it’s really old, it’s still pretty fast. If we take into consideration that it uses old methods, it’s a really good job.(modifiers in this case)
So I would take a look at the things, in this script, if I may find something useful.
Maxscript has a huge runtime overhead compared to compiling a plugin in C++. When you are running a maxscript and then break in the visual studio debugger, you can easily see a callstack as tall as your screen (And then some too). Now that you have a proof of concept, write it in C++ and stop using maxscript: a dead language.
Just for fun, using polyop methods. First, slicing for each pair:
try destroyDialog ::NightmareFracture catch()
rollout NightmareFracture "NightmareFracture" width:200 height:200
(
local obj, points
button __testScene "Create Test Objects"
button __fracture "Fracture"
colorPicker __minCol "Low Color:" color:[5, 5, 5] pos:[20, 150] width: 120 height:15
colorPicker __maxCol "High Color:" color:[220, 220, 220] pos:[20, 175] width: 120 height:15
local getVertPos = polyOp.getVert
local deleteVerts = polyOp.deleteVerts
local cap = polyOp.capHolesByEdge
local slice = polyOp.slice
fn isPositiveDir pos vector vectorPos =
dot vector (pos - vectorPos) > 0
fn slicePoly obj pos vector =
(
local verts = obj.verts as bitArray
slice obj #all (ray pos vector)
deleteVerts obj (for vert in verts where isPositiveDir (getVertPos obj vert) vector pos collect vert)
cap obj #all
)
on __testScene pressed do
(
obj = Box lengthSegs:1 widthSegs:1 heightSegs:1 length:150 width:150 height:150 mapCoords:on pivot:[0, 0, 75] pos:[0, 0, 0]
vor = convertToMesh (GeoSphere radius:25 pos:[0, 0, 0] segs:3 boxMode:on)
addModifier vor (NoiseModifier scale:10 strength:[100, 100, 100] seed:(random 0 1e6))
points = for vert in vor.verts collect vert.pos
)
on __fracture pressed do with undo off, redraw off if obj != undefined AND points != undefined do
(
local start = timeStamp()
local pointCount = points.count
setCommandPanelTaskMode mode:#create
local tempMesh = copy obj pivot:[0, 0, 0]
resetXForm tempMesh
convertTo tempMesh Editable_Poly
local chunks = for p in points collect copy tempMesh prefix:#voro pivot:p isHidden:true
for p1 = 1 to pointCount - 1 do for p2 = p1 + 1 to pointCount while NOT keyboard.ESCpressed do
(
local pos = points[p1]
local vec = (points[p2] - pos) / 2
slicePoly chunks[p1] (pos + vec) vec
slicePoly chunks[p2] (pos + vec) -vec
)
delete tempMesh
select chunks
selection.isHidden = false
redrawViews()
format "fractured % fragments within: % seconds
" pointCount ((timeStamp() - start) / 1e3)
)
)
createDialog NightmareFracture
Second, sorting by distance, slicing stops when the diagonal of the chunk is smaller than double the distance from the next point (i.e. next slice would be outside the mesh, no point continuing). Won’t make a difference here (might even slow it down instead) but useful in other cases:
try destroyDialog ::NightmareFracture catch()
rollout NightmareFracture "NightmareFracture" width:200 height:200
(
local obj, points
button __testScene "Create Test Objects"
button __fracture "Fracture"
colorPicker __minCol "Low Color:" color:[5, 5, 5] pos:[20, 150] width: 120 height:15
colorPicker __maxCol "High Color:" color:[220, 220, 220] pos:[20, 175] width: 120 height:15
local getVertPos = polyOp.getVert
local deleteVerts = polyOp.deleteVerts
local cap = polyOp.capHolesByEdge
local slice = polyOp.slice
struct pointProps (id, pos)
fn getLengthSquared v =
dot v v
fn isPositiveDir pos vector vectorPos =
dot vector (pos - vectorPos) > 0
fn compareDistance p1 p2 pointArr: centerPoint: =
getLengthSquared (pointArr[p1].pos - centerPoint) - getLengthSquared (pointArr[p2].pos - centerPoint) -- assuming the distance won't be smaller than one
fn slicePoly obj pos vector =
(
local verts = obj.verts as bitArray
slice obj #all (ray pos vector)
deleteVerts obj (for vert in verts where isPositiveDir (getVertPos obj vert) vector pos collect vert)
cap obj #all
)
on __testScene pressed do
(
obj = Box lengthSegs:1 widthSegs:1 heightSegs:1 length:150 width:150 height:150 mapCoords:on pivot:[0, 0, 75] pos:[0, 0, 0]
vor = convertToMesh (GeoSphere radius:25 pos:[0, 0, 0] segs:3 boxMode:on)
addModifier vor (NoiseModifier scale:10 strength:[100, 100, 100] seed:(random 0 1e6))
points = for vert in vor.verts collect pointProps id:i pos:vert.pos
)
on __fracture pressed do with undo off, redraw off if obj != undefined AND points != undefined do
(
local start = timeStamp()
local pointCount = points.count
setCommandPanelTaskMode mode:#create
local tempMesh = copy obj pivot:[0, 0, 0]
resetXForm tempMesh
convertTo tempMesh Editable_Poly
local chunks = for p = 1 to pointCount while NOT keyboard.ESCpressed collect
(
local center = points[p].pos
local neighborPoints = (#{1..pointCount} - #{p}) as array
qsort neighborPoints compareDistance pointArr:points centerPoint:center
local chunk = copy tempMesh prefix:#voro pivot:center isHidden:true
local distSquared = 0
local radiusSquared = getLengthSquared (points[neighborPoints[neighborPoints.count]].pos - center)
local i = 0
do
(
local vector = points[neighborPoints[i += 1]].pos - center
distSquared = getLengthSquared vector
radiusSquared = amin radiusSquared (4 * getLengthSquared (chunk.max - chunk.min))
slicePoly chunk (center + vector / 2) vector
)
while radiusSquared >= distSquared AND i < pointCount - 1
chunk
)
delete tempMesh
select chunks
selection.isHidden = false
redrawViews()
format "fractured % fragments within: % seconds
" pointCount ((timeStamp() - start) / 1e3)
)
)
createDialog NightmareFracture
And how much faster the new with polyop than the original with modifiers? (Just courious. I can’t test it right now since I’m not at home) :rolleyes:
Note that much? It can make things almost realtime, so it’s a huge improvement. For a realtime tool every second or fraction of a second counts. And it’s a huge jump especially because the code is unoptimized yet.
Ok…
@Swordslayer – that is some hardcore mxs. It was supposed to be an exercise and you have completed it for me I need to take a closer look and study your code. Because some things are just complete magic for me and I have no idea what they do, eg. line 42.
for p1 = 1 to pointCount - 1 do for p2 = p1 + 1 to pointCount while NOT keyboard.ESCpressed do
As well that you are calling polyOp.getVert instead meshop.getVert. I was sure that was a meshop function and you have to run it like that. Does this mean that I can run all meshop functions like polyOps?
As for the C++. I’m not very familiar with it. I have installed SDK and tried to do sth with it, but for someone who didn’t do any programming before… I really have no idea how it works. There is a tutorial in help about how to install and run SDK on Visual Studio, but unfortunatelly there seem to be an error in it and nobody ever fixed it.
Probably for people who have some programming experiance it’s a piece of cake. But for me it’s like a huge gap between that I have no idea how to jump and move foreward.
Well, it’s still far from being realtime and I thought that’s what you’re after in the first place. Line 42 is a nested loop:
for p1 = 1 to pointCount - 1 do -- stop one step before, we are comparing pairs so it will end up with p1 = (count - 1), p2 = count
for p2 = p1 + 1 to pointCount do -- start from (p1 + 1) as we've already done previous pairs in the iterations before
The while not keyboard.ESCpressed part of the inner loop checks if esc is pressed to interrupt at that moment (which will exit from the inner loop but not the outer, you’d have to hold it for a sec till the outer loop finishes iterating).
MeshOp methods are for meshes, polyOp methods are for editable poly, that’s the difference. When working with meshes, obviously you won’t be using polyop.getVert; look up the list of polyop and meshop methods in the reference to see the differences.
Yes, that’s exactly what I’m after
When I find some time I’ll take a closer look at your code and will try to make my code run on polyops instead. Right now I’m at work and we have some serious deadlines to deliver, I shouldn’t even be at forum right now
code is optimized (at least the slicing part). it will hard to do it faster.
the bottleneck is poly object update. system does do it three times: slice, delete verts, cap
Quick 80% optimization to the SlicePoly() function ?
fn slicePoly obj pos vector =
(
local verts = obj.verts as bitArray
beforeVerts = obj.numverts
slice obj #all (ray pos vector)
if beforeVerts != obj.numverts do
(
deleteVerts obj (for vert in verts where isPositiveDir (getVertPos obj vert) vector pos collect vert)
cap obj #all
)
)
– Original Code: fractured 92 fragments within: 2.595 seconds
-- Before: fractured 92 fragments within: 0.621 seconds
-- After: fractured 92 fragments within: 0.338 seconds
Didn't really test it too much, so it could fail.
if (slice obj #all (ray pos vector)) do
you don’t need to check number of verts
Absolutely. No difference in performance but more elegant and correct use of the returned value.