Repository: silx-kit/silx
Issue: [plot3d] ScalarFieldView cut plane might drift

When the cut plane is set to use nearest interpolation, it might slowly drift over time (actually repaints).
This is due to bug in an offset that is added to avoid visual glitches when the cut plane is exactly at the edge between 2 slices of the 3D volume.
Instead of being added once, this offset is accumulated at each repaint.

To reproduce: With examples/viewer3DVolume.py set the cut plane to be visible and use nearest interpolation and then rotate the scene for a while (in order to perform many repaints).

Changed files:
silx/gui/plot3d/scene/cutplane.py

diff --git a/silx/gui/plot3d/scene/cutplane.py b/silx/gui/plot3d/scene/cutplane.py
index 79b416884..5d6f1df53 100644
--- a/silx/gui/plot3d/scene/cutplane.py
+++ b/silx/gui/plot3d/scene/cutplane.py
@@ -53,6 +53,7 @@ class ColormapMesh3D(Geometry):
     uniform mat4 transformMat;
     //uniform mat3 matrixInvTranspose;
     uniform vec3 dataScale;
+    uniform vec3 texCoordsOffset;
 
     varying vec4 vCameraPosition;
     varying vec3 vPosition;
@@ -64,7 +65,7 @@ class ColormapMesh3D(Geometry):
         vCameraPosition = transformMat * vec4(position, 1.0);
         //vNormal = matrixInvTranspose * normalize(normal);
         vPosition = position;
-        vTexCoords = dataScale * position;
+        vTexCoords = dataScale * position + texCoordsOffset;
         vNormal = normal;
         gl_Position = matrix * vec4(position, 1.0);
     }
@@ -113,6 +114,8 @@ class ColormapMesh3D(Geometry):
                                              normal=normal)
 
         self.isBackfaceVisible = True
+        self.textureOffset = 0., 0., 0.
+        """Offset to add to texture coordinates"""
 
     def setData(self, data, copy=True):
         data = numpy.array(data, copy=copy, order='C')
@@ -209,6 +212,7 @@ class ColormapMesh3D(Geometry):
         shape = self._data.shape
         scales = 1./shape[2], 1./shape[1], 1./shape[0]
         gl.glUniform3f(program.uniforms['dataScale'], *scales)
+        gl.glUniform3f(program.uniforms['texCoordsOffset'], *self.textureOffset)
 
         gl.glUniform1i(program.uniforms['data'], self._texture.texUnit)
 
@@ -281,15 +285,6 @@ class CutPlane(PlaneInGroup):
 
             contourVertices = self.contourVertices
 
-            if (self.interpolation == 'nearest' and
-                    contourVertices is not None and len(contourVertices)):
-                # Avoid cut plane co-linear with array bin edges
-                for index, normal in enumerate(((1., 0., 0.), (0., 1., 0.), (0., 0., 1.))):
-                    if (numpy.all(numpy.equal(self.plane.normal, normal)) and
-                            int(self.plane.point[index]) == self.plane.point[index]):
-                        contourVertices += self.plane.normal * 0.01  # Add an offset
-                        break
-
             if self._mesh is None and self._data is not None:
                 self._mesh = ColormapMesh3D(contourVertices,
                                             normal=self.plane.normal,
@@ -310,6 +305,23 @@ class CutPlane(PlaneInGroup):
                     self._mesh.setAttribute('normal', self.plane.normal)
                     self._mesh.setAttribute('position', contourVertices)
 
+            needTextureOffset = False
+            if self.interpolation == 'nearest':
+                # If cut plane is co-linear with array bin edges add texture offset
+                planePt = self.plane.point
+                for index, normal in enumerate(((1., 0., 0.),
+                                                (0., 1., 0.),
+                                                (0., 0., 1.))):
+                    if (numpy.all(numpy.equal(self.plane.normal, normal)) and
+                            int(planePt[index]) == planePt[index]):
+                        needTextureOffset = True
+                        break
+
+            if needTextureOffset:
+                self._mesh.textureOffset = self.plane.normal * 1e-6
+            else:
+                self._mesh.textureOffset = 0., 0., 0.
+
         super(CutPlane, self).prepareGL2(ctx)
 
     def renderGL2(self, ctx):