Skip to content

SDD-010 · Hình vẽ bằng thuật toán ​

Một phần của SDD-010.

11. Hình vẽ bằng thuật toán (REQ-LRN-20 — SRC-114) ​

Luật: mọi hình trong nội dung học là SVG sinh từ tham số. Cấm file bitmap (JPEG/PNG).

Lý do không phải dung lượng mà là học được gì: hình sinh từ tham số thì learner kéo được (đổi số đo → hình đổi theo, thấy quan hệ chứ không thuộc lòng), sửa nội dung không phải vẽ lại ảnh, và hình luôn khớp đúng số liệu trong đề. Đây cũng là triết lý zero-asset của §7.

  • Lưu: bảng item_figures (migration 0038) — figure_json là một FigureSpec thuần dữ liệu, không phải mã. /practice/start trả kèm figure cho từng câu (bọc try/catch: thiếu bảng thì bài vẫn chạy, chỉ không có hình).
  • Dựng: apps/learn/src/figures/engine.tsx — 10 archetype phủ 28 node hình học: cặp góc (đối đỉnh · phụ · bù · kề), góc đơn, hai đường song song + cát tuyến (so le trong · đồng vị · trong cùng phía), tam giác (thường · vuông · cân), đa giác n cạnh, đường tròn (bán kính · tiếp tuyến · góc nội tiếp · cung), diện tích (chữ nhật · bình hành · thang · tam giác), mặt phẳng toạ độ / vectơ, đối xứng trục, khối 3D. Mỗi hình có thanh kéo đổi tham số.
  • Gắn hình: scripts/attach-figures.mjs quyết định theo hai tầng — node → dạng hình mặc định, rồi từ khoá trong đề bài chỉnh variant và số đo (ví dụ đề ghi "35°" thì hình vẽ đúng 35°). Node chưa có luật thì bỏ trống và báo ra, không đoán bừa. Hiện phủ 280/280 câu hình học của Toán.
  • Kiểm: apps/learn/src/figures/engine.test.tsx (27 test) kiểm tính đúng của hình, không chỉ render được: đối đỉnh phải hiện hai lần cùng số đo, bù của 120° phải ra 60°, 3–4 phải ra cạnh huyền 5.00, ngũ giác 540° và bát giác 1080°, góc nội tiếp bằng nửa góc ở tâm… và không có thẻ <img> hay đuôi .png/.jpg nào trong output. Hình sai còn tệ hơn không có hình.

11.1 Đường đi của engine hình: ba tầng, và chỗ dừng của tầng ba (SRC-494) ​

Chủ dự án 2026-08-21: "một mặt áp cơ chế SVG hiện nay sang các môn khác, mặt khác bổ sung các cơ chế vẽ khác, dùng threejs hoặc engine khác mà bạn đề xuất". Phân xử:

Tầng 1 · mở rộng chính engine SVG đang có (làm ngay, và đã làm). Việc thiếu ở Lý, Hoá, Sinh phần lớn KHÔNG cần công nghệ mới, chỉ cần thêm dạng hình. Đã thêm ba dạng mở đường: bar-chart (đọc bảng và biểu đồ, số liệu thí nghiệm), venn (phân loại, giao hai nhóm), force-body (vật kèm các lực vẽ thành mũi tên có nhãn). Chi phí gần bằng không: cùng file, cùng bảng, cùng cách kiểm, không thêm một byte nào vào gói tải của learner chưa gặp dạng đó.

Tầng 2 · dạng hình 2D chuyên môn còn thiếu (làm khi có câu hỏi cần). Sơ đồ mạch điện, sơ đồ tia sáng, công thức cấu tạo phẳng, hộp và râu (box plot). Vẫn là SVG tham số, vẫn nằm trong engine.tsx. Thứ tự ưu tiên do dữ liệu quyết định: dạng nào có nhiều câu đang cần thì làm trước, không làm trước cho đẹp danh sách.

Tầng 3 · three.js. Chủ dự án bác lập luận dung lượng của tôi ngày 2026-08-22: "three.js mà 150KB thì nó quá nhẹ, chỉ bằng dung lượng một tấm ảnh. Dùng three.js cho các lab vật lý, hoá học, sinh học. Tóm lại cần sinh động hoá các lab, không chỉ bằng three.js mà còn bằng thư viện khác."

Chủ dự án đúng, và tôi ghi lại chỗ mình sai thay vì lặng lẽ sửa: Nemo12 cấm ảnh bitmap CHÍNH VÌ hình sinh từ tham số vừa nhẹ vừa học được nhiều hơn. Lấy dung lượng ra chặn một engine tương tác thật là dùng sai chính nguyên tắc ấy, nhất là khi 131KB nén chỉ tải cho lab nào cần và nằm ở Lab, nơi learner đã sẵn sàng ngồi lâu. Xem §11.2 cho phần đã làm.

Hai lý do còn lại vẫn đứng, và chúng quyết định RANH GIỚI chứ không còn quyết định có hay không:

  1. Hình của CÂU HỎI là thứ liếc rồi trả lời. learner nhìn vài giây rồi chọn đáp án. Một cảnh 3D xoay được mời gọi nghịch, tức nó cướp sự chú ý khỏi chính câu hỏi. Nên 3D vào Lab, nơi nghịch CHÍNH LÀ việc học, chứ không vào item_figures.
  2. 3D phải KIẾM ĐƯỢC chỗ của nó. Khối lập phương, hình trụ, hình nón đã đọc được ngay bằng phép chiếu SVG; dựng lại chúng bằng WebGL là đổi công lấy hiệu ứng. Thước đo: xoay được thì learner thấy thêm điều gì mà hình phẳng không nói nổi? Hình học phân tử trả lời được (góc 104,5 độ và cặp electron chưa liên kết), một khối lập phương thì không.

Có một ngoại lệ đáng cân nhắc sau: đồ thị hàm số vẽ động (kéo hệ số, thấy parabol đổi hình). Cái này không cần three.js, chỉ cần cho coordinate nhận một biểu thức và một thanh kéo, tức vẫn tầng 1.

11.2 Lab sống động hơn (SRC-505) ​

Đã làm, chạy thật trên production:

  • explore/three/Molecule3D.tsx — mô hình bi và que xoay được bằng chuột hoặc ngón tay, nạp động (lazy(() => import(...))), gói riêng 131KB nén nên learner chưa mở lab loại này thì không tải. Không asset ngoài: hình cầu và hình trụ là dạng dựng sẵn, màu theo quy ước CPK của sách giáo khoa. Liên kết đôi và ba vẽ thành các thanh SONG SONG để learner ĐẾM được bậc, không phải một thanh dày. Máy thiếu WebGL thì nói ra một câu, không để trang trắng.
  • Archetype molecule-3d trong explore/spec.ts: dữ liệu là danh sách nguyên tử, liên kết và cặp electron chưa liên kết, nằm trong lab_content.viz như mọi archetype khác. Không có file model.
  • Hai lab Hoá đã đổi sang nó (chl-covalent với H₂O, chl-detergent với CH₄), bản picker cũ giữ nguyên trong viz_picker để đổi lại được.

Đang mở, xếp theo mức đáng làm. Nguyên tắc chọn thư viện: mỗi thư viện phải trả lời được câu "learner làm được gì mà trước đó không làm được", và nạp động cho đúng lab cần nó.

ViệcThư việnVì sao đángTình trạng
Va chạm, con lắc, mặt phẳng nghiêng (Lý)tự viết, không thêm thư việnlearner đổi khối lượng rồi THẤY hệ quả, thay vì thay số vào công thứcxong SRC-510
Mạch điện lắp được (Lý)SVG + một bộ giải mạch nhỏ tự viếtnối sai thì đèn không sáng, đó mới là phản hồixong SRC-510
Mô hình phân tử cho Sinh (ADN, protein)three.js, dùng lại đúng engine trênxoắn kép chỉ hiểu được khi xoayđang mở
Đồ thị hàm số kéo hệ số (Toán)không cần thư viện, mở rộng coordinate của engine SVGkéo a, b rồi thấy parabol đổi hìnhxong SRC-510

11.2a Bốn archetype MÔ PHỎNG — hình chạy bằng công thức thật (SRC-510) ​

Bốn archetype đã đóng hàng chờ trên, tất cả trong engine khám phá, không thêm một thư viện nào:

ArchetypeLearner làm gìMáy tính gì
pendulumkéo dài dây, đổi biên độchu kỳ T = 2π√(L/g) tính thật — nên đổi biên độ nhỏ thì T không đổi
collisionhai xe, chọn va chạm đàn hồi hay mềmgiải từ bảo toàn động lượng
circuitnối bóng đèn nối tiếp hay song songđịnh luật Ôm: độ sáng vẽ từ dòng THẬT — nối tiếp thì mọi bóng cùng mờ, song song thì sáng như một bóng
quadratickéo a, b, cparabol dựng lại từ hệ số

Luật thiết kế, và đây là chỗ chúng khác mọi hình minh hoạ trước đó: ba archetype đầu CHẠY MÔ PHỎNG, không vẽ minh hoạ. Giá trị của chúng nằm đúng ở những chỗ learner hay nói ngược (biên độ đổi thì chu kỳ đổi; mắc nối tiếp thì đèn gần nguồn sáng hơn) — hình vẽ tay theo trực giác người soạn sẽ tái tạo lại chính hiểu lầm ấy, còn công thức thì không.

Hệ quả cho kiểm thử: test canh CON SỐ, không canh hình. Mô phỏng vẽ đẹp mà ra số sai thì đang dạy sai, tệ hơn là không có mô phỏng.

11.3 Hình phải cầm được ở chính chỗ nó nói (SRC-460, SRC-466, SRC-524) ​

Ba đợt sửa hình rời nhau nhưng cùng rút ra một luật chung, đặt ở đây làm chuẩn cho mọi archetype mới:

Thao tác phải nằm trên chính hình mang ý nghĩa, và mọi quy ước hình học của môn phải được tôn trọng — hình là thứ learner đọc để rút ra kết luận, không phải phần trang trí quanh câu hỏi.

Trục số kéo trực tiếp (SRC-460). Bản cũ để điểm nằm trên trục nhưng bắt kéo ở một thanh trượt rời bên dưới: hai hình cùng nói một giá trị mà thao tác ở hình kia, tức bắt trẻ tự dựng cầu nối trong đầu trước khi học được điều lab định dạy. Nay:

  • SVG của archetype number-line nhận Pointer Events trực tiếp: chạm hay kéo ở đâu trên trục thì điểm nhảy về giá trị nguyên gần nhất.
  • setPointerCapture để kéo trượt ra ngoài khung vẫn bám — trẻ kéo bằng ngón tay thì đi chệch là chuyện thường, mất bám giữa chừng đọc thành "hỏng".
  • Một quầng mờ quanh điểm nói "chỗ này cầm được" — bằng hình, không bằng chữ.
  • Thanh trượt rời bị bỏ.
  • A11y không mất: role="slider" trên SVG + một input ẩn (sr-only) để bàn phím và trình đọc màn hình vẫn chỉnh được giá trị.

Trục số có mũi tên hai đầu, số 0 khác màu (SRC-466). Trục vẽ mũi tên ở cả hai đầu, cùng màu trục — nói "còn rất nhiều nữa về cả hai phía", chứ đoạn thẳng cụt ngụ ý dãy số dừng ở đó. Vạch 0 cao hơn, dày hơn và mang màu đối tượng chính (aqua --fig-a1), nhãn 0 to đậm cùng màu: 0 là mốc quy chiếu của cả trục, mọi vạch cùng màu thì mỗi lần định vị mắt phải đếm lại từ nhãn. Áp ở tầng archetype nên ăn cho mọi lab dùng trục số, mọi môn.

Hình góc: đỉnh ở tâm, 90° vẽ dấu vuông (SRC-524).

  • Đỉnh góc đặt ở giữa khung (x = 150 thay vì x = 60): đỉnh là chỗ mắt nhìn vào để đọc một góc, nên nó phải là tâm bố cục; đặt lệch trái thì ở 180° hai cạnh không trải đều được hai bên.
  • Đủ 90° thì cung tròn đổi thành dấu vuông. Đây là quy ước hình học, không phải trang trí: cung nói "một góc nào đó", ô vuông nói "đúng 90°". Vẽ cung ở 90° là bỏ mất đúng điều lab muốn learner nhận ra.
  • Bỏ dòng số đo dưới hình: khung chỉ cao 160 nên dòng đó nằm ngoài vùng nhìn thấy, mà số đo đã có sẵn ở nhãn thanh kéo.

Chữ rò ra mặt trang (cùng đợt SRC-524). Trong phần con của JSX, /* … */ không phải comment mà là chữ — một ghi chú mã nguồn kèm mã SRC đã lên tới production ở đầu trang Toàn cảnh. Không cổng nào bắt được vì không test nào đọc chữ thừa trên trang. Nay có một test quét ba trang chính, chặn cả /* lẫn chuỗi SRC- lọt ra giao diện.