Align UV script optimizations


#16

Exactly.
The purpose is to be able to quickly texture thousands of low poly buildings (Blender-OSM plugin), which are at varying heights, so that the bottom of each building is at the bottom of the UV space. This gives a little more control and prevents any building from starting with a row of cut off windows at the base.
The left align prevents one side from having cut off windows, so better than nothing. The ability to select which islands to align is just nice to have, but not strictly necessary.


#17
fn getMapElements node channel:1 = if iskindof node Editable_Poly or iskindof node.baseobject Editable_Poly do
(
	if polyop.getmapsupport node channel do
	(
		mesh = snapshotasmesh node
		if not iskindof node Editable_Poly do node = node.baseobject
		elements = #()
		
		done = #{}
		num = mesh.numfaces
		for f=1 to num while not keyboard.escpressed where not done[f] do 
		(
			last = -1
			faces = #{f}
			verts = #{}
			while faces.numberset != last and not keyboard.escpressed do
			(
				last = faces.numberset
				verts = meshop.getmapvertsusingmapface mesh channel faces
				faces = meshop.getmapfacesusingmapvert mesh channel verts
			)
			append elements verts
			join done faces
		)
		free mesh
		elements
	)
)

fn getMapElementBBox3 node tverts channel:1 = if iskindof node Editable_Poly or iskindof node.baseobject Editable_Poly do
(
	if polyop.getmapsupport node channel do
	(
		mesh = snapshotasmesh node

		bmin = [1e9,1e9,1e9]
		bmax = -[1e9,1e9,1e9]
		
		pp = for tv in tverts collect 
		(
			p = meshop.getMapVert mesh channel tv

			if p.x < bmin.x do bmin.x = p.x
			if p.y < bmin.y do bmin.y = p.y
			if p.z < bmin.z do bmin.z = p.z
				
			if p.x > bmax.x do bmax.x = p.x
			if p.y > bmax.y do bmax.y = p.y
			if p.z > bmax.z do bmax.z = p.z
				
			p
		)
		free mesh
		#(box3 bmin bmax, tverts, pp)
	)
)


fn alignMapElement node data channel:1 = if iskindof node Editable_Poly or iskindof node.baseobject Editable_Poly do
(
	if polyop.getmapsupport node channel do
	(
		-- data == #(box3 bmin bmax, tverts, pp)
		shift = data[1].min 
		k = 1
		for tv in data[2] do
		(
			p = data[3][k] - shift
			polyop.setmapvert node channel tv p
			k += 1
		)
	)
)


/****************************************************************************************/

sp = 
(
	max create mode
	delete objects
	sp = teapot name:"test_mesh" mapcoords:on
	addmodifier sp (Uvwmap maptype:0)
	sp = converttopoly sp
	for k=1 to 3 do polyop.attach sp (copy sp)
	update sp
	
	sp
)


clusters = getMapElements sp
data = for cluster in clusters collect (getMapElementBBox3 sp cluster)
for d in data do alignMapElement sp d 

(
	modi = Unwrap_UVW()
	addmodifier sp modi
	max modify mode
	modpanel.setcurrentobject modi
)

I hope I didn’t forget anything… of course, this can be algorithmically optimized, but already much faster than the built-in unwrap…


#18

Very cool, I did a few tests and, according to those tests, this is about twice as fast as my previous script. The blender python script (uv align distribute) is still about 8x faster than this, which is pretty crazy. Maybe Blender has less overhead or something.

I’m not exactly sure what happens in this script, so I don’t know how to optimize it further, but I’ll take some time to go through it and learn.

It shouldn’t matter whether you’re using ms or python, right? They should be equally fast/slow within the Max environment?


#19

maxscript has not very good loop iteration performance and you’re bound to use high level abstractions, so on large polycounts mxs will lose to python for sure. For python you can try MaxPlus or whatever came as a replacement in newer max versions to improve timings. But you’ll have to use c++ sdk sources to learn how it all works under the hood. Which can be overwhelming
In general x10 speedup can be achieved depending on the task if you switch to low level implementation with c++ or c#.


#20

you can rewrite it so that you don’t have to make a separate mesh snapshot for each uv-element. If you have lots of elements it should definitely improve timings

...
fn getMapElementBBox3 node tverts channel:1 = if iskindof node Editable_Poly or iskindof node.baseobject Editable_Poly do
(
	if polyop.getmapsupport node channel do
	(
		mesh = snapshotasmesh node

#21

here’s what I suggested doing with uv-mesh representation
don’t know why but subsequent execution of this code doing twice as fast than the first run after max start


t1=timestamp();hf = heapfree
max create mode
with redraw off
(
	
-- example scene
sp = 
(
	max create mode
	delete objects
	sp = teapot name:"test_mesh" mapcoords:on isSelected:true
	addmodifier sp (Uvwmap maptype:0)
	sp = converttopoly sp
	for k=1 to 3 do polyop.attach sp (copy sp)
	update sp
	
	sp
)




fn GetAllMeshElements node =
(
	local tmesh = snapshotasmesh node
	local faces = #{1..tmesh.numfaces}
	local verts = for v = 1 to tmesh.numverts collect #()
	local undone = #{1..tmesh.numverts}
	local elements = #()
	
	for j in faces do
	(
		f = getface tmesh j

		append verts[f[1]] j
		append verts[f[2]] j
		append verts[f[3]] j		
		
	)

	for i in faces do
	(		
		element = #(i)
		for j in element where faces[j] do
		(
			faces[j] = false
			f = getface tmesh j
			
			if undone[f[1]] do (join element verts[f[1]]; undone[f[1]] = false )
			if undone[f[2]] do (join element verts[f[2]]; undone[f[2]] = false )
			if undone[f[3]] do (join element verts[f[3]]; undone[f[3]] = false )
			
		)		
		append elements (element as bitarray)
	)
	
	elements
)

sel = selection[1]
convertToMesh sel

tri = snapshotasmesh sel
meshUV = mesh mesh:tri
channelInfo.CopyChannel meshUV 3 1
channelInfo.PasteChannel meshUV 1 0
collapseStack meshUV

elems = GetAllMeshElements meshUV

meshop_getVertsUsingFace = meshop.getVertsUsingFace

vts = #()
vts.count = tri.numverts
for el in elems do
(
	fv = meshop_getVertsUsingFace meshUV el
	
	shift = [ 1e9, 1e9, 0 ]
	
	for v in fv do
	(
		vts[v] = vv = getVert meshUV v
				
		if vv.x < shift.x do shift.x = vv.x
		if vv.y < shift.y do shift.y = vv.y
	)
	
	for v in fv do setvert meshUV v (vts[v] - shift)
		
)

update meshUV

channelInfo.CopyChannel  meshUV 1 0
channelInfo.PasteChannel sel 3 1
delete meshUV
free tri
)
	

format "Time: %sec. Mem: %\n" ((timestamp()-t1)/1000 as float) (hf-heapfree)

#22

Could you post an example of the scene you usually work with? (preferably MAX 2020 version). I need to see the type of data you have. How best to optimize depends on it.


#23

It’s easy not to use a snapshot for a mesh, as well as any conversion to mesh geometry at all. I do this in order to use the methods of meshop mapfaces to elements that are not part of polyop.
I assume that after improving the code and algorithms, we can increase the performance up to 4 times continuing to use only pure maxscript.


#24

Here is an example file.
Katwijk.max (25.9 MB)

You can ignore the roofs, only the walls are relevant.

@Serejah I did a quick test and it seems fast, but I’m not sure how fast. I only tried a small portion of the example file. It was faster than my original script anyway.

I did a test in Blender, which is the source app for the buildings, and it did it in about 4 min 45 sec. I first clicked left, and then bottom. Maybe it could do it twice as fast if there was a button for bottom left align, but there’s not.


#25

I’ve made the test - do all using c++ :
roofs :
CPP SDK elements:22610 time:205

walls:
CPP SDK elements:137464 time:4315

the time in msec… :wink: it definitely beats the Blender


#26

not bad for 131391 uv elements :slight_smile:


#27

walls are 137464 elements… what I found


#28

Very nice :slight_smile:
I guess I will have to learn a bit more to progress further :wink:
@denisT What’s your opinion on python in Max?


#29

Most likely your number is more correct. I just copy pasted uvs to mesh and used MNMeshElements to check the count. And maybe it isn’t a valid method at all.


#30

I think that is possible to make pure MXS version which can do the job for about 40-60 sec


#31

Oh wow, that would still be fantastic! So there is still reason for me to dig deeper :+1:


#32

Serejah,
do you remember that MXS algorithm to find all mesh geom elements? You already found it once on this forum…


#33

this was a greatest thread of all times


#34

I posted above my updated version of Jorge’s code. If I remember correctly it was working bit faster, but I didn’t do muchs tests to see if it doesn;t break on some corner case scenarios


#35

Thanks! :+1:
I will try to make this UV shift thing based on fast element search using only MXS… the same I do in my cpp code