【征文计划】Trea AI 赋能 Rokid JSAR 驱动:探索 AR 视角下的地球运动 —— j

游戏

【征文计划】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(感谢作者开源贡献)
  • 核心功能:地球自转 / 公转动态模拟、黄赤交角可视化展示、手势交互控制、语音解说同步

项目文件结构解析

picture.image

├── .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模块进行统一管理,实现音频的加载、播放控制以及与用户交互的联动)

picture.image

2、earth.png准备,实现项目中地球逼真视觉效果的关键资源文件,通过Babylon.js的材质系统被处理和应用到3D模型上

picture.image

3、icon.png 应用图标,帮助用户识别应用

picture.image

核心代码深度解析:从场景构建到交互逻辑

picture.image

场景结构定义:main.xsml

1、main.xsml 是整个 Rokid JSAR 应用可视化核心定义文件,将空间元素、模型引用与交互节点统一在一个结构化描述中,不需要手动创建三维对象,只需声明式地编写结构,就能构建复杂的 AR 场景,结合 TypeScript 脚本逻辑与手势控制,实现从模型加载到物理旋转的完整空间交互闭环

picture.image

<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 场景中的文本和控制面板,统一设置文本样式,并绑定交互事件,实现场景内容的美观展示和可操作控制

picture.image

/** 通用文本样式设置 */
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 构建一个动态的太阳系运动模拟,通过定时器驱动地球自转与公转,以及太阳自转的动画演示,不仅形象再现了日夜交替与四季变换的核心天文学现象,为用户提供了模式切换与交互操作的沉浸式体验

picture.image

// 定义地球和太阳的关系
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 通过监听手势识别事件捕捉用户右手的捏合动作:首次触发时负责启动程序并进入 "自转" 模式,之后每次捏合在自转和公转之间循环切换,从而以简洁直观的交互方式实现对太阳系模拟的动态控制

picture.image

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 通过贴图与半透明平面材质的构建,实现地球表面的真实渲染,提供可视化的辅助平面,用于清晰演示地球的自转与公转运动

picture.image

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场景的交互感非常直观自然:通过手势可以轻松控制地球的自转和公转,四季文本与轨道倾角的显示同步更新,背景黑色增强了模型的立体感和沉浸感,滑动控制器和面板按钮的响应流畅,手势触发音频播放让解说与视觉效果紧密结合,整体感觉像在手中 "掌控" 一个微型太阳系,既直观又具有教育趣味性

picture.image picture.image picture.image

总结

jsar-our-earth 用 AR 技术打破天体科普的抽象感,以 TypeScript、Babylon.js、JSAR 为技术支撑,把地球自转公转、黄赤交角等难理解的知识,变成能看能控能听的体验,项目文件结构清晰,五大核心模块配合流畅,既实用又能给 AR 开发提供参考,对开发者是好案例,对用户能让天文知识 "活" 起来,也为 AR 科普指明了 "用直观体验简化复杂知识" 的方向!

0
0
0
0
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论