如何在Haskell中旋转OpenGL图形而又不重新评估图形对象?

斯蒂芬·洛朗(Stephane Laurent)

为简化现实,我的OpenGL程序具有以下结构:

  • 一开始有一个功能f : (Double,Double,Double) -> Double

  • 然后有一个函数triangulize :: ((Double,Double,Double) -> Double) -> [Triangle]可以triangulize f计算表面的三角形网格f(x,y,z)=0

  • 然后是displayCallback,该函数display :: IORef Float -> DisplayCallBack显示图形(也就是说,它显示三角形网格)。这里的第一个参数IORef Float是旋转图形,由于keyboardCallback稍后定义,因此当用户按下键盘上的一个键时,其值(旋转角度)会发生变化不要忘记display函数调用triangulize f

  • 那么问题就出在下一个。当用户按下键旋转图形时,将display触发功能。然后triangulize f进行重新评估,而无需重新评估:旋转图形不会更改三角形网格(即,结果triangulize f与以前相同)。

因此,有没有一种方法可以通过按一个键来旋转图形而不触发triangulize f换句话说,要“冻结”triangulize f以便仅对其进行一次评估,并且永远不要对其进行重新评估,这既费时又无用,因为无论如何结果始终是相同的。

我相信这是在Haskell OpenGL中旋转图形的标准方法(我在某些tuto中以这种方式查看),因此我认为不必发布代码。但是,如果需要,我当然可以发布它。

由于存在其他IORef控制表面参数的参数,因此实际情况更为复杂但是,我首先想知道一些针对这种简化情况的解决方案。

编辑:更多详细信息和一些代码

简化代码

所以,如果我按照上面的简化描述,我的程序看起来像

fBretzel5 :: (Double,Double,Double) -> Double
fBretzel5 (x,y,z) = ((x*x+y*y/4-1)*(x*x/4+y*y-1))^2 + z*z

triangles :: [Triangle] -- Triangle: triplet of 3 vertices
triangles =
  triangulize fBretzel5 ((-2.5,2.5),(-2.5,2.5),(-0.5,0.5))
-- "triangulize f (xbounds, ybounds, zbounds)"
--   calculates a triangular mesh of the surface f(x,y,z)=0

display :: IORef Float -> DisplayCallback
display rot = do
  clear [ColorBuffer, DepthBuffer]
  rot' <- get rot
  loadIdentity
  rotate rot $ Vector3 1 0 0
  renderPrimitive Triangles $ do
    materialDiffuse FrontAndBack $= red
    mapM_ drawTriangle triangles
  swapBuffers
  where
    drawTriangle (v1,v2,v3) = do
      triangleNormal (v1,v2,v3) -- the normal of the triangle
      vertex v1
      vertex v2
      vertex v3

keyboard :: IORef Float -- rotation angle
         -> KeyboardCallback
keyboard rot c _ = do
  case c of
    'e' -> rot $~! subtract 2
    'r' -> rot $~! (+ 2)
    'q' -> leaveMainLoop
    _   -> return ()
  postRedisplay Nothing

这会导致上述问题。每次按键'e''r'triangulize功能运行时其输出将保持不变。

真实代码(几乎)

现在,这是我的程序最接近实际的版本。实际上,它会为曲面计算一个三角形网格f(x,y,z)=l,在该网格上l可以使用键盘更改“等值线”

voxel :: IO Voxel
voxel = makeVoxel fBretzel5 ((-2.5,2.5),(-2.5,2.5),(-0.5,0.5))
-- the voxel is a 3D-array of points; each entry of the array is
--   the value of the function at this point
-- !! the voxel should never changes throughout the program !!

trianglesBretz :: Double -> IO [Triangle]
trianglesBretz level = do
  vxl <- voxel
  computeContour3d vxl level
-- "computeContour3d vxl level" calculates a triangular mesh
--   of the surface f(x,y,z)=level

display :: IORef Float -> IORef Float -> DisplayCallback
display rot level = do
  clear [ColorBuffer, DepthBuffer]
  rot' <- get rot
  level' <- get level
  triangles <- trianglesBretz level'
  loadIdentity
  rotate rot $ Vector3 1 0 0
  renderPrimitive Triangles $ do
    materialDiffuse FrontAndBack $= red
    mapM_ drawTriangle triangles
  swapBuffers
  where
    drawTriangle (v1,v2,v3) = do
      triangleNormal (v1,v2,v3) -- the normal of the triangle
      vertex v1
      vertex v2
      vertex v3

keyboard :: IORef Float  -- rotation angle
         -> IORef Double -- isolevel
         -> KeyboardCallback
keyboard rot level c _ = do
  case c of
    'e' -> rot $~! subtract 2
    'r' -> rot $~! (+ 2)
    'h' -> level $~! (+ 0.1)
    'n' -> level $~! subtract 0.1
    'q' -> leaveMainLoop
    _   -> return ()
  postRedisplay Nothing

解决方案的一部分

实际上,我已经找到一种解决方案以“冻结”体素:

voxel :: Voxel
{-# NOINLINE voxel #-}
voxel = unsafePerformIO $ makeVoxel fBretzel5 ((-2.5,2.5),(-2.5,2.5),(-0.5,0.5))

trianglesBretz :: Double -> IO [Triangle]
trianglesBretz level =
  computeContour3d voxel level

这样,我认为voxel永远不会重新评估。

但是仍然存在问题。IORef rot变化,旋转图形,那么就没有理由重新评估trianglesBretz:三角网的f(x,y,z)=level总是相同的任何旋转。

因此,我如何对display函数“嘿!rot更改时,请勿重新评估trianglesBretz,因为您会发现相同的结果”

我不知道怎么用NOINLINEtrianglesBretz,因为我做了voxeltrianglesBretz level除非level更改,否则将“冻结”的东西

这是5孔椒盐脆饼:

在此处输入图片说明

编辑:基于@PetrPudlák的答案的解决方案。

在@PetrPudlák很好的回答之后,我来到了下面的代码。我在这里给出此解决方案,以便将答案更多地放在的上下文中OpenGL

data Context = Context
    {
      contextRotation  :: IORef Float
    , contextTriangles :: IORef [Triangle]
    }

red :: Color4 GLfloat
red = Color4 1 0 0 1

fBretz :: XYZ -> Double
fBretz (x,y,z) = ((x2+y2/4-1)*(x2/4+y2-1))^2 + z*z
  where
  x2 = x*x
  y2 = y*y

voxel :: Voxel
{-# NOINLINE voxel #-}
voxel = unsafePerformIO $ makeVoxel fBretz ((-2.5,2.5),(-2.5,2.5),(-1,1))

trianglesBretz :: Double -> IO [Triangle]
trianglesBretz level = computeContour3d voxel level

display :: Context -> DisplayCallback
display context = do
  clear [ColorBuffer, DepthBuffer]
  rot <- get (contextRotation context)
  triangles <- get (contextTriangles context)
  loadIdentity
  rotate rot $ Vector3 1 0 0
  renderPrimitive Triangles $ do
    materialDiffuse FrontAndBack $= red
    mapM_ drawTriangle triangles
  swapBuffers
  where
    drawTriangle (v1,v2,v3) = do
      triangleNormal (v1,v2,v3) -- the normal of the triangle
      vertex v1
      vertex v2
      vertex v3

keyboard :: IORef Float      -- rotation angle
         -> IORef Double     -- isolevel
         -> IORef [Triangle] -- triangular mesh
         -> KeyboardCallback
keyboard rot level trianglesRef c _ = do
  case c of
    'e' -> rot $~! subtract 2
    'r' -> rot $~! (+ 2)
    'h' -> do
             l $~! (+ 0.1)
             l' <- get l
             triangles <- trianglesBretz l'
             writeIORef trianglesRef triangles
    'n' -> do
             l $~! (- 0.1)
             l' <- get l
             triangles <- trianglesBretz l'
             writeIORef trianglesRef triangles
    'q' -> leaveMainLoop
    _   -> return ()
  postRedisplay Nothing

main :: IO ()
main = do
  _ <- getArgsAndInitialize
  _ <- createWindow "Bretzel"
  windowSize $= Size 500 500
  initialDisplayMode $= [RGBAMode, DoubleBuffered, WithDepthBuffer]
  clearColor $= white
  materialAmbient FrontAndBack $= black
  lighting $= Enabled
  lightModelTwoSide $= Enabled
  light (Light 0) $= Enabled
  position (Light 0) $= Vertex4 0 0 (-100) 1
  ambient (Light 0) $= black
  diffuse (Light 0) $= white
  specular (Light 0) $= white
  depthFunc $= Just Less
  shadeModel $= Smooth
  rot <- newIORef 0.0
  level <- newIORef 0.1
  triangles <- trianglesBretz 0.1
  trianglesRef <- newIORef triangles
  displayCallback $= display Context {contextRotation = rot,
                                      contextTriangles = trianglesRef}
  reshapeCallback $= Just yourReshapeCallback
  keyboardCallback $= Just (keyboard rot level trianglesRef)
  idleCallback $= Nothing
  putStrLn "*** Bretzel ***\n\
        \    To quit, press q.\n\
        \    Scene rotation:\n\
        \        e, r, t, y, u, i\n\
        \    Increase/Decrease level: h, n\n\
        \"
  mainLoop

现在,无需执行无用的计算,即可旋转我的bretzel。

在此处输入图片说明

彼得·普德拉克

我对OpenGL不太熟悉,因此在理解详细代码方面有些困难-如果我误解了一些内容,请纠正我。

I'd try to abstain from using unsafe functions or relying on INLINE as much as possible. This usually makes code brittle and obscures more natural solutions.

In the simplest case, if you don't need to re-evaluate triangularize, we could just replace it with its output. So we'd have

data Context = Context
    { contextRotation :: IORef Float,
    , contextTriangles :: [Triangle]
    }

and then

display :: Context -> DisplayCallback

which won't reevaluate triangles at all, they'll be computed only once when Context is created.

Now if there are two parameters, rotation and level, and triangles depend on the level, but not on rotation: The trick here would be to manage dependencies properly. Now we expose the storage for parameters explicitly (IORef Float), and as a consequence, we can't monitor when the value inside changes. But the caller doesn't need to know the representation of how the parameters are stored. It just needs to store them somehow. So instead, let's have

data Context = Context
    { contextRotation :: IORef Float,
    , contextTriangles :: IORef [Triangle]
    }

and

setLevel :: Context -> Float -> IO ()

That is, we expose a function to store the parameter, but we hide the internals. Now we can implement it as:

setLevel (Context _ trianglesRef) level = do
    let newTriangles = ... -- compute the new triangles
    writeIORef trianglesRef newTriangles

And as triangles don't depend on the rotation parameter, we can have just:

setRotation :: Context -> Float -> IO ()
setRoration (Context rotationRef _) = writeIORef rotationRef

现在,依赖关系对于调用者是隐藏的。他们可以设置水平或旋转,而无需知道什么取决于它们。同时,仅在需要时(级别更改)才更新三角形。Haskell的惰性评估提供了一个不错的好处:如果在需要三角形之前级别发生多次更改,则不会对其进行评估。[Triangle]内部的thunkIORef会时要求只评估display

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何在屏幕上一次显示多个图形而又不闪烁?

如何在dplyr中对许多列进行突变而又不重复多次?

如何在Haskell中反转图形?

如何在标签中创建图形对象

如何在 NetLogo 中创建图形对象

如何在Java中创建图形对象?

如何在Java中获取图形对象?

如何在资源约束环境中运行同一进程的许多实例而又不重复存储内容

如何在C ++中返回对象而又不会超出范围?

如何在D3中旋转图形?

如何在python中围绕中心点旋转图形

如何重用功能而又不重复呢?

如何在PixiJS中通过全局坐标缩放图形对象?

如何在OpenLayers中绘制更复杂的图形对象?

如何在 LINK 字段中调整图形对象的大小?

如何在Google函数中绘制圆形或矩形等图形对象?

您如何在Haskell中表示图形?

如何对图形中的标签重新排序?-Python

如何在多线程JAVA环境中保护对象而又不损失性能?

如何在基类内部声明子类对象而又不导致递归错误?

如何旋转图形对象而不更改其位置?

在F#中,如何在不重新评估序列的情况下获得序列的头/尾

如何在ImageView中从相机放映中获取图像而又不损失其在Android中的质量?

如何在图形上旋转文本值

Java 8:如何在列表内部的对象中获取特定值,而又不将流转换回列表并在第0个位置获取值?

如何在T SQL中对XML进行编码而又不增加XML开销

如何在Anaconda中卸载pip ipykernel而又不卸载ipykernel?

如何在FlatList中更改单个图标的颜色而又不同时更改它们的颜色

如何在Windows中通过命令行退出Firefox而又不强制退出?