godotengine / godot

Godot Engine – Multi-platform 2D and 3D game engine
https://godotengine.org
MIT License
89.4k stars 20.24k forks source link

math2d cross product method not accesible #7054

Closed JohnMeadow1 closed 7 years ago

JohnMeadow1 commented 7 years ago

Operating system or device - Godot version: Any. Godot 2.1 stable

Issue description (what happened, and what was expected): Vector2().cross() method is defined in math2d.cpp but not accessible form engine itself. 3D counterpart: Vector3().cross() method works fine.

Steps to reproduce: calling from within gdscript: Vector2().cross(Vector2()) result in: method not defined for Vector2

akien-mga commented 7 years ago

Cross product does not exist in 2D space. There are analogs to it, but they're not the same as the 3D cross product which takes two 3D vectors and gives you the orthogonal one.

http://www.gamedev.net/topic/289972-cross-product-of-2d-vectors/#entry2828973 http://www.gamedev.net/topic/289972-cross-product-of-2d-vectors/#entry2828289

JohnMeadow1 commented 7 years ago

I did not explained myself properly. I am aware of that cross product of 2D vectors is different thing than cross product of 3D vectors. After all in 2D you get scalar. Which is useful on its own. You can actually calculate coss product for n-dimensions if you like. Not useful for most, but still possible.

My issue is that we actually have correct implementation of cross product of 2D vectors in the source code, but it is not accessible from the engine.

In: _core/math/math_2d.h_ we have:

float dot(const Vector2& p_other) const;
float cross(const Vector2& p_other) const;

and in: _core/math/math_2d.cpp_ we have:

float Vector2::dot(const Vector2& p_other) const {

    return x*p_other.x + y*p_other.y;
}

float Vector2::cross(const Vector2& p_other) const {

    return x*p_other.y - y*p_other.x;
}

Again Vector2.dot() is available, Vector2.cross() not available. To me it looks like a minor bug.

Using general equation for cross product. In 2D we obviously lose Z component and in return, scalar is calculated. I personally use this to determine angle between two vectors and whether one of the vectors is on the left or right side of the other one. My usage:

target    = object.get_pos() - self.get_pos()
var dot   = target.x * self.direction.x + target.y * self.direction.y
var cross = target.x * self.direction.y - target.y * self.direction.x
angle_to_target = atan2( cross, dot )
ghost commented 7 years ago

Cross product exists only in 3 and 7 dimensions. It doesn't exist in 2 dimensions. What that piece of code does is, it embeds the 2D vector in the xy plane of 3D space, and takes the cross product with the unit vector along z in 3D, which results in another vector in the xy plane. You can achieve the same effect using the Vector2::tangent, and with math that does make sense in 2D.

I think it should rather be removed. I'll ask @reduz and if he agrees, I'd like to remove it as a part of PR #7445. It seems to me it just confuses people.

bojidar-bg commented 7 years ago

@tagcup It is really useful to find the direction in which to rotate a vector in order to make it reach another vector -- it is even used deep inside the 2D physics engine (for impulse rotation calculations). I think removing it won't be as good idea as exposing it for reals.

ghost commented 7 years ago

@bojidar-bg I'm not arguing it's usefulness. There're dozens of operations that can be useful with 2D number arrays. What I'm saying is that, Vector2 supposedly implements a 2D vector in Euclidian space, for which "cross" makes no sense, meaning cross doesn't belong with Vector2.

I think the best course of action is to move it to 2D physics code (which is the only place it is used), and call it the z-component of the angular momentum, which is the only component that can exist for motion confined to the xy plane.

Vector2::tangent will still be there, and you can use it for such calculations.

Furthermore, to do different operations with 2D embedded in 3D, you can still create an intermediate 3D vector, and pick out the components you need at the end.

ghost commented 7 years ago

You can actually calculate coss product for n-dimensions if you like. Not useful for most, but still possible.

No, you can't. You can actually prove that you can't. If you go through that preprint (mind you, that's not even published stuff), you'll see that their construction (I wouldn't call it "cross product") is not anticommutative, which a cross product should be. You can't simply change the definition of cross product to suit your needs. It's something else, you can call it "a certain generalization of cross-product we invented", "crossy-product", or whatever you want, it just isn't a cross product.

You can get the both cross products using Cayley-Dickson construction: you can start from real numbers to complex numbers (isomorphic to SO(2)), and go to quaternions (isomorphic to SU(2) and a double cover of SO(3)) which gives you a 3-dimensional cross product. Further applying Cayley-Dickson construction to quaternions, you get octonions, which gives you a family of seven dimensional cross products although you lose associativity and Jacobi identity doesn't work at that point.

bojidar-bg commented 7 years ago

@tagcup I'm not talking of "correctness", I'm talking of "usefulness" -- even if it is mathematically incorrect to speak of a cross product of two 2D vectors, practically it is very evil to force people to reimplement the square wheel every time because they have to cast it to 3D vector and back to 2D vector again.

Basically the difference between this:

  var direction = sign(current.cross(target))

and that:

  var direction = sign(Vector3(current.x, current.y, 0).cross(Vector3(target.x, target.y, 0)).x)

lies in the maintainability and simplicity of the expression of logic. Programmers aren't expect to reimplement the wheel themselves in Godot, but rather we should polish some wheels and tires beforehand for them to use when needed. The difference between using C and JS isn't just the static typing or something like that, it is that in C you have to reimplement all kinds of wheels for dynamic arrays, maps, objects, etc., but JS has them all out of the box (no, I'm not saying you should use JS, I'm saying that using C is much more complex for simpler tasks).

ghost commented 7 years ago

@bojidar-bg Yes, I understand your point. We can define dozens of small mathematical functions acting on 2D float arrays (shearing, complex numbers, strange bit-twiddling operations on IEEE-754 vectors, rsqrt, dozens of SIMD-related functions, exotic algebras, embedding 2D vectors in higher dimensional spaces, 2D vectors in Riemannian manifolds, you name it), should we define all because they are useful in a certain context, even though they don't represent vectors in 2D Euclidian geometry? If you draw your line at usefulness, you should include those too and more because I can tell each one of those can be useful in certain applications in games.

It's not my decision at the end of the day, but I don't agree that usefulness trumps mathematical correctness in a mathematical library.

You can build on top of that library by defining your own functions.

Again, I'm repeating this for the third time, but you can simply use Vector2::tangent in GDScript, which correctly describes what's going on there geometrically in that case. You can wrap it in a function in you want self-documenting function names because sign(current.cross(target)) doesn't seem to be meaningful in 2D either.

And I wouldn't call defining a one-liner domain-specific function "reimplement the square wheel".

JohnMeadow1 commented 7 years ago

I'll argue that, cross product in 2D is not correct concept. I'll quote other people on the subject:

However, the "cross product" of two 2D vectors is not a 2D vector; instead, it is a scalar. How does this work? The cross product is basically treating the 2D vectors like 3D vectors with their z-component equal to zero, and the result is the z-component of the resulting 3D vector. http://allenchou.net/2013/07/cross-product-of-2d-vectors/

As we can see from the figure above, the cross product is only valid for 3D. But for rotations in 2D, the cross product is a requirement. Since we can imagine the x and y Cartesian coordinates to be on the same 2D plane, the cross product of any two 2D vectors will point in the direction of the normal of that plane (z-axis). Thus this result can be returned as a scalar. https://www.codeproject.com/Articles/1029858/Making-a-D-Physics-Engine-The-Math

We are developing game engine that is useful to people. And people use this 2D Cross product. They implement this in their math libraries for a reason. Example implementations I have found :

float cross(const Vec2 &a, const Vec2 &b)
{
  // just calculate the z-component
  return a.x * b.y - a.y * b.x;
}
// Two crossed vectors return a scalar pointing in the z direction
float cross(Vector2 a, Vec2 b)
  return a.x * b.y - a.y * b.x;
//A vector crossed with a scalar (z-axis) will return a vector on the 2D Cartesian plane
Vector2 cross(Vector2 a, float s)
  return Vec2(s * a.y, -s * a.x);

I do not see the point to force people to write custom math libraries to supplement "mathematical correct" libraries. Especially if this supplementary library will have around 3 functions.

Even Unreal engine has this: https://docs.unrealengine.com/latest/INT/BlueprintAPI/Math/Vector/CrossProduct_2D/index.html

ghost commented 7 years ago

But for rotations in 2D, the cross product is a requirement

No, it isn't. Rotations in 2D are elements of the Lie group SO(2), and Godot uses the fundamental representation of it Mat(2,R). It has nothing to do with cross product.

@JohnMeadow1 Apart from this, I'm not seeing anything new in those blog posts. It's the same wrong arguments I already replied to above.

And people use this 2D Cross product. They implement this in their math libraries for a reason.

There's lots of nonsensical stuff in other people's math libraries. This doesn't sound like a convincing argument to me.

I also mentioned this in #7849 but Godot's 2D engine is separate from its 3D engine and uses a different coordinate system: a left-handed coordinate system with Y-axis pointing down.

The cross function exists in C++ mainly for calculations related to torques in the physics code.

So even from a pure practical point of view which totally ignores mathematical correctness, given the fact that Godot uses a Y-down convention for 2D, I'd say adding such functions (that to get everything right as a whole, they have to embed 2D into 3D but in a left-handed coordinate system) will only confuse people. And I don't just mean GDScript users --this is why there were so many troubles with atan2 in the core.

I also don't think the comparison with Unreal is fair even from a practical point of view, because Godot also has functions that don't exist in Unreal, which includes tangent, angle_to, etc.

And I'm not sure how many times I'm repeating myself about this, but you can simply use Vector2::tangent and Vector2::angle_to, which do make sense in 2D mathematically. These cover all the realistic use cases mentioned in this issue.

ghost commented 7 years ago

@JohnMeadow1 By the way, Vector2::angle_to does exactly what you described earlier.

JohnMeadow1 commented 7 years ago

@tagcup I had to check twice. I was definitely confused by angle_to function. You are correct I can achieve the same using angle_to function. Cross is redundant here, and I can see why it might be confusing to other users.

lmjk commented 6 years ago

Please, could this be reopened?

I have needed the cross product of 2D vectors (by treating them as vectors [x y 0]), starting from the first weeks of fiddling with Godot and GDScript. Because I'm not that familiar with actually calculating it myself by hand, I had to invest effort finding out how to do it. (However, at first I defined it quickly by mapping Vector2 to Vector3 and picking the z coordinate of the cross product. It looked messy and was probably inefficient.)

Where do I need such a cross product? At least for doing custom physics, calculating torque, but there probably are others. It is pretty elementary in vector math and not nonsensical at all. Regarding mathematical correctness, there are no such things as correct and incorrect definitions of mathematical ideas, as long as they are consistent by themselves. You simply want to choose the definitions that are most meaningful for the application at hand. This one, the cross product in 2D, is meaningful here for helping create games that have a 2D space.

Handedness of the provided operation and Godot's coordinate systems can be stated in the documentation, along with the tip that you can calculate the left-handed cross product using the right-handed operation by swapping the order of the vectors, since U×V = -(V×U).