[Closed] Creating Geometry from Planes
I am trying to write an importer that gets data where all the faces of geometry are defined only by three vertices, regardless of how many sides that face has. While it makes sense that an object can be defined this way as the objects are always presumed to be convex, it is a little baffling for me to actually figure out how to create the geometry.
Below is a simplified set of data that can be used to recreate a cube.
allPlanes = #()
/*Add all the known points where each defines a plane: */
append allPlanes #([-64,64,64],[64,64,64],[64,-64,64])
append allPlanes #([-64,-64,-64],[64,-64,-64],[64,64,-64])
append allPlanes #([-64,64,64],[-64,-64,64],[-64,-64,-64])
append allPlanes #([64,64,-64],[64,-64,-64],[64,-64,64])
append allPlanes #([64,64,64],[-64,64,64],[-64,64,-64])
append allPlanes #([64,-64,-64],[-64,-64,-64],[-64,-64,64])
allPlanes.count /* How many faces in the resulting mesh */
If you were to create a plane for each item in the array, they would all intersect and the innermost section would be a cube. You could then select a face and, in this example, see that each has a fourth vertex. More complex geometry would have more verts added in faces .
Can anyone share some expertise on a way to convert this into a full working piece of geometry? The various methods I’ve come up just don’t work… and the easiest cheat I’ve tried ( nvpx.CreateConvexFromPoints() ) always ends up with verts that are not exactly where I fed in the positions (always moving a tiny fraction off… but unusable because of this).
Yes, that is really the goal. The hull has to use the points provided. Like mentioned above, when I used the nvpx.CreateConvexFromPoints() method, it moves the verts ever so slightly; for example, if a point passed into it as [883,1323,177] will result in [883.004,1323.992,177.004], etc., which is not appropriate for my needs. I need the points to be precisely at the coordinates specified.
that’s true. CreateConvexFromPoints and CreateConvexHull both have some inflation precision and there is no way to set it up for a perfect hull.
the only way that i see is to write your own Convex Hull making tool. Algorithms are know and can be easily found in the internet. MXS version of this tool probably will be slow for big meshes, but will work for sure.
Well, this might be an overkill but did you try slicing the stuff? With a reasonably small number of planes it might be good enough:
(
local allPlanes = #(
#([-64,64,64],[64,64,64],[64,-64,64]),
#([-64,-64,-64],[64,-64,-64],[64,64,-64]),
#([-64,64,64],[-64,-64,64],[-64,-64,-64]),
#([64,64,-64],[64,-64,-64],[64,-64,64]),
#([64,64,64],[-64,64,64],[-64,64,-64]),
#([64,-64,-64],[-64,-64,-64],[-64,-64,64]))
local maxFloat = 3.4028e+038
local minFloat = -3.4028e+038
local totalMin = [maxFloat, maxFloat, maxFloat]
local totalMax = [minFloat, minFloat, minFloat]
local getVertPos = polyOp.getVert
local slice = polyOp.slice
local deleteVerts = polyOp.deleteVerts
local cap = polyOp.capHolesByEdge
fn isPositiveDir pos vector vectorPos =
dot vector (pos - vectorPos) > 0
fn slicePoly obj pos vector =
(
local verts = obj.verts as bitArray
slice obj #all (ray pos vector)
for vert in verts where NOT isPositiveDir (getVertPos obj vert) vector pos do verts[vert] = false
deleteVerts obj verts
cap obj #all
)
for points in allPlanes do
(
for pos in points do
(
if pos.x < totalMin.x then totalMin.x = pos.x else if pos.x > totalMax.x do totalMax.x = pos.x
if pos.y < totalMin.y then totalMin.y = pos.y else if pos.y > totalMax.y do totalMax.y = pos.y
if pos.z < totalMin.z then totalMin.z = pos.z else if pos.z > totalMax.z do totalMax.z = pos.z
)
)
local center = (totalMax + totalMin)/2
local dimensions = (totalMax - totalMin) * 2
local poly = convertToPoly (Box pivot:(center + [0, 0, dimensions.z/2]) width:dimensions.x length:dimensions.y height:dimensions.z pos:center)
for points in allPlanes do
(
local vector = normalize (cross (points[2] - points[1]) (points[3] - points[2]))
if isPositiveDir center vector points[1] do vector *= -1
slicePoly poly points[1] vector
)
)
This is really helpful. The thought of slice was on my radar.
Your example works great for points around the origin… but if they are all offset somewhere then it fails.
For example, use these points:
allPlanes = #(#([185,-1685,0], [21,-1685,0], [21,-1942,0]), #([185,-1685,176], [185,-1942,176], [21,-1942,176]), #([185,-1942,176], [185,-1942,0], [21,-1942,0]), #([185,-1685,176], [185,-1685,0], [185,-1942,0]), #([21,-1685,176], [21,-1685,0], [185,-1685,0]), #([21,-1942,176], [21,-1942,0], [21,-1685,0]))
I thought maybe it was that the coordinate system was wrong at the time slice is called, hoping changing the slice() line to this would fix it:
in coordsys world (
slice obj #all (ray pos vector) node:obj
)
But that does not help.
Any ideas?
Fiddling some more… I wanted to try this:
in coordsys world (
slice obj #all (ray pos vector) node:obj
for vert in verts where NOT isPositiveDir (getVertPos obj vert node:obj) vector pos do verts[vert] = false
)
Whether this might help I don’t know because there is always a MAXScript error:
>> MAXScript Rollout Handler Exception:
– Argument count error: slice wanted 3, got 6 <<
In this instance, slice() is an alias for polyOp.slice . According to the docs, polyOp.slice takes three manadatory parameters and one optional parameter (node:) so that you can designate a coordinate system. Whenever I try setting node: there is an exception.
Is this a bug in MAXScript (currently using 2014) or are the docs incorrect (it lists node: as a parameter in MXS docs for 2011 and 2014… didn’t check others).
I think that Vojtech’s solution works if the Box constructor doesn’t include the pivot parameter! I need to test some more but I changed:
local poly = convertToPoly (Box pivot:(center + [0, 0, dimensions.z/2])
width:dimensions.x length:dimensions.y height:dimensions.z pos:center)
to
local poly = convertToPoly (Box width:dimensions.x length:dimensions.y
height:dimensions.z pos:center)
and it seems to work.
I’ll report back in a bit.
Yeah, sorry about that, the pivot part should have been
pivot:[0, 0, dimensions.z/2]
Or use some other primitive that already has a pivot in the center.
Vojtech … do not apologize. I am very grateful. You saved me several hours of work learning those functions, and taught me how to use them in practice more quickly. I will share some credit in my docs when I’ve got the tool done.
Thank goodness for people like you, Denis and all the other guys in the Max dev community. You’ve all helped me learn or saved me time a million times! I personally think that the Max dev community is one of the coolest crowds in the world!
I need to return to this thread because there is a problem related to this that I just can’t figure out.
When using Vojtech’s code, the functions work as expected 99.9% of the time. However, some of the objects that I’m creating are broken.
I’ve narrowed it down to the function isPositiveDir() from above
fn isPositiveDir pos vector vectorPos =
dot vector (pos - vectorPos) > 0
When I changed it to this:
fn isPositiveDir pos vector vectorPos =
dot vector (pos - vectorPos) >= 0
It now seems to work 99.99% (an extra smidgeon of correctness).
But there are still occasions where it is returning false when it should be true… and I cannot see any kind of differences between the giant majority that work and the very small percentage that just fail.
My guess is some kind of rounding error that makes position values go just slightly off… but if that is that case I don’t know what I can do about that.
Any ideas?
Below is a sample chunk of code edited to display the problem. It includes a set points that cause the functions to fail. I also modified it to add Point() objects at the locations where the object’s vertices should have been had the function worked correctly:
(
local allPlanes = #()
append allPlanes #([5664,-368,328], [5264,-416,376], [5264,-576,376])
append allPlanes #([5664,-496,280], [5264,-576,232], [5264,-416,232])
append allPlanes #([5264,-416,376], [5264,-416,232], [5264,-576,232])
append allPlanes #([5664,-496,328], [5664,-496,280], [5664,-368,280])
append allPlanes #([5664,-368,328], [5664,-368,280], [5264,-416,232])
append allPlanes #([5664,-496,280], [5664,-496,328], [5264,-576,376])
local maxFloat = 3.4028e+038
local minFloat = -3.4028e+038
local totalMin = [maxFloat, maxFloat, maxFloat]
local totalMax = [minFloat, minFloat, minFloat]
local getVertPos = polyOp.getVert
local slice = polyOp.slice
local deleteVerts = polyOp.deleteVerts
local cap = polyOp.capHolesByEdge
fn isPositiveDir pos vector vectorPos =
dot vector (pos - vectorPos) > 0
fn slicePoly obj pos vector =
(
local verts = obj.verts as bitArray
slice obj #all (ray pos vector)
for vert in verts where NOT isPositiveDir (getVertPos obj vert) vector pos do verts[vert] = false
deleteVerts obj verts
cap obj #all
)
for points in allPlanes do
(
for pos in points do
(
if pos.x < totalMin.x then totalMin.x = pos.x else if pos.x > totalMax.x do totalMax.x = pos.x
if pos.y < totalMin.y then totalMin.y = pos.y else if pos.y > totalMax.y do totalMax.y = pos.y
if pos.z < totalMin.z then totalMin.z = pos.z else if pos.z > totalMax.z do totalMax.z = pos.z
Point pos:pos
)
)
local center = (totalMax + totalMin)/2
local dimensions = (totalMax - totalMin) * 2
local poly = convertToPoly (Box pivot:(center + [0, 0, dimensions.z/2]) width:dimensions.x length:dimensions.y height:dimensions.z pos:center)
for points in allPlanes do
(
local vector = normalize (cross (points[2] - points[1]) (points[3] - points[2]))
if isPositiveDir center vector points[1] do vector *= -1
slicePoly poly points[1] vector
)
)
EDIT… oops typo in post earlier.
So I found a fix to this problem… here is modified code for anyone having a similar problem. The problem was related again to where the pivot is in a Box when created.
(
local allPlanes = #()
append allPlanes #([5664,-368,328], [5264,-416,376], [5264,-576,376])
append allPlanes #([5664,-496,280], [5264,-576,232], [5264,-416,232])
append allPlanes #([5264,-416,376], [5264,-416,232], [5264,-576,232])
append allPlanes #([5664,-496,328], [5664,-496,280], [5664,-368,280])
append allPlanes #([5664,-368,328], [5664,-368,280], [5264,-416,232])
append allPlanes #([5664,-496,280], [5664,-496,328], [5264,-576,376])
local maxFloat = 3.4028e+038
local minFloat = -3.4028e+038
local totalMin = [maxFloat, maxFloat, maxFloat]
local totalMax = [minFloat, minFloat, minFloat]
local getVertPos = polyOp.getVert
local slice = polyOp.slice
local deleteVerts = polyOp.deleteVerts
local cap = polyOp.capHolesByEdge
fn isPositiveDir pos vector vectorPos =
dot vector (pos - vectorPos) > 0
fn slicePoly obj pos vector =
(
local verts = obj.verts as bitArray
slice obj #all (ray pos vector)
for vert in verts where NOT isPositiveDir (getVertPos obj vert) vector pos do verts[vert] = false
deleteVerts obj verts
cap obj #all
)
for points in allPlanes do
(
for pos in points do
(
if pos.x < totalMin.x then totalMin.x = pos.x else if pos.x > totalMax.x do totalMax.x = pos.x
if pos.y < totalMin.y then totalMin.y = pos.y else if pos.y > totalMax.y do totalMax.y = pos.y
if pos.z < totalMin.z then totalMin.z = pos.z else if pos.z > totalMax.z do totalMax.z = pos.z
Point pos:pos
)
)
local center = (totalMax + totalMin)/ 2
local dimensions = (totalMax - totalMin) * 2
local poly = convertToPoly (Box width:dimensions.x length:dimensions.y height:(dimensions.z) pos:[( center.x),( center.y),( (totalMin.z ))] )
for points in allPlanes do
(
local vector = normalize (cross (points[2] - points[1]) (points[3] - points[2]))
if isPositiveDir center vector points[1] do vector *= -1
slicePoly poly points[1] vector
)
)