2

I have a sphere (representing Earth) and I want the user to be able to move the mouse while I track the point underneath that mouse location on the sphere.

Everything works fine as long as the camera is at a reasonable altitude from the surface of the sphere (say, the equivalent of at least a few hundred meters in real life).

But if I zoom in too closely to the surface of the sphere (say, 30 meters above the surface), then I start observing a bizarre behavior: all the points I draw now seem to start "snapping" to some predefined lattice in space, and if I try to draw a few lines that intersect at the point on the surface directly beneath the mouse, they instead "snap" to some nearby point, nowhere underneath the cursor.

Specifically, I'm using the following code to map the point from 3D to 2D and back:

double line_sphere_intersect(double const (&o)[3], double const (&d)[3], double const r)
{
    double const
        dd = d[0] * d[0] + d[1] * d[1] + d[2] * d[2],
        od = o[0] * d[0] + o[1] * d[1] + o[2] * d[2],
        oo = o[0] * o[0] + o[1] * o[1] + o[2] * o[2],
        left = -od,
        right = sqrt(od * od - dd * (oo - r * r)),
        r1 = (left + right) / dd,
        r2 = (left - right) / dd;
    return ((r1 < 0) ^ (r1 < r2)) ? r1 : r2;
}

Point3D mouse_pos_to_coord(int x, int y)
{
    GLdouble model[16];  glGetDoublev(GL_MODELVIEW_MATRIX, model);
    GLdouble  proj[16];  glGetDoublev(GL_proj_MATRIX, proj);
    GLint view[4];       glGetIntegerv(GL_view, view);  
    y = view[3] - y;  // invert y axis

    GLdouble a[3]; if (!gluUnProject(x, y, 0       , model, proj, view, &a[0], &a[1], &a[2])) { throw "singular"; }
    GLdouble b[3]; if (!gluUnProject(x, y, 1 - 1E-4, model, proj, view, &b[0], &b[1], &b[2])) { throw "singular"; }
    for (size_t i = 0; i < sizeof(b) / sizeof(*b); ++i) { b[i] -= a[i]; }
    double const t = line_sphere_intersect(a, b, earth_radius / 1000);
    Point3D result = Point3D(t * b[0] + a[0], t * b[1] + a[1], t * b[2] + a[2]);
    Point3D temp;
    if (false /* changing this to 'true' changes things, see question */)
    {
        gluProject(result.X, result.Y, result.Z, model, proj, view, &temp.X, &temp.Y, &temp.Z);
        gluUnProject(temp.X, temp.Y, 1 - 1E-4, model, proj, view, &result.X, &result.Y, &result.Z);
        gluProject(result.X, result.Y, result.Z, model, proj, view, &temp.X, &temp.Y, &temp.Z);
    }
    return result;
}

with the following matrices:

glMatrixMode(GL_PROJECTION);
gluPerspective(
    60, (double)viewport[2] / (double)viewport[3], pow(FLT_EPSILON, 0.9),
    earth_radius_in_1000km * (0.5 + diag_dist / tune_factor / zoom_factor));
glMatrixMode(GL_MODELVIEW);
gluLookAt(eye.X, eye.Y, eye.Z, 0, 0, 0, 0, 1, 0);

where eye is the current camera location above Earth.

(And yes, I'm using double everywhere, so it shouldn't be a precision issue with float.)

Furthermore, I've observed that if I change the if (false) to if (true) in my code, then the red lines now seem to intersect directly underneath the cursor, which I find baffling. (Edit: I'm not sure if the mapped point is still correct, though... it's hard for me to tell.)

This implies that the red lines intersect correctly when the corresponding "Z" coordinate of the 2D cursor position (i.e. the relative to the window) is nearly 1... but when it degenerates to approximately 0.9 or lower, then I start seeing the "snapping" issue.

I don't understand how or why this affects anything, though. Why does the Z coordinate affect things like this? Is this normal? How do I fix this issue?

user1118321
  • 25,567
  • 4
  • 55
  • 86
user541686
  • 205,094
  • 128
  • 528
  • 886

2 Answers2

1

Just because you're using a double doesn't mean you won't have precision issues. If you're using large numbers then you're able to represent small fractional changes less precisely. Wikipedia has a decent explanation of floating point precision:

Small values, close to zero, can be represented with much higher resolution (e.g. one femtometre) than large ones because a greater scale (e.g. light years) must be selected for encoding significantly larger values.

You could try a long double as an experiment. If you try that and the problem resolves or at least improves then you know there is a problem with precision.

Start reporting some numbers that you can compare rather than relying on graphical representation. This will also eliminate the drawing code as a source of issues. If the numbers look right then there's probably something wrong with the drawing code rather than the intersection calculations.

Get some unit tests for your functions that prove that you're getting the numbers you'd expect at intermediate points.

Jon Cage
  • 36,366
  • 38
  • 137
  • 215
  • 1
    Are you aware that (1) `long double` and `double` have the same precision, and that (2) even if `long double` had greater precision it would be useless because OpenGL still only accepts `double`? From what I've been able to tell so far by examining a few numbers and looking at the graphics, I'm fairly sure that this isn't a precision issue (or if it is, it's not an obvious one), and in any case I'm using `double` everywhere, so unless you can explain specifically *what* variable's precision is too low and how that could cause what I'm seeing, it's not really going to help me track it down. – user541686 Jun 03 '14 at 08:34
  • I was under the impression [it depended upon your compiler](http://stackoverflow.com/questions/3454576/long-double-vs-double). Should be simple to check and worth a shot. If you break your code down and test the numbers at each point that should help identify _where_ the issue is occurring even if it's not a precision issue. – Jon Cage Jun 03 '14 at 08:44
  • If the limit is in OpenGL then you're going to have to adjust your coordinate system when you're at a high zoom level. – Jon Cage Jun 03 '14 at 08:46
  • One more thing you could try is simplifying your example to a single dimension (i.e. use the origin in x and y) so that you can observe the behaviour in Z more easily. Also, have you tried drawing the mouse position to the screen to double-check you're recording that information correctly? – Jon Cage Jun 03 '14 at 08:47
  • Right, but (as it turns out) I'm on Visual C++ x64 so it is impossible for `long double` to have a higher precision. Regarding the mouse position, do you mean the 2D position on the window or the 3D position on the sphere? If you mean the 2D position then yes I've verified it; if you mean the 3D position then that's exactly what the question is about: red lines are supposed to be intersecting at the corresponding 3D point on the sphere, and they're not, hence the question. – user541686 Jun 03 '14 at 08:53
  • Adjusting the coordinate system sounds like a possibility, thanks for mentioning that. Not terribly sure how well it would work though, and it might be a fair amount of work (I'm still somewhat new to 3D programming) so I'll consider it after I find easier things to try (if I do)... not terribly convinced it's a precision problem, though I can't think of anything else either. – user541686 Jun 03 '14 at 08:56
  • I was referring to the 2D position and given your platform (which you hadn't mentioned) that too is a dead a dead end then; Just making sure you'd covered all easy stuff in case there was a quick win there (I've had issues with mapping to points on the screen in the dim and distance past which turned out to be the function I was using to get the X/Y mouse coordinates). Again, I think your best bet to get to the bottom of this is to break the problem down and start reporting some more numbers so you can analytically identify which bits are working and which bits are not. Good luck! :-) – Jon Cage Jun 03 '14 at 09:14
  • If double precision was the issue here, then the drawing performed with OpenGL would suffer from the very same problem. OP didn't provide screenshots, but I'm pretty sure had this been an issue OP would address this first. Just my 2 cents. – datenwolf Jun 03 '14 at 09:14
  • @datenwolf, JonCage: Haha okay thanks anyway. While I feel there's something else going on, I still can't rule out precision issues *entirely* -- I suspect it may have to do something with the precision of the graphics card rather than my own calculations (e.g. the near and far clip rectangles)... I'll keep on looking into it and will let you know if I figure it out, thanks. – user541686 Jun 03 '14 at 09:18
  • ...and that's why I'd recommend simplified examples and looking at the numbers; If they all tie up then you know it's a rendering issue... – Jon Cage Jun 03 '14 at 09:25
  • @JonCage: Yeah, it'll take a while for me to simplify it unfortunately. In the meantime, I do have a small video of it to show what exactly is happening (notice how at low elevation, the point on the surface no longer tracks the mouse): http://1drv.ms/1huM0dF – user541686 Jun 03 '14 at 09:37
  • @JonCage: I can also confirm (by printing the values) that the coordinates **do** change smoothly as the mouse moves; it seems that the lines I subsequently render at that location end up being "snapped" to the grid for some reason. However, the values I'm printing are **exactly** the values being passed to `glVertex3d`, so does that mean the graphics card is having trouble handling small changes in the values? The changes are on the order of going from `4.85008254` to `4.85008257`, so they might not be withing the limits of a `float`, but these are `double`... – user541686 Jun 03 '14 at 09:42
  • ... actually, I just rounded the intermediate values to `float`, and now I see the values change *exactly* when the lines change. That means internally the graphics card is internally single-precision! :( @JonCage: Do you happen to know if that is normal? (Note that I'm working in the old, fixed-pipeline model; I'm not using shaders.) – user541686 Jun 03 '14 at 09:49
  • @Merhdad: Video doesn't work for me. I've personally never seen an issue like the one you're seeing here but it's been a while since I did any OpenGL. Does this help? http://blog.hvidtfeldts.net/index.php/2012/07/double-precision-in-opengl-and-webgl/ – Jon Cage Jun 03 '14 at 09:52
  • @JonCage: Try downloading the video if you tried to view it directly online? Regarding your link, I think it's about shaders, not about the fixed-function pipeline. But thanks. – user541686 Jun 03 '14 at 19:08
0

It definitely seems like the graphics card is internally truncating to 32-bit floats for rendering (but possibly using 64-bit floats for some other calculations, I'm not sure).

This seems to be true both of my Intel card and my NVIDIA card.

Re-centering the coordinate system around the center of the map seems to fix the issue.

user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    Consumer GPUs do not expose 64-bit FP in the fixed-function pipeline. You can send GL `double` precision floating-point values, but they are going to lose precision. Likewise, you can read back your matrices from GL as `double`, but when the GPU does T&L the matrices are single-precision. The API is fooling you by allowing you to pass `doubles`. There are only a handful of functions that really pass extended precision data to GL and they are all GL 3.3/4+ era extensions. – Andon M. Coleman Jun 03 '14 at 15:25
  • 1
    Long story short, the only place double precision is being observed in your code are in the functions like `gluUnProject (...)`. They are implemented on the CPU-side and actually do use the values at the precision you pass them. – Andon M. Coleman Jun 03 '14 at 15:27