物理实验猿 发布的文章

与之前的虚拟仿真实验相比,这次的「物理世界奇遇记」,是另一种做法——不是让你做实验,而是给你一个目标,让你自己想办法把它完成。

地址先放这里:https://aphysics.cn/game/,打开就能玩,不用注册、不用下载。

一、起因:看得懂,和玩得通,是两件事

做交互式动画那阵子,我观察到一个现象:学生拖滑块拖得挺开心,但拖完之后,脑子里留下的往往只是"这个画面挺好玩的"。

原因我想了很久,大概是——没有目标。参数是他随手拖的,结果是他随便看的,错了也没关系,对了也不知道为什么对。

所以这次我换了个思路:给每一关一个明确的任务。"让球落进筐里""让线圈转起来""让光谱屏上只亮起红色这一条线"。然后给他几个能调的参数,一个"开始实验"按钮,剩下的,自己想办法。

任务完成的那一刻,弹出知识卡片——先自己摸出规律,再看它叫什么名字。我一直觉得这个顺序,比反过来有效得多。

二、它长什么样

打开是一片关卡地图,八个章节,一章六关,一共 48 关。章节是逐章解锁的,通关一章,下一章才会亮起来。

进到关卡里,画面是一个 3D 小场景,鼠标拖拽可以转动视角。右侧是这一关的参数面板,每个滑块都对应一个真实的物理量;滑块下面是实时读数表,实验过程中数据会一直跳——这一点很重要,读数不是通关时才给的结果,而是过程的仪表盘。屏幕底部是一条目标提示,随时提醒你这一关要达成什么。

点"开始实验",场景就会按真实物理跑起来。球真的会落下,线圈真的会转,光谱屏上的亮线真的会出现在它该出现的位置。

过关会弹出一张知识卡片,讲清这一关背后的原理与公式。没过去也不罚:随时可以暂停、可以改参数重来,卡住了还有"提示"可以看。

进度存在你自己浏览器的本地,下次打开接着玩(换设备或清缓存会重置,这一点先提前说清楚)。

三、八章巡礼

力学之光 —— 撬动巨石 / 阿基米德的浮沉 / 抛石入筐 / 共振的桥 / 台球大师 / 链球投手

从一个撬不动的石头开始。杠杆那一关,你要在力臂和力之间找平衡;共振的桥,得先把驱动频率调到摆的固有频率上,看着振幅突然飙起来——那一瞬间,比公式的印象深得多。

电与磁 —— 点亮那盏灯 / 让线圈转起来 / 让磁铁发电 / 电磁铁起重机 / 洛伦兹回旋 / 电容秒表

串联并联、安培力、电磁感应、RC 电路。电容那一关我改了好几版:充电和放电必须用双掷开关真正断开电源,你得先想明白"放电到底放的是谁的电"。

波与光 —— 让光聚焦 / 双缝的秘密 / 听见振动 / 镜中之光 / 多普勒警报 / 棱镜彩虹

双缝的光波长、双缝间距、屏距都能调,条纹间距会跟着变——想得到"恰好 5 条亮纹"并不容易;棱镜那一关,三束光要在屏上各自命中自己的色带,色散角不够大,红绿蓝就会挤成一团。

热与量子 —— 活塞与压强 / 热气球起飞 / 快速冷冻 / 光电效应 / 能级跃迁 / 半衰期倒数

这一章我改了最多遍。光电效应里能亲眼看到:波长太长,光再强也一个电子都打不出来;能级跃迁里,电子束能量必须卡在那一段"台阶"上,光谱屏才只亮红线——能量不够,原子理都不理你。

引力与宇宙 —— 点火入轨 / 开普勒钟表匠 / 引力弹弓 / 双星共舞 / 黑洞掠影 / 洛希极限造环

让卫星入轨、让周期精确到 12 秒、用木星做一次引力弹弓。洛希极限那一关最过瘾:看着一颗卫星被潮汐力一点点撕碎,碎片慢慢铺成一道行星环。

流体与声 —— 伯努利悬浮球 / 液压大力士 / 虹吸引水 / 音爆冲靶 / 消音大师 / 共鸣管

看不见的空气和水,藏着托举、传力与发声的全部秘密。音爆那一关,你得亲手把物体推过声速墙,才能看见那道锥面。

混沌与复杂系统 —— 双摆混沌 / 洛伦兹吸引子 / 三体编排 / 蝴蝶效应 / 耦合摆同步 / 混沌保密通信

这一章我最偏爱。确定的方程,不确定的远方。双摆初始角度差一点点,几秒后就是完全不同的舞蹈——这不是程序的随机数,是方程自己的性格。

光速与相对论 —— 光速极限 / 时间膨胀列车 / μ子的旅行 / 双生子佯谬 / 同时性的相对性 / E=mc²

当速度逼近光速,时间会变慢、同时会错位、质量会变成光。这些"反直觉",用一段动画讲十遍,不如让你自己把速度调上去看一眼。

四、它不是播动画,是真在算

这一点我想单独说,因为它决定了整个游戏的可信度。

这些关卡背后是真实的物理计算:场景用 three.js 渲染,刚体碰撞交给 Rapier 物理引擎,而每一关的判定式,写的是真正的物理公式——

  • 抛体的轨迹就是重力场里的一条抛物线;
  • 理想气体满足 PV = nRT,压强表上的数字是算出来的,不是画出来的;
  • 光子的能量 E = 1240/λ,波长一调,光谱屏上亮线的位置和颜色跟着变;
  • 卫星周期 T = 2π√(r³/GM),开普勒钟表匠那一关你调的就是这个式子。

参数要真——这是我给自己定的老规矩。物理是诚实的,游戏里的物理也糊弄不得:如果你把参数调对了,结果就必须对;调错了,就得按真实的规律错下去。

所以判定也尽量"认物理不认答案"。比如能级跃迁,不是看你有没有把滑块滑到某个值,而是要看光谱屏上的红线有没有真的被光子点亮;半衰期那一关,因为衰变本身是随机的,我把判定窗放宽到 ±3σ,算得准的人不该被运气判死。

五、关于难度和门槛

我一直在一条很窄的路上找平衡:太简单就变成看动画,太难就变成做题。

现在的做法是:先现象、后原理。每关的提示只给物理直觉,公式放在过关后的知识卡片里。不背公式也能玩,玩通了自然会想知道它叫什么。

还有几条一贯的坚持:免费、打开即玩、没有弹窗广告、不打扰。它可以当课前导入用,也可以当课后复习的玩具,或者只是你某天闲着无聊点开的四十分钟。

六、写在最后

做这 48 关的过程,其实是我自己把物理重学了一遍的过程。很多当年囫囵吞下的公式,是在调参数、改判定的深夜里,突然"通"了的。比如"激发到 n 能级"和"光谱上出现哪几条线"的关系,我是改到第四版才把它理顺——那一刻的爽,我很想分享给更多人多几次。

物理学不会,多半是因为没"看见"过。 或者说,没亲手让它发生过。

欢迎来玩:https://aphysics.cn/game/

玩到哪一关卡住了,或者哪一关的物理你觉得不对、哪个细节让你出戏,都欢迎在留言板告诉我。这个游戏还会继续更新,新的关卡我会慢慢加。

物理之美,值得被更多人玩着看见。

—— aphysics.cn 站长

缘起

让孩子在家有一个方便、好用的英语绘本阅读环境,是我做这个系统的初衷。市面上的英语绘本 App 不少,但要么内容封闭、要么功能零散,很难把"阅读—跟读—查词—统计"串成一条完整的链路。于是我决定自己动手,用 PHP + MySQL + 原生前端写一套轻量的英语绘本在线阅读系统。

这篇文章介绍这套系统的功能设计与技术实现,既是对已完成工作的梳理,也希望给有类似需求的朋友一些参考。


系统概览

整套系统分为四个角色页面:登录页、用户仪表盘(Dashboard)、阅读器(Reader)、管理后台(Admin)。用户登录后进入 Dashboard 选书、查看统计;点击书籍进入 Reader 阅读;管理员通过 Admin 后台上传绘本、管理用户和发布公告。

技术栈很朴素:前端是纯 HTML/CSS/JavaScript,没有使用任何框架;后端是一个 api.php 文件统一处理所有请求,基于 action 参数做路由分发;数据库用 MySQL,图片处理依赖 ImageMagick。部署在 nginx + PHP-FPM 上。

选择这种"零框架"的方式并非偷懒,而是考虑到系统的目标用户规模不大(家庭或小班级),维护者可能也只有自己一人,减少依赖意味着减少长期维护成本。


绘本管理:从 PDF 到可阅读

批量上传与自动转换

管理员在后台选择一个分级(如 RAZ 的 A、B、C 等),拖拽上传多个 PDF 文件。系统对每个 PDF 依次执行三步操作:保存原始文件、通过 ImageMagick 生成封面缩略图(300px 宽、JPEG 格式)、将每一页转换为 JPEG 图片(150dpi、最大宽度 1600px、质量 75%)。

系统最初使用的是 PDF.js 在浏览器端直接渲染 PDF,但实际运行中受限于服务器带宽,大文件的传输不够流畅,翻页体验打折扣。后期改为服务端预转换方案:上传时通过 ImageMagick 将每一页转为压缩后的 JPEG 图片,阅读时按需加载单张图片,单页体积大幅缩小,翻页变得丝滑。转换过程逐页进行以控制内存占用,ImageMagick 的资源限制设为 512MB 内存、1GB 内存映射、4GB 磁盘。

分级体系

系统支持多套分级体系(Reading System),目前内置了 RAZ(Reading A-Z)的 27 个级别,从 aa 到 Z。每个级别下可以挂载若干绘本,Dashboard 左侧按级别导航,并显示每个级别的完成进度(已读/总数)。

音频匹配

绘本的配套音频是教学中的刚需。我写了一个 CLI 脚本 match_audio.php,扫描 audio/ 目录下的音频文件,通过标题归一化、模糊匹配(similar_text 函数,60% 以上阈值)、子串包含三种策略,自动将音频文件与绘本关联。匹配结果导出为 CSV,管理员人工审核后,另一个脚本 apply_audio_match.php 将高置信度和中置信度的匹配写入数据库。这比逐本手动关联高效得多。


阅读器:核心体验

阅读器是整套系统最花心思的部分,所有交互逻辑集中在 reader.html 一个文件里。

翻页模式

支持三种阅读模式:单页、双页(模拟翻开书的左右两页)、以及"分裂模式"——将一页上下分割,下半部分旋转 180 度,模拟把书摊开在桌面上的效果。用户可以通过点击屏幕左右 30% 区域、键盘方向键、触摸滑动、底部进度条拖拽、以及直接输入页码来翻页。相邻页面会预加载到缓存中,保证翻页流畅。

缩放与平移

鼠标滚轮可以在 1x 到 5x 之间缩放,缩放以鼠标位置为中心点。放大后可以拖拽平移,带边界约束。缩放时会在画面中央短暂显示当前缩放百分比。

三级音频逻辑

音频播放遵循一个清晰的优先级:如果绘本有配套音频(管理员上传),播放配套音频;如果没有配套音频,但用户自己录过音,则播放用户的录音,并标注"我的录音";如果两者都没有,显示一个静音图标。这样用户在任何情况下都能得到明确的音频状态反馈。

浏览器内录音

点击麦克风按钮,系统通过 MediaRecorder API 开始录音(webm 格式),界面上出现脉动的红点和计时器。录音结束后通过 FormData 上传到服务器,保存在 uploads/recordings/{user_id}_{book_id}.webm。录音功能不仅可以在阅读器中使用,也可以在 Dashboard 的书籍详情中直接录制,方便用户在不打开书的情况下快速跟读。

查词词典

顶部搜索栏支持输入英文单词,300ms 防抖后调用后端查询。后端连接一个独立的词典数据库 reading_dict,其中收录了超过 14000 个词条(包含音标和释义),先精确匹配再前缀模糊匹配,最多返回 8 个结果。选中单词后显示音标、释义,并可通过 SpeechSynthesis API 朗读发音(0.85 倍速)。

阅读进度与计时

阅读器内置计时器,记录每次阅读的时长。进度每 3 秒自动保存一次(记录当前页、总页数、累计阅读时长),离开页面时通过 navigator.sendBeacon 做最后一次保存。当进度达到 95% 时,系统自动标记为"已读"。

成就系统

每次保存进度或录音后,后端会检查成就是否解锁。系统定义了 16 个成就,分布在四个类别:阅读量(读完 1/5/10/25/50 本)、连续天数(3/7/30 天)、累计时长(1/5/20/50 小时)、录音次数(1/10/50 次),以及一个"通关"成就(完成某个级别的所有绘本)。解锁新成就时,阅读器右上角会弹出带动画的 Toast 通知。


用户仪表盘

Dashboard 是用户的"主页",包含两个标签页:书架和我的统计。

书架

左侧是按分级的层级导航,右侧是书籍卡片网格,每张卡片展示封面、标题、星级评分、阅读进度条,以及"已读""录音"等标签。点击卡片弹出详情弹窗,显示封面大图、简介、页数、进度、评分组件,以及录音区——可以听自己的录音、重新录制、也可以听其他用户的录音。

书架上方还有一个"最近录音"动态流,展示全站最新的 5 条录音,附带用户头像、书名、级别、时长和播放控件,营造一种"学习社区"的氛围。

我的统计

六个统计卡片:已读完数、阅读中数、总阅读时长、录音次数、已读页数、本周时长。下方是最近阅读列表(最近 10 本)、最爱书籍(按阅读时长 Top 5)、各级别进度条,以及成就墙——已解锁的成就高亮显示并附解锁日期,未解锁的灰色展示。

公告展示

管理员发布的公告以轮播形式展示在 Dashboard 顶部信息栏中间,支持左右翻页和详情查看。这个设计让管理员可以灵活地向用户推送通知,而无需额外的消息系统。


管理后台

管理后台分为五个标签页:总览、书籍管理、上传、用户管理、公告管理。

总览页展示全局统计(总书籍数、用户数、已读完数)、各级别书籍分布、以及每个用户的阅读摘要表格,管理员可以一键查看某个用户的详细阅读报告。

书籍管理支持按级别筛选和标题搜索,可以编辑标题、简介、级别,以及切换封面显示模式(contain 完整显示 / cover 裁切填充)。

上传页前面已经介绍过,支持拖拽批量上传和自动转换。

用户管理可以新增、编辑、删除用户账号,重置密码,查看单个用户的完整阅读报告(包括分级进度、阅读记录明细、录音列表)。

公告管理支持创建、编辑、删除公告,以及切换启用/禁用状态。


技术实现的一些细节

单文件 API 设计

所有后端逻辑集中在一个 api.php 中,通过 action 参数路由。这种设计在小项目中非常实用:不需要路由框架,不需要 Composer,部署时只需确保 nginx 将请求转发到这一个文件。缺点是文件会越写越长(目前 1600 多行),但对于一个人维护的项目来说,找一个函数比找一个文件容易得多。

并发友好的图片服务

页面图片接口在返回数据前调用 session_write_close(),释放 session 锁,这样浏览器可以同时加载多张图片而不用排队等待。图片响应还设置了 1 周的缓存头,减少重复请求。

双数据库架构

主应用数据(用户、书籍、阅读记录等)和词典数据分别放在两个 MySQL 数据库中。这样做的好处是词典数据可以独立更新和备份,不影响主库。词典库中收录了 ECDICT 项目的约 14000 个词条。


数据模型

系统核心涉及以下数据表:

reading_systems 和 levels 构成分级体系;users 存储用户信息,区分 admin 和 reader 两种角色;books 是绘本主表,包含文件路径、页数、音频路径、封面设置等;reading_records 记录每个用户每本书的阅读进度;book_reviews 存储评分和笔记;book_recordings 存储录音文件路径和时长;achievements 和 user_achievements 构成成就系统;announcements 存储管理员公告。

所有用户相关的表都通过 user_id 外键关联,reading_records 对 (user_id, book_id) 设置了唯一约束,保证每个用户每本书只有一条进度记录。


总结与展望

这套系统目前运行稳定,核心功能——绘本上传、分级阅读、音频播放、录音跟读、查词、进度追踪、成就激励、公告通知——都已经可用。它证明了一件事:用传统的 LAMP 栈和原生前端,不依赖任何现代框架,也能做出功能完整、体验流畅的教育工具。

未来可以考虑的方向包括:增加拼读练习和阅读理解题目、支持移动端 PWA 离线阅读、引入更多分级体系的内容、以及优化图片加载策略(如 WebP 格式或懒加载)以进一步降低带宽消耗。

如果你也有类似的需求,希望这篇文章能给你一些启发。技术不一定需要多新,解决问题才是最重要的。

2026-08-26_153631.JPG
课堂互动有个老问题——学生想说的很多,但敢举手的很少。有的怕答错,有的声音小后排听不见,有的下课后才想起该问什么。我就想要一个东西:学生扫码就能写下来,大屏实时滚出来,我能管、能审、能挑着讲。于是做了一套扫码留言墙。

是什么

老师登录后台建一个项目(比如"周三答疑"),系统自动生成专属二维码,投屏或贴出来。学生扫码打开留言页,填昵称、写问题、提交。我在后台审核,通过的大屏实时弹出。二维码里存的是带随机 pid 的链接,扫开就是这节课的留言页,互不串扰。

课堂怎么用

  1. 课前建项目,设个主题色
  2. 二维码投在课件首页,或贴在教室
  3. 学生扫码写问题、反馈、心得
  4. 我在后台挑值得讲的通过
  5. 大屏实时滚出来,挑着回应

为课堂调的几个点

  • 审核:提交默认不公开(也可以默认通过),我点通过才上墙,防刷屏、防跑题
  • 实时:3 秒轮询拉新留言,带动画弹入,不用手动刷新
  • 主题色:每节课一个色,学生扫码进来就知道是哪节
  • 悬浮二维码:大屏右下角常驻二维码,学生边看边扫,不用找海报
  • 防灌水:IP 冷却 + 内容去重 + 敏感词 + 字数限制,课堂秩序保住

技术与部署

PHP + SQLite,一个数据库文件,零运维。全自研,无第三方 API 依赖,没有调用费用。安全上:密码 bcrypt 加密、pid 用 random_bytes 防遍历、CSRF + XSS 防护、全程 HTTPS。跑在一台轻量云上,月付几十块,数据在自己手里。

适合什么场景

课堂提问、课后反馈、讲座互动、沙龙收集。凡是学生有话想说、又不好意思当场说的场景,都合适。

一点体会

工具越简单越好用。没有注册、没有 App、没有第三方账号,扫码就走。课堂时间宝贵,少一步操作就多一分互动。

缘起

"用冷却法测金属的比热容"是大学物理实验中的经典项目,我觉得它适合做成一个3D场景,把它搬进浏览器——打开网页就能操作,不用安装软件,不用担心烫伤。仿真实验在线地址:https://aphysics.cn/new3D/bireshang.html

最终呈现:一个浏览器里的完整实验台

打开页面,你会看到一个用 three.js 搭建的三维实验台。左边是加热仪器——底座、立柱、齿轮齿条、手轮、可升降的加热炉体、透明风罩、隔热盖,一样不少。右边是测试仪器——银色箱体上嵌着两块 LED 数字屏(一块显示热电势、一块显示计时),还有加热指示灯、加热电流输出口、热电偶输入端子。两台仪器之间,热电偶引线和加热导线按照实际走线方式连接:黑红相间的热电偶线从探针底部接到插孔,再经保温杯(冰水混合物,冷端参考)分路到红黑端子;褐色加热线从输出口绕到仪器面板前方,再折回到加热器顶部。

实验流程被拆成了 8 个步骤,顶部有一排步骤指示点实时高亮。操作者首先从"铜 Cu""铁 Fe""铝 Al"三个按钮中选择样品(页面上有一行小字说明:实际实验中三种样品表面都是银白色的,这里用不同颜色是为了方便识别),然后依次执行:下降加热器、设置目标温度并加热、等待自动恒温后加热器升起、盖上隔热盖开始冷却测量。

冷却阶段是整个实验的核心。系统基于牛顿冷却定律做数值模拟,热电偶信号通过一张 Cu-Constantan 分度表实时换算——当热电势从 4.20 mV 降到 4.00 mV,系统自动完成计时,记录 Δt。每种金属重复测量 6 次,三种金属共 18 次测量全部完成后,系统以铜的标准比热容为基准,用比较公式算出铁和铝的比热容,弹出结果面板。

整个过程中,右侧状态面板实时显示当前样品、质量、热电势、温度、计时和加热状态;右下方数据表格逐行填入每次测量的原始数据,并自动汇总各金属的平均 Δt。三维场景支持鼠标拖拽旋转和滚轮缩放,方便从不同角度观察。

需要说明的是,这个虚拟实验并不追求与真实实验完全一致。在一些细节上我做了取舍——比如加热过程简化为线性升温、散热系数做了理想化处理、部分操作步骤的顺序和真实实验有差异——这样做的目的是减少开发工作量,把精力集中在核心物理过程的模拟上。操作流程也不完全照搬真实实验,毕竟虚拟环境有自己的交互特点,有些步骤用鼠标点击比手动操作更自然。但实验的目的完全一致:通过比较三种金属在相同条件下的冷却时间,用已知比热容的铜作为基准,推导出铁和铝的比热容。

开发手记:在反复打磨中逼近真实

从零到一:先让东西"能看"

最初版本只有几个基本几何体——方盒子当底座,圆柱体当炉子,勉强能看出是台仪器。然后一步步细化:给立柱加上齿轮齿条(用 InstancedMesh 做了 25 个小方块),给加热器装上可旋转的手轮(圆环加辐条),给测试仪器面板贴上 Canvas 纹理的 LED 数字显示。场景从"能看"慢慢变成"像那么回事"。

热电偶的布线是早期花心思比较多的地方。真实的实验台上,线缆在仪器之间蜿蜒穿梭,如果直接用直线连接就会穿模——线从箱体中间穿过去,视觉上很假。我改用 CatmullRomCurve3 来定义空间曲线,让线缆沿着合理的路径绕行,经过保温杯、贴着面板前方走,最终到达端子。每根线用 TubeGeometry 生成管状网格,视觉上就有了线缆的质感。

物理模拟:让数据"说得通"

三维场景搭好之后,真正的挑战是物理模拟。这个实验的灵魂在于冷却过程的数据必须自洽——三种金属的冷却时间之比要能反推出正确的比热容关系。

根据牛顿冷却定律,样品的温度变化率 dT/dt = -k(T - T₀)/(M·c),其中 k 是散热系数,M 是质量,c 是比热容。当三种样品几何形状相同、表面状态相同(实验中都是银白色),k 可以视为相同,于是冷却时间 Δt 正比于 M·c 的乘积。代入比较公式 C₂ = C₁ × (M₁/M₂) × (Δt₂/Δt₁),就能从铜的已知比热容推出铁和铝的值。

调参的过程颇为曲折。最初我凭直觉设了一组冷却常数,结果铜的 Δt 只有几秒,铁铝的偏差更大,算出来的比热容跟理论值相去甚远。后来我反过来从目标 Δt 出发,先确定铜的冷却时间约 15+ 秒,再根据 M·c 的比值推算铁和铝的冷却时间,由此反推出每种金属的 coolK 参数。最终代入公式后铁算出 0.1121(理论值 0.112)、铝算出 0.2171(理论值 0.217),完美吻合。

热电偶的分度表也是一个细节。我用了 Cu-Constantan 的标准数据,从 -10°C 到 200°C 逐 10°C 一个节点,线性插值实现双向换算。4.20 mV 对应约 98.3°C,4.00 mV 对应约 94.0°C,5°C 的冷却区间在时间尺度上足够拉开差异,又不会让等待太久。

反复打磨:细节里藏着魔鬼

开发过程中最耗时间的不是大功能的实现,而是一个个细节的修正。

加热线的连接就是一例。褐色加热线从测试仪器的输出口连到加热器顶部,最初端点位置不对,跟输出口之间差了约 0.2 的距离,悬在空中。我用 getWorldPosition 加 worldToLocal 动态计算端口在局部坐标系中的精确位置,再把曲线的第一个控制点显式设为该位置,才彻底解决了"对不上"的问题。后来用户又反馈线缆从仪器面板中间穿了过去,于是加了 z=1.2 的前绕路径,让线缆先走到面板前方再折回来。高处弧顶最初比加热器顶部高出太多,调了几轮才降到合适的位置。这根线前后改了四五轮,每次都是肉眼可见的小问题,但修起来需要精确定位坐标系和曲线参数。

手轮的旋转也是反复调过的。手轮通过一根轴连到立柱上,加热器升降时手轮跟着上下移动,同时绕轴旋转——模拟真实齿轮传动的效果。旋转角度用 lerp 平滑插值逼近目标值,视觉上就有了"转着圈升上去"的感觉。用户要求手轮整体向 z 负方向移动 0.2,虽然只是一行代码的改动,但说明他在认真观察三维场景里每个部件的相对位置。

元素符号的显示也踩过坑。最初用 toUpperCase() 来显示金属符号,结果铜显示成"CU"、铁显示成"FE"、铝显示成"AL"——化学上正确的写法应该是 Cu、Fe、Al,第二个字母小写。后来在 METALS 对象里加了 sym 属性,所有显示位置统一改用 sym,才修正了这个看起来不起眼但化学老师一定会指出的问题。

还有一个逻辑上的坑:切换金属样品时,最初代码会清空测量数据数组,导致前一种金属的 6 次测量数据全部丢失。用户希望三种金属的数据同时保留在表格里,最后一起参与计算。去掉那一行清空语句后,数据表格就能完整展示 18 次测量的全部记录了。

加热状态的显示也有类似问题。状态面板里的"加热状态"始终显示"关闭",但仪器面板上的 LED 指示灯是对的。原因是 LED 状态在物理更新函数里实时刷新,而状态面板的文字只在步骤切换时才更新。把加热状态的刷新也放进物理更新的定时显示逻辑里,问题就解决了。

一些让自己满意的小设计

隔热盖的联锁逻辑是我比较满意的一个设计。真实实验中,隔热盖放在风罩顶部之前,加热器不能下降(否则会撞到盖子);加热器升起之后才能放盖子。在仿真里,隔热盖初始状态在台面左侧,只有当加热器处于升起位置时才能被放到风罩口上;而加热器只有在隔热盖回到台面时才能下降。这种操作顺序的约束避免了物理上不可能的状态,也让操作流程更接近真实实验的手感。

数据表格的设计也花了心思。最终版本是一个统一的表格,三种金属的测量数据按时间顺序排列在一起,下方自动汇总每种金属的平均 Δt。这样用户可以直观地对比三种金属的冷却时间差异,而不是在三个分开的表格之间来回切换。

最后一步的计算结果面板会展示完整的公式推导过程:以铜为标准,列出每种金属的平均 Δt 和计算得到的比热容,让用户能清楚地看到从原始数据到最终结果的完整链路。

回头看

这个项目前前后后经历了十几轮迭代,从最初的"几个方块搭个架子"到现在这个有完整实验流程、物理模拟自洽、交互细节到位的版本。每一轮修改都来自实际使用中的观察——有时候是我自己操作时发现数据不对、视觉穿模,有时候是用户反馈的"这里差了一点""那里不太合理"。

做虚拟仿真最大的挑战不是技术本身,而是如何在"简化"和"真实"之间找到平衡。太简化了,学生学不到东西;太复杂了,又变成了另一个让人望而却步的实验装置。我希望这个版本刚好卡在中间:操作足够直观,数据足够真实,过程足够完整——让学生能在安全的虚拟环境里,把整个实验从头到尾做一遍,真正理解冷却法测比热容的原理。

一个网页,打开就能用。这大概就是虚拟仿真最朴素的价值。

最后提一句性能方面的事。三维场景在浏览器里实时渲染,对硬件还是有一定要求的。我在开发过程中做了不少优化——帧率限制在 30fps、像素比上限 1.5 倍、关闭抗锯齿、用 InstancedMesh 合并重复几何体、加热线的几何体每帧重建但其他部分保持静态——整体 CPU 占用率控制在了比较合理的范围。不过还是建议在 PC 上使用,手机和平板上跑这么重的三维场景体验会打折扣。