Converting between cartesian and spherical coordinates

Hi, my game is played on the surface of a planet (which is basically a sphere). I need to be able to translate between scene coordinates (cartesian x, y and z axes) and polar coordinates (alpha, beta and distance as used by the arc rotate camera).

Using my basic knowledge of trigonometry, I figured out this routine:

    const polarToCartesian = function(position) {
        const alpha = position.x;
        const beta = position.y;
        const distance = position.z
        const x = -Math.sin(alpha) * Math.cos(beta) * distance;
        const y = Math.cos(alpha) * Math.cos(beta) * distance;
        const z = Math.cos(alpha) * Math.sin(beta) * distance;
        return new BABYLON.Vector3(x, y, z);
    }

This works perfectly and allowed me to create a ground mesh that covers part of a sphere, but my math knowledge is not enough to figure out the reverse

    const cartesianToPolar = function(position) {
        const x = position.x;
        const y = position.y;
        const z = position.z;
        const alpha = ???;
        const beta = ???;
        const distance = Math.sqrt(x * x + y * y + z * z);
        return new BABYLON.Vector3(alpha, beta, distance);
    }

Is there anyone here with the math skill to figure this out, or alternatively are there helper classes in Babylon that can help with this kind of conversion?

Note that this page and this page are not the answer because they define the angles differently from the way that the arc rotate camera does it.

If you want to see how I am using the arc rotate camera, refer to this PG

Your PG test of my function is very interesting. It appears not to work over the range where you tested it.
My ground is also not completely spherical, but much closer to what I expect. Here is an example

Note that the map is defined as hexagons on the back-end but I am drawing rectangles for now to keep is simple and will convert it to hexagons and smooth the edges later.
I can’t do a PG because it relies on the back-end server to generate the map.

1 Like

I linked the English version of this page in my original question. The reason that I can’t use these formulas is that these are not the angles that the ArcRotateCamera uses for its alpha and beta, and it’s not a simple question of switching the axes around. The reason why I want this conversion is to position the ArcRotateCamera above a specific point on the globe without changing its height above the surface.
My camera code looks like this:

const OrbitalCamera = function (canvas, scene, planet) {
    // See https://www.babylonjs-playground.com/#1A3M5C#169

    const zeroAlpha = -Math.PI / 2;
    const zeroBeta = Math.PI / 2;

    const camera = new BABYLON.ArcRotateCamera(
        "orbitalCamera",
        zeroAlpha, zeroBeta, planet.radius + planet.maxElevation,
        BABYLON.Vector3.Zero(),
        scene,
        true);
    camera.upVector = new BABYLON.Vector3(0, 0, 1);
    camera.panningSensibility = 0; // Disabling panning makes the right mouse button rotate the camera
    camera.angularSensibilityX = 20000;
    camera.angularSensibilityY = 20000;
    camera.fov = Math.PI / 8;
    camera.lowerAlphaLimit = -Math.PI;
    camera.upperAlphaLimit = 0;
    camera.lowerBetaLimit = 0;
    camera.upperBetaLimit = Math.PI;
    camera.lowerRadiusLimit = sceneParams.planetRadius + 10;
    camera.upperRadiusLimit = sceneParams.skyboxHeight / 2;

    camera.inputs.attached.pointers.buttons = [2];
    delete camera.inputs.attached.keyboard;

    camera.attachControl(canvas, true, false);

    const positionCamera = function(position){
        if (position) {
            const polar = planet.cartesianToPolar(focus.position);
            camera.alpha = zeroAlpha + polar.x;
            camera.beta = zeroBeta + polar.y;
        }
    }
    var focusedMesh;
    const focusOn = function (mesh, sticky) {
        if (sticky) focusedMesh = mesh;
        else {
            focusedMesh = null;
            if (mesh) positionCamera(mesh.position);
        }
    }
    scene.registerBeforeRender(function () {
        if (focusedMesh) {
            positionCamera(focusedMesh.position);
        }
    });

    return {
        camera,
        focusOn
    };
}

I updated your Babylon Playground to reflect the way that I set up the scene in my game, and my formula seems to work reasonably well over the range that I am using it - although I agree that your test shows that it is not useful for the more general case.

If position is the camera world position,
Won’t you need a target or direction vector to calculate alpha & beta?

The camera always points to (targets) the center of the globe. I need to move it to a position where it is looking down on a particular spot on the globe, but at the height that the user chose via camera inputs (mouse scroll wheel or pinch zoom).
This PG shows what my scene looks like

Okay, to clarify,
you want the camera to rotate (change alpha & beta) so it looks directly at a specific box,
but continue to target center of sphere,
so targeted box will be between camera position and center of sphere

Yes, exactly.
To move the camera using only alpha and beta, such that if I was to draw a line from the camera to the center of the globe, the line would hit the ground at a specific x, y, z position within the scene.

Ok,
i’m not sure about your function,
but we can create a direction vector from sphere.position to picked box position,
then scale it by camera’s radius (distance) and apply that to the camera.position

If this won’t fit your needs, arcRotateCamera must have some conversions in it’s code… somewhere :slight_smile:

https://www.babylonjs-playground.com/#6H1SAC#3

Thank you, this is really close to a solution. In the PG if I click the same box over again it gets closer and closer to the camera, but other that this issue, your solution works.

odd, doesn’t for me,
but a quick uniqueId check should sort that!
https://www.babylonjs-playground.com/#6H1SAC#5

Ah, it only happens after panning the scene (with right mouse).

In my case I am not always picking the mesh with the pointer, the player can also pick things from a list. Does your solution rely on mesh picking with the pointer to work?

you just need the position of the mesh to create the direction vector and it should work :slight_smile:

when you pan the camera you change the camera target, so it’s no longer centered on the sphere

Got it. In my game the player cannot pan anyway, so this won’t be an issue.
Thank you so much for your help.

No problem!

One with animated position change just for fun (there’s probably a better method to do it)
https://www.babylonjs-playground.com/#6H1SAC#6

1 Like

Cool, I didn’t try playing with animation yet, but it looks way better.

FYI this is my the part of my code that moves the camera focus

    const positionCamera = function(mesh){
        if (mesh.position) {
            const cameraPosition = mesh.position.clone();
            while (mesh.parent) {
                mesh = mesh.parent;
                cameraPosition.addInPlace(mesh.position);
            }
            cameraPosition.normalize().scaleInPlace(camera.radius);
            camera.setPosition(cameraPosition);
        }
    }
    var focusedMesh;
    const refocus = function () {
        if (focusedMesh) {
            positionCamera(focusedMesh);
        }
    }
    const setFocusedMesh = function (mesh) {
        if (mesh) {
            if (!focusedMesh) scene.registerBeforeRender(refocus);
        } else
        {
            if (focusedMesh) scene.unregisterBeforeRender(refocus);
        }
        focusedMesh = mesh;
    }
    const focusOn = function (mesh, sticky) {
        if (sticky) {
            setFocusedMesh(mesh);
        } else {
            setFocusedMesh(null);
            if (mesh) positionCamera(mesh);
        }
    }

1 Like

pull a set of static data from your backend that you are happy to use for testing, and just dump that in a the pg. if you want to :slight_smile: if you dont then no problem :slight_smile:

R = sqrt (x^2+y^2+z^2 ),
θ =arccos z/sqrt(x^2+y^2+z^2) = arccos z/r = arctan (sqrt (x^2+y^2)/+z),
φ = arctan(y/x).

x =r cos φ sin θ,
y =r sin φ sin θ ,
z =r cos θ,

1 Like