AI 协作日志
学生 Web 实验作品平台
学号:202412211402019 · 来源:ai-log.md
# 实验四 AI协作记录
> 学号:202412211402019
> 姓名:厉梦瑜
> AI工具与使用版本(如适用):豆包
## 协作1:变换次序核验(Task A 验证 + Task B)
### 我的目标与先验预测
Task A 刚写完 `M = T·Ry·S` 那一行时,我先不急着跑程序,而是在草稿纸上预测:
滑块初始值 tx=1.2、ty=0.3、tz=0,θ=35°,s=1.2。
局部原点是 (0,0,0)。过 S(1.2) 不动,过 Ry(35°) 也不动原点,最后过 T(1.2,0.3,0) 应该落到 **(1.2, 0.3, 0)**。
我还预测:把 scale 和 angle 随便拖,这个中心应该不动,因为缩放和旋转都不动原点。
进入 Task B 后,要并排比较 `T·R·S` 和 `R·T·S`。我同样先手算:
固定 tx=2、θ=90°、s=1。
- M1 = T(2,0,0)·Ry(90°)·S(1)。原点过 S 不动,过 Ry 不动,最后被 T 搬到 **(2, 0, 0)**。
- M2 = Ry(90°)·T(2,0,0)·S(1)。先 T 到 (2,0,0),再被 Ry(90°) 旋转。绕 Y 轴右手系把 (1,0,0) 转到 (0,0,-1),所以 (2,0,0) 应该落到 **(0, 0, -2)**。
### 关键提示词
在 WebGL2 的列向量约定下,局部原点是 (0,0,0),平移向量是 (2,0,0),绕 Y 轴转 90°。
我自己手算 T·R 作用在原点上得到 (2,0,0),R·T 作用在原点上得到 (0,0,-2)。
想确认两件事:第一,Ry(90°) 对 X 方向向量的旋转方向是不是我记的这样,会不会应该是 +Z 而不是 -Z;
第二,我应该在程序里打印哪些中间量,才能区分"矩阵顺序写错"和"我手算符号记错"?
### AI建议摘要
AI 先把 Ry(θ) 的矩阵写出来核对:
```
Ry(θ) = [ cosθ 0 sinθ ]
[ 0 1 0 ]
[ -sinθ 0 cosθ ]
```
列向量约定下,(1,0,0) 乘上去得到 (cosθ, 0, -sinθ)。θ=90° 时确实是 (0,0,-1),我符号没记反。
然后建议我:不要只看最终中心,应该在控制台分别打印 point(M1, 0,0,0) 和 point(M2, 0,0,0),
并且再取一个非原点测试点,比如局部点 (1,0,0),看它变到哪里——这样能确认我乘矩阵时顺序没写反。
### 我的判断与手算
我接受 AI 的核对,Ry(90°) 把 X 正方向甩到 Z 负方向这一点没问题。
我再推一遍 M2:M2 作用于原点,先 S 不动,再 T(2,0,0) 得到 (2,0,0),
再被 Ry(90°) 变换,代入矩阵得到 (2·cos90°, 0, -2·sin90°) = (0, 0, -2),和预测一致。
### 我实际测试的参数与坐标
Task A 页面跑出来:状态栏显示模型中心 (1.20, 0.30, 0.00),黄色标记正好停在那个点。
我把 scale 从 1.2 拖到 0.3、angle 从 35° 拖到 120°,标记位置始终不动,证实"缩放和旋转不影响中心坐标"。
Task B 点"一键设置90°测试"按钮(tx=2, angle=90, scale=1),状态栏打印:
- T·R·S 中心:(2.00, 0.00, 0.00)
- R·T·S 中心:(0.00, 0.00, -2.00)
和手算完全对上。我又把 angle 改成 180° 试了一次:
- M1 中心仍是 (2,0,0);
- M2 中心变成 (-2.00, 0.00, 0.00)。
手算:(2,0,0) 绕 Y 转 180°,cos180°=-1,-sin180°=0,得到 (-2,0,0),也对。
### 运行现象/数值结果
θ=0° 时两个中心都是 (2,0,0),画面完全重合。
我一开始以为这是不是说明 T 和 R 在某种情况下可交换,
AI 提醒我:θ=0° 时 Ry 是单位矩阵,两边都退化成 T,当然相同;这是平凡情形,不能推广。
我把 θ 改成 30°、60° 再看,两个中心立刻分开,确认矩阵乘法一般不可交换。
### 修正结论
M1 里 T 在最左边,平移向量是在旋转完成之后、在最终世界坐标系里加的,
所以立方体转成什么角度,都沿世界 X 轴平移 2。
M2 里 T 在 R 右边,平移先发生,于是 (2,0,0) 这个向量本身也被旋转矩阵"带走"了,
最后落点会随着 θ 改变。这就是为什么同样的 T 和 R,换个顺序结果完全不同。
## 协作2:程序调试(Task C 的 speed 滑块不生效)
### 错误现象与自己的初步分析
Task C 写完之后,立方体能动,暂停按钮也正常,
但我在"动画速度(度/秒)"那个输入框里把 35 改成 120,画面转速一点没变。
我第一反应是 loop 里 dt 没更新,或者 angle 累加那一行被 playing 条件挡住了。
但我看了一眼,暂停时画面确实是停的,说明 playing 判断是对的,angle 也在累加,
问题应该不在时间步长本身。
### 提示词
我的 WebGL2 动画里,HTML 上有一个 id="speed" 的数字输入框,单位是度/秒。
我在 requestAnimationFrame 回调里写的是 angle = (angle + 45 * dt) % 360,
改 speed 输入框的值,立方体转速完全不变。
我已经确认暂停按钮生效、dt 是正常递增的。
请帮我列出三个最可能的原因,每个原因告诉我应该在哪一行打断点或者打印什么变量来区分,
### AI提出的可能原因
AI 给了三个怀疑方向:
1. loop 里角速度是写死的常量 45,根本没有读 speed 这个控件的值。
2. 就算读了 speed,onInputs 绑定列表里没包含 "speed",改完输入框不会触发重绘,要等下一帧才生效(但下一帧其实也会读,所以这不是主因)。
3. 输入框的值可能是字符串而不是数字,直接乘 dt 产生 NaN,导致 angle 停在 NaN,但这种情况画面会直接卡住,不会"转得很慢但匀速"。
### 我逐项完成的测试
- 我先在 loop 里 console.log 了一下 angle,发现它在匀速增长,不是 NaN,排除原因 3。
- 然后我回头看 loop 那一行,确实写的是 `45 * dt`,根本没出现 `c.num("speed")`。原因 1 命中。
- 我又检查了 onInputs 绑定列表,里面只有 ["px","pz","mode"],speed 没绑。原因 2 也命中。
### 真实原因与修复
两个问题都存在:
1. 把 `45 * dt` 改成 `c.num("speed") * dt`,让每帧都读当前滑块值;
2. 把 onInputs 改成 `["px","pz","mode","speed"]`,改完立刻重绘状态栏。
改完后把 speed 从 35 调到 120,立方体转速立刻变成原来的三倍多,符合预期。
### AI建议是否采纳及理由
三条建议我都采纳了。最关键的是第一条——我之前一直以为"loop 每帧都在跑,速度自然会跟着控件走",
但其实我把角速度写死在代码里了,控件只是个摆设。
这让我意识到:HTML 里出现一个 input,不代表 JS 真的读了它,必须去 JS 里 grep 一下这个 id 有没有被用到。
## 协作3:主题场景协商与层次验证(Task D + Task E)
### 我希望实现的对象关系
Task D 写完两级父子(基座→肩膀→末端探头)后,我做了一组控制变量测试:
固定 pitch=0,拖 yaw 从 0° 到 60°,肩部和末端世界坐标一起绕基座转,相对夹角不变;
固定 yaw=0,拖 pitch 从 -25° 到 30°,末端跟随臂段俯仰,基座位置始终是 (0,-1.3,0) 不动。
这证明子对象的局部 R 只影响自己和下游,不会反向修改父位姿。
进入 Task E 时,我已经实现了 T·R·S 复合变换、绕任意点 P 公转、以及 base→shoulder→tip 的两级父子继承。
现在要把这些机制合在一个场景里,至少 3 个可区分部件、至少 1 组真实父子关系(不能靠相同角速度伪装)、至少 2 个独立可调参数。
我不想只是把老师给的医学设备原样复制,想稍微改一下让它"是我自己做的"。
### 提示词
我已经实现了 T·R·S 复合变换、绕任意点 P 公转、以及 base→shoulder→tip 的两级父子继承。
现在要做一个自主三维联动场景,约束是:只能用单位立方体、不能引入光照和相机、
至少 3 个可区分部件、至少 1 组真实父子关系(不能靠相同角速度伪装)、至少 2 个独立可调参数。
请给我两个 40 分钟内能做完的方案,比较一下:部件关系、矩阵链复杂度、
### AI提供的两个方案与比较
AI 给了两个:
方案一:行星—卫星系统。中心行星自转,卫星绕行星公转,最外层再加一个更远的小卫星。
矩阵链简单,但父子关系很弱(卫星其实是绕点公转,不是真正的层级关节),
容易被老师判定为"只是两个独立旋转,没有父子"。
方案二:保留老师示例的"定位机械臂 + 旋转样本台",但做两处实质改动:
机械臂这边做两级父子(基座偏航→肩膀俯仰→末端探头跟随),
样本台这边做自转 + 标记公转两套机制并列。
这样父子关系是真实的(tipPose = shoulderPose·T),
公转也是真实的(T(P)·R·T(-P) 结构),机制数够,验证点也多。
### 我的选择理由
我选方案二。理由是:方案一看着炫,但父子关系其实是"假"的——
卫星绕行星转本质是绕点公转,和我 Task C 做的东西重复,没有体现层次变换。
方案二把父子和公转放在同一个场景里,正好把 Task C 和 Task D 的知识点各用一次,
老师一眼能看出两种机制都在。
我做的实质改动不是改颜色:
- 把机械臂初始 yaw 设成 25°、pitch 设成 -20°,避免和老师默认值一样;
- 样本台的自转角速度设成 65°/s,标记公转角速度设成 42°/s,故意让两个角速度不同,
这样不会被误以为是"同一个转速复制了两份"。
### 实际矩阵链与交互参数
机械臂侧:
- basePose = T(-2,-1.3,0) · Ry(yaw)
- shoulderPose = basePose · T(0,0.55,0) · Rz(pitch)
- tipPose = shoulderPose · T(0,2.2,0)
样本台侧:
- specimenDraw = T(samplePivot) · Ry(spinAngle) · S(0.7,0.78,0.7)
- orbitMarker = T(samplePivot) · Ry(orbitAngle) · T(1.16,0.28,0) · S(0.22)
交互参数:yaw、pitch、speed 三个滑块,加暂停/继续、重置、PNG 截图。
### 两组对照测试
测试一(父动带子):固定 pitch=-20°,把 yaw 从 0° 拖到 60°。
观察:基座转了,整个机械臂和探头跟着一起绕基座转,相对夹角不变。
状态栏末端坐标从大约 (-2, 0.4, 0) 一带平滑变化。
测试二(子动不影响父):固定 yaw=25°,把 pitch 从 -20° 拖到 30°。
观察:机械臂上下俯仰,探头跟着臂端走,但基座在地面上完全不动。
这就证明了子对象的局部旋转不会反向影响父对象的位姿。
### 最终结果和反思
做完之后我最大的体会是:位姿矩阵和绘制矩阵必须分开。
一开始我差点把臂段的非均匀缩放 S(0.38, 2.2, 0.45) 直接乘进 shoulderPose,
那样下一步 tipPose 再乘 T(0,2.2,0) 时,这个 2.2 会被拉伸,探头位置就错了。
现在的写法是位姿里只放 T 和 R,S 永远放在每个部件自己的绘制链最后,
这样父子传递的永远是"干净的位置和朝向",不会被形状缩放污染。
PNG 截图保存后打开是一张正常的三维场景图,不是空白,说明 preserveDrawingBuffer
在 engine.js 里已经处理好了。
## Challenge:三自由度机械臂
在 Task D 两级父子基础上增加肘关节,形成基座→肩→肘→手三级链:
```
basePose = T(0,-1.3,0) · Ry(yaw)
shoulderPose = basePose · T(0,0.55,0) · Rz(pitch)
elbowPose = shoulderPose · T(0,1.3,0) · Rz(elbow) ← 新增
handPose = elbowPose · T(0,1.3,0)
```
验证:拖 elbow 时大臂和基座不动,只有小臂和手弯曲;拖 yaw 时全部跟随。
两段臂长度各 1.3,便于手算末端位置。新增 challenge.html 与 js/challenge.js,
并在 index.html 加入口卡片。