Catapult PackedVector2Array::find() likely uses == to check if values match. Floating point precision errors are nasty, chaotic and hard to reproduce after the fact. For example, everything could seemingly work fine when your coordinates are in the order of magnitude close to 1.0. Then after three months of development you decide to expand your maps so now coordinate ranges get much larger. Due to nature of floating point representation, you'll lose precision, comparison may start to fail sporadically and you end up going mad wondering how code that worked flawlessly for three months started exhibiting erratic behavior out of the blue.

    xyz A sample from my current coordinates:

    [(0, 0), (3.926271, -0.012693), (7.852598, -0.00889), (11.77886, 0.011278), (15.70493, 0.047683), (19.6307, 0.100194), (23.55603, 0.168685), (27.48082, 0.253023), (31.40493, 0.353082), (35.32825, 0.468731), (39.25066, 0.599841), (43.17204, 0.746282), (47.09224, 0.907927), (51.01118, 1.084645), (54.92871, 1.276307), (58.84473, 1.482784), (62.75911, 1.703947), (66.67172, 1.939666), (70.58245, 2.189812), (74.49117, 2.454257), (78.39777, 2.73287), (82.30212, 3.025522), (86.2041, 3.332085), (90.1036, 3.65243), (94.00047, 3.986425), (97.89462, 4.333943), (101.7859, 4.694855), (105.6742, 5.069031), (109.5595, 5.456342), (113.4415, 5.856658), (117.3201, 6.269851), (121.1954, 6.69579), (125.067, 7.134348), (128.9349, 7.585395), (132.799, 8.0488), (136.6592, 8.524435), (140.5153, 9.012174), (144.3672, 9.511883), (148.2148, 10.02343), (152.0579, 10.5467), (155.8966, 11.08155), (159.7305, 11.62785), (163.5597, 12.18548), (167.3839, 12.7543), (171.2031, 13.3342), (175.0172, 13.92503), (178.8259, 14.52666), (182.6293, 15.13898), (186.4272, 15.76185), (190.2194, 16.39514), ...

    And I still don't get the problem: what should change the coordinates? When I change the path, the coordinates change accordingly and are cached once again. And, if Godot returns my path points AND caches the points, along with many others, in a PackedVector2Array, how could a point be missed? The points are always part of the array set representing the curve.

    If you are suggesting the representation of the points could change anytime I start Godot, I'll look into this and see if the backed array and my points coordinates suddenly change. :-)

    get_baked_points() will return tessellated points. Tessellation involves calculation during which some precision could be lost, resulting in your tessellated (baked) points that should coincide with original curve points to end up slightly off. It's an extremely small precision error that could happen, depending on actual numbers and calculations involved. But when it happens it can cause the == operator to return false when you're expecting it to return true. Consider this example:

    var a = 1.0 / 3.0
    var v = Vector2()
    v.x = a
    print(v.x == a)

    You'd expect this to print true, right? Well, try to run it and see what happens.

      xyz

      But when it happens it can cause the == operator to return false when you're expecting it to return true

      Sorry, but, if the problem lies in the engine floats management, this can be easily solved by simply manually putting the (main) points (target of my tweens) at integer coords. For some reason, even if Vector2 expects two floats, you can pass in integers (in fact typeof returns 3, TYPE_REAL):

      var a = 45
      var v = Vector2()
      v.x = a
      print(v)
      print(v.x == a)  # true
      print(typeof(v.x))
      • xyz replied to this.

        Megalomaniak

        I know this problem, but from the beginning I have considered and accepted the possibility that there may be some inaccuracies when summing up floats. I am not even launching the Ariane 5 rocket :-)

        However, I was really surprised when I found Godot has a method that returns the length of a curve and doesn't have one to calculate the distance between two points in that curve. I'd like to know how they calculate the total length and if it always returns the same float.

        xyz

        func split_curve(curve: Curve2D):
        var out_sub_curves = []
        for i in curve.point_count-1:
        var sub_curve = Curve2D.new()
        sub_curve.add_point(curve.get_point_position(i), curve.get_point_in(i), curve.get_point_out(i) )
        sub_curve.add_point(curve.get_point_position(i+1), curve.get_point_in(i+1), curve.get_point_out(i+1) )
        out_sub_curves.push_back(sub_curve)
        return out_sub_curves

        Ok, so basically, correct me if I'm wrong, you create a new curve for every point in the main curve and you add two points to it (begin/end), then you return an array of subcurves.

        But this is a part of your design, how the sprite is supposed to move? Is there any sample I can look at?

        • xyz replied to this.

          Catapult You're missing the point. Using integers carries exact same risks. It's not a matter of "engine's float management", but rather a side effect of floating point representation itself, and it happens across platforms and languages. The precision we're talking about here is negligible as far as most calculation results are concerned. You can safely lunch Ariane 5 with that kind of precision margins. However when comparison via operator == is involved, the precision mishaps can break your code. It's sort of a rule of thumb that if you want to produce bulletproof code you should never ever use == on floats, or rely on code that you suspect might use == on floats.

          In Godot, all vector/matrix data components are 32 bit floats. Even if you assign integers to it, they'll be converted to floats. Doing calculations with 32 bit floats and then using == to compare results is always risky. Precisely because it appears to work without any problems almost all the time, until a case comes that it doesn't, producing hard to detect bugs.

          There's a reason why all Godot's vector types have is_equal_approx() method. This method should always be used instead of == when comparing float data. Now PackedVector2Array::find() probably uses it internally but we don't have a guarantee on this. It could be implementation dependent. If it uses operator ==, the find() will fail in some cases where you think it shouldn't.

            Catapult But this is a part of your design, how the sprite is supposed to move? Is there any sample I can look at?

            It's simple. On each mouse click, just assign the next sub-curve to Path2D::curve and launch a tween that animates PathFollow2D::progress_ratio or PathFollow2D::progress

              xyz

              If it uses operator ==, the find() will fail in some cases where you think it shouldn't.

              But I'm the engine user, not the engine developer: if the engine has a method find that works with Vector2 collections, I should use it safely, without caring about its implementation. Otherwise, in my humble opinion, the documentation should warn not to use this method at all, or to use it at your own risk.

              • xyz replied to this.

                xyz

                It's simple (...)

                Ok, thanks, I'll give it a try.

                Catapult It probably is safe to use. But again, the documentation does not explicitly guarantee it. So we can't be sure. IMHO it should mention how it internally determines if two vectors are equal.

                And you're not only an engine user. You're a software developer as well. And every software developer should know how floats operate. That's why the article @Megalomaniak linked is called What Every Programmer Should Know About Floating-Point Arithmetic 😉

                Imagine you want to port your code to a different language or need to use a different type of array. In c++ for example, all find() methods for all standard array types always default to using == for comparison.

                  xyz

                  IMHO it should mention how it internally determines if two vectors are equal.

                  I agree with you. Maybe PackedVector2Array should feature a find_approx method. Sorry, just jocking. :-)

                  And you're not only an engine user. You're a software developer as well. And every software developer should know how floats operate. That's why the article @Megalomaniak linked is called What Every Programmer Should Know About Floating-Point Arithmetic

                  I was quite surprised by the links he/she posted (I said I'm a novice Godot user, not a novice developer).

                  Nonetheless, I see very little danger for my code, but I will definitely remember this discussion in case something happens in the future. :-D

                    Catapult I was quite surprised by the links he/she posted (I said I'm a novice Godot user, not a novice developer).

                    @xyz was the one to bring it up, I just provided the link in response to what I quoted there(you said you were unsure you completely understood what they said). But also the link will be useful for anybody else that stumbles across the topic. 😉

                    I did a quick test and it appears that find() in packed arrays indeed uses some epsilon when comparing values. However it can still bite you in the butt when you least expect.
                    Consider this piece of code:

                    	# add an integer value to float32 array
                    	var a = PackedFloat32Array()
                    	a.push_back(1)
                    	
                    	# do some calculations that should result in the same value we started with 
                    	a[0] -= 1.1
                    	a[0] += 1.1
                    	
                    	# prints true as expected
                    	print(a[0] == 1)
                    	
                    	# prints true as expected
                    	print(is_equal_approx(a[0], 1))
                    	
                    	# prints 0, the value is fond in the array as expected
                    	print(a.find(1))

                    Now check this out. Exactly the same code, only that addition/subtraction in our dummy calculation swapped places. The code now behaves differently for seemingly no apparent reason:

                    	# add an integer value to float32 array
                    	var a = PackedFloat32Array()
                    	a.push_back(1)
                    	
                    	# do some calculations that should result in the same value we started with 
                    	a[0] += 1.1
                    	a[0] -= 1.1
                    	
                    	# prints false
                    	print(a[0] == 1)
                    	
                    	# prints true
                    	print(is_equal_approx(a[0], 1))
                    	
                    	# prints -1, the value is NOT found in the array
                    	print(a.find(1))

                    That's the kind of gotchas I was talking about.

                    But there's more 🙂 If you change 1.1 to 1.2 in the second piece of code, things now again work as expected. But if you change it yet again to 1.4 - the bug is back.
                    Is my computer possessed by a demon? 😃

                      xyz

                      That's the kind of gotchas I was talking about.

                      Is my computer possessed by a demon?

                      Well, you should report something like this to the engine developers, if you have not done it yet. Or you could call an exorcist. ;-)
                      [post update: everything written here was meant to be ironic]

                      • xyz replied to this.

                        Megalomaniak

                        if you want I can split the topic into 2 so the float precision topic is a separate one.

                        Don't worry, it's ok the way it is. :-)

                        Catapult Well, you should report something like this to the engine developers, if you have not done it yet. Or you could call an exorcist. ;-)

                        There's nothing to be done about it. It's just the nature of number representation in floating point format. The best we can do is to learn about it and adapt our code to avoid bugs. Although truth be told I haven't tried the exorcist approach yet 🙂

                          xyz

                          In Godot, all vector/matrix data components are 32 bit floats.

                          Many methods and properties in the engine use 32-bit single-precision floating-point numbers instead, equivalent to float in C++, which have 6 reliable decimal digits of precision. For data structures such as Vector2 and Vector3, Godot uses 32-bit floating-point numbers by default, but it can be changed to use 64-bit doubles if Godot is compiled with the precision=double option. (source)

                          • xyz replied to this.