Notifications
Clear all

[Closed] Find one of each instance – Efficiently

I need to collect all unique objects and one of each instance in a scene.
The way I’ve done it looks a bit awkward and inefficient:

fn getUniqueObs obs = -- finds all unique objects in 'obs'
(
	local oI =1
	while oI < obs.count do
	(
		InstanceMgr.GetInstances obs[oI] &insts
		if insts.count >1 then
		(
			for i = 2 to insts.count do
			(
				deleteItem obs (findItem obs insts[i])
			)
		)
		oI+=1
	)
	obs
)

Am I missing a ‘trick’ or a better way?
I would be grateful for any pointers, thanks.

12 Replies

I think this might be more efficient:

(
	fn collectUniqueObjs =
	(
		local objs = #()
		local handles = #{}
		
		for o in objects where not handles[o.inode.handle] do (
			append objs o
			
			local temp = #()
			InstanceMgr.GetInstances o &temp
			
			for x in temp do
				handles[x.inode.handle] = true
		)
		
		objs
	)
)

EDIT: after testing, I found your method is much faster

3 Replies
(@denist)
Joined: 11 months ago

Posts: 0

getting <node>.inode.handle is bottleneck of your method. it’s very slow. much faster is to use AnimHandle System (GetHandleByAnim and GetAnimByHandle) . it’s more than 10 times faster.

 lo1
(@lo1)
Joined: 11 months ago

Posts: 0

Wow, that is fast. It makes Matan’s method 10 times faster than the other 2 methods (though twice as memory intensive).

(@matanh)
Joined: 11 months ago

Posts: 0

I assumed the inode.handle is what slowed me down but I didn’t know about the GetHandleByAnim, so I gave up. Thanks! This is very useful.
Cheers.

P.S. here is a version that is a bit faster:

fn collectUniqueObjs2 = 
(
	local nodes = objects as array
	local handles = #{}
	local inst = #()
	
	for node in nodes where not handles[GetHandleByAnim node] collect (
		InstanceMgr.GetInstances node &inst
		if inst.count > 1 do
			for o in inst do
				handles[GetHandleByAnim o] = on
		node
	)
)

post deleted

 lo1

I don’t know about efficiency, but your method is simply wrong. you can’t delete elements in a collection while you’re iterating over it by index. You will miss a lot of objects.

this works, and seems fast enough (I clocked 4000 objects at 40 ms):

fn getUniqueObs obs = -- finds all unique objects in 'obs'
(
	local uniques = #()
	
	while obs.count > 0 do
	(
		InstanceMgr.GetInstances obs[1] &insts
		append uniques insts[1]
		for ins in insts do
		(
			deleteItem obs (findItem obs ins)
		)
	)
	uniques
)

Wow thanks for your quick responses.

@TzMtN
I like the idea of bitarray with handles as the index but you’re as you found going through every node is slowing it down.

@lo
Thanks, your way is slightly faster than mine and looks neater.

I don’t know about efficiency, but your method is simply wrong. you can’t delete elements in a collection while you’re iterating over it by index. You will miss a lot of objects.
I’m sorry I think you’re mistaken. I’m deleting elements I know are instances of the current element so I’m shortening the number of object I need to check. It’s not that different to your way, mine’s not as neat so it’s harder to read

 lo1

Except that InstanceMgr.GetInstances doesn’t necessarily return the nodes in the same order as they appear in the obs array, therefore you are sometimes deleting objects behind your iterator. You could write your code like this instead, to work around this problem:

for i in insts where i != obs[oI] do
(
	deleteItem obs (findItem obs i)
)
1 Reply
(@raytracer05)
Joined: 11 months ago

Posts: 0

Sorry you’re right, I see what you mean. I could delete the current element, shortening the array and moving to the next element in one step and therefore skipping an element.

Thanks, that works.

here is my version that i have used for many years:


fn uniqueNodes nodes: = 
(
	if nodes == unsupplied do nodes = objects as array
	local inst, instances = #()
	for n in nodes where findItem instances n == 0 collect
	(
		instanceMgr.getInstances n &inst
		join instances inst
		n
	)
)

my version loses to lo’s version…
playing with different versions i have a leader (Matan’s method with optimization):


fn uniqueNodes nodes: = 
(
	if nodes == unsupplied do nodes = objects as array

	local handles = #{}
	for node in nodes where not handles[GetHandleByAnim node] collect
	(
		InstanceMgr.GetInstances node &inst
		for i in inst do handles[GetHandleByAnim i] = on
		node
	)
)

here is my test scene:


(
	delete objects
	count = 5000
	b = box name:"copy"
	seed 0
	for k=1 to count-1 do case (random 1 3) of 
	(
		1: copy b name:"copy"
		2: instance b name:"instance"
		3: reference b name:"reference"
	)
	
	gc()
)

Thanks for your input Denis. Good job you stopped by and saw the potential in Matan’s code.
And thanks again lo and Matan, I have to go and pick up a sick child from nursery now but I’ll do some more tests tomorrow.