Align and Size a Plane() to EPoly Quads...


#1

I have a MXS function that is supposed to create Plane() primitives that align with rectangular quads on EPoly objects and make the Plane match the size of the quad. My problem is determining the the Length/Width of the Plane–I can get the correct values for length and width I cannot figure out how to determine which is actually the length and the width (for example, the value I get for width sometimes belongs to length and vice versa).

Here is the function:


function wallworm_create_plane_from_polygon polynode faceId forceQuad:true power:2 = (
 if ww_wwdt_control_mesh == undefined then (
  in coordsys world (
   if classOf polynode == editable_poly then (
 
	--verts = polyop.getVertsUsingFace polynode #{faceId}
	verts = polyop.getFaceVerts polynode faceId
 
	if verts.count == 4 OR forceQuad==false then (
 
 
	 v1 = polyop.getVert polynode verts[1] node:polynode
	 v2 = polyop.getVert polynode verts[2] node:polynode
	 v3 = polyop.getVert polynode verts[3] node:polynode
 
	 v4 = polyop.getVert polynode verts[4] node:polynode
 
	 d1 = distance (v1) (v2) 
	 d2 = distance (v2) (v3) 
 
	 --segs = ww_wwdt_vmfpowertosegs power
	 --func not present in sample... assign manually
	 segs = 4
 
	 l = d2
	 w=d1
 
	 faceNormal = in coordsys polynode (polyop.getFaceNormal polynode faceId node:polynode)
	 local brsh = Plane length:l width:w isSelected:off lengthsegs:1 widthsegs:1 pos:(polyop.getSafeFaceCenter polynode faceId node:polynode)
 
	 /*
	 worldUpVector = [0,0,1] 
	 rightVector = normalize (cross worldUpVector faceNormal) 
	 upVector = normalize ( cross rightVector faceNormal )
	 theMatrix = matrix3 rightVector upVector faceNormal [0,0,0] 
	 brsh.transform =  theMatrix
	 */
 
 
	 --Move the new mesh to the center of the face.
	 brsh.pos = (polyop.getSafeFaceCenter polynode faceId node:polynode) 
 
	 --Make the new mesh face in the direction of the face
	 brsh.dir = faceNormal
 
	 --make a copy of the new mesh to determine if the verts of the mesh are aligned to verts of the original face.
	 --This does assume rectangular faces.
	 tmp = copy brsh
	 convertToPoly tmp
 
	 tmpverts = polyop.getFaceVerts tmp 1
 
	 tmpv1 = polyop.getVert tmp tmpverts[1] node:tmp
	 nverts = #(v1,v2,v3,v4)
 
	 format "
###############
Face Verts: % % % %
" v1 v2 v3 v4
	 format "New Verts: %
" tmpv1 
 
	 /*
	 See if the first vert in the tmp mesh aligns with a vert in the original face.
	 If none, swap the length and width.
	 */	 
	 if  findItem nverts tmpv1 == 0  then (
	  print "changing l/w"
	  brsh.length = w
	  brsh.width = l
	 )
	 delete tmp
	 brsh.lengthsegs = segs
	 brsh.widthsegs = segs
 
	 if polynode.mat != undefined then brsh.mat = polynode.mat
	 return brsh
 
	) else (
	 format "The selected face % has % verts!
" faceId verts.count
 
	)  
 
 
   ) else (
 
 
 
   )
  )
 )
 undefined
)
 
 

I have assumed that the following code would determine if the plane dimensions match the face… but it simply fails sometimes:


 
 if  findItem nverts tmpv1 == 0  then (
      print "changing l/w"
      brsh.length = w
      brsh.width = l
     )
 

What is odd is that the findItem sometimes (but not always) fails when the values look identical to me… for example, here is a line in the listener from a failure:


###############
Face Verts: [-70,146,128] [-70,146,256] [-70,274,256] [-70,274,128]
New Verts: [-70,274,256]
"changing l/w"
 

But as you can see, the Point in New Verts [-70,274,256] does exist in the Face Verts… but the findItem function returns 0 (and the script fails).

Maybe I should try another approach altogehter… but I still want to know how that fails.

Question 2:

Also, while discussing this, which of the following two methods is best for the orientation? I always see docs mentioning the first but the second seems to produce the same results:

Method 1:


worldUpVector = [0,0,1] 
     rightVector = normalize (cross worldUpVector faceNormal) 
     upVector = normalize ( cross rightVector faceNormal )
     theMatrix = matrix3 rightVector upVector faceNormal [0,0,0] 
     brsh.transform =  theMatrix
 
 --Move the new mesh to the center of the face.
     brsh.pos = (polyop.getSafeFaceCenter polynode faceId node:polynode)  

Method 2:


--Move the new mesh to the center of the face.
     brsh.pos = (polyop.getSafeFaceCenter polynode faceId node:polynode) 
 
     --Make the new mesh face in the direction of the face
     brsh.dir = faceNormal


#2

Hi, Shawn!
This

findItem sometimes (but not always) fails

happens to me sometimes. I think(but I am not sure) that max prints
[-70,274,256], but the real values is
[-70.0001,274,256] or something like this. And when two point3 values are compared, the findItem fails.
The maxscript Pro guys will give you the best advice, but you can try to compare values, using
close_enough


#3

Another stupid idea - convert the positions of the verts to strings and then use findItem.


#4

what if the quad is not a perfect rectangle (not-flat or oblique-angled)? how should the plane be aligned in this case? what priorities are?


#5

@Kostadin … thanks for those ideas. I started to use the close_enough() approach but while doing it I thought of just checking to see if the distance is closer than 0.5 … so in the example above, I used this:


if distance v1 tmpv1 < 0.1 OR distance v2 tmpv1 < 0.1 OR distance v3 tmpv1  < 0.1   OR distance v4 tmpv1  < 0.1  then (
  --the length and width are correct
) else (
  --the length and width need to be swapped
)

Of course, it makes assumptions. For example… if the length and width are within 0.1 units it will probably fail. But for the moment it meets my needs.

@Denis … I’m not really certain about what to do in the case of a non-flat surface. But I do want to find a way to get the Plane object to match the shape of the underlying face (but always remain planar). Off the top of my head, I might apply a FFD2x2x2 and make the corners align.

The reason I am starting with a Plane primitive is that the target geometry needs to always be a plane with 4, 8 or 16 widthsegs and lengthsegs … and I think it will be easier to make a plane and deform the outline to a quad face with FFD than to try and calculate where the positions of the verts would be for an object created from scratch; plus… all of the functions used for this geometry all assume a certain ordering of the verts–which matches that of a Plane converted to EPoly.


#6

Yeah, FFD sounds like a good idea/approach.

If u did it that way, Length and Width might not matter as much, but then its more of just putting the FFD points in the right spots/getting the plane facing the right direction.


#7

Yeah… that’s what I’m thinking. I played with it a little earlier today and think it should be straight forward.


#8

The hard part, maybe, might be getting the points to form a square/rectangle and not criss-cross at the end and form an X.

I had this issue before when trying to create new polys from verts. I’m not sure how u can easily fix that, but maybe you know.


#9

Using the FFD concept has become a little trickier than I thought. I found this nifty set of functions ( http://forums.cgsociety.org/archive/index.php/t-704392.html ) to help along the project, and I see that there is already a script on SP called FFD2Quadthat has the functionality that I want generally. After looking through the code for FFD2Quad I see he used the functions linked above also.

FFD2Quad has a lot more going on that what I need… so I’m trying to filter it down to the simplest form. But I cannot seem to figure it out…

Here is the sample code I have so far (warning … script deletes all object in current scene):


 

-- these functions from [http://forums.cgsociety.org/archive/index.php/t-704392.html](http://forums.cgsociety.org/archive/index.php/t-704392.html)
-- convert world coordinates to FFD local (normalized) coordinates
-- obj = object containing the modifier
-- ffdmod = the actual modifier
-- pos = the world coordinate
fn wallworm_ffdWorldToLocalPos obj ffdmod pos = (
 objTM = obj.objecttransform
 modTM =  (getModContextTM obj ffdmod) * ffdmod.lattice_transform.value
 modBBMin = getModContextBBoxMin obj ffdmod
 modBBMax = getModContextBBoxMax obj ffdmod
 (pos - modBBMin) * (inverse objTM) * inverse modTM / (modBBMax-modBBMin)
)
-- set an FFD mod's control point's world coordinate
-- obj = object containing the modifier
-- ffdmod = the actual modifier
-- i = control point index
-- pos = new world position
fn wallworm_ffdSetWorldPos obj ffdmod i pos = (
 cp = ffdmod["Master"][i]
 ps = wallworm_ffdWorldToLocalPos obj ffdmod pos
 cp.value = ps
)
/*End  funcs from ZeBoxx2*/
 
actionMan.executeAction 0 "40021"  -- Selection: Select All
delete $

clearlistener()
a = Box length: 64 width:64 height:64 
tp = Taper amount:-0.7
addmodifier a tp
convertToPoly a
 
for f = 1 to a.numfaces do (
pos = polyop.getFaceCenter a f node:a
 
b=Plane pos:pos mapCoords:true lengthsegs:4 widthsegs:4
b.dir = polyop.getFaceNormal a f node:a
 
ffd=FFD2x2x2 ()
 
addModifier b ffd
 
--conformToShape ffd
--ffd.deformType  = 1
 
animateVertex ffd #all 
face = polyop.getFaceVerts a f
 
in coordsys world ( 
v1 = polyop.getVert a face[1] node:a
v2 = polyop.getVert a face[2] node:a
v3 = polyop.getVert a face[3] node:a
v4 = polyop.getVert a face[4] node:a
)
format "############
Face %	%
v1	%
v2	%
v3	%
v4	%

" f face v1 v2 v3 v4
wallworm_ffdSetWorldPos b ffd 1 v1
/*
wallworm_ffdSetWorldPos b ffd 1 v2
wallworm_ffdSetWorldPos b ffd 1 v3
wallworm_ffdSetWorldPos b ffd 1 v4
*/
format "Control Points
1	%
2	%
3	%
4	%
5	%
6	%
7	%
8	%
"  ffd.control_point_1  ffd.control_point_2  ffd.control_point_3  ffd.control_point_4  ffd.control_point_5  ffd.control_point_6  ffd.control_point_7  ffd.control_point_8
)

 

No matter which FFD control point I choose, it never aligns to the verts (v1, v2, v3 and v4). Any input?


#10

i really don’t understand the idea with FFD. everything seems overcomplicated.
here is how i see it:


 fn alignToQuad node:selection[1] face:undefined = if iskindof node Editable_Poly do 
 (
 	if face == undefined and node.selectedfaces.count > 0 do face = node.selectedfaces[1].index 
 	if face != undefined and (vv = polyop.getfaceverts node face).count == 4 do
 	(
 		v1 = polyop.getvert node vv[1]
 		v2 = polyop.getvert node vv[2]
 		v3 = polyop.getvert node vv[3]
 		
 		front = normalize (v1-v2)
 		side = normalize (v2-v3)
 		
 		up = normalize (cross front side) 
 		side = normalize (cross up front) 
 		tm = matrix3 front side up v1
 		
 		w = (v2*(inverse tm)).x
 		l = (v3*(inverse tm)).y
 		tm = pretranslate tm [w/2, l/2, 0]
 		p = plane width:w length:l widthsegs:1 lengthsegs:1 transform:tm
 	)
 )
 
 /*
 delete objects
 b = box length:64 width:64 height:32
 addmodifier b (taper amount:-0.7)
 convertToPoly b
 for f in b.faces do alignToQuad node:b face:f.index
 */
 

you can add some extra logic… let’s say the width is always shorter than length. in this case you have to check distances between v1,v2,v3.


#11

but the correct way is to find the best BBOX around the quad and use its min/max as values for width/length


#12

Denis,

Thanks for responding. Aligning the planes isn’t the issue now… I am trying to take it a step further and force the plane to match the shape of the quad too. That is where the FFD comes in.


#13

could you post the image that shows how planes should be aligned and deformed in your sample?


#14

Attached is an example … on the left is where the planes are aligned to quads only… whereas on the right is how those planes should be.


#15

is it not easier to equally divide the quad and flatten it?


#16

It’s something I thought about… but the functions I am using already presume Plane() primitives as input objects (later down the road) … so if I can figure out how to properly apply the FFD it will serve to 1) save me recoding other things and 2) teach me a little more about how the FFD works.


#17

does it mean that you want to find a solution yourselves?


#18

Well I would like help… as I’ve already pounded my head on the issue quite a bit. Sometimes seeing a solution is just as educational as figuring it out.

Because I could not figure out how to properly align the FFD control points, I started down another avenue (which is promising but has one major problem). I decided that I could just detach the faces as individual objects and tesselate them to my needs as the tesselation can create the same kind of geometry*.

*The problem is that the ordering of the vertices is very important and must match those of a Plane() that is converted to EPoly (where the bottom row is verts 1…5, row 2 is 6…10, etc). Unfortunately, when I use tessellate, that ordering is entirely wrong. And I know of no way to re-order verts.

So my next step was to do that tesselation and also create a plane (which is where the code below stops). The next step is going to be mapping the verts from tesselated quads to corresponding verts in the Plane… then moving the plane verts to those positions and deleting the tessellated mesh. That seems like a lot of steps (and depends on the tessellation providing consistent vertex ordering… which I have to test and figure out).

In the end, I am leaning toward the FFD being the easiest method (except that I cannot properly place the FFD control points… which is why I have been experimenting with other ideas).


 
 
clearlistener()
a = Box length: 64 width:64 height:64 
a.wirecolor = green
tp = Taper amount:-0.7
addmodifier a tp
convertToPoly a
power = 2
for p = 1 to polyop.getNumFaces a  do (
 newName = UniqueName "CutFace"
 polyop.detachFaces a #{p} asNode:true delete:false name:(newName) 
 thenode = getNodeByName newName
 thenode.wirecolor = red
 thenode.tesselateBy = 0
 thenode.tessTension = 0.0
 
 base = 1
 for its = 1 to power do (
  polyop.tessellateByFace thenode #all
  base *= 2 -- set the lengthsegs of plane below
 )
  vv = polyop.getfaceverts a p
  v1 = polyop.getvert a vv[1]
  v2 = polyop.getvert a vv[2]
  v3 = polyop.getvert a vv[3]
  v4 = polyop.getvert a vv[4]
  
  front = normalize (v1-v2)
  side = normalize (v2-v3)
  
  up = normalize (cross front side) 
  side = normalize (cross up front) 
  tm = matrix3 front side up v1
  
  w = (v2*(inverse tm)).x
  l = (v3*(inverse tm)).y
  tm = pretranslate tm [w/2, l/2, 0]
   
  p = plane width:w length:l widthsegs:base lengthsegs:base transform:tm
  p.name = uniqueName "PlaneAligned"
  p.wirecolor = blue
 
  convertToPoly p
)

 

#19

i did it with FFD. do you want to see?


#20

Yes, please do share!