Files
tactileipc3d/AUTO_PANEL_SIZING.md
2025-12-18 09:19:39 +08:00

4.4 KiB
Raw Permalink Blame History

自动适配:点阵不越界 + 面板尺寸跟随点阵

这份文档说明本项目里为了实现:

  1. 圆点(点阵)始终落在面板顶面上,不越界
  2. 面板的宽/深W/D根据点阵规格自动缩放
  3. 面板厚度H保持独立可控不随点阵改变

所做的代码改动和使用方式。


现象 & 根因

当前点阵的摆放逻辑(src/glwidget.cppGLWidget::updateInstanceBufferIfNeeded_())是:

  • 点阵中心围绕原点居中摆放
  • 每个点的中心坐标:
    • x = c * pitch - (cols-1)*pitch/2
    • z = r * pitch - (rows-1)*pitch/2
  • 所以 点中心 的范围一定在:
    • x ∈ [-gridW/2, +gridW/2],其中 gridW = (cols-1)*pitch
    • z ∈ [-gridD/2, +gridD/2],其中 gridD = (rows-1)*pitch

但每个点是“有半径”的(dotRadius),在 shader 里实际画出来的圆会占据:

  • x 方向还会向外多出 dotRadius
  • z 方向还会向外多出 dotRadius

如果面板的 W/D 没有随点阵变化而调整,就会出现你截图里那种:外圈点的圆形被画到了面板外(看起来像“越界”)。


解决思路(数学上怎么保证不越界)

要让“圆点的边缘”也不越界,需要满足:

  • 面板半宽 panelW/2 >= gridW/2 + dotRadius
  • 面板半深 panelD/2 >= gridD/2 + dotRadius

等价于:

  • panelW >= gridW + 2*dotRadius
  • panelD >= gridD + 2*dotRadius

本项目采用的就是这条公式。

如果你希望四周再留白一点,只要把公式改成:+ 2*(dotRadius + margin) 即可。


代码改动点(做了什么)

1) setSpec() 里自动计算面板 W/D

文件:src/glwidget.cpp,函数:GLWidget::setSpec(...)

做的事:

  • rows/cols/pitch/dotRadius 做了下限保护(避免负数)
  • 根据点阵规格计算面板顶面 m_panelW/m_panelD
    • gridW = (cols-1) * pitch
    • gridD = (rows-1) * pitch
    • m_panelW = gridW + 2*dotRadius
    • m_panelD = gridD + 2*dotRadius
  • 标记面板几何和 dots 几何需要重建dirty flag

这样一旦你改点阵规格,面板会自动缩放到刚好包住点阵。


2) 厚度单独控制:setPanelThickness()

文件:src/glwidget.h / src/glwidget.cpp

新增接口:

  • void setPanelThickness(float h);

它只修改 m_panelH(厚度),并标记面板几何需要重建。


3) 由于 W/D 会变:需要“重建几何体 buffer”

原因:面板顶面矩形的顶点坐标依赖 m_panelW/m_panelD,改变后必须重建 VBO/IBO/VAO 才能反映到 GPU。

做法:

  • 新增两个 dirty flag
    • m_panelGeometryDirty
    • m_dotsGeometryDirty
  • GLWidget::paintGL() 的开头判断 dirty
    • dirty 就调用 initPanelGeometry_() / initDotGeometry_() 重建
    • dots 重建后会重新分配 instanceVBO所以把 m_valuesDirty = true,确保下一帧重新上传 instance 数据

4) initPanelGeometry_() / initDotGeometry_() 现在会先删旧 buffer

因为会重复调用重建,所以这两个函数里加入了:

  • glDeleteBuffers / glDeleteVertexArrays
  • 再重新 glGen* 创建新资源

保证不会重复堆积资源(泄漏)或引用旧 VAO。


5) main.cpp 不再手动写死面板 W/D

文件:main.cpp

把原来的:

  • glw->setPanelSize(1.2f, 0.08f, 1.2f);

改为:

  • glw->setPanelThickness(0.08f);

原因:如果继续调用 setPanelSize(),你会把 setSpec() 自动算出来的 W/D 覆盖掉,越界问题就会回来。


使用方式(你应该怎么调用)

推荐顺序:

  1. glw->setSpec(rows, cols, pitch, dotRadius);
    • 会自动算出 panelW/panelD,保证点不越界
  2. glw->setPanelThickness(panelH);
    • 只改厚度,不影响自动宽深
  3. glw->setRange(minV, maxV);
  4. glw->submitValues(values);

如果你确实想手动指定面板大小(不走自动适配),可以再调用:

  • glw->setPanelSize(w, h, d);

但要理解:这会覆盖自动计算的 W/D,点可能再次越界(除非你按本文公式算好)。


可选改进(按你需求继续拓展)

  • 加边距(不要贴边):在 setSpec() 里引入 margin,把公式改成:
    m_panelW = gridW + 2*(m_dotRadius + margin)
    m_panelD = gridD + 2*(m_dotRadius + margin)
  • 让点阵填满面板但不改变 pitch:目前面板跟着 pitch 走;如果你希望面板固定而 pitch 自适配,需要反过来解 pitch。