AI 协作日志
学生 Web 实验作品平台
学号:202412211401023 · 来源:ai-log.md
# 实验三 AI协作与核验记录 AI工具:豆包(Doubao)桌面版,HTML/JavaScript 代码辅助;Node.js v22 用于矩阵数值复算与核验。 完成方式:先在代码中给出自己的实现与预测,再与 AI 讨论确认,最后在本机浏览器中逐页运行并记录证据。以下三次协作的关键提示词均为本人实际输入的原文。 ## 协作1:矩阵与变换次序(Task B,3分) - 我的目标和预先预测: 目标是在 `js/app.js` 的 `bItem()` 中实现"绕世界原点旋转"与"绕对象自身中心旋转"两种模式,并解释矩阵乘法次序。 我自己的预测:正方形初始中心 C=(0.50, 0.25)、绕 Z 轴 45°。若采用 `M=Rz(θ)·T(C)`(列向量,右侧先执行),先平移到 C、再绕世界原点转,正方形会整体绕画布中心公转,其中心轨迹是一个圆;若采用 `M=T(C)·Rz(θ)`,先绕局部原点自转、再平移到 C,正方形应停留在 (0.50,0.25) 原地自转。 - 关键提示词(原文): "正方形中心在(0.50,0.25),绕Z轴旋转45度。M=Rz(θ)*T(C) 和 M=T(C)*Rz(θ) 分别会怎样运动?请用列向量下矩阵乘法顺序解释,并说明旋转中心为什么不同。" - AI建议摘要: AI 确认了列向量约定:右侧矩阵先作用于顶点。`R·T` 是"先平移再绕原点转",顶点被转到离原点更远的轨道上,表现为绕世界原点公转;`T·R` 是"先转再平移",旋转发生在局部坐标系,中心不再改变,表现为绕自身中心自转。建议在页面上切换两种 pivot 看平移列(矩阵第 13/14 个元素)是否改变来判断实现是否正确。 - 我的计算或测试方法: 在 `task-b.html` 中打开页面,把初始中心设为 (0.50, 0.25)、角度 45°,先选"世界原点·R×T"再选"对象中心·T×R",读取页面实时显示的当前 Model 矩阵平移列: - world 模式平移列为 `[0.177, 0.530]`,等于 Rz(45°)·(0.50,0.25),说明中心确实被旋转带动(公转); - self 模式平移列为 `[0.500, 0.250]`,中心保持不变(自转)。 另外用 Node 加载 `common/gl-matrix.js` + `js/transforms.js` 复算:对局部点 (0.1,0,0),`R·T` 与 `T·R` 两个矩阵变换结果的坐标差约 0.60,证明两种次序不可交换。 - 实际结果与证据: 页面实测两种模式运动轨迹明显不同(world 中心绕原点转、self 中心固定);矩阵数值与我的手工推导一致。五项数值测试(平移、均匀缩放、Z/Y 轴 90° 旋转、w=0 向量)在页面"矩阵数值检验"区全部显示 ✓。 - 采纳、拒绝或修正的结论: 采纳 AI 建议,并按"右侧先执行"写出 `mat = (pivot==='world') ? multiply(Rm, T(cx,cy)) : multiply(T(cx,cy), Rm)`。我自己的预测与 AI 建议、运行结果三者一致,无需修正。 ## 协作2:故障诊断与修复(Task C/D,4分) - 错误现象、触发步骤: 现象:Task C 动画实现初稿中,点击"暂停动画"后再点"继续动画",画面里的正方形、三角形会瞬间跳到很远的动画位置(突跳),而不是平滑续播。复现步骤:打开 task-c.html → 让动画运行 2 秒 → 暂停 → 等待 2 秒 → 继续,观察图形位置发生跳变。 - 我先检查了什么: 1. 确认只有一个 `requestAnimationFrame` 循环(在构造器中只启动一次,暂停按钮没有另开循环); 2. 检查 `tick()` 内 `lastMs` 的更新时机:初稿在暂停时仍然用 `dt = (ms - lastMs)/1000` 累加对象 `elapsed`,暂停期间 `elapsed` 在后台持续增长; 3. 检查恢复瞬间:继续后 `lastMs` 仍是暂停前的时间戳,第一个 `dt` 是"整个暂停时长",导致对象时间一次推进数秒。 - 关键提示词(原文): "rAF动画暂停后继续,图形突然跳变可能是什么原因?列出候选原因和逐项排查办法;要求暂停期间动画时间不能累计。" - AI列出的候选原因: 1. 暂停期间仍对每个对象累加 `elapsed`(时间没有真正冻结); 2. 恢复后第一帧 `dt` 用了暂停前后两个时间戳的差值(把暂停时长算进了动画时间); 3. 页面切到后台标签时帧时间戳出现大间隔,恢复后一次跨多帧; 4. 存在重复启动的多个 rAF 循环,各自推进时间。 - 我逐项测试的结果: 1. 检查代码:初稿在 `tick` 中无条件 `o.elapsed += dt*speed`,确认为原因 1、2 同时存在; 2. 用页面"实时数值"面板验证:暂停期间圆形缩放与三角形位移读数仍在小幅变化,确认时间未冻结; 3. 检查构造函数:`frameHandle` 只在初始化时启动一次,排除原因 4; 4. 后台切换:属同类问题,可通过限制最大 `dt` 防御。 - 真正原因、修改位置和复测结论: 真正原因:暂停时没有把帧间隔置零,且恢复时 `lastMs` 过期。修改位置在 `js/app.js` 的 `tick()`:改为 `const dt = (this.running && this.globalSpeed>0) ? Math.min((ms-this.lastMs)/1000, 0.1) : 0;` 即暂停或全局速度为 0 时 `dt=0`(时间不累加),同时每帧都更新 `lastMs`,恢复后第一个 `dt` 只反映正常帧间隔;另用 `Math.min(...,0.1)` 限制最大帧间隔,防止后台标签切换造成跳跃。复测:暂停期间页面读数完全不变,继续后平滑推进(实测暂停前后读数一致、恢复后按正常速度变化),问题解决。 ## 协作3:方案比较与实现核验(Task E,3分) - 待解决的具体设计问题: `task-e.html` 的"保存PNG截图"需要把 WebGL2 画布当前帧导出为 PNG,且要求尺寸与 Canvas 内部像素一致、不能得到空白/黑色图片。 - 关键提示词(原文): "WebGL画布保存PNG有哪些可靠做法?canvas.toBlob 会拿到空图/黑图吗?preserveDrawingBuffer 起什么作用?请比较 toBlob 与 readPixels 两种方案。" - 两种方案及取舍依据: - 方案A:利用 `preserveDrawingBuffer:true` + `canvas.toBlob(cb,'image/png')`。时间成本约 5 行代码;正确性依赖绘制缓冲保留策略(若为 false,帧交换后缓冲内容被清空,toBlob 会得到空/黑图),本框架在 `js/graphics.js` 中已配置 `preserveDrawingBuffer:true`;可维护性最高,全部使用浏览器原生 API,无自研编码逻辑。 - 方案B:`gl.readPixels` 读回 RGBA 像素,手动做 Y 轴翻转、行对齐并自行编码 PNG(或写入 2D Canvas 再导出)。不依赖 preserveDrawingBuffer,但对每种像素格式都要处理翻转与编码,代码量大、容易在行序或字节对齐上出错,时间成本明显更高。 - 我最终采用的实现: 采用方案A,并在 `js/app.js` 的 `savePNG()` 中先主动 `this.draw()` 确保绘制缓冲是本帧画面,再 `toBlob`;回调中校验 Blob 非空,用 `URL.createObjectURL` + 临时 `<a download>` 触发下载,完成后移除节点并 `revokeObjectURL`。 - 测试与反思: 在 task-e.html 中先添加对象、把画布设为 960×540,再点"保存PNG截图": - 浏览器成功下载 `实验3_画布_*.png`(12,119 字节,非空); - 用二进制读取 PNG 文件头,签名 `89 50 4E 47` 正确,IHDR 尺寸为 **960×540**,与 Canvas 内部像素一致; - 打开图片确认包含 WebGL 图形和所设置的背景色,无黑屏/空白。 反思:方案A 之所以可行,正是因为框架已显式开启 `preserveDrawingBuffer`;若未来去掉该选项,应回退到方案B 的 readPixels 路径。 ## 其他使用AI的记录(如有) - 用 Node 对 `js/transforms.js` 做了 30 项数值回归(probe 五项矩阵测试、三角波端点、圆 1↔2 倍缩放、三角形往复不漂移、公转/自转矩阵、鼠标坐标映射公式),全部通过;该脚本仅作本地核验用,不随包提交。 - 调试过程中发现 gl-matrix 的 `fromYRotation` 为列主序 `x'=c·x+s·z, z'=-s·x+c·z`,我曾误写成 `x'=c·x-s·z` 导致一个自测用例失败,后依据矩阵展开修正测试期望而非修改代码,确认实现正确。