// ============================================================ // SpineOverlayTransform —— WebGL 叠加层坐标标定(纯函数) // // 职责:把 gameabc 主画布的权威变换矩阵换算成 WebGL 的 MVP 矩阵。 // 无副作用、无 DOM 依赖,可独立单测。 // // 为什么不逆向 gameabc.min.js:那是混淆代码(变量名全打乱),读不出它内部如何 // 映射坐标。而 Spine 绘制发生在 gameenddraw(gameabc 绘制流程末尾),此刻 ctx // 上已带着 gameabc 设好的全局变换 —— 直接向 ctx 要矩阵即可,权威、且 gameabc // 后续改缩放/响应旋转时自动跟随。 // // ⚠️ 真机实测确认:Spine 实际画在 gameabc_face.dc.canvas 这张离屏画布上 // (backing 约 1280x720,不在 DOM),gameabc 再把它 blit 到 DOM 中可见的显示 // 画布 #canvas(backing 约 1912x880),最后经 CSS 缩放到屏幕。本函数的 // canvasW/canvasH 必须传绘制缓冲画布(dc.canvas)的 backing 尺寸,因为 // Spine 坐标正是喂给这张画布的,分母须与其同源;叠加层自身的定位/CSS 尺寸/ // drawing buffer 则是另一个来源(显示画布 #canvas),由调用方 // SpineBackendWebGL 负责,不在本函数职责内。 // // 推导(Spine 世界坐标 Y-up,画布 Y-down): // 位移由调用方经 skeleton.x = e.x; skeleton.y = -e.y 承担,本矩阵只处理 // scale(1,-1) 与投影,等价于 Canvas 后端的 ctx.translate(e.x,e.y)+ctx.scale(1,-1)。 // // 1) Spine 世界 → 逻辑坐标: Lx = vx, Ly = -vy // 2) 逻辑 → 画布像素: Px = a*Lx + c*Ly + e, Py = b*Lx + d*Ly + f // 3) 画布像素 → NDC: x = 2*Px/W - 1, y = 1 - 2*Py/H // // 合并: // x_ndc = (2a/W)*vx + (-2c/W)*vy + (2e/W - 1) // y_ndc = (-2b/H)*vx + ( 2d/H)*vy + (1 - 2f/H) // // 注意:叠加层 drawing buffer 尺寸在推导中被约掉(gl.viewport 覆盖整个 buffer, // NDC 自动铺满),故本矩阵只依赖 ctx 变换与绘制缓冲画布 backing 尺寸。因此所有 // entry 共用同一个 MVP,可一次 batcher.begin/end 批量绘制。 // ============================================================ (function(){ var SpineOverlayTransform = { // ---------------------------------------------------------- // buildMvp(m, canvasW, canvasH, out) // m : {a,b,c,d,e,f} —— ctx.getTransform() 的六元素 // canvasW : 绘制缓冲画布(gameabc_face.dc.canvas)的 backing 宽(实测约 1280) // canvasH : 绘制缓冲画布(gameabc_face.dc.canvas)的 backing 高(实测约 720) // out : 可选,长度 16 的复用数组(避免每帧分配) // 返回列主序 4x4 矩阵 // ---------------------------------------------------------- buildMvp: function(m, canvasW, canvasH, out) { var r = out || new Array(16); var sx = 2 / canvasW; var sy = 2 / canvasH; // 第 0 列:vx 的系数 r[0] = sx * m.a; r[1] = -sy * m.b; r[2] = 0; r[3] = 0; // 第 1 列:vy 的系数 r[4] = -sx * m.c; r[5] = sy * m.d; r[6] = 0; r[7] = 0; // 第 2 列:z 保持 r[8] = 0; r[9] = 0; r[10] = 1; r[11] = 0; // 第 3 列:平移 r[12] = sx * m.e - 1; r[13] = 1 - sy * m.f; r[14] = 0; r[15] = 1; return r; } }; if (typeof module !== "undefined" && module.exports) { module.exports = SpineOverlayTransform; } // 友乐运行时有 module 无 require 且共享全局作用域,须无条件暴露全局名 window.SpineOverlayTransform = SpineOverlayTransform; })();