AI 协作日志
学生 Web 实验作品平台
学号:202412211401003 · 来源:ai-log.md
# 实验三 AI协作记录(请学生在实际完成实验后填写)
> 教师参考代码包不包含虚构的AI协作过程。学生提交时需填写真实工具、提示词、独立测试与结论。
## AI协作1:矩阵和变换次序
- 我自己的预测:
- AI工具与原始提示词:
- AI的建议:
- 本人计算、运行与证据:
- 采纳或修正结论:
## AI协作2:实际故障排查
- 错误现象与重现步骤:
- 我先检查了什么:
- AI工具与原始提示词:
- AI建议的候选原因与测试:
- 实测结果、修复与复测:
## AI协作3:设计方案比较
- 我考虑的两种实现:
- AI工具与原始提示词:
- 对比、取舍与原因:
- 实际实现及核验:
## 提示词:
四面体含4个面,每面3个三角形顶点,开启深度测试。
打开 js/geometry.js 查找局部顶点;在 js/graphics.js 查找GPU Buffer初始化和 gl.drawArrays。这两个是什么意思
## ai回答:
三维形状有前后遮挡,必须开深度测试,否则后画的三角形会盖住前面的。在 graphics.js 构造函数里:
javascript
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
每帧 draw() 里还要清深度缓冲:
javascript
gl.clearDepth(1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
这是让你在代码里定位三段东西,理解"数据从 CPU 到 GPU 到屏幕"的链路。 createMeshes() 用 appendTri 拼出每个形状的 p(位置)和 c(颜色)数组,再用 make 包成 Float32Array。这就是局部顶点数据——形状在自身局部坐标系里的坐标,只生成一次,之后不变。
createBuffer 在 GPU 上建缓冲对象。
bufferData(..., gl.STATIC_DRAW) 把 geometry.js 的顶点/颜色数组上传到 GPU,STATIC_DRAW 表示数据不变、只上传一次。
vertexAttribPointer 告诉 GPU "这个 Buffer 里每 3 个 float 是一个顶点位置/颜色"。
createVertexArray(VAO)把位置和颜色两个 Buffer 的绑定打包,绘制时一次 bindVertexArray 就恢复全部状态。
这一段就是GPU Buffer 初始化:把局部顶点从 CPU 内存搬到 GPU 显存,之后每帧复用,不重复上传。
bindVertexArray 选回该形状的 VAO(GPU 上的顶点数据)。
drawArrays(gl.TRIANGLES, 0, count):从第 0 个顶点开始,每 3 个画一个三角形,画 count 个顶点。四面体 count=12,就是 4 个三角面。
## ai提示词
taskb 五项测试数据怎样更好
## 回答
translate:transform(T(.3,-.2),p), // (1.3,-.2,0,1)
scale:transform(S(2),p), // (2,0,0,1)
rotateZ:transform(R('Z',Math.PI/2),p),// (0,1,0,1)
rotateY:transform(R('Y',Math.PI/2),p),// (0,0,-1,1)
vector:transform(T(.3,-.2),v) // (1,0,0,0),w=0 平移无效
平移作用于点
(1.3, -0.2, 0, 1)
✓
均匀缩放
(2, 0, 0, 1)
✓
Z轴+90°旋转
(0, 1, 0, 1)
✓
Y轴+90°旋转
(0, 0, -1, 1)
✓
平移作用于向量w=0
## ai提示词
task b哪个实现了矩阵相乘,代码怎么写的
## 回答
底层实现:common/gl-matrix.js 的 mat4.multiply
javascript
multiply(out, a, b) {
const temp = new Float32Array(16);
for (let col = 0; col < 4; col++) {
for (let row = 0; row < 4; row++) {
temp[col * 4 + row] =
a[row] * b[col * 4] + a[4 + row] * b[col * 4 + 1] +
a[8 + row] * b[col * 4 + 2] + a[12 + row] * b[col * 4 + 3];
}
}
out.set(temp); return out;
}
包装层:js/transforms.js 的 multiply
javascript
function multiply(a,b){const out=M.create();M.multiply(out,a,b);return out;}
M 就是 glMatrix.mat4。它创建一个新矩阵 out,调底层 M.multiply(out,a,b),返回 out。这样调用方写 X.multiply(A,B) 就得到 A·B,不用自己管 out 分配。
## ai提示词
taskc怎样做到三角形不漂移
## 回答
平移:sin(2πt/period) 平滑往复,乘幅度;(cos d, sin d) 是方向单位向量。始终 c + offset,不拿上一帧坐标累加,所以不漂移。
## ai提示词
时间怎么设置的
## 回答
C-05、C-06 在 app.js 的 tick():
javascript
let dt=(ms-this.lastMs)/1000;
if(!Number.isFinite(dt)||dt<0)dt=0;
dt=Math.min(dt,0.1);
this.lastMs=ms;
if(this.running && this.globalSpeed>0){
for(const o of this.objects){ o.elapsed += dt*this.globalSpeed*o.speed; }
}
ms 是 rAF 时间戳,相邻差除 1000 得秒。
首帧 lastMs 已设为 ms,dt=0 不跳。
Math.min(dt,0.1):切后台再回来时间差可能几秒,限制后不会"瞬移"。
只有 running && globalSpeed>0 才累加 elapsed。暂停时停住,恢复从原值继续。
每对象自己的 speed 乘全局速度,所以调全局速度对全体生效,对象独立速度保留。
循环体只改 elapsed(JS 状态),下一行 this.draw() 用 modelFor 重算矩阵,GPU Buffer 完全不动。
## ai提示词
task d怎样实现参数在点击那一刻保存;之后改变菜单不会修改先前对象。
## 回答
点击时把菜单的当前值读成一个普通 JS 对象,存进新对象的字段;之后渲染只读这个对象,不再去读菜单。关键在 selectOptions() 和 createObject() 两处。
this.selectOptions() 在点击这一刻被调用一次,返回的快照对象作为 options 传下去。
渲染只读对象,不读菜单
每帧 draw():
javascript
const items=this.objects.map(o=>({type:o.type,matrix:X.modelFor(o)}));
modelFor(obj) 用的全是 obj 的字段:
javascript
const a = Math.sin(Math.PI*2*t/obj.period) * obj.amplitude;
const d = deg2rad(obj.directionDeg);
...
const s = 1 + (obj.scaleMax-1)*triangle01(t/obj.period);
...
const r = R(obj.axis, ang);
return obj.pivot==='world' ? ... : ...;
菜单值只在 selectOptions() 里被读,而它只在点击时被调用一次。
读出的值被 createObject 拷进对象字段。
渲染路径 draw → modelFor(obj) 只访问对象字段。
菜单之后变化,只改变 DOM 的值;旧对象字段是独立内存,不受影响。
下一次点击才再次 selectOptions(),读到新菜单值,写进新对象。
## ai提示词
哪里改canvas
## 回答
applyCanvas():
javascript
this.renderer.resize(w,h);
this.renderer.setBackground(color);
this.draw();
resize 做三件事:改 canvas.width/height、gl.viewport(0,0,w,h)、按新宽高比重算正交投影 ortho(-a,a,-1,1,...)。所以圆还是圆,鼠标映射(每次读 canvas.width/height)仍准。setBackground 只改 bg 字符串,下次 draw 的 clearColor 用它。已有对象不动。
## ai提示词
怎么截屏
## 回答
savePNG():
javascript
this.draw();
this.canvas.toBlob(blob=>{
if(!blob){this.setNotice('截图失败:Blob 为空');return;}
const url=URL.createObjectURL(blob);
const a=document.createElement('a');
a.href=url;
a.download='实验3_画布_'+Date.now()+'.png';
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
this.setNotice('PNG 已下载,尺寸 '+this.canvas.width+'×'+this.canvas.height);
},'image/png');
先 draw() 确保绘制缓冲是当前帧。
toBlob(cb,'image/png') 异步取 PNG 数据;回调里检查空 Blob。
用临时 <a download> 触发下载,appendChild 是某些浏览器要求,remove + revokeObjectURL 及时清理。
preserveDrawingBuffer:true 保证读到的就是刚才那帧,不会空白/黑。
下载图的像素尺寸 = canvas.width×height,与画布内部尺寸一致。