Notifications
Clear all

[Closed] Adjust vert normals between two meshes

another technique is to store the current normals in a map channel before breaking the mesh, then restoring the previous normals from the map channel. We use it this way on terrain export which gets broken up automatically prior to export to hide any seams.

fn addNormalModifier obj displaylen:10.0 = 
   (	
   	if obj.modifiers[#Edit_Normals] != undefined then
   		editnorm = obj.modifiers[#Edit_Normals];
   	else
   	(
   		editnorm = Edit_Normals();
   		editnorm.displayLength = displaylen;
   		if validModifier obj editnorm then			
   			addmodifier obj editnorm;
   		else
   			editnorm = undefined;
   	)	
   	editnorm;
   )
   
   fn ExplodeMapChannel obj mapchannel = 
   (
   	polyop.setMapSupport obj mapchannel on
   	polyOp.applyUVWMap obj #face channel:mapchannel
   )
   
   fn CopyNormalsToMapChannel obj mapchannel inworldsys =
   (
   	editnorm = addNormalModifier obj;
   	ExplodeMapChannel obj mapchannel;	
   
   	for i = 1 to editnorm.GetNumFaces() do
   	(
   		mface = polyOp.getMapFace obj  mapchannel  i;
   		for j = 1 to mface.count do
   		(
   			if inworldsys then in coordsys world
   			(
   				n = editnorm.GetNormal (editnorm.GetNormalID i j);
   				polyOp.setmapvert obj mapchannel mface[j] n;
   			)
   			else in coordsys local
   			(
   				n = editnorm.GetNormal (editnorm.GetNormalID i j);
   				polyOp.setmapvert obj mapchannel mface[j] n;
   			)
   		)	
   	)
   	update obj
   )
   
   fn CopyMapChannelToNormals obj mapchannel inworldsys =
   (
   	editnorm = addNormalModifier obj;
   	editnorm.reset selection:#{1..editnorm.GetNumNormals()}
   
   	--polyop.setFaceSmoothGroup $ $.faces 0;
   	disableRefMsgs();
   	for i = 1 to obj.numFaces  do
   	(
   		mface = polyOp.getMapFace obj  mapchannel  i;
   		for j = 1 to mface.count do
   		(		
   			mapnormal = (polyOp.getmapvert obj mapchannel mface[j]);
   			if inworldsys then in coordsys world
   				editnorm.SetNormal (editnorm.GetNormalID i j) mapnormal;	
   			else in coordsys local
   				editnorm.SetNormal (editnorm.GetNormalID i j) mapnormal;
   		)	
   	)
   	editnorm.MakeExplicit selection:#{1..editnorm.GetNumNormals()}
   	enableRefMsgs();
   )	
   
   num_segs = 32;
   MAPCH = 5;
   delete objects;
   sph1 = sphere segs:num_segs;
   converttoPoly sph1;
   polyop.setFaceSmoothGroup sph1 #{1..96} 2 -- some added normal quirkyness

   select  sph1
   CopyNormalsToMapChannel sph1 MAPCH false;
   
   polyop.detachFaces sph1 #{1..sph1.numfaces/2}  asNode:true name:"Sphere02";
   sph2= $Sphere02;
   
   
   select  sph1
   CopyMapChannelToNormals sph1 MAPCH false;
   select  sph2
   CopyMapChannelToNormals sph2 MAPCH false;
   
   converttoPoly sph1;
   converttoPoly sph2;
   

Klunk are you saw this small problem

did you see this line ?

   polyop.setFaceSmoothGroup sph1 #{1..96} 2 -- some added normal quirkyness

Sorry I miss that
We use sphere here where we detache some faces to get 2 separate objects.
You are right. It’s better copy all normals before we use detach operation.
But in real I need to assigne this method to already detached objects (two or more)
edit: Can we consider also only faces and verts of open Edges to speedup the process?

… deleted … never mind

2 Replies
(@gazybara)
Joined: 11 months ago

Posts: 0

Denis? Are you tried to add something?

(@denist)
Joined: 11 months ago

Posts: 0

no. i’ve deleted :).

I’ve managed to correct first function

(
	fn setCommandPanelRedraw act =
	(
		WM_SETREDRAW = 0x000B
		windows.sendmessage (windows.getchildhwnd #max "Command Panel")[1] WM_SETREDRAW act 0
	)
	fn fixNormals objA objB = with redraw off
	(
		local getOpenEdge = meshop.getOpenEdges
		local getVertByEdge = meshop.getVertsUsingEdge
		local objA_borderE = getOpenEdge objA
		local objA_borderV = getVertByEdge objA objA_borderE
		local objB_borderE = getOpenEdge objB
		local objB_borderV = getVertByEdge objB objB_borderE
		local norm = Edit_Normals()
		local vertToNorm = norm.ConvertVertexSelection
		local avgTwo = norm.AverageTwo
		local vData = #()
		
		for i in objA_borderV do (for j in objB_borderV where (distance (getVert objA i) (getVert objB j)) < 0.1 do join vData #(#{i},#{j}))
		select #(objA,objB) ; max modify mode
		modPanel.addModToSelection norm
		setCommandPanelRedraw 0
		disableRefMsgs()
		for i = 1 to vData.count by 2 do
		(
			n = #{}
			nData = join (vertToNorm vData[i] &n node:objA ; n as array) (vertToNorm vData[i+1] &n node:objB ; n as array)
			avgTwo objA nData[1] objB nData[2]
		)
		enableRefMsgs() ; clearSelection()
		setCommandPanelRedraw 1
		converttomesh #(objA,objB)
	)
	-- example
	delete objects
	sp = converttopoly (sphere name:"A" radius:50 segs:24 wirecolor:[30,110,0])
	polyop.detachFaces sp #{145..288} delete:on asNode:on name:"B"
	objA = $A ; objB = $B
	converttomesh #($A,$B)
	with redraw off fixNormals objA objB
	CompleteRedraw()
)

edit: Optimized code : edit_Normal methods are stored in variable

Now is the time for the next challenge
Example scene

delete objects
cube = (Box name:"part6" lengthsegs:10 widthsegs:10 heightsegs:10 length:100 width:100 height:100 mapcoords:on)
addmodifier cube (spherify())
parts = #(convertTomesh cube)
for id = 1 to 5 do
(
	faces = for f = 1 to cube.numfaces where (getFaceMatID cube f) == id collect f
	part = mesh mesh:(meshop.detachFaces cube faces delete:on asMesh:on) name:("part"+id as string) wirecolor:(random black white)
	append parts part
)
parts.mat = standard()

For above example the most difficult task is to collect open edges verts of six object parts which
share same position and after that find normals of these verst.

1 Reply
(@gazybara)
Joined: 11 months ago

Posts: 0

This will be pseudo code.
Let’s imagine that vars p1…p6 represent objects parts and array values are verts pos (not vert index). We need to collect some data with parts which verts share same position in space.

p1 = #(1,2,3,4,9,10,11,12)
 p2 = #(1,2,7,8,9,16,18,20)
 p3 = #(2,3,6,7,11,14,19,20)
 p4 = #(3,4,5,6,12,13,17,19)
 p5 = #(1,4,5,8,10,15,17,18)
 p6 = #(5,6,7,8,13,14,15,16)
 arr = #(p1,p2,p3,p4,p5,p6)
 data = #()
 cnt = 0
 
 while arr.count != 1 do
 (
 	for i in arr[arr.count] do
 	(
 		cnt += 1
 		for j = 1 to arr.count-1 do
 		(
 			for n = arr[j].count to 1 by -1 where arr[j][n]-i == 0 do 
 			(
 				if data[cnt] == null then
 				(
 					append data #(#(("p"+ arr.count as string),("p"+j as string)),#(i,arr[j][n]))
 				)
 				else
 				(
 					append data[cnt][1] ("p"+j as string)
 					append data[cnt][2] arr[j][n]
 				)
 				deleteItem arr[j] n
 			)
 		)
 	)
 	deleteItem arr arr.count
 )
 print data
 
 /*
 result >>>
 #(#("p6", "p4", "p5"), #(5, 5, 5))
 #(#("p6", "p3", "p4"), #(6, 6, 6))
 #(#("p6", "p2", "p3"), #(7, 7, 7))
 #(#("p6", "p2", "p5"), #(8, 8, 8))
 #(#("p6", "p4"), #(13, 13))
 #(#("p6", "p3"), #(14, 14))
 #(#("p6", "p5"), #(15, 15))
 #(#("p6", "p2"), #(16, 16))
 #(#("p5", "p1", "p2"), #(1, 1, 1))
 #(#("p5", "p1", "p4"), #(4, 4, 4))
 #(#("p5", "p1"), #(10, 10))
 #(#("p5", "p4"), #(17, 17))
 #(#("p5", "p2"), #(18, 18))
 #(#("p4", "p1", "p3"), #(3, 3, 3))
 #(#("p4", "p1"), #(12, 12))
 #(#("p4", "p3"), #(19, 19))
 #(#("p3", "p1", "p2"), #(2, 2, 2))
 #(#("p3", "p1"), #(11, 11))
 #(#("p3", "p2"), #(20, 20))
 #(#("p2", "p1"), #(9, 9))
 */

Is there faster and better way for this method?

Solved pseudo code (collect array groups which share same index). Not perfect but works.
This concept can be also used for collecting verst which have same position on multiple objects.

(
	p1 = #(1,2,3,4,9,10,11,12,25,30)
	p2 = #(1,2,7,8,9,16,18,20,81,999,214)
	p3 = #(2,3,6,7,11,14,19,20,458,111,5468)
	p4 = #(3,4,5,6,12,13,17,19,454,5789)
	p5 = #(1,4,5,8,10,15,17,18,111223,12456,547,886)
	p6 = #(5,6,7,8,13,14,15,16,989,777,48795,221)
	arr = #(p1,p2,p3,p4,p5,p6)
	an = #("p1","p2","p3","p4","p5","p6")
	data = #()
	for a = 1 to arr.count-1 while a < arr.count where arr[a].count > 0 do
	(
		for c = 1 to arr[a].count do
		(
			ar = #(#(),#())
			for b = (limit=a+1) to arr.count where arr[b].count > 0 do
			(
				for d = arr[b].count to 1 by -1 where arr[a][c]-arr[b][d] == 0 do
				(	
					append ar[1] an[b]
					append ar[2] arr[b][d]
					deleteItem arr[b] d
				)
			)
			if ar[1].count > 0 do (append data #((join #(an[a]) ar[1]), join #(arr[a][c]) ar[2]))
		)
	)
	print data
)

This is my last try. Result is not correct, unfortunately 🙁
I’m trying all day to solve the puzzle:banghead:

(
  	fn setCommandPanelRedraw act =
  	(
  		WM_SETREDRAW = 0x000B
  		windows.sendmessage (windows.getchildhwnd #max "Command Panel")[1] WM_SETREDRAW act 0
  	)
  	fn sortByCount arr1 arr2 =
  	(
  		case of (
  			(arr1[1].count < arr2[1].count): 1
  			(arr1[1].count > arr2[1].count): -1
  			default:0
  		)
  	)	
  	fn arrangeData vData minDist: =
  	(
  		local data = #()
  		local arr = for d in vData collect d[3]
  		for a = 1 to arr.count-1 while a < arr.count where arr[a].count > 0 do
  		(
  			for c = 1 to arr[a].count do
  			(
  				ar = #(#(),#())
  				for b = (a+1) to arr.count where arr[b].count > 0 do
  				(
  					for d = arr[b].count to 1 by -1 where (distance arr[a][c] arr[b][d] < minDist) do
  					(	
  						append ar[1] vData[b][1]
  						append ar[2] vData[b][2][d]
  						deleteItem arr[b] d
  					)
  				)
  				if ar[1].count > 0 do (append data #((join #(vData[a][1]) ar[1]), join #(vData[a][2][c]) ar[2]))
  			)
  		) ; data
  	)	
  	fn fixOpenEdgesNormals nodes = with redraw off
  	(
  		if getCommandPanelTaskMode() != #create do setCommandPanelTaskMode mode:#create
  		clearSelection()
  		local getOpenEdges = meshop.getOpenEdges
  		local getVertsByEdge = meshop.getVertsUsingEdge
  		local eNorm = Edit_Normals(), n = #{}
  		local vertToNorm = eNorm.ConvertVertexSelection
  		local avgTwo = eNorm.AverageTwo
  		local pickNormal = eNorm.Select
  		local clearPicked = eNorm.SetSelection
  		local avgPicked = eNorm.AverageGlobal
  
  		local vData = with undo off for node in nodes collect
  		(
  			nodeVData = #(node,#(),#())
  			borderVerts = getVertsByEdge node (getOpenEdges node)
  			for v in borderVerts do (append nodeVData[2] #{v} ; append nodeVData[3] (getVert node v))
  			nodeVData
  		)
  		vData = arrangeData vData minDist:0.01
  		qsort vData sortByCount
  		--print vData
  		with redraw off
  		(
  			select nodes
  			setCommandPanelTaskMode mode:#modify
  			modPanel.addModToSelection eNorm
  			--setCommandPanelRedraw 0
  			--disableRefMsgs()
  			for i = 1 to vData.count do
  			(
  				if vData[i][1].count == 2 then
  				(
  					nData = join (vertToNorm vData[i][2][1] &n node:vData[i][1][1] ; n as array) (vertToNorm vData[i][2][2] &n node:vData[i][1][2] ; n as array)
  					avgTwo vData[i][1][1] nData[1] vData[i][1][2] nData[2]
  				)
  				else
  				(
  					for v = 1 to vData[i][2].count do pickNormal (vertToNorm vData[i][2][v] &n node:vData[i][1][v] ; n) node:vData[i][1][v]
  					avgPicked() ; clearPicked #{}
  				)
  			)
  			--enableRefMsgs() ; clearSelection()
  			--setCommandPanelRedraw 1
  			--converttomesh nodes
  		)
  	)
  	-- example
  	delete objects
  	cube = (Box name:"part6" lengthsegs:10 widthsegs:10 heightsegs:10 length:100 width:100 height:100 mapcoords:on)
  	addmodifier cube (spherify())
  	parts = #(convertTomesh cube)
  	for id = 1 to 5 do
  	(
  		faces = for f = 1 to cube.numfaces where (getFaceMatID cube f) == id collect f
  		part = mesh mesh:(meshop.detachFaces cube faces delete:on asMesh:on) name:("part"+id as string) wirecolor:(random black white)
  		append parts part
  	)
  	parts.mat = standard()
  	fixOpenEdgesNormals parts
  	CompleteRedraw()
  )
Page 2 / 2