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

152 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 自动适配:点阵不越界 + 面板尺寸跟随点阵
这份文档说明本项目里为了实现:
1. **圆点(点阵)始终落在面板顶面上,不越界**
2. **面板的宽/深W/D根据点阵规格自动缩放**
3. **面板厚度H保持独立可控不随点阵改变**
所做的代码改动和使用方式。
---
## 现象 & 根因
当前点阵的摆放逻辑(`src/glwidget.cpp``GLWidget::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。