Select vertices with mouse

Hello. I wonder if there is a simple solution for this problem. I have a grid at 0,0,0 that is 64x64x1 cells big. The camera is looking down the y axis at position (-0.049091205, 48.19402, -1.963456). I want now that the user can drag a rectangle and select every triangle that are under the rectangle.

I already have a mouse listener that draws the rectangle and I can use a ray to select the triangles under the mouse. My problem is that I want to select all triangles that are inside the rectangle.

The rectangle can get bigger and smaller, depending how the user is dragging the mouse. If the rectangle is getting smaller then the triangles must be de-selected that are no longer under the rectangle.

I was using a Box to do that and using rootCam.getWorldCoordinates() with projectionZPos=0 and projectionZPos=1 but the Box gets so big that every triangle is selected.

Currently I just slice the rectangle in 10x10 parts and check every part where it hits the terrain grid using a Ray. But it’s getting slow and slower the bigger the rectangle gets. Also, I don’t really like to slice it up like this. Maybe 10x10 is too big and the user will have trouble selecting the correct triangles. But smaller slices are too slow.

Maybe there is a better solution? I still like to just use a Box, because then I can get the collision between the terrain grid and the box.

private void findSelected(int x, int y) {
    TempVars t = TempVars.get();
    try {
        Vector3f trans = ribbonNode.getLocalTranslation();
        Vector2f size = ribbon.getSize();
        Vector2f click2d = t.vect2d;
        terrainSelectedIndices.clear();
        results.clear();
        for (float xx = trans.x; xx < trans.x + size.x; xx += 10f) {
            for (float yy = trans.y; yy < trans.y + size.y; yy += 10f) {
                click2d.x = x;
                click2d.y = y;
                Vector3f click3d = rootCam.getWorldCoordinates(click2d, 0f, t.vect1);
                System.out.println(click3d);
                Vector3f dir = rootCam.getWorldCoordinates(click2d, 1f).subtractLocal(click3d).normalizeLocal();
                ray.setOrigin(click3d);
                ray.setDirection(dir);
                terrainNode.collideWith(ray, results);
                for (CollisionResult c : results) {
                    terrainSelectedIndices.add(c.getTriangleIndex());
                }
            }
        }

        terrainSelectedIndices.stream().forEach(i -> System.out.print("" + i + ","));
        System.out.println();

    } finally {
        t.release();
    }
}

For the Box I had something like this:

w1 = rootCam.getWorldCoordinates(rect.bottomLeft, 0f);
w2 = rootCam.getWorldCoordinates(rect.topRight, 1f);
box.updateGeometry(w1, w2);
boxNode.updateModelBound();
terrainNode.collideWith(ray, results);
1 Like

Perhaps approach this from the other direction: compute the screen coordinates of each vertex and test whether it lies in selected rectangle.

If the rectangle was in world Space just ensure that every vertex of the triangle is within the box (thats 6 ifs for an AABB)

EDIT: Ok, I had to translate the triangles local coordinates to world coordinates. That makes sense. Now it works.

I tries this but I don’t understand why rootCam.getScreenCoordinates() returns me the wrong values. I select the top left edge and I already know that the indices must start with 128. But returned is index 1290, which is a few y-rows below. Also, if I try and select outside of the grid then there are still triangles that are selected. Please see the screenshot. Is getScreenCoordinates() not what I think it is?

Returned values for the screenshot:

Index, edge1, edge2, edge3
380 (525.8105, 831.6968, 0.98455703)-(498.7928, 831.6968, 0.98455703)-(498.7192, 798.3162, 0.98455155)
381 (525.8105, 831.6968, 0.98455703)-(498.7192, 798.3162, 0.98455155)-(525.7459, 798.3162, 0.98455155)
382 (498.7928, 831.6968, 0.98455703)-(471.7752, 831.6968, 0.98455703)-(471.6927, 798.3162, 0.98455155)

The code is:

private Map<Integer, CollisionResult> processing(Map<Integer, CollisionResult> set) {
    TempVars t = TempVars.get();
    try {
        Triangle tri = t.triangle;
        Vector3f s1 = t.vect1;
        Vector3f s2 = t.vect2;
        Vector3f s3 = t.vect3;
        Mesh mesh = terrainNode.getMesh();
        for (int i = triangleStart; i < triangleEnd; i++) {
            mesh.getTriangle(i, tri);
            rootCam.getScreenCoordinates(tri.get1(), s1);
            rootCam.getScreenCoordinates(tri.get2(), s2);
            rootCam.getScreenCoordinates(tri.get3(), s3);
            if (s1.x < 0 || s1.y < 0) {
                continue;
            }
            if (s1.x > rootCam.getWidth() || s1.y > rootCam.getHeight()) {
                continue;
            }
            if (s1.x >= x1 && s1.y >= y1 && s1.x <= x2 && s1.y <= y2) {
                if (s2.x >= x1 && s2.y >= y1 && s2.x <= x2 && s2.y <= y2) {
                    if (s3.x >= x1 && s3.y >= y1 && s3.x <= x2 && s3.y <= y2) {
                        set.put(i, new CollisionResult(terrainNode, null, 0, i));
                        System.out.printf("%d %s-%s-%s%n", i, s1, s2, s3);
                    }
                }
            }
        }
        return set;
    } finally {
        t.release();
    }
}

Correct code:

private Map<Integer, CollisionResult> processing(Map<Integer, CollisionResult> set) {
    TempVars t = TempVars.get();
    try {
        Triangle tri = t.triangle;
        Vector3f s1 = t.vect1;
        Vector3f s2 = t.vect2;
        Vector3f s3 = t.vect3;
        Mesh mesh = terrainNode.getMesh();
        for (int i = triangleStart; i < triangleEnd; i++) {
            mesh.getTriangle(i, tri);
            terrainNode.localToWorld(tri.get1(), s1);
            terrainNode.localToWorld(tri.get2(), s2);
            terrainNode.localToWorld(tri.get3(), s3);
            rootCam.getScreenCoordinates(s1, s1);
            rootCam.getScreenCoordinates(s2, s2);
            rootCam.getScreenCoordinates(s3, s3);
            if (s1.x < 0 || s1.y < 0) {
                continue;
            }
            if (s1.x > rootCam.getWidth() || s1.y > rootCam.getHeight()) {
                continue;
            }
            if (s1.x >= x1 && s1.y >= y1 && s1.x <= x2 && s1.y <= y2) {
                if (s2.x >= x1 && s2.y >= y1 && s2.x <= x2 && s2.y <= y2) {
                    if (s3.x >= x1 && s3.y >= y1 && s3.x <= x2 && s3.y <= y2) {
                        set.put(i, new CollisionResult(terrainNode, null, 0, i));
                        System.out.printf("%d %s-%s-%s%n", i, s1, s2, s3);
                    }
                }
            }
        }
        return set;
    } finally {
        t.release();
    }
}