jnsmalm / pixi3d

The 3D renderer for PixiJS. Seamless integration with 2D applications.
https://pixi3d.org
MIT License
759 stars 44 forks source link

Camera translations #46

Closed djlastnight closed 3 years ago

djlastnight commented 3 years ago

Hey, thank you for this extremely cool api. After 5 days head banging, I found that I need to take into account the pixi's renderer resolution in order to make the camera coord conversion to work as it should. Here are the wrapped methods (I am not sure 100% about the z values, because I do not need z at all in my app).

public static toWorld(
        container: Container,
        anchor: Point,
        model: Model
    ): ObservablePoint3D {
        let bounds = container.getBounds();
        let x = bounds.x + anchor.x * bounds.width;
        let y = bounds.y + anchor.y * bounds.height;
        return this.screenToWorld(x, y, model);
    }

    public static screenToWorld(
        x: number,
        y: number,
        model: Model
    ): ObservablePoint3D {
        let worldCoordinates = Camera.main.screenToWorld(
            x,
            y,
            Camera.main.z - model.z,
            undefined,
            {
                width:
                    App.instance.renderer.width /
                    App.instance.renderer.resolution,
                height:
                    App.instance.renderer.height /
                    App.instance.renderer.resolution,
            }
        );

        return worldCoordinates;
    }

    public static worldToScreen(
        x: number,
        y: number,
        z: number = Camera.main.z
    ): Point {
        let worldCoordinates = Camera.main.worldToScreen(x, y, z, undefined, {
            width:
                App.instance.renderer.width / App.instance.renderer.resolution,
            height:
                App.instance.renderer.height / App.instance.renderer.resolution,
        });

        return worldCoordinates;
    }

I hope this helps and it will save time to someone using your api.

Thanks, Ivan

jnsmalm commented 3 years ago

Hey, thanks for this! Is the standard methods worldToScreen/screenToWorld not working when resolution is above 1?

djlastnight commented 3 years ago

Yes, the standard methods works only for resolution 1, that's why I am sending you the solution.

The easiest way to replicate the bug is to zoom in/out the page - this makes the pixi renderer change its resolution, so try it. -edit- I am not sure if this is related, but the PostProcessingSprite scale behaves strangely, when you resize the page/container. You might wish to check if this fixes this problem too (I abandoned the PPS class usage completely for this reason + I think it is eating too much resources, probably because of the frequent [pre]render event calls).

jnsmalm commented 3 years ago

I included your fixes in the dev branch, please check if this resolves the issues you had with it.

Could you please give some more details about PostProcessingSprite? The only thing happening in prerender is that the PPS is being resized (but only if you didn't provide a size yourself) - by default it want to be fullscreen. It could be an issue though that it's not resizing to correct resolution if above 1.

djlastnight commented 3 years ago

I still need to pass the viewSize arg to the worldToScreen and screenToWorld methods. If I omit it, it does not work.

This is really strange, because I can see that you are now using the renderer.screen property, which gives the correct width and height, i.e it takes into account the resolution. The following code works:

    public static worldToScreen(
        x: number,
        y: number,
        z: number = Camera.main.z
    ): Point {
        let worldCoordinates = Camera.main.worldToScreen(x, y, z, undefined, {
            width:
                App.instance.renderer.screen.width,
            height:
                App.instance.renderer.screen.height,
        });

        return worldCoordinates;
    }

The following does not work:

    public static worldToScreen(
        x: number,
        y: number,
        z: number = Camera.main.z
    ): Point {
        let worldCoordinates = Camera.main.worldToScreen(x, y, z);

        return worldCoordinates;
    }

As you can see we are using a singleton App class, which extends the PIXI.Application class. I am not sure if this could be a problem somehow. I guess the PPS will behave the same way, so let's first fix this issue and I will test and give you more feedback about it. image

-edit- About the App instance. Both App.instance.renderer.screen and Camera.main.renderer.screen returns the same rectangle. The following code works:

    public static screenToWorld(
        x: number,
        y: number,
        model: Model
    ): ObservablePoint3D {
        let worldCoordinates = Camera.main.screenToWorld(
            x,
            y,
            Camera.main.z - model.z,
            undefined,
            Camera.main.renderer.screen
        );

        return worldCoordinates;
    }

Really strange!

-- another edit --- About the PPS. It seems I must omit the width and height at its constuctor in order to make it scale correctly, so I guess I was using your class incorrectly. Sorry about that. Unfortunately the PPS does not position itself correctly, when the resolution differs from 1. Please see the code and comments below.

            let resource = PIXI.Loader.shared.resources["test_model"];
            let asset = (<any>resource).gltf as glTFAsset;
            let model = Model.from(asset);
           // Important. Omit the pps width and height args, otherwise the scale will be weird.
            let pps = new PostProcessingSprite(Camera.main.renderer, {
                objectToRender: <any>model,
            });

            // Computing the screen center point for any resolution
            let pos = Model3D.screenToWorld(
                Camera.main.renderer.screen.width /
                   Camera.main.renderer.resolution /
                   2 ,
                Camera.main.renderer.screen.height /
                    Camera.main.renderer.resolution /
                    2,
                model
            );

            // The pps position will be set to the center of the screen
            // If the resolution equals 1, the resize works perfectly - the model stays always at the screen center.
            // If the resolution differs from 1, the resize does not work as it should (model does not stay at the screen center)
            model.position.set(pos.x, pos.y, pos.z);
            App.instance.stage.addChild(pps);

It seems I should prepare a test project for you to test this stuff easily. I got a feeling our game does something fishy with the renderer sizes on each window resize and your api does not like this much.

jnsmalm commented 3 years ago

Would be great help if you could reproduce these issues in a simple example.

jnsmalm commented 3 years ago

And about PPS, if you give it a width and height where the aspect ratio differs from the camera aspect ratio it will look wrong. The objects rendered to the PPS need to use a camera which have the same aspect ratio as the PPS. This is handled automatically if you don’t give it a width and height.

djlastnight commented 3 years ago

Yep, my bad about the PPS width and height, thank you for the clarification. I made a test repo, everything is described at index.ts. https://github.com/djlastnight/Pixi3DCameraTests

jnsmalm commented 3 years ago

I created a PR which should fix the issues: https://github.com/djlastnight/Pixi3DCameraTests/pull/1

djlastnight commented 3 years ago

Thank you, it is merged, I will test it when I go back to home. -edit- Yes, it works now. The white rect is not centered on zoom change, but this is not important, because the model and pps works as they should.

djlastnight commented 3 years ago

I guess we could close this now, thank you again ;)