Repository: ManimCommunity/manim

Issue:
Wrong tip orientation when using `put_start_and_end_on` on `Arrow` in a `ThreeDScene`
## Description of bug / unexpected behavior
When using the [`put_start_and_end_on`](../blob/4831e1dc77bfd69eb749532929cdb75ed2800f35/manim/mobject/geometry.py#L1020) on a [`Arrow`](../blob/4831e1dc77bfd69eb749532929cdb75ed2800f35/manim/mobject/geometry.py#L1316) object in a [`ThreeDScene`](../blob/4831e1dc77bfd69eb749532929cdb75ed2800f35/manim/scene/three_d_scene.py#L25), the arrow tip does not rotate correctly.

## How to reproduce the issue
```py
class BadArrowOrientation(ThreeDScene):
    def construct(self):
        self.set_camera_orientation(
            phi=70 * DEGREES,
            theta=50 * DEGREES
        )
        axes = ThreeDAxes()

        vec = Arrow(
            start=(0, 2, 3), 
            end=(0, 5, 1.5),
            max_tip_length_to_length_ratio=0.5,
            buff=0
        )

        self.add(axes, vec)
        self.wait()

        self.play(
            vec.animate.put_start_and_end_on(
                (0, 3, 2.5),
                (0, -2, 2.5)
            )
        )
        self.wait()
```

## Additional media files

![BadArrowOrientation](https://user-images.githubusercontent.com/65306057/135140620-4c8381c0-7529-49ff-bef9-5800e6aa1afc.png)

## Logs
<details><summary>Terminal output</summary>
<!-- Add "-v DEBUG" when calling manim to generate more detailed logs -->

```
Manim Community v0.10.0

[09/28/21 20:03:27] DEBUG    Animation with empty mobject      animation.py:149
                    INFO     Caching disabled.            cairo_renderer.py:100
                    DEBUG    List of the first few        cairo_renderer.py:115
                             animation hashes of the                           
                             scene: ['uncached_00000']                         
[09/28/21 20:03:29] INFO     Animation 0 : Partial     scene_file_writer.py:422
                             movie file written in 'fakepath                   
                             \1080p60\partial_movie_files\Test\uncac           
                             hed_00000.mp4'                                    
                    INFO     Caching disabled.            cairo_renderer.py:100
                    DEBUG    List of the first few        cairo_renderer.py:115
                             animation hashes of the                           
                             scene: ['uncached_00000',                         
                             'uncached_00001']                                 
[09/28/21 20:03:30] INFO     Animation 1 : Partial     scene_file_writer.py:422
                             movie file written in 'fakepath                   
                             \1080p60\partial_movie_files\Test\uncac           
                             hed_00001.mp4'                                    
                    DEBUG    Animation with empty mobject      animation.py:149
                    INFO     Caching disabled.            cairo_renderer.py:100
                    DEBUG    List of the first few        cairo_renderer.py:115
                             animation hashes of the                           
                             scene: ['uncached_00000',                         
                             'uncached_00001',                                 
                             'uncached_00002']                                 
[09/28/21 20:03:32] INFO     Animation 2 : Partial     scene_file_writer.py:422
                             movie file written in 'fakepath                   
                             \1080p60\partial_movie_files\Test\uncac           
                             hed_00002.mp4'                                    
                    DEBUG    Partial movie files to    scene_file_writer.py:471
                             combine (3 files): ['fakepath                     
                             \\1080p60\\p                                      
                             artial_movie_files\\Test\                         
                             \uncached_00000.mp4', 'fakepath                   
                             \\1080p60\                                        
                             \partial_movie_files\\Tes                         
                             t\\uncached_00001.mp4', '                         
                             fakepath\\1080p6                                  
                             0\\partial_movie_files\\T                         
                             est\\uncached_00002.mp4']                         
[09/28/21 20:03:33] INFO                               scene_file_writer.py:606
                             File ready at 'fakepath\1080p60\Test.mp4'         
                                                                               
                    INFO     Rendered Test                         scene.py:233
                             Played 3 animations                               
                    INFO     Previewed File at: 'fakepath\      file_ops.py:193
                             1080p60\T                                         
                             est.mp4'                                          

```

<!-- Insert screenshots here (only when absolutely necessary, we prefer copy/pasted output!) -->

</details>


## System specifications

<details><summary>System Details</summary>

- OS (with version, e.g Windows 10 v2004 or macOS 10.15 (Catalina)): Windows 10 21H1
- RAM: 8 GB
- Python version (`python/py/python3 --version`): 3.8.5
- Installed modules (provide output from `pip list`): a lot
</details>

<details><summary>LaTeX details</summary>

+ LaTeX distribution (e.g. TeX Live 2020): MiKTeX
+ Installed LaTeX packages:
<!-- output of `tlmgr list --only-installed` for TeX Live or a screenshot of the Packages page for MikTeX -->
</details>

<details><summary>FFMPEG</summary>

Output of `ffmpeg -version`:
```
ffmpeg version 4.4-full_build-www.gyan.dev Copyright (c) 2000-2021 the FFmpeg developers
built with gcc 10.2.0 (Rev6, Built by MSYS2 project)
configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-lzma --enable-libsnappy --enable-zlib --enable-librist --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-libbluray --enable-libcaca --enable-sdl2 --enable-libdav1d --enable-libzvbi --enable-librav1e --enable-libsvtav1 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-libass --enable-frei0r --enable-libfreetype --enable-libfribidi --enable-libvidstab --enable-libvmaf --enable-libzimg --enable-amf --enable-cuda-llvm --enable-cuvid --enable-ffnvcodec --enable-nvdec --enable-nvenc --enable-d3d11va --enable-dxva2 --enable-libmfx --enable-libglslang --enable-vulkan --enable-opencl --enable-libcdio --enable-libgme --enable-libmodplug --enable-libopenmpt --enable-libopencore-amrwb --enable-libmp3lame --enable-libshine --enable-libtheora --enable-libtwolame --enable-libvo-amrwbenc --enable-libilbc --enable-libgsm --enable-libopencore-amrnb --enable-libopus --enable-libspeex --enable-libvorbis --enable-ladspa --enable-libbs2b --enable-libflite --enable-libmysofa --enable-librubberband --enable-libsoxr --enable-chromaprint
libavutil      56. 70.100 / 56. 70.100
libavcodec     58.134.100 / 58.134.100
libavformat    58. 76.100 / 58. 76.100
libavdevice    58. 13.100 / 58. 13.100
libavfilter     7.110.100 /  7.110.100
libswscale      5.  9.100 /  5.  9.100
libswresample   3.  9.100 /  3.  9.100
libpostproc    55.  9.100 / 55.  9.100
```
</details>

## Additional comments

Maybe using [`position_tip`](../blob/4831e1dc77bfd69eb749532929cdb75ed2800f35/manim/mobject/geometry.py#L171) could help. This bug doesn't appear when the arrow is in the `x` and `y` plane.



Discussion:
Are you using v.0.10.0 ? I thought this was fixed in #1924 
> 
> 
> Are you using v.0.10.0 ? I thought this was fixed in #1924

I am indeed. The issue you are referencing is only fixing tip orientation when adding object on scene. It is indeed working in `v0.10.0` when adding an arrow, but the bug still appears when using [`put_start_and_end_on`](../blob/4831e1dc77bfd69eb749532929cdb75ed2800f35/manim/mobject/geometry.py#L1020) on it.

**_Edit_**:
Just took a deeper look at it. A workaround that could do it would be to set the `scale_tips` parameter of the [`scale`](../blob/c4b96ffc8013e27df3409d02f7d7e2df12e20e3d/manim/mobject/geometry.py#L1425) method (from the `Arrow` object) to `True`. This way, the tip gets oriented correctly, but is also scaled in accordance with the new length of the arrow.

I think the best would be to have a new `scale_tips` parameter for the [`put_start_and_end_on`](../blob/4831e1dc77bfd69eb749532929cdb75ed2800f35/manim/mobject/geometry.py#L1020) method. If when using it, the user wants to have the tip scaled in accordance to the new length, he will then set this parameter to `True`.

Now the thing is without `scale_tips` set to `True`, the bug remains. Taking a look at the [`scale`](../blob/c4b96ffc8013e27df3409d02f7d7e2df12e20e3d/manim/mobject/geometry.py#L1425) method:

https://github.com/ManimCommunity/manim/blob/c4b96ffc8013e27df3409d02f7d7e2df12e20e3d/manim/mobject/geometry.py#L1425-L1473

We can see that the tips get removed before scaling with `pop_tips`, and are then readded with `add_tip`. In a way that I can not really understand, using `add_tip` without any parameters (that is without the tip from `pop_tips`) will fix the orientation.

By taking a look at the [`add_tip`](../blob/c4b96ffc8013e27df3409d02f7d7e2df12e20e3d/manim/mobject/geometry.py#L132) method, calling it without any tip will create a new one and position it.
But what's weird is when calling `add_tip` with the old tip (like it is done right now), it should also position it using the [`position_tip`](../blob/c4b96ffc8013e27df3409d02f7d7e2df12e20e3d/manim/mobject/geometry.py#L171) method (which has been modified to take orientation into account in #1924).
So that is why I can't really understand why creating a new tip will have a correct orientation, while using the old tip as the `tip` argument will not (knowing that both will use the `position_tip` method).

Resolving patch:
diff --git a/manim/mobject/geometry.py b/manim/mobject/geometry.py
--- a/manim/mobject/geometry.py
+++ b/manim/mobject/geometry.py
@@ -181,15 +181,17 @@ def position_tip(self, tip, at_start=False):
         tip.rotate(
             angles[2] - PI - tip.tip_angle,
         )  # Rotates the tip along the azimuthal
-        axis = [
-            np.sin(angles[2]),
-            -np.cos(angles[2]),
-            0,
-        ]  # Obtains the perpendicular of the tip
-        tip.rotate(
-            -angles[1] + PI / 2,
-            axis=axis,
-        )  # Rotates the tip along the vertical wrt the axis
+        if not hasattr(self, "_init_positioning_axis"):
+            axis = [
+                np.sin(angles[2]),
+                -np.cos(angles[2]),
+                0,
+            ]  # Obtains the perpendicular of the tip
+            tip.rotate(
+                -angles[1] + PI / 2,
+                axis=axis,
+            )  # Rotates the tip along the vertical wrt the axis
+            self._init_positioning_axis = axis
         tip.shift(anchor - tip.tip_point)
         return tip
 


Test patch: