Home› Technical Talk

3ds max camera based selection

polycounter lvl 11
Offline / Send Message
Monophobe polycounter lvl 11
Hi all,

This seems to be an issue lots of people have asked about online but I've yet to find a solution.

I have a bunch of objects that I've dynameshed and decimated in zbrush, and now I need to optimise based on a camera position. i.e. select all faces visible to the camera and delete the rest.

Ignore backfacing isn't sufficient as this doesn't excluse non-backfacing faces that are occluded by the rest of the mesh.

I found this script which apparently works but my knowledge of maxscipt is nil - http://www.illusioncatalyst.com/mxs_files/getFrontFaces.html

I've tried running it and get errors, and I expect it's to do with the line on that site which says
Required functions: getPolyFromMeshFace() found under: "Editable Poly data from underlying Mesh"


If anyone can shed any light on this for me I'd appreciate it. I'm installing Maya right now as I've read it can do what I'm after out of the box, but it would be incredibly useful to be able to do this in the package I'm familiar with.

Thanks.

Replies

  • Mark Dygert
    Options
    Offline / Send Message
    Make sure you convert your object to Editable Poly and collapse your modifier stack before running the script.

    On the surface a lot of the max modeling tools seem similar but they actually call different commands.
    Editable poly, is an object.
    Edit Poly, is a modifier.
    Edit Poly, is a modifier that is instanced across multiple objects.
    Editable Mesh, is an object.
    Edit Mesh, is a modifier.
    Edit Mesh, is a modifier that is instanced across multiple objects.

    All of these seem similar but commands like "cut" are called in different ways in each of them, if cut even exists in the tool at all.

    For script authors this is a clusterf*ck because one method or command might only be accessible under certain circumstances and not available in other methods, or it might but you have to code 4-5-6 different ways to handle it, then come up with a bunch of checks to find out what kind of object you have and convert it over to what you need and then back to what they had.

    Most script authors will just say "use editable poly" since its the newest and most robust and has the least chance of encountering totally random, uncontrollable modifier stack results.
  • Monophobe
    Options
    Offline / Send Message
    Monophobe polycounter lvl 11
    With help from someone at work I've kind of got it working.

    Ran the script and then put "getFrontFaces $ steps:5" (or variations of the steps integer) into the maxscript listener.

    It does a reasonable job but is still missing random faces that are obviously visible (ie not just small faces on the objects outline that whatever magic this script uses could possibly miss)

    Had to comment out the line with the getPolyFromMeshFace function in it to get it working, so maybe that's why it's failing on some faces.

    As I say, I know nothing about maxscript sadly but could really do with getting this to work before the end of the week.
  • Noors
    Options
    Offline / Send Message
    Noors greentooth
    I didn't test but yeah, he says to use a function from another snippet but i don't see it on his website. The man is SyncViewS here, you might want to contact him.
  • Monophobe
    Options
    Offline / Send Message
    Monophobe polycounter lvl 11
  • Monophobe
    Options
    Offline / Send Message
    Monophobe polycounter lvl 11
    Just about to test it, but found the aforementioned function from an old shot of his website - http://web.archive.org/web/20090717043646/http://illusioncatalyst.com/mxs.php#null
  • Mark Dygert
    Options
    Offline / Send Message
    Oh also forgot, The Graphite modeling tools has a select option "By View" called "Grow From Perspective View". You go into polygon sub-object mode, click the spinner and drag it up or down to dial in the angle fall off.

    http://docs.autodesk.com/3DSMAX/15/ENU/3ds-Max-Help/index.html?url=files/GUID-694580CD-2CEA-49B6-A5EE-278C50390616.htm,topicNumber=d30e128633

    It operates sort of like a slice plane moving through your scene from the viewport. Anything that is behind the plane is selected and the farther you push it into your scene the more it selects.

    Personally I like syncviews IC script is a lot better.
  • SyncViewS
    Options
    Offline / Send Message
    SyncViewS polycounter lvl 13
    Hi,
    the function has gone missing in a website update somewhere.

    I've rewritten it on the fly and it should do the job. It is required only if the object is an Editable Poly, the Editable Mesh doesn't call it.

    [html]function getPolyFromMeshFace theEditablePoly iMeshFace =
    (
    local iResultPoly = 0

    if ( (Filters.Is_EditPoly()) and (iMeshFace > 0) ) do
    (
    local theMesh = theEditablePoly.mesh
    local baMeshVerts = meshOp.getVertsUsingFace theMesh iMeshFace

    local abaPolysFromVert = for iVert in baMeshVerts collect (polyOp.getFacesUsingVert theEditablePoly iVert)
    local baPolyFace = abaPolysFromVert[1] * abaPolysFromVert[2] * abaPolysFromVert[3]

    if (baPolyFace.numberSet == 1) then
    (
    iResultPoly = (baPolyFace as Array)[1]
    )
    else
    (
    local baMeshFaceInPoly = meshOp.getPolysUsingFace theMesh iMeshFace ignoreVisEdges:false threshhold:179.5
    local baMeshFaceVert = meshOp.getVertsUsingFace theMesh baMeshFaceInPoly

    local baPolyFaceVert = #{}
    for iPoly in baPolyFace while (iResultPoly == 0) do
    (
    baPolyFaceVert = polyOp.getVertsUsingFace theEditablePoly iPoly

    if ( ((baPolyFaceVert - baMeshFaceVert).isEmpty) and ((baMeshFaceVert - baPolyFaceVert).isEmpty) ) do
    (
    iResultPoly = iPoly
    )
    )
    )
    )

    return iResultPoly
    )[/html]

    I've gone quickly through the code to select front faces and I saw it is not 100% accurate mainly for two reasons: it uses a sampling raycast system, which means it can miss faces in between rays, it uses the intersectRayEx function which is known to be not very reliable (should use the RayMeshGridIntersect), plus it is very old code.

    You can give it a go with the function I provided, but it might not make a huge difference. Keep in mind it is going to take longer with Editable Polys, because of the additional calculations.

    Thanks
  • Monophobe
    Options
    Offline / Send Message
    Monophobe polycounter lvl 11
    Thanks for the help SyncViewS. I've got it working but as you said, the missed faces are still present. On the basic shape I'm testing on now I get the same results with 50 steps as I do with 1.

    I don't suppose there's a way to increase the number of rays that are cast?

    Speed hasn't been a problem so far as long as I keep my meshes fairly low poly.
  • SyncViewS
    Options
    Offline / Send Message
    SyncViewS polycounter lvl 13
    If I remember correctly, that "steps" parameter is the subdivision of each edge of each face that becomes a ray generator to cast on the possibly occluded meshes (I might be wrong). Increasing it, should do more raycasts.

    Have you fiddled with these variable at the beginning of the script?

    local fDotThresh = 1e-3
    local fMatchThresh = 1e-3
    local fOffsetThresh = 1e-2

    They are meant to set the accuracy of some tests, the lower the better, but don't take it lower than 1e-6.

    Can you post a snapshot of the model and the result of the selection? Just to see if I can spot anything clearly wrong.

    Thanks
  • Monophobe
    Options
    Offline / Send Message
    Monophobe polycounter lvl 11
    I haven't tried altering any of the values, as I said, I haven't the foggiest when it comes to maxScript I'm afraid.

    I can show you the basic noisy mesh test I'm doing which would be very easy to fix by hand, but the project I need this for would take a loooong time to sort by hand (sorry, can't show you the meshes I need it for but they're effectively mountain ranges that have been dynameshed/decimated and then broken down into a lot of more manageable objects poly count wise)

    test:
    IP803uK.jpg

    EDIT: I should point out that this isn't the best example of what I've been seeing. I've had faces right in the center of the nearest part of the mesh that are being missed, rather than just edge cases.
  • SyncViewS
    Options
    Offline / Send Message
    SyncViewS polycounter lvl 13
    It might sound very strange, but the script is doing what's supposed to: "Returns a list of faces not occluded, with normal facing camera direction".

    The faces that appear to be not selected in the farther end of the bent tube are actually occluded by the faces of the part of the tube closer to the camera.

    If a ray, cast from a face closer to the camera, hits a face farther from the camera, the latter is not selected. I suspect what you're looking for is a different script, which would select everything that can be seen from the camera and leave unselected everything that is not visible at all from the camera. Is it right?
  • Monophobe
    Options
    Offline / Send Message
    Monophobe polycounter lvl 11
    Ah, yeah, that's right. I got the impression that this script would behave that way. Not sure why it would be missing completely un-occluded faces on the more complex meshes I tried earlier today.

    Really appreciate the help by the way. I wish autodesk would just implement Mayas camera based selection :(
  • SyncViewS
    Options
    Offline / Send Message
    SyncViewS polycounter lvl 13
    Hi,
    here is the script you're looking for (hopefully :)

    It is not super tested (it's late). It works on Editable Polys and select all the faces visible from camera in the active viewport. It is a function and a call on the selected node, I think you should be able to run it.
    (
        function getVisiblePolys theEditablePoly steps:0 =
        (
            local baVisibleFaces = #{}
    
            if ( (isValidNode theEditablePoly) and (classOf theEditablePoly == Editable_Poly) ) do
            (
                local fOffsetFactor = 0.01
    
                local rmgi = RayMeshGridIntersect()
                rmgi.initialize 10
                rmgi.addNode theEditablePoly
                rmgi.buildGrid()
    
                local m3CameraW = inverse (getViewTM())
                local p3CameraPosW = m3CameraW.row4
                local p3CameraDirW = -m3CameraW.row3
    
                local p3Offset = m3CameraW.row3 * fOffsetFactor
    
                local bIsPerspective = viewport.isPerspView()
    
                -- Test faces by vertices
    
                local iNumVerts = polyop.getNumVerts theEditablePoly
                local ap3VertsPosW = #()
    
                local p3VertCameraDirW = -p3CameraDirW
                for iVert = 1 to iNumVerts do
                (
                    local p3VertPosW = polyOp.getVert theEditablePoly iVert
                    append ap3VertsPosW p3VertPosW
    
                    if (bIsPerspective) do
                    (
                        p3VertCameraDirW = normalize (p3CameraPosW - p3VertPosW)
                        p3Offset = p3VertCameraDirW * fOffsetFactor
                    )
    
                    local iNumHits = rmgi.intersectRay (p3VertPosW + p3Offset) p3VertCameraDirW true
                    if (iNumHits == 0) do
                    (
                        baVisibleFaces += polyOp.getFacesUsingVert theEditablePoly iVert 
                    )
                )
    
                -- Test faces center
                -- Commented because not correct: To be fixed :)
    /*
                local iNumFaces = polyOp.getNumFaces theEditablePoly
                local baAllFaces = #{1..iNumFaces}
                local baFacesToTest = baAllFaces - baVisibleFaces
    
                local p3CenterCameraDirW = -p3CameraDirW
                for iFace in baFacesToTest do
                (
                    local p3FaceCenterPosW = polyop.getFaceCenter theEditablePoly iFace
    
                    if (bIsPerspective) do
                    (
                        p3CenterCameraDirW = normalize (p3CameraPosW - p3FaceCenterPosW)
                        p3Offset = p3CenterCameraDirW * fOffsetFactor
                    )
    
                    local iNumHits = rmgi.intersectRay (p3FaceCenterPosW + p3Offset) p3CenterCameraDirW true
                    if (iNumHits == 0) do
                    (
                        baVisibleFaces[iFace] = true
                    )
                )
    */
                -- Test faces sub-edges
    
                if (steps > 0) do
                (
                    baFacesToTest = baAllFaces - baVisibleFaces
    
                    local iNumEdges = polyop.getNumEdges theEditablePoly
    
                    local baAllEdges = #{1..iNumEdges}
                    local baEdgesToTest = baAllEdges - (polyOp.getEdgesUsingFace theEditablePoly baFacesToTest)
    
                    local p3PointCameraDirW = -p3CameraDirW
                    for iEdge in baEdgesToTest do
                    (
                        local aiEdgeVerts = polyOp.getEdgeVerts theEditablePoly iEdge
    
                        local p3VertPosW_1 = ap3VertsPosW[aiEdgeVerts[1]]
                        local p3VertPosW_2 = ap3VertsPosW[aiEdgeVerts[2]]
    
                        local p3EdgeVecW = p3VertPosW_2 - p3VertPosW_1
    
                        local ap3TestPosW = for fParam = 0.0 to 1.0 by (1.0 / (steps + 1.0)) collect (p3VertPosW_1 + (p3EdgeVecW * fParam))
    
                        local iNumHits = -1
                        for iPoint = 2 to (ap3TestPosW.count-1) while (iNumHits != 0) do
                        (
                            if (bIsPerspective) do
                            (
                                p3PointCameraDirW = normalize (p3CameraPosW - ap3TestPosW[iPoint])
                                p3Offset = p3PointCameraDirW * fOffsetFactor
                            )
    
                            iNumHits = rmgi.intersectRay (ap3TestPosW[iPoint] + p3Offset) p3PointCameraDirW true
                            if (iNumHits == 0) do
                            (
                                baVisibleFaces += polyOp.getFacesUsingEdge theEditablePoly iEdge 
                            )
                        )
                    )
                )
    
                -- Select Visible Faces
    
                subObjectLevel = 4
                polyOp.setFaceSelection theEditablePoly baVisibleFaces
    
                rmgi.free()
                gc light:true
    
                return baVisibleFaces
            )
        )
    
        local theNode = (selection as Array)[1]
        getVisiblePolys theNode steps:0
    )
    
  • Monophobe
    Options
    Offline / Send Message
    Monophobe polycounter lvl 11
    I've seen this functionality in max asked for a lot online and I'm sure I won't be alone in finding this incredibly useful and labour saving.

    You sir, have absolutely made my day! :) Thank you so so much.



    And for the benefit of anyone who stumbles across this thread, a quick example image:

    grzj31b.jpg
  • SyncViewS
    Options
    Offline / Send Message
    SyncViewS polycounter lvl 13
    Thanks, I am glad it helped.
    There is a bug in the code. I assumed the center of the polygons is within the polygon face (as happens for meshes) but it is incorrect. I commented the test of the center of the face for now. I am going to fix it later. The script is still working anyway, just a little less accurate.

    Cheers
Sign In or Register to comment.