【征文计划】Trea AI 赋能 Rokid JSAR 驱动:探索 AR 视角下的地球运动 —— jsar-our-earth 项目开发实践
前言
课本上学习 "地球自转形成昼夜、公转造就四季" 时,抽象的文字描述与静态的示意图,往往难以让我们直观感知天体运动的规律 —— 黄赤交角如何影响太阳直射点变化?地球自转轴的倾斜为何会导致南北半球季节相反?这些曾困扰多数人的天文学概念,如今正随着增强现实 AR 技术的发展迎来全新的科普方式
jsar-our-earth(我们的地球)实用的 AR 天体模拟工具,用 TypeScript、Babylon.js 和 JSAR 框架做的,把课本里抽象的地球运动,变成了能在现实里看见的 3D 效果,你能直接瞅见地球绕太阳转的轨迹、地轴咋倾斜的,动手捏合一下还能切自转 / 公转模式,再配上语音解说,昼夜交替、四季变化这些难理解的原理能够非常清晰理解。
项目背景与核心概述
- 项目名称:我们的地球(jsar-our-earth)
- 当前版本:1.2.0
- 核心技术栈:TypeScript、Babylon.js、JSAR 框架
- 开源地址:ylsislove/jsar-our-earth(感谢作者开源贡献)
- 核心功能:地球自转 / 公转动态模拟、黄赤交角可视化展示、手势交互控制、语音解说同步
项目文件结构解析
├── .gitignore # Git忽略文件配置
├── .vscode\ # VS Code编辑器配置
│ └── settings.json # VS Code设置文件
├── LICENSE # 项目许可证文件
├── README.md # 项目说明文档
├── audio\ # 音频资源文件夹
│ ├── bgMusic.mp3 # 背景音乐
│ ├── earthOrbit.mp3 # 地球轨道音效
│ └── earthRotation.mp3 # 地球自转音效
├── icon.png # 项目图标
├── jsar-our-earth-1.0.0.zip # 项目1.0.0版本压缩包
├── jsar-our-earth-1.1.0.zip # 项目1.1.0版本压缩包
├── jsar-our-earth-1.2.0.zip # 项目1.2.0版本压缩包
├── lib\ # 源代码文件夹
│ ├── audioManager.ts # 音频管理模块
│ ├── gameManager.ts # 游戏管理模块
│ ├── gestureManager.ts # 手势管理模块
│ ├── main.ts # 主入口模块
│ └── matManager.ts # 材质管理模块
├── main.xsml # 主场景文件
├── model\ # 3D模型文件夹
│ └── sun.glb # 太阳模型文件
├── package-lock.json # npm依赖锁文件
├── package.json # npm项目配置文件
├── texture\ # 纹理资源文件夹
│ └── earth.png # 地球纹理贴图
└── tsconfig.json # TypeScript配置文件
功能模块分工与关键资源准备
| 模块文件 | 核心职责 |
|---|---|
| gameManager.ts | 加载地球 / 太阳模型、创建轨道平面,通过定时器驱动自转 / 公转动画,同步更新四季文本 |
| gestureManager.ts | 监听手势(如右手捏合),实现自转 / 公转模式切换,触发对应解说音频播放 |
| matManager.ts | 管理材质与纹理,渲染地球表面、赤道面(蓝色半透明)、黄道面(黄色半透明) |
| audioManager.ts | 加载背景音乐与解说音频,提供播放 / 暂停控制,实现音频与交互动作的联动 |
1、audio 语音素材准备(bgMusic.mp3作为背景音乐营造氛围,earthRotation.mp3和earthOrbit.mp3则分别用于解说地球自转和公转的相关知识这些音频通过audioManager.ts模块进行统一管理,实现音频的加载、播放控制以及与用户交互的联动)
2、earth.png准备,实现项目中地球逼真视觉效果的关键资源文件,通过Babylon.js的材质系统被处理和应用到3D模型上
3、icon.png 应用图标,帮助用户识别应用
核心代码深度解析:从场景构建到交互逻辑
场景结构定义:main.xsml
1、main.xsml 是整个 Rokid JSAR 应用可视化核心定义文件,将空间元素、模型引用与交互节点统一在一个结构化描述中,不需要手动创建三维对象,只需声明式地编写结构,就能构建复杂的 AR 场景,结合 TypeScript 脚本逻辑与手势控制,实现从模型加载到物理旋转的完整空间交互闭环
<xsml version="1.0"> <head> <title>Our Earth</title> <!-- 引用模型与主逻辑脚本 --> <link id="sun" rel="mesh" type="octstream/glb" href="./model/sun.glb" /> <script src="./lib/main.ts"></script> </head> <space> <!-- 🌞 太阳模型 --> <mesh id="sun" ref="sun" selector="__root__" /> <!-- 🌍 地球与地轴 --> <sphere id="earth" diameter="6"> <cylinder id="earthAxis" diameter="0.1" height="8" /> </sphere> <!-- 📏 地轴与极点文字 --> <sphere id="radLineParent" diameter="1"> <plane id="radLineText"><span>66.5°</span></plane> <plane id="arcticText"><span>北极</span></plane> <plane id="antarcicText"><span>南极</span></plane> <plane id="earthAxisText"><span>地轴</span></plane> </sphere> <!-- 🪐 四季节点 --> <sphere id="spring"><plane><span>春分</span></plane></sphere> <sphere id="summer"><plane><span>夏至</span></plane></sphere> <sphere id="autumn"><plane><span>秋分</span></plane></sphere> <sphere id="winter"><plane><span>冬至</span></plane></sphere> <!-- 🎛 控制面板 --> <plane id="ctrlPanel"> <div> <span>公转</span> <span>自转</span> </div> </plane> </space> </xsml>
场景初始化:main.ts
2、
main.ts初始化 3D 场景中的文本和控制面板,统一设置文本样式,并绑定交互事件,实现场景内容的美观展示和可操作控制
/** 通用文本样式设置 */ function setTextStyle(id: string, options: { fontSize?: string; height?: string; width?: string; color?: string; textAlign?: string; border?: string; borderRadius?: string; interactive?: boolean } = {}) { const element = spaceDocument.getSpatialObjectById(id); const root = element.shadowRoot; const children = root.querySelectorAll('.sub, .title, .content'); children.forEach((child) => { const el = child as HTMLElement; el.style.height = options.height || '100%'; el.style.width = options.width || '100%'; el.style.fontSize = options.fontSize || '100px'; el.style.color = options.color || '#ffffff'; el.style.textAlign = options.textAlign || 'center'; if (options.border) el.style.border = options.border; if (options.borderRadius) el.style.borderRadius = options.borderRadius; if (options.interactive) { el.addEventListener('mouseup', () => ctrlPanelListener("start")); } }); } /** 控制面板事件绑定 */ function setCtrlPanelStyle(id: string) { const panel = spaceDocument.getSpatialObjectById(id); const root = panel.shadowRoot; const children = root.querySelectorAll('.sub'); children.forEach((child) => { const el = child as HTMLElement; el.style.backgroundColor = '#ffffff0f'; el.style.height = '200px'; el.style.width = '80%'; el.style.fontSize = '80px'; el.style.color = '#fff'; el.style.textAlign = 'center'; el.style.border = '1px solid white'; el.style.borderRadius = '50px'; el.addEventListener('mouseenter', () => el.style.backgroundColor = 'rgba(20,33,33,.95)'); el.addEventListener('mouseleave', () => el.style.backgroundColor = '#ffffff0f'); el.addEventListener('mouseup', () => { el.style.backgroundColor = 'rgba(60,33,33,.95)'; ctrlPanelListener(el.textContent); }); }); }
运动模拟核心:gameManager.ts
3、
gameManager.ts中,通过 Babylon.js 构建一个动态的太阳系运动模拟,通过定时器驱动地球自转与公转,以及太阳自转的动画演示,不仅形象再现了日夜交替与四季变换的核心天文学现象,为用户提供了模式切换与交互操作的沉浸式体验
// 定义地球和太阳的关系 const earth2Sun = 30; // 地球与太阳的距离(轨道半径) let earthAngle = 0; // 公转角度(弧度) let earthX = earth2Sun * Math.cos(earthAngle); let earthY = earth2Sun * Math.sin(earthAngle); // 🌏 地球自转动画 let earthRotationSpeed = 1/2.4; // 自转速度(数值越小转得越慢) function earthRotationAnim() { // 绕Y轴自转(负号表示方向) earth.rotate(new BABYLON.Vector3(0, -1, 0), earthRotationSpeed, BABYLON.Space.LOCAL); } let earthRotationTimer = setInterval(earthRotationAnim, 16); // 每16ms执行一次(约60帧/s) // 🌞 太阳自转动画 function sunRotationAnim() { // 绕Z轴自转,周期约25.38天 sun.rotate(new BABYLON.Vector3(0, 0, 1), 1/2.4/25.38, BABYLON.Space.LOCAL); } let sunRotationTimer = setInterval(sunRotationAnim, 16); // 🌀 地球公转动画 function earthOrbitAnim() { earthAngle += 1/2.4/365; // 公转角度每帧递增(周期=365天) earthX = earth2Sun * Math.cos(earthAngle); earthY = earth2Sun * Math.sin(earthAngle); // 更新地球位置,实现绕太阳旋转 earth.position = new BABYLON.Vector3(earthX, 0, earthY); } let earthOrbitTimer = setInterval(earthOrbitAnim, 16);
手势交互控制:gestureManager.ts
4、
gestureManager.ts通过监听手势识别事件捕捉用户右手的捏合动作:首次触发时负责启动程序并进入 "自转" 模式,之后每次捏合在自转和公转之间循环切换,从而以简洁直观的交互方式实现对太阳系模拟的动态控制
let audioIndex = 0; let isFirst = true; export function initGesture(callback) { spaceDocument.addEventListener("handtracking", (event) => { const { inputData } = event; // 右手拳头捏合 if (inputData.Type === 1 && inputData.Gesture === 1) { if (isFirst) { isFirst = false; callback("start"); callback("自转"); audioIndex = 1; } else { if (audioIndex === 0) { callback("自转"); audioIndex = 1; } else { callback("公转"); audioIndex = 0; } } } }); spaceDocument.watchInputEvent(); }
材质与纹理渲染:matManager.ts
5、matManager.ts 通过贴图与半透明平面材质的构建,实现地球表面的真实渲染,提供可视化的辅助平面,用于清晰演示地球的自转与公转运动
import earth from '../texture/earth.png'; export async function getEarthMat() { const texture = await matBuilder(earth); // 创建材质 const material = new BABYLON.StandardMaterial('matEarth', spaceDocument.scene); material.diffuseTexture = texture; // 返回材质 return material; }; export async function getEarthPlaneMat() { // 地球赤道面材质 const material = new BABYLON.StandardMaterial('matEarthPlane', spaceDocument.scene); material.diffuseColor = new BABYLON.Color3(0, 0, 1); material.alpha = 0.5; material.transparencyMode = BABYLON.Material.MATERIAL_ALPHABLEND; // 返回材质 return material; }; export async function getEarthOrbitPlaneMat() { // 地球公转黄道面材质 const material = new BABYLON.StandardMaterial('matEarthOrbitPlane', spaceDocument.scene); material.diffuseColor = new BABYLON.Color3(1, 1, 0); material.alpha = 0.5; material.transparencyMode = BABYLON.Material.MATERIAL_ALPHABLEND; // 返回材质 return material; }; export async function matBuilder(image: any) { // 读取图片 const bitmap = await createImageBitmap(new Blob([image], { type: 'image/png' })) // 创建画布 const canvas = new OffscreenCanvas(bitmap.width, bitmap.height); const ctx = canvas.getContext('2d'); // 绘制图片 ctx.drawImage(bitmap, 0, 0); const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); // 创建纹理 const texture = new BABYLON.RawTexture( imageData.data, imageData.width, imageData.height, BABYLON.Engine.TEXTUREFORMAT_RGBA, spaceDocument.scene, false, false, BABYLON.Texture.TRILINEAR_SAMPLINGMODE ); // 返回纹理 return texture; };6、
audioManager.ts封装音频管理功能:异步加载音频生成可控播放函数,存入audios对象,支持循环背景音乐和按顺序播放地球自转/公转解说音,通过播放状态标记和定时器控制单次播放与索引切换。let isEarthOrbitAudioPlaying = false; let isEarthRotationAudioPlaying = false; // const audios = {} as Record<string, HTMLAudioElement>; const audios = {} as Record<string, (volume?: number, loop?: boolean) => void>; async function createAudioPlayer(name: string, type: string) { const arrayBuffer = await import(`../audio/${name}`); const blob = new Blob([arrayBuffer], { type: type }); const objectUrl = URL.createObjectURL(blob); // return new Audio(objectUrl); return (volume?: number, loop?: boolean) => { const audio = new Audio(objectUrl); if (volume) { audio.volume = volume; } if (loop) { audio.loop = loop; } audio.play(); return audio; }; } (async function () { audios["earthOrbit"] = await createAudioPlayer("earthOrbit.mp3", "audio/mpeg"); audios["earthRotation"] = await createAudioPlayer("earthRotation.mp3", "audio/mpeg"); audios["bgMusic"] = await createAudioPlayer("bgMusic.mp3", "audio/mpeg"); })(); // 播放背景音乐 export function playBgMusic() { const bgMusicFunc = audios["bgMusic"]; if (!bgMusicFunc) return; const bgMusic = bgMusicFunc(0.2, true); // bgMusic.volume = 0.3; // bgMusic.loop = true; // bgMusic.play(); } let index = 0 export function playExplainAudio() { if (index === 0) { playEarthRotationAudio() } else { playEarthOrbitAudio() } } function playEarthOrbitAudio() { const earthOrbitAudio = audios['earthOrbit']; if (earthOrbitAudio && !isEarthOrbitAudioPlaying && !isEarthRotationAudioPlaying) { isEarthOrbitAudioPlaying = true; earthOrbitAudio(1.0); // 58s后停止播放 setTimeout(() => { isEarthOrbitAudioPlaying = false; index = 0 }, 50000); } } function playEarthRotationAudio() { const earthRotationAudio = audios['earthRotation']; if (earthRotationAudio && !isEarthRotationAudioPlaying && !isEarthOrbitAudioPlaying) { isEarthRotationAudioPlaying = true; earthRotationAudio(1.0); // 47s后停止播放 setTimeout(() => { isEarthRotationAudioPlaying = false; index = 1 }, 31000); } }
AR交互效果体验
AR场景的交互感非常直观自然:通过手势可以轻松控制地球的自转和公转,四季文本与轨道倾角的显示同步更新,背景黑色增强了模型的立体感和沉浸感,滑动控制器和面板按钮的响应流畅,手势触发音频播放让解说与视觉效果紧密结合,整体感觉像在手中 "掌控" 一个微型太阳系,既直观又具有教育趣味性
![]()
![]()
总结
jsar-our-earth 用 AR 技术打破天体科普的抽象感,以 TypeScript、Babylon.js、JSAR 为技术支撑,把地球自转公转、黄赤交角等难理解的知识,变成能看能控能听的体验,项目文件结构清晰,五大核心模块配合流畅,既实用又能给 AR 开发提供参考,对开发者是好案例,对用户能让天文知识 "活" 起来,也为 AR 科普指明了 "用直观体验简化复杂知识" 的方向!
