prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Incorrect behavior with multipe clipping planes 
  I was checking up on that nice trick where the clipping planes logic is done in the vertex shader and then interpolated to the fragment shader, with the intention of applying it in pygfx too. However, I found that this trick does not work in the case of multiple clipping planes.
  
  This can be shown with the following example:
  ```py
  import numpy as np
  from vispy import app, scene, io
  from vispy.visuals.filters.clipping_planes import PlanesClipper
  
  canvas = scene.SceneCanvas(keys='interactive', size=(800, 600), show=True)
  view = canvas.central_widget.add_view()
  
  cube = scene.visuals.Box(100, 100, 100, color=(1, 0, 0, 1), parent=view.scene)
  
  view.camera = scene.cameras.TurntableCamera(parent=view.scene, fov=60)
  
  clip_center = (0, 20, 60)
  clipping_planes = np.concatenate(
      [ np.array([[clip_center, [1, 0, 0]]]), np.array([[clip_center, [0, 1, 0]]])]
  )
  
  clipper = PlanesClipper()
  clipper.clipping_planes = clipping_planes
  cube.attach(clipper)
  
  if __name__ == '__main__':
      app.run()
  ```
  
  If you turn the camera to look from above, you'll see this:
  
  ![image](https://user-images.githubusercontent.com/3015475/135463433-c3a23bf3-842b-4982-ae78-7f0a3634300b.png)
  
  
  I think this can be explained with the following figure:
  
  ![image](https://user-images.githubusercontent.com/3015475/135464082-98220a3e-7177-4a7e-9b18-cad18c8b67d1.png)
  
  The black lines indicate two clipping planes (the shaded side is where they clip). The two blue dots represent two vertices with a line or polygon interpolating between them. Both dots are of equal distance two a plane, one on the + side and one on the - side. Now if the `min_plane_distance` (or whatever we ended up calling it :D ) is interpolated, it will have its zero point (the point where it starts clipping) in the middle.
  
  cc @brisvag 
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `vispy/visuals/filters/clipping_planes.py`
  Content:
  ```
  1 # -*- coding: utf-8 -*-
  2 # Copyright (c) Vispy Development Team. All Rights Reserved.
  3 # Distributed under the (new) BSD License. See LICENSE.txt for more info.
  4 
  5 from functools import lru_cache
  6 
  7 import numpy as np
  8 
  9 from ..shaders import Function, Varying
  10 from .base_filter import Filter
  11 
  12 
  13 class PlanesClipper(Filter):
  14     """Clips visual output based on arbitrary clipping planes.
  15 
  16     Parameters
  17     ----------
  18     cliping_planes : ArrayLike
  19         Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)
  20     coord_system : str
  21         Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)
  22 
  23     """
  24 
  25     VERT_CODE = """
  26     void clip() {
  27         // Transform back to visual coordinates and clip based on that
  28         $v_distance_from_clip = $clip_with_planes($itransform(gl_Position).xyz);
  29     }
  30     """
  31 
  32     FRAG_CODE = """
  33     void clip() {
  34         if ($v_distance_from_clip < 0.)
  35             discard;
  36     }
  37     """
  38 
  39     def __init__(self, clipping_planes=None, coord_system='scene'):
  40         tr = ['visual', 'scene', 'document', 'canvas', 'framebuffer', 'render']
  41         if coord_system not in tr:
  42             raise ValueError(f'Invalid coordinate system {coord_system}. Must be one of {tr}.')
  43         self._coord_system = coord_system
  44 
  45         super().__init__(
  46             vcode=Function(self.VERT_CODE), vhook='post', vpos=1,
  47             fcode=Function(self.FRAG_CODE), fhook='pre', fpos=1,
  48         )
  49 
  50         v_distance_from_clip = Varying('v_distance_from_clip', 'float')
  51         self.vshader['v_distance_from_clip'] = v_distance_from_clip
  52         self.fshader['v_distance_from_clip'] = v_distance_from_clip
  53 
  54         self.clipping_planes = clipping_planes
  55 
  56     @property
  57     def coord_system(self):
  58         """
  59         Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)
  60         """
  61         # unsettable cause we can't update the transform after being attached
  62         return self._coord_system
  63 
  64     def _attach(self, visual):
  65         super()._attach(visual)
  66         self.vshader['itransform'] = visual.get_transform('render', self._coord_system)
  67 
  68     @staticmethod
  69     @lru_cache(maxsize=10)
  70     def _build_clipping_planes_func(n_planes):
  71         """Build the code snippet used to clip the volume based on self.clipping_planes."""
  72         func_template = '''
  73             float clip_planes(vec3 loc) {{
  74                 float distance_from_clip = 3.4e38; // max float
  75                 {clips};
  76                 return distance_from_clip;
  77             }}
  78         '''
  79         # the vertex is considered clipped if on the "negative" side of the plane
  80         clip_template = '''
  81             vec3 relative_vec{idx} = loc - $clipping_plane_pos{idx};
  82             float distance_from_clip{idx} = dot(relative_vec{idx}, $clipping_plane_norm{idx});
  83             distance_from_clip = min(distance_from_clip{idx}, distance_from_clip);
  84             '''
  85         all_clips = []
  86         for idx in range(n_planes):
  87             all_clips.append(clip_template.format(idx=idx))
  88         formatted_code = func_template.format(clips=''.join(all_clips))
  89         return Function(formatted_code)
  90 
  91     @property
  92     def clipping_planes(self):
  93         """Get the set of planes used to clip the mesh.
  94         Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)
  95         """
  96         return self._clipping_planes
  97 
  98     @clipping_planes.setter
  99     def clipping_planes(self, value):
  100         if value is None:
  101             value = np.empty([0, 2, 3])
  102         self._clipping_planes = value
  103 
  104         clip_func = self._build_clipping_planes_func(len(value))
  105         self.vshader['clip_with_planes'] = clip_func
  106 
  107         for idx, plane in enumerate(value):
  108             clip_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])
  109             clip_func[f'clipping_plane_norm{idx}'] = tuple(plane[1])
  110
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/vispy/visuals/filters/clipping_planes.py b/vispy/visuals/filters/clipping_planes.py
  --- a/vispy/visuals/filters/clipping_planes.py
  +++ b/vispy/visuals/filters/clipping_planes.py
  @@ -24,14 +24,15 @@
   
       VERT_CODE = """
       void clip() {
  -        // Transform back to visual coordinates and clip based on that
  -        $v_distance_from_clip = $clip_with_planes($itransform(gl_Position).xyz);
  +        // pass position as varying for interpolation
  +        $v_position = gl_Position;
       }
       """
   
       FRAG_CODE = """
       void clip() {
  -        if ($v_distance_from_clip < 0.)
  +        float distance_from_clip = $clip_with_planes($itransform($v_position).xyz);
  +        if (distance_from_clip < 0.)
               discard;
       }
       """
  @@ -47,9 +48,9 @@
               fcode=Function(self.FRAG_CODE), fhook='pre', fpos=1,
           )
   
  -        v_distance_from_clip = Varying('v_distance_from_clip', 'float')
  -        self.vshader['v_distance_from_clip'] = v_distance_from_clip
  -        self.fshader['v_distance_from_clip'] = v_distance_from_clip
  +        v_position = Varying('v_position', 'vec4')
  +        self.vshader['v_position'] = v_position
  +        self.fshader['v_position'] = v_position
   
           self.clipping_planes = clipping_planes
   
  @@ -63,7 +64,7 @@
   
       def _attach(self, visual):
           super()._attach(visual)
  -        self.vshader['itransform'] = visual.get_transform('render', self._coord_system)
  +        self.fshader['itransform'] = visual.get_transform('render', self._coord_system)
   
       @staticmethod
       @lru_cache(maxsize=10)
  @@ -102,7 +103,7 @@
           self._clipping_planes = value
   
           clip_func = self._build_clipping_planes_func(len(value))
  -        self.vshader['clip_with_planes'] = clip_func
  +        self.fshader['clip_with_planes'] = clip_func
   
           for idx, plane in enumerate(value):
               clip_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])
  
verification_info: {"golden_diff": "diff --git a/vispy/visuals/filters/clipping_planes.py b/vispy/visuals/filters/clipping_planes.py\n--- a/vispy/visuals/filters/clipping_planes.py\n+++ b/vispy/visuals/filters/clipping_planes.py\n@@ -24,14 +24,15 @@\n \n     VERT_CODE = \"\"\"\n     void clip() {\n-        // Transform back to visual coordinates and clip based on that\n-        $v_distance_from_clip = $clip_with_planes($itransform(gl_Position).xyz);\n+        // pass position as varying for interpolation\n+        $v_position = gl_Position;\n     }\n     \"\"\"\n \n     FRAG_CODE = \"\"\"\n     void clip() {\n-        if ($v_distance_from_clip < 0.)\n+        float distance_from_clip = $clip_with_planes($itransform($v_position).xyz);\n+        if (distance_from_clip < 0.)\n             discard;\n     }\n     \"\"\"\n@@ -47,9 +48,9 @@\n             fcode=Function(self.FRAG_CODE), fhook='pre', fpos=1,\n         )\n \n-        v_distance_from_clip = Varying('v_distance_from_clip', 'float')\n-        self.vshader['v_distance_from_clip'] = v_distance_from_clip\n-        self.fshader['v_distance_from_clip'] = v_distance_from_clip\n+        v_position = Varying('v_position', 'vec4')\n+        self.vshader['v_position'] = v_position\n+        self.fshader['v_position'] = v_position\n \n         self.clipping_planes = clipping_planes\n \n@@ -63,7 +64,7 @@\n \n     def _attach(self, visual):\n         super()._attach(visual)\n-        self.vshader['itransform'] = visual.get_transform('render', self._coord_system)\n+        self.fshader['itransform'] = visual.get_transform('render', self._coord_system)\n \n     @staticmethod\n     @lru_cache(maxsize=10)\n@@ -102,7 +103,7 @@\n         self._clipping_planes = value\n \n         clip_func = self._build_clipping_planes_func(len(value))\n-        self.vshader['clip_with_planes'] = clip_func\n+        self.fshader['clip_with_planes'] = clip_func\n \n         for idx, plane in enumerate(value):\n             clip_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])\n", "issue": "Incorrect behavior with multipe clipping planes \nI was checking up on that nice trick where the clipping planes logic is done in the vertex shader and then interpolated to the fragment shader, with the intention of applying it in pygfx too. However, I found that this trick does not work in the case of multiple clipping planes.\r\n\r\nThis can be shown with the following example:\r\n```py\r\nimport numpy as np\r\nfrom vispy import app, scene, io\r\nfrom vispy.visuals.filters.clipping_planes import PlanesClipper\r\n\r\ncanvas = scene.SceneCanvas(keys='interactive', size=(800, 600), show=True)\r\nview = canvas.central_widget.add_view()\r\n\r\ncube = scene.visuals.Box(100, 100, 100, color=(1, 0, 0, 1), parent=view.scene)\r\n\r\nview.camera = scene.cameras.TurntableCamera(parent=view.scene, fov=60)\r\n\r\nclip_center = (0, 20, 60)\r\nclipping_planes = np.concatenate(\r\n    [ np.array([[clip_center, [1, 0, 0]]]), np.array([[clip_center, [0, 1, 0]]])]\r\n)\r\n\r\nclipper = PlanesClipper()\r\nclipper.clipping_planes = clipping_planes\r\ncube.attach(clipper)\r\n\r\nif __name__ == '__main__':\r\n    app.run()\r\n```\r\n\r\nIf you turn the camera to look from above, you'll see this:\r\n\r\n![image](https://user-images.githubusercontent.com/3015475/135463433-c3a23bf3-842b-4982-ae78-7f0a3634300b.png)\r\n\r\n\r\nI think this can be explained with the following figure:\r\n\r\n![image](https://user-images.githubusercontent.com/3015475/135464082-98220a3e-7177-4a7e-9b18-cad18c8b67d1.png)\r\n\r\nThe black lines indicate two clipping planes (the shaded side is where they clip). The two blue dots represent two vertices with a line or polygon interpolating between them. Both dots are of equal distance two a plane, one on the + side and one on the - side. Now if the `min_plane_distance` (or whatever we ended up calling it :D ) is interpolated, it will have its zero point (the point where it starts clipping) in the middle.\r\n\r\ncc @brisvag \n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n# Copyright (c) Vispy Development Team. All Rights Reserved.\n# Distributed under the (new) BSD License. See LICENSE.txt for more info.\n\nfrom functools import lru_cache\n\nimport numpy as np\n\nfrom ..shaders import Function, Varying\nfrom .base_filter import Filter\n\n\nclass PlanesClipper(Filter):\n    \"\"\"Clips visual output based on arbitrary clipping planes.\n\n    Parameters\n    ----------\n    cliping_planes : ArrayLike\n        Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)\n    coord_system : str\n        Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)\n\n    \"\"\"\n\n    VERT_CODE = \"\"\"\n    void clip() {\n        // Transform back to visual coordinates and clip based on that\n        $v_distance_from_clip = $clip_with_planes($itransform(gl_Position).xyz);\n    }\n    \"\"\"\n\n    FRAG_CODE = \"\"\"\n    void clip() {\n        if ($v_distance_from_clip < 0.)\n            discard;\n    }\n    \"\"\"\n\n    def __init__(self, clipping_planes=None, coord_system='scene'):\n        tr = ['visual', 'scene', 'document', 'canvas', 'framebuffer', 'render']\n        if coord_system not in tr:\n            raise ValueError(f'Invalid coordinate system {coord_system}. Must be one of {tr}.')\n        self._coord_system = coord_system\n\n        super().__init__(\n            vcode=Function(self.VERT_CODE), vhook='post', vpos=1,\n            fcode=Function(self.FRAG_CODE), fhook='pre', fpos=1,\n        )\n\n        v_distance_from_clip = Varying('v_distance_from_clip', 'float')\n        self.vshader['v_distance_from_clip'] = v_distance_from_clip\n        self.fshader['v_distance_from_clip'] = v_distance_from_clip\n\n        self.clipping_planes = clipping_planes\n\n    @property\n    def coord_system(self):\n        \"\"\"\n        Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)\n        \"\"\"\n        # unsettable cause we can't update the transform after being attached\n        return self._coord_system\n\n    def _attach(self, visual):\n        super()._attach(visual)\n        self.vshader['itransform'] = visual.get_transform('render', self._coord_system)\n\n    @staticmethod\n    @lru_cache(maxsize=10)\n    def _build_clipping_planes_func(n_planes):\n        \"\"\"Build the code snippet used to clip the volume based on self.clipping_planes.\"\"\"\n        func_template = '''\n            float clip_planes(vec3 loc) {{\n                float distance_from_clip = 3.4e38; // max float\n                {clips};\n                return distance_from_clip;\n            }}\n        '''\n        # the vertex is considered clipped if on the \"negative\" side of the plane\n        clip_template = '''\n            vec3 relative_vec{idx} = loc - $clipping_plane_pos{idx};\n            float distance_from_clip{idx} = dot(relative_vec{idx}, $clipping_plane_norm{idx});\n            distance_from_clip = min(distance_from_clip{idx}, distance_from_clip);\n            '''\n        all_clips = []\n        for idx in range(n_planes):\n            all_clips.append(clip_template.format(idx=idx))\n        formatted_code = func_template.format(clips=''.join(all_clips))\n        return Function(formatted_code)\n\n    @property\n    def clipping_planes(self):\n        \"\"\"Get the set of planes used to clip the mesh.\n        Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)\n        \"\"\"\n        return self._clipping_planes\n\n    @clipping_planes.setter\n    def clipping_planes(self, value):\n        if value is None:\n            value = np.empty([0, 2, 3])\n        self._clipping_planes = value\n\n        clip_func = self._build_clipping_planes_func(len(value))\n        self.vshader['clip_with_planes'] = clip_func\n\n        for idx, plane in enumerate(value):\n            clip_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])\n            clip_func[f'clipping_plane_norm{idx}'] = tuple(plane[1])\n", "path": "vispy/visuals/filters/clipping_planes.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n# Copyright (c) Vispy Development Team. All Rights Reserved.\n# Distributed under the (new) BSD License. See LICENSE.txt for more info.\n\nfrom functools import lru_cache\n\nimport numpy as np\n\nfrom ..shaders import Function, Varying\nfrom .base_filter import Filter\n\n\nclass PlanesClipper(Filter):\n    \"\"\"Clips visual output based on arbitrary clipping planes.\n\n    Parameters\n    ----------\n    cliping_planes : ArrayLike\n        Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)\n    coord_system : str\n        Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)\n\n    \"\"\"\n\n    VERT_CODE = \"\"\"\n    void clip() {\n        // pass position as varying for interpolation\n        $v_position = gl_Position;\n    }\n    \"\"\"\n\n    FRAG_CODE = \"\"\"\n    void clip() {\n        float distance_from_clip = $clip_with_planes($itransform($v_position).xyz);\n        if (distance_from_clip < 0.)\n            discard;\n    }\n    \"\"\"\n\n    def __init__(self, clipping_planes=None, coord_system='scene'):\n        tr = ['visual', 'scene', 'document', 'canvas', 'framebuffer', 'render']\n        if coord_system not in tr:\n            raise ValueError(f'Invalid coordinate system {coord_system}. Must be one of {tr}.')\n        self._coord_system = coord_system\n\n        super().__init__(\n            vcode=Function(self.VERT_CODE), vhook='post', vpos=1,\n            fcode=Function(self.FRAG_CODE), fhook='pre', fpos=1,\n        )\n\n        v_position = Varying('v_position', 'vec4')\n        self.vshader['v_position'] = v_position\n        self.fshader['v_position'] = v_position\n\n        self.clipping_planes = clipping_planes\n\n    @property\n    def coord_system(self):\n        \"\"\"\n        Coordinate system used by the clipping planes (see visuals.transforms.transform_system.py)\n        \"\"\"\n        # unsettable cause we can't update the transform after being attached\n        return self._coord_system\n\n    def _attach(self, visual):\n        super()._attach(visual)\n        self.fshader['itransform'] = visual.get_transform('render', self._coord_system)\n\n    @staticmethod\n    @lru_cache(maxsize=10)\n    def _build_clipping_planes_func(n_planes):\n        \"\"\"Build the code snippet used to clip the volume based on self.clipping_planes.\"\"\"\n        func_template = '''\n            float clip_planes(vec3 loc) {{\n                float distance_from_clip = 3.4e38; // max float\n                {clips};\n                return distance_from_clip;\n            }}\n        '''\n        # the vertex is considered clipped if on the \"negative\" side of the plane\n        clip_template = '''\n            vec3 relative_vec{idx} = loc - $clipping_plane_pos{idx};\n            float distance_from_clip{idx} = dot(relative_vec{idx}, $clipping_plane_norm{idx});\n            distance_from_clip = min(distance_from_clip{idx}, distance_from_clip);\n            '''\n        all_clips = []\n        for idx in range(n_planes):\n            all_clips.append(clip_template.format(idx=idx))\n        formatted_code = func_template.format(clips=''.join(all_clips))\n        return Function(formatted_code)\n\n    @property\n    def clipping_planes(self):\n        \"\"\"Get the set of planes used to clip the mesh.\n        Each plane is defined by a position and a normal vector (magnitude is irrelevant). Shape: (n_planes, 2, 3)\n        \"\"\"\n        return self._clipping_planes\n\n    @clipping_planes.setter\n    def clipping_planes(self, value):\n        if value is None:\n            value = np.empty([0, 2, 3])\n        self._clipping_planes = value\n\n        clip_func = self._build_clipping_planes_func(len(value))\n        self.fshader['clip_with_planes'] = clip_func\n\n        for idx, plane in enumerate(value):\n            clip_func[f'clipping_plane_pos{idx}'] = tuple(plane[0])\n            clip_func[f'clipping_plane_norm{idx}'] = tuple(plane[1])\n", "path": "vispy/visuals/filters/clipping_planes.py"}]}