Let’s look at these numbers … We have a model size that is quite normal for current tasks (~ 100K vert). To find the 20 closest neighbors, you need to spend ~1 s. Where 20 verts is a normal amount for soft-selection, for example. Can we live with this? Probably not.
Agreed. Did you spot a bug in the k-D tree implementation, @denisT? With the bottleneck, so far, being mostly due to the environment I am not sure where to start with finding or writing a better option.
this one works better
(
struct AutodeskOctree
(
Points,
IArray,
Octree,
fn MakeListOfType type_name =
(
local genericType = dotnet.GetType "System.Collections.Generic.List`1"
local innerType = dotnet.GetType type_name
local specificType = genericType.MakeGenericType #(innerType)
(dotnetclass "System.Activator").CreateInstance specificType
),
vec3_class,
fn AsVec3 vec = dotNetObject vec3_class vec.x vec.y vec.z,
fn OctreeFromPoints pts =
(
local lst = MakeListOfType "microsoft.xna.framework.vector3"
local ListAdd = lst.add
for p in pts do ListAdd (dotNetObject vec3_class p.x p.y p.z)
local ia = dotnet.GetType (dotNetClass "autodesk.sequences.immutablearray")
local method = (ia.GetMethods())[ 67 ] -- .Create that takes List<T> as a parameter
local vec_type = dotNet.getType vec3_class
local meth = method.makegenericmethod (dotNet.ValueToDotNetObject #( vec_type ) (dotNetClass "system.type[]"))
-- 'IArray<Vector3>'
IArray = meth.invoke (dotNetObject "system.object") (dotNet.ValueToDotNetObject #(lst) (dotNetClass "system.object[]"))
Octree = dotNetObject "autodesk.geometry3d.vertexoctree" IArray
if Octree != undefined do
(
Points = pts
Octree
)
),
fn FindClosest pt =
(
index = Octree.FindClosestPoint (dotNetObject vec3_class pt.x pt.y pt.z)
if index < 0 then undefined else Points[ index + 1 ]
),
on create do
(
dotNet.loadAssembly @"C:\Program Files\Autodesk\3ds Max 2016\Geometry3D.dll"
dotNet.loadAssembly @"C:\Program Files\Autodesk\3ds Max 2016\ImmutableArray.dll"
dotNet.loadAssembly @"C:\Program Files\Autodesk\3ds Max 2016\MonoGame.Framework.dll"
vec3_class = dotnetclass "microsoft.xna.framework.vector3"
)
)
delete objects
gc()
txt = Text text:"MAXS\r\ncript"
addModifier txt (subdivide())
txt.modifiers[1].size = (distance txt.max txt.min) / 250.0
pts = for i=1 to txt.numverts collect getvert txt i
t1=timestamp();hf = heapfree
oc = AutodeskOctree()
format "1 Time: %sec. Mem: %\n" ((timestamp()-t1)/1000 as float) (hf-heapfree)
oc.OctreeFromPoints pts
format "2 Time: %sec. Mem: %\n" ((timestamp()-t1)/1000 as float) (hf-heapfree)
delete helpers
::h1 = point centermarker:on cross:off wirecolor:red
::h2 = point pos:txt.center centermarker:off cross:true wirecolor:green isSelected:true
deleteAllChangeHandlers id:#xxx
when transform h2 changes id:#xxx val do
(
t1=timestamp();hf = heapfree
n = oc.FindClosest ::h2.pos
format "Lookup: Time: %sec. Mem: %\n" ((timestamp()-t1)/1000 as float) (hf-heapfree)
::h1.pos = n
)
)
--dotNetObject "microsoft.xna.framework.vector3" 1 2 3
-- make it in advance and put to the struct:
vec3_class = dotnetclass "microsoft.xna.framework.vector3"
dotnetobject vec3_class x y z
Nice, thanks!
8x faster with no effort.
updated the code in previous post
Wonder if it is possible to skip maxscript loop over points at all and somehow cast mesh verts array (from c# IMesh) to Xna.Vector3 array. It should be way faster.
global AutodeskOctree
(
struct AutodeskOctreeStruct
(
points,
octree,
fn createGenericList class =
(
local genericType = dotnet.GetType "System.Collections.Generic.List`1"
if iskindof (local elementType = dotnet.GetType class) dotnetobject do
(
(dotnetclass "System.Activator").CreateInstance (genericType.MakeGenericType #(elementType))
)
),
vec3_class = dotnetclass "microsoft.xna.framework.vector3",
fn as_vec3 vec = dotnetobject vec3_class vec.x vec.y vec.z,
fn create_from_points pp =
(
t0 = timestamp()
h0 = heapfree
list = createGenericList "microsoft.xna.framework.vector3"
for p in pp do list.add (as_vec3 p)
t1 = timestamp()
h1 = heapfree
-- vec3_tab = dotnet.valuetodotnetobject vec3_tab (dotnetclass "system.object[]")
ia_type = dotnet.gettype (dotnetclass "autodesk.sequences.immutablearray")
ia_methods = ia_type.getmethods() -- [ 67 ] >> .create that takes list<t> as a parameter
v_type = dotnet.gettype (dotnetclass "microsoft.xna.framework.vector3")
create_method = ia_methods[67].makegenericmethod (dotnet.valuetodotnetobject #(v_type) (dotnetclass "system.type[]"))
-- 'IArray<Vector3>'
vec3_arr = create_method.invoke (dotnetobject "system.object") (dotnet.valuetodotnetobject #(list) (dotnetclass "system.object[]"))
oc_tree_class = dotnetclass "autodesk.geometry3d.vertexoctree"
oc_tree = dotnetobject "autodesk.geometry3d.vertexoctree" vec3_arr
t2 = timestamp()
h2 = heapfree
format "\t >> time:%(%, %) heap:%(% %)\n" (t2 - t0) (t1 - t0) (t2 - t1) (h0 - h2) (h0 - h1) (h1 - h2)
oc_tree
),
fn find_nearest_index p =
(
octree.FindClosestPoint (as_vec3 p) + 1
),
fn find_nearest_point pt =
(
if (i = find_nearest_index p) > 0 do points[i]
),
on create do
(
dotnet.loadassembly @"geometry3d.dll"
dotnet.loadassembly @"immutablearray.dll"
dotnet.loadassembly @"monogame.framework.dll"
)
)
AutodeskOctree = AutodeskOctreeStruct
ok
)
delete objects
gc()
pp =
(
s = geosphere segs:100
addmodifier s (Edit_Poly())
for k=1 to numpoints s collect (getpointpos s k)
)
_oc = AutodeskOctree()
(
t0 = timestamp()
h0 = heapfree
if (oc_tree = _oc.create_from_points pp) != undefined do
(
_oc.octree = oc_tree
_oc.points = deepcopy pp
)
format "% time:% heap:%\n" pp.count (timestamp() - t0) (h0 - heapfree)
)
(
num = 1000
v = -1
index = random 1 oc.points.count
t0 = timestamp()
h0 = heapfree
for k=1 to num do
(
v = _oc.find_nearest_index pp[index]
)
format "(%) closest:(% >> %) time:% heap:%\n" num index v (timestamp() - t0) (h0 - heapfree)
)
Thanks for the nice find with VertexOctree. This is the right direction (MXS + C#)
(I’ve cleaned up your code to “my style” to make it easier for me to debug)
As we can see the only problem is the casting mxs point3 to Vector3 and add the value to List.
And it’s the only problem. All other looks good enough to play with.
For some unknown reason max doesn’t allow to make a IntPtr to the first mesh vert from maxscript.
When I do it from compiled c# dll it works nicely, but I’d love not to compile a thing if it is possible.
ImmutableArray can be created from arrays as well, so probably we can copy whole IPoint3 memory block and use it to make Vec3 array. But how do we add the first point?
g = (dotNetClass "Autodesk.Max.GlobalInterface").Instance
tri = (createInstance box).mesh
v = g.executescript (g.stringstream.create "::tri") true
vts = v.ToMesh.Verts
base = (vts.item 0).INativeObject__Handle -- max 2014
offset = 12 -- 3 * 4 bytes to skip first Point3 entirely
floats = dotNetObject "system.single[]" (vts.Count * 3)
m = (dotNetClass "System.Runtime.InteropServices.Marshal")
m.copy (dotNetObject "System.IntPtr" (base + offset)) floats 0 (vts.Count * 3)
for i = 0 to 20 by 3 do format "%: [%,%,%] - %\n" (1 + ((i+3)/3)) (floats.get i) (floats.get (i+1)) (floats.get (i+2)) (getvert tri (1 + (i+3)/3))
this is what i would do …
I would make an assembly to convert IMesh or system.single[] to IArray . Everything else will be slow anyway I guess
Kinda works, but I hate the fact that I have to compile here and there for every little thing
Time: 0.02sec. Mem: 3688L – for converting 9167 point3 to vec3 array
global MemUtils =
(
source = "using System;\n"
source += "using System.Runtime.InteropServices;\n"
source += "class MemUtils\n"
source += "{\n"
source += "
public static T[] MarshalUnmananagedArray2Struct<T>(Int64 unmanagedArray, int length )
{
var size = Marshal.SizeOf(typeof(T));
var mangagedArray = new T[length];
for (int i = 0; i < length; i++)
{
IntPtr ins = new IntPtr(unmanagedArray + i * size);
mangagedArray[i] = Marshal.PtrToStructure<T>(ins);
}
return mangagedArray;
}
"
source += "}\n"
csharpProvider = dotnetobject "Microsoft.CSharp.CSharpCodeProvider"
compilerParams = dotnetobject "System.CodeDom.Compiler.CompilerParameters"
compilerParams.ReferencedAssemblies.Add("System.dll");
compilerParams.GenerateInMemory = on
compilerResults = csharpProvider.CompileAssemblyFromSource compilerParams #(source)
compilerResults.CompiledAssembly.CreateInstance "MemUtils"
)
fn UnmananagedArray2Struct first_item_handle count struct_class =
(
local mi = (::MemUtils.gettype()).getmethod "MarshalUnmananagedArray2Struct"
local meth = mi.makegenericmethod (dotNet.ValueToDotNetObject #( dotNet.getType struct_class ) (dotNetClass "system.type[]"))
meth.invoke (dotNetObject "system.object") (dotNet.ValueToDotNetObject #( first_item_handle, count ) (dotNetClass "system.object[]")) asdotnetobject:true
)
tri = (createInstance Teapot).mesh
v = g.executescript (g.stringstream.create "::tri") true
vts = v.ToMesh.Verts
base = (vts.item 0).INativeObject__Handle
teapot_vec3 = UnmananagedArray2Struct base tri.numverts (dotNetClass "microsoft.xna.framework.vector3")
This is not for a little thing. It can be a part of MXS_Octree (or some Quick_Verts) structure with another helpful methods
hmm… doesn’t work for me:
– Unknown property: “INativeObject__Handle” in dotNetObject:Autodesk.Max.Wrappers.Point3