Grid support for PointerDragBehavior - struggle with math

Hello, I’m working on custom PointerDragBehavior implementation and I would like to add a grid feature.

For example consider the grid cell size of 0.5 x 0.5 and the dragPlaneNormal of the PointerDragBehavior set to Vector3(0, 0, 1). User picks the target mesh at position Vector3(3, 3, 3) and drops it at Vector3(4.1, 5.4, 3). Since the dragPlaneNormal is set to very obvious value, I can tell that closest drop point snapped to the grid should be Vector3(4, 5.5, 3).

But I don’t know how to calculate it for less obvious values. What if the dragPlaneNormal was set to Vector3(1, 2, 3)? Can you help me with the math and explain the thoughts behind this a little bit please?

Hi freex,

Welcome to Babylon! If I’m understanding what you’re asking correctly, then part of why this math may be confusing is that, without additional parameters beyond just the dragPlaneNormal, the problem is under-constrained, meaning there are infinite solutions. The idea of a plane with a grid on it needs more than just a normal; it needs an orientation to prevent the gridded plane from being allowed to “spin” around its dragPlaneNormal, which would cause each grid location to trace a circle of places where it’s mathematically allowed to be. If you don’t have a specific grid orientation you’re looking for, then we can just pick one arbitrarily, at which point the problem becomes more straightforward linear algebra.

With orientation in hand, the way I would think about this problem would be to transform the problem into an unorientated space, solve it there, then transform the solution back. Essentially what you want to do is take whatever your dragPlaneNormal is and “move it” to be something obvious like (0, 0, 1). In such a space, as you mentioned, the problem is quite straightforward to solve, and you can then move the solution back into the original space, where you can use it.

To implement this, the first thing you need is to get the orientation associated with your dragPlaneNormal. You could do this any number of ways, but I’d personally recommend constructing a basis (a set of three axes) using your dragPlaneNormal, then using those axes to create a Quaternion. You can construct an arbitrary using cross products: dragPlaneNormal is your third axis; you can find your second axis by taking the cross product of dragPlaneNormal with an arbitrary other vector like (1, 0, 0) (note that, if you do this, you’ll need one fallback to avoid singularities if dragPlaneNormal ever happens to be (1, 0, 0)); and you can find your second axis as the cross product of your first and third. Note that you need your axes to be constructed sanely and you probably want them to resemble Babylon’s standard basis as closely as possible (especially regarding handedness) and that the order of arguments with cross products matters deeply, so I’d definitely recommend displaying your calculated axes in a debug view to check that you’ve got the math right. If one axis happens to be pointing in the wrong direction, reverse the order of the arguments in the cross product that created it. Once you’ve got this looking correct for one dragPlaneNormal, you should never have to fiddle with that math again.

Once you have your basis and orientation Quaternion, the rest should be pretty straightforward. Get the translation your user moved the object in world space by subtracting the original position from the new position: trans := newPos - pos. Move this translation into unoriented space by transforming it by the inverse of your Quaternion; if this worked, the Z value (or whichever axis of your basis was dragPlaneNormal) of your transformed translation should be zero. Your grid is now effectively axis-aligned, which should make it easy to “snap” the translation to a gridded value in this space. Once you have that, all you have to do is transform the snapped translation back into oriented space using your Quaternion, then add back the original position. The result should be the “gridded” drop point in your arbitrarily oriented drag plane.

(NOTE: A lot of this math depends on normals, so you need to make sure all vectors are correctly normalized before sending them into cross products and basis math. I only mention this because (1, 2, 3) is not a unit vector and would need to be normalized before it could be used in this context.)

Hope this helps, and best of luck!

2 Likes

Thank you so much for such a complex answer. I’m gonna need some time to process it, but I definitely have a clue now.