Fitting an object inside the screen.

Started by tulsi, August 20, 2020, 07:33:55 AM

Previous topic - Next topic

tulsi

Hello once again!!

In my app I will most probably have only one object on screen at a time. But that object may not be the same object every time like it can be a car, a chair, or something else like that.
Now the problem is those object may not have same size. But I want them to fit inside the screen.

I am able to get the realworld size of the object by using following function. Also I can get the screen resolution. But I dont know how to make relationship between them so that I can scale them accordingly.

public static SimpleVector getSize(Object3D object){
        float[] bbox = object.getMesh().getBoundingBox();
        Log.i("Util","getSize-bbox:"+bbox);
        float s = object.getScale();
        Log.i("Util","getSize-scale:"+object.getScale());
        Object3D[] par;
        par = object.getParents();
        while(par.length>0){
            s = s*par[0].getScale();
            Log.i("Util","getSize-parent:"+par[0].getScale());
            par = par[0].getParents();
        }
        SimpleVector out = new SimpleVector((bbox[1]-bbox[0])*s,(bbox[3]-bbox[2])*s,(bbox[5]-bbox[4])*s);
        Log.i("Util", "getSize:"+out.toString());
        return out;
    }


Thank you.. :)

AeroShark333

#1
Hello,

Whether it fits on the screen or not properly depends on the FOV too.
3 implementations I can think of:
- Complete calculation of where the camera should move to using FOV, bounding box coordinates and screen resolution so it all fits in screen. I believe you can find the ideal position for the camera that way
- Reproject bounding box coordinates to screenspace and see if the coordinates are on the screen or out of the screen. First, you should probably zoom in untill one of the coordinates falls out of the screen size in screenspace. Then zoom out again until all bounding box coordinates are in the screen size in screenspace.
- A little more hacky but to find a correlation between bounding box size (largest distance I'd go for) and zoomfactor. Probably this option is easiest

Good luck

I hope this is helpful

EgonOlsen


tulsi

Thank you AeroShark333 and EgonOlsen for your time.

As I am new to JPCT and 3D programming it will take some more time for me to understand these components.

I had searched on this forum and found some code related to my problem as below:

private Point[] get2DVertices(SimpleVector[] vertices3d, Camera theCamera, FrameBuffer buffer) {
        Point[] vertices = new Point[vertices3d.length];
        for (int i = 0; i < vertices.length; i++) {
            SimpleVector point = Interact2D.project3D2D(theCamera, buffer, vertices3d[i]);
            vertices[i] = new Point((int)(point.x+.5f), (int)(point.y+.5f));
        }
        return vertices;
    }
    public float[] get2DBounds(Camera theCamera, FrameBuffer buffer) {
        System.out.println("get2DBounds");
        object3D.enableLazyTransformations();
        PolygonManager polyManager = object3D.getPolygonManager();
        SimpleVector[] vertices3d = new SimpleVector[polyManager.getMaxPolygonID()*3];
        int currentPoly = 0;
        int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE, minZ = Integer.MAX_VALUE;
        int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE, maxZ = Integer.MIN_VALUE;
        for (int i = 0; i < vertices3d.length; i+=3) {
            vertices3d[i] = polyManager.getTransformedVertex(currentPoly, 0);
            vertices3d[i+1] = polyManager.getTransformedVertex(currentPoly, 1);
            vertices3d[i+2] = polyManager.getTransformedVertex(currentPoly++, 2);
        }
        Point[] vertices = this.get2DVertices(vertices3d, theCamera, buffer);
        for (int i = 0; i < vertices.length; i+=3) {
            if (minX > vertices[i].x)
                minX = vertices[i].x;
            if (minX > vertices[i+1].x)
                minX = vertices[i+1].x;
            if (minX > vertices[i+2].x)
                minX = vertices[i+2].x;
            if (maxX < vertices[i].x)
                maxX = vertices[i].x;
            if (maxX < vertices[i+1].x)
                maxX = vertices[i+1].x;
            if (maxX < vertices[i+2].x)
                maxX = vertices[i+2].x;
            if (minY > vertices[i].y)
                minY = vertices[i].y;
            if (minY > vertices[i+1].y)
                minY = vertices[i+1].y;
            if (minY > vertices[i+2].y)
                minY = vertices[i+2].y;
            if (maxY < vertices[i].y)
                maxY = vertices[i].y;
            if (maxY < vertices[i+1].y)
                maxY = vertices[i+1].y;
            if (maxY < vertices[i+2].y)
                maxY = vertices[i+2].y;
        }
        object3D.disableLazyTransformations();
//        return new float[](minX, minY, (int)Math.abs(maxX-minX), (int)Math.abs(maxY-minY));
        return new float[]{minX, minY, maxX, maxY};
    }


:) :) This code goes over my mind..

I think this one is the second solution as discussed by AeroShark333 and EgonOlsen.
But this code is quite expensive to run multiple times. It takes about 2 seconds to execute. May be there is another way for this method.

I even tried FOV method as below:

double height = Math.max(getSize(object3D).x, getSize(object3D).z);
double fov = world.getCamera().getFOV();
double distance =(height/2)/Math.tan(fov/2);
world.getCamera().setPosition(0,0,-distance);


But this gave wired results.
May be this method has something to do with type of camera.

I know I donot have concepts on 3D programming but I am trying to learn. It will take some time for me.

Thank you for your support.

EgonOlsen

That code is quite expensive, because it uses the PolygonManager to access each polygon individually. For most applications, the bounding box should be fine, which is way cheaper. I looked into my own stuff to find out, if I did something like this and, lo and behold, I actually did. However, it makes the object fit the screen by adjusting the FOV instead of zooming in and out, which is ok for what I used it for, but in your case, I don't think that it's feasible.

The wiki has some code to get an object's bounding box in world space coordinates: http://www.jpct.net/wiki/index.php?title=Getting_Worldspace_Bounds

You might be able to use these coordinates with Interact2D to get the the actual screen coordinates and then do some iterative process to adjust the zoom until it fits. I bet that there is way to calculate this directly, but I'm not sure how to do that myself ATM.

tulsi

Thank you for your help.

I implemented that code as below.


float[] cor = getWorldSpaceBounds(object3D);
Log.d("Interact", "onScale: "+Interact2D.project3D2D(world.getCamera(),fb, new SimpleVector(cor[1], cor[3], cor[5])));
Log.d("Interact", "onScale: "+Interact2D.project3D2D(world.getCamera(),fb, new SimpleVector(cor[0], cor[2], cor[4])));


But I think Output is not accurate:

Interact: onScale: (828.88245, 1424.3342, 8.988686E-4)
Interact: onScale: (-425.23065, -14.657715, 0.002846251)


The 3D object is inside the screen but the coordinates seems like they are not matching. But by using polygonManager method output was correct.
I dont know if i am using syntax correctly. :) :)

EgonOlsen

Hard to tell, if that's correct or not. You have to keep in mind that the bounding box is rotated in world space as well, so it might be outside of the screen even if the object isn't. I guess this method works best for simple objects like a cube or a sphere. That's why the other method used the polygons directly, but that's much more computational intense.

tulsi

I dont know whether i will be able to solve this or not.

I have two more questions for now.

1. One of my object is about 19m width (for now not considering height)  in real world. And It fits well in the  screen at scale 1. Now what will happen if I will load that on bigger screen like tablet or smaller screen than that at same scale.


2. Initially without any transformation all of my object are getting rotated by some angle, how can i know that by what factor they are being rotated?

Thank you.

EgonOlsen

The scaling itself is device independent. You can think of the screen coordinates as normalized to 1 and then mapped onto the actual coordinates. So it should render more or less the same. Some variance can be introduced by different aspect ratios of different screens, but that shouldn't really matter.

If there is some rotation after loading, it's either the model that is rotated in the first place or you are doing something to the camera (maybe some lookAt()-call) that might rotate it. You might want to check the camera's matrix (getBack()) and the object's rotation matrix (getRotationMatrix()) to see, if one of these differs from the identity matrix.

tulsi

Thank you.

Yes the getBack() was not identity. I removed lookat() method and now the object is inverted.


I am using code as below:


public void onSurfaceChanged(GL10 gl, int w, int h) {
            if (fb != null) {
                fb.dispose();
            }
            fb = new FrameBuffer(gl, w, h);

            if (master == null) {

                world = new World();
                world.setAmbientLight(150, 150, 150);

                sun = new Light(world);
                sun.setIntensity(40, 40, 40);

                for (File f: getFilesDir().listFiles()){

                    if (MimeTypeMap.getFileExtensionFromUrl(f.getName()).equalsIgnoreCase("jpg") || MimeTypeMap.getFileExtensionFromUrl(f.getName()).equalsIgnoreCase("png") || MimeTypeMap.getFileExtensionFromUrl(f.getName()).equalsIgnoreCase("jpeg")){
                        Log.d("Files", f.getName());
                        try {
                            TextureManager.getInstance().addTexture(f.getName(), new Texture(new FileInputStream(new File(getFilesDir(), f.getName()))));
                        } catch (FileNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
                object3D = loadModel();
                world.addObject(object3D);
                world.getCamera().setPosition(0,0,-20);

                MemoryHelper.compact();
                if (master == null) {
                    Logger.log("Saving master Activity!");
                    master = jpct.this;
                }
            }

        }



    private Object3D loadModel() {
        Object3D[] model = new Object3D[0];
        try {
            model = Loader.loadOBJ(new FileInputStream(new File(getFilesDir(), "01Alocasia_obj.obj")), new FileInputStream(new File(getFilesDir(), "01Alocasia_obj.mtl")), 0.01f);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        Object3D o3d = new Object3D(0);
        Object3D temp = null;
        for (int i = 0; i < model.length; i++) {
            temp = model[i];
            temp.setCenter(SimpleVector.ORIGIN);
//            temp.rotateX((float)( -.5*Math.PI));
//            temp.rotateMesh();
            temp.setRotationMatrix(new Matrix());
            o3d = Object3D.mergeObjects(o3d, temp);
            o3d.build();
        }
        return o3d;
    }

EgonOlsen

Uncomment these lines to see if that helps:


//   temp.rotateX((float)( -.5*Math.PI));
//   temp.rotateMesh();


It's an issue with different file formats using different coordinate systems.

tulsi

This is the output after uncommenting those lines.



Facing Inward.

EgonOlsen


tulsi

Thanks for your support.

The rotation problem is solved.

Please watch the below video.
https://raw.githubusercontent.com/tulsiojha/data/master/elongated.mp4

I dont know how to explain this. But you can see while i am rotating the the object its getting elongated.
I think it is a camera problem.

But i dont know how to fix it.

Thank you.

AeroShark333

I don't see anything particularly wrong with it..? But it might be me...

Perhaps you could try playing with this:
Config.defaultCameraFOV = 0.5f; // default = 1.25f
Make sure to call this before you're creating your camera/world.