---
name: threejs.aicomponent
description: Three.js 3D 引擎运行时 + 完整可运行空项目。提供 15 个文件：场景生命周期管理（Preloader→Main）、PBR 渲染管线（阴影/色调映射）、响应式视口（ResizeObserver）、资源加载（Texture/GLTF/Audio/CubeTexture）、OrbitControls。安装依赖后 `pnpm dev` 即可看到旋转 Torus Knot + 网格地面。
triggers: 需要初始化一个新的 Three.js 3D 项目、添加 3D 游戏引擎基础层，或配置 WebGL 渲染器和 3D 场景时触发。
---

# Three.js 3D 引擎（Three.js Engine + Complete Project Skeleton）

## 说明

本 skill 是所有 Three.js skill 的基础。它贡献的 **15 个文件**构成了一个**完整可运行的 Three.js 空项目**，开箱即用：

```bash
pnpm install
pnpm dev      # 开发模式 → 浏览器显示旋转 Torus Knot + 网格地面 + 坐标轴
pnpm build    # 构建产物
```

**已内置的基础能力：**

- **场景生命周期管理**：`SceneManager` + `BaseScene` 抽象类，支持 Preloader → Main 多场景切换
- **PBR 渲染管线**：ACES Filmic 色调映射 + PCF 软阴影 + 自动 HiDPI 像素比
- **响应式视口**：`ViewportManager` 基于 ResizeObserver，自动更新 Renderer + Camera
- **资源加载系统**：`AssetLoader` 支持 Texture / GLTF / Audio / CubeTexture 批量加载 + 进度回调
- **OrbitControls**：带阻尼的轨道相机控制，可通过配置开关
- **HTML Loading Overlay**：CSS 动画进度条，加载完成自动淡出
- **TypeScript 完整支持**：资源模块声明（图片/音频/3D模型/HDR/EXR）+ `__DEV__` 全局变量

---

## Scaffold

| 目标路径 | 来源 | 说明 |
|---------|-----|-----|
| `src/main.ts` | `ref/main.ts` | **webpack 入口**：import CSS → DOMContentLoaded → StartGame |
| `src/index.html` | `ref/index.html` | **HTML 容器**：viewport meta + `#game-container` + `#loading-overlay` |
| `src/index.css` | `ref/index.css` | **全屏样式**：移动端适配 + Loading overlay CSS 动画 |
| `globals.d.ts` | `ref/globals.d.ts` | **类型声明**：资源模块（图片/音频/3D模型/HDR）+ `__DEV__` 全局 |
| `tsconfig.json` | `ref/tsconfig.json` | **TypeScript 配置**：target es2020, moduleResolution bundler, paths assets/* |
| `src/game/index.ts` | `ref/index.ts` | **`StartGame(containerId)`**：配置 Renderer + Camera + SceneManager + 动画主循环 |
| `src/game/GameConfig.ts` | `ref/GameConfig.ts` | **游戏配置**：渲染参数、相机、灯光、调试开关 |
| `src/game/SceneKeys.ts` | `ref/SceneKeys.ts` | **场景名枚举**：Preloader / Main |
| `src/game/SceneManager.ts` | `ref/SceneManager.ts` | **场景管理器**：注册、切换、更新、销毁 |
| `src/game/scenes/BaseScene.ts` | `ref/BaseScene.ts` | **场景基类**：init/enter/update/exit/dispose 生命周期 |
| `src/game/scenes/PreloaderScene.ts` | `ref/PreloaderScene.ts` | **预加载场景**：HTML overlay 进度条 + AssetLoader 批量加载 |
| `src/game/scenes/MainScene.ts` | `ref/MainScene.ts` | **主场景**：Hello Three.js（旋转 Torus Knot + 灯光 + 地面 + 网格） |
| `src/game/utils/ViewportManager.ts` | `ref/ViewportManager.ts` | **响应式视口**：ResizeObserver 自动更新 Renderer + Camera |
| `src/game/utils/AssetLoader.ts` | `ref/AssetLoader.ts` | **资源加载器**：Texture/GLTF/Audio/CubeTexture 批量加载 |
| `package.json` | `ref/package.json` | **依赖**：three + @types/three + webpack 工具链 |
| `webpack.config.js` | `ref/webpack.config.js` | **Webpack 5 配置**：ts-loader + 3D 模型/HDR 资源 + __DEV__ |

`package.json` 新增：`"three": "^0.172.0"`, `"@types/three": "^0.172.0"`

---

## 空项目文件结构

安装本 skill 后 Remix 项目的完整文件树：

```
<remix-project>/
├── src/
│   ├── main.ts               ← webpack 入口
│   ├── index.html            ← HTML 容器（含 viewport meta + loading overlay）
│   ├── index.css             ← 全屏 + 移动端适配 + Loading 动画样式
│   └── game/
│       ├── index.ts          ← StartGame()
│       ├── GameConfig.ts     ← 通用游戏配置
│       ├── SceneKeys.ts      ← 场景名枚举
│       ├── SceneManager.ts   ← 场景生命周期管理器
│       ├── scenes/
│       │   ├── BaseScene.ts      ← 场景抽象基类
│       │   ├── PreloaderScene.ts ← 资源加载 + HTML 进度条
│       │   └── MainScene.ts      ← Hello Three.js（主场景骨架）
│       └── utils/
│           ├── ViewportManager.ts ← 响应式视口管理
│           └── AssetLoader.ts     ← 统一资源加载器
├── assets/
│   ├── models/               ← 3D 模型（.glb, .gltf, .fbx）
│   ├── textures/             ← 贴图（.png, .jpg, .webp, .hdr）
│   └── sounds/               ← 音频（.mp3, .ogg, .wav）
├── globals.d.ts              ← 完整类型声明
├── tsconfig.json             ← TypeScript 配置
├── package.json              ← three + webpack 依赖
└── webpack.config.js         ← 构建配置
```

---

## 文件详解

### src/index.ts（webpack 入口）

```typescript
import "./index.css";
import { StartGame } from "./game";

document.addEventListener("DOMContentLoaded", () => {
  StartGame("game-container");
});
```

### src/game/index.ts（StartGame 函数）

```typescript
import * as THREE from "three";
import { GameConfig } from "./GameConfig";
import { SceneManager } from "./SceneManager";
import { ViewportManager } from "./utils/ViewportManager";

export function StartGame(containerId: string): GameInstance {
  // 1. 创建 WebGLRenderer（抗锯齿 + 阴影 + ACES Filmic 色调映射）
  // 2. 创建 PerspectiveCamera（fov/near/far/position 来自 GameConfig）
  // 3. 注册 PreloaderScene + MainScene → SceneManager
  // 4. 启用 ViewportManager（ResizeObserver 响应式）
  // 5. requestAnimationFrame 主循环：update → render
}
```

### src/game/GameConfig.ts（通用配置）

```typescript
export const GameConfig = {
  antialias: true,                 // 抗锯齿
  shadows: true,                   // 阴影
  toneMapping: "ACESFilmic",       // 色调映射
  toneMappingExposure: 1.0,        // 曝光度
  maxPixelRatio: 2,                // HiDPI 像素比上限

  camera: {
    fov: 60,                       // 视场角
    near: 0.1, far: 1000,         // 裁剪面
    position: { x: 0, y: 3, z: 5 },
    lookAt: { x: 0, y: 0, z: 0 },
  },

  lighting: {
    ambientColor: 0x404040,        // 环境光
    directionalColor: 0xffffff,    // 平行光
    directionalPosition: { x: 5, y: 8, z: 5 },
  },

  clearColor: 0x1a1a2e,           // 场景背景色
  debugScene: "main",              // 启动场景
  showFps: false,                  // FPS 显示
  showGrid: true,                  // 网格/坐标轴
  enableOrbitControls: true,       // 轨道控制器
};
```

### src/game/SceneManager.ts（场景管理器）

```typescript
class SceneManager {
  register(...scenes: BaseScene[])  // 注册场景
  switchTo(key: SceneKey)           // 切换场景（自动调用 exit/init/enter）
  update(dt, elapsed)               // 每帧更新活跃场景
  dispose()                         // 销毁所有场景
}
```

### src/game/scenes/BaseScene.ts（场景基类）

```typescript
abstract class BaseScene {
  readonly key: SceneKey;
  readonly scene: THREE.Scene;    // 每个场景拥有独立的 Three.Scene

  protected onInit(): void {}     // 首次进入时调用（创建 3D 对象）
  protected onEnter(): void {}    // 每次切入时调用
  protected onUpdate(dt, elapsed): void {}  // 每帧调用
  protected onExit(): void {}     // 离开时调用
  protected onDispose(): void {}  // 销毁时调用

  protected goToScene(key): void  // 切换到其他场景
}
```

**使用示例**：

```typescript
import { BaseScene } from "./BaseScene";
import { SceneKeys } from "../SceneKeys";

export class MyScene extends BaseScene {
  private cube!: THREE.Mesh;

  constructor() {
    super(SceneKeys.Main);
  }

  protected onInit(): void {
    const geo = new THREE.BoxGeometry(1, 1, 1);
    const mat = new THREE.MeshStandardMaterial({ color: 0xff6600 });
    this.cube = new THREE.Mesh(geo, mat);
    this.scene.add(this.cube);
  }

  protected onUpdate(dt: number): void {
    this.cube.rotation.y += dt;
  }
}
```

### src/game/utils/ViewportManager.ts（响应式视口）

```typescript
class ViewportManager {
  enable()    // 开始监听 ResizeObserver
  disable()   // 停止监听
  refresh()   // 手动触发一次 resize
  getSize()   // 获取当前 { width, height, aspect }
}
```

与 Phaser 的 `resize → reload` 不同，Three.js 版使用 ResizeObserver 动态更新，无需刷新页面。

### src/game/utils/AssetLoader.ts（资源加载器）

```typescript
const loader = new AssetLoader();

await loader.loadAll({
  textures:     [{ key: "wood", url: woodTextureUrl }],
  models:       [{ key: "character", url: characterGlbUrl }],
  audio:        [{ key: "bgm", url: bgmMp3Url }],
  cubeTextures: [{ key: "skybox", urls: [px, nx, py, ny, pz, nz] }],
}, (progress) => {
  console.log(`Loading: ${(progress * 100).toFixed(0)}%`);
});

const tex  = loader.getTexture("wood");      // THREE.Texture
const gltf = loader.getModel("character");   // GLTF (含 scene/animations)
const buf  = loader.getAudioBuffer("bgm");   // AudioBuffer
```

### src/game/scenes/MainScene.ts（Hello Three.js 空白主场景）

显示旋转 Torus Knot + 网格地面 + 坐标轴 + "Hello Three.js!" 文字精灵。
作为开发起点，替换 `onInit()` / `onUpdate()` 内容即可开始编写游戏逻辑。

---

## 架构对比：Phaser vs Three.js

| 概念 | Phaser skill | Three.js skill |
|------|-------------|---------------|
| **渲染器** | `Phaser.Game` (内置 WebGL) | `THREE.WebGLRenderer` (显式创建) |
| **场景系统** | `Phaser.Scene` (引擎内置) | `BaseScene` + `SceneManager` (自建) |
| **相机** | 2D 隐式相机 | `PerspectiveCamera` (显式 3D) |
| **生命周期** | `preload/create/update` | `onInit/onEnter/onUpdate/onExit/onDispose` |
| **资源加载** | `this.load.image()` (内置) | `AssetLoader` (封装 TextureLoader/GLTFLoader) |
| **响应式** | resize → reload | ResizeObserver (无需刷新) |
| **Loading UI** | Phaser 场景内绘制 | HTML overlay (CSS 动画) |
| **坐标系** | 2D 左上角原点 | 3D 右手坐标系 (Y-up) |
| **设计基准** | 750×1334 dp | 视口宽高比 (aspect ratio) |

---

## 3D 坐标系与基础概念

### 坐标系

- **右手坐标系**：X 向右，Y 向上，Z 朝屏幕外（标准 Three.js / OpenGL 约定）
- **单位**：1 Three.js 单位 = 1 米（推荐约定，方便物理/灯光计算）
- **相机默认位置**：(0, 3, 5) 看向原点 (0, 0, 0)

### PBR 渲染管线

| 参数 | 默认值 | 说明 |
|------|-------|------|
| `antialias` | true | 抗锯齿 |
| `shadows` | true | PCF 软阴影 |
| `toneMapping` | ACESFilmic | ACES 电影级色调映射 |
| `toneMappingExposure` | 1.0 | 曝光度 |
| `maxPixelRatio` | 2 | HiDPI 像素比上限（防止 3× 设备过度渲染） |

### 使用规则

1. 每个场景继承 `BaseScene`，在 `onInit()` 中创建 3D 对象
2. 通过 `this.scene.add(object)` 添加到场景图
3. `onUpdate(dt, elapsed)` 中执行动画逻辑，`dt` 单位秒
4. 切场景用 `this.goToScene(SceneKeys.XXX)`
5. 重型资源在 `PreloaderScene.getManifest()` 中声明，通过 `AssetLoader` 预加载
6. `onDispose()` 中清理事件监听等自定义资源（GPU 资源由基类自动清理）

---

## 移动端适配清单

| 特性 | 实现位置 | 说明 |
|------|---------|------|
| **HiDPI 清晰度** | `StartGame()` | `maxPixelRatio: 2` 限制过高 DPR |
| **禁止页面缩放** | `index.html` | `viewport` meta：`user-scalable=no, maximum-scale=1` |
| **iOS 全屏** | `index.html` | `apple-mobile-web-app-capable` + `black-translucent` 状态栏 |
| **安全区（刘海屏）** | `index.css` | `env(safe-area-inset-*)` padding |
| **禁止文字选中** | `index.css` | `user-select: none` |
| **禁止长按菜单** | `index.css` | `-webkit-touch-callout: none` |
| **禁止 iOS 回弹** | `index.css` | `overscroll-behavior: none; touch-action: none` |
| **禁止点击高亮** | `index.css` | `-webkit-tap-highlight-color: transparent` |
| **响应式 resize** | `ViewportManager` | ResizeObserver 自动更新 Renderer + Camera |
| **Canvas 全屏** | `index.css` | `width: 100% !important; height: 100% !important` |

---

## 新增场景的步骤

1. 在 `SceneKeys.ts` 添加新的场景键：

```typescript
export const SceneKeys = {
  Preloader: "Preloader",
  Main: "Main",
  GameOver: "GameOver",   // ← 新增
} as const;
```

2. 创建场景类（继承 `BaseScene`）：

```typescript
// src/game/scenes/GameOverScene.ts
import { BaseScene } from "./BaseScene";
import { SceneKeys } from "../SceneKeys";

export class GameOverScene extends BaseScene {
  constructor() { super(SceneKeys.GameOver); }

  protected onInit(): void {
    // 创建 3D 对象...
  }

  protected onUpdate(dt: number): void {
    // 动画逻辑...
  }
}
```

3. 在 `src/game/index.ts` 注册场景：

```typescript
import { GameOverScene } from "./scenes/GameOverScene";

sceneManager.register(
  new PreloaderScene(),
  new MainScene(),
  new GameOverScene(),  // ← 新增
);
```

4. 在任意场景中切换：

```typescript
this.goToScene(SceneKeys.GameOver);
```

---

## Imports

本 skill 是所有 Three.js 组件的基础，无 skill 层依赖。

**配套 skill（推荐一起使用）：**
- `webpack_build.aicomponent` — 如需自定义 webpack 配置（本 skill 已内置基础版 webpack.config.js）

**不再需要单独安装**（已内置）：
- 资源模块声明 — 已合入 `globals.d.ts`
- 响应式视口 — `ViewportManager.ts` 已内置
- 资源加载器 — `AssetLoader.ts` 已内置

## Skill Definition

```yaml
tools:
  - read_file
  - write_file
inputs:
  - source: ref/main.ts          (webpack entry)
  - source: ref/index.html       (HTML container with viewport meta + loading overlay)
  - source: ref/index.css        (fullscreen + mobile + loading styles)
  - source: ref/globals.d.ts     (asset module declarations: images/audio/3D models/HDR + __DEV__)
  - source: ref/tsconfig.json    (TypeScript config)
  - source: ref/index.ts         (StartGame function)
  - source: ref/GameConfig.ts    (generic game config: rendering/camera/lighting/debug)
  - source: ref/SceneKeys.ts     (scene key enum)
  - source: ref/SceneManager.ts  (scene lifecycle manager)
  - source: ref/BaseScene.ts     (abstract scene base class)
  - source: ref/PreloaderScene.ts (preloader scene with HTML overlay)
  - source: ref/MainScene.ts     (main scene: Hello Three.js skeleton)
  - source: ref/ViewportManager.ts (responsive viewport via ResizeObserver)
  - source: ref/AssetLoader.ts   (unified asset loader: Texture/GLTF/Audio/CubeTexture)
outputs:
  - projectSkeleton: 15 files forming a complete runnable Three.js project
  - threeDep: three ^0.172.0 + @types/three ^0.172.0 npm dependencies
  - sceneLifecycle: BaseScene + SceneManager multi-scene architecture
  - pbrPipeline: ACES Filmic tone mapping + PCF soft shadows + HiDPI
  - responsiveViewport: ViewportManager with ResizeObserver
  - assetLoading: AssetLoader supporting Texture/GLTF/Audio/CubeTexture
  - mobileAdaptation: viewport meta, safe areas, touch handling
```

## Recipe

| 决策 | 原因 |
|-----|-----|
| **BaseScene + SceneManager** | Three.js 没有内置场景生命周期，需自建；保持与 Phaser skill 相似的开发体验 |
| **HTML Loading Overlay** | 加载期间无需 Three.js 渲染，CSS 动画更轻量；加载完成后 `hidden` 类触发淡出 |
| **ResizeObserver 而非 reload** | 3D 场景状态丰富（相机位置、动画进度），reload 会丢失；ResizeObserver 高效且无副作用 |
| **maxPixelRatio: 2** | 3D 渲染远比 2D 昂贵，3× 设备（如 iPhone）全 DPR 渲染会严重影响帧率 |
| **ACES Filmic 色调映射** | 影视级色彩表现，比 Linear 更自然，是 PBR 管线的标准选择 |
| **PCF 软阴影** | 平衡画质与性能；如需更高质量可切 VSM，更轻量可关闭 |
| **OrbitControls 可配置** | 开发调试必备，正式游戏可通过 `enableOrbitControls: false` 关闭 |
| **每场景独立 THREE.Scene** | 隔离场景图、灯光、后处理；切场景时只需切 renderer.render 的 target |
| **AssetLoader 按 key 存储** | 全局唯一 key → 资源映射，任何场景都可按 key 取用；避免重复加载 |
| **ES2020 target** | Three.js 广泛使用现代 JS 特性（class fields、可选链），ES5 target 会导致问题 |

## Adapter

- **Role**: `threejsEngine` — Three.js 3D 游戏引擎基座，所有 Three.js 系 skill 的运行时依赖
- **Provides**: `StartGame()` 启动入口、`SceneManager` 场景管理器、`BaseScene` 场景抽象基类、`ViewportManager` 响应式视口、`AssetLoader` 统一资源加载器、`GameConfig` 渲染配置
- **Requires**: 无 skill 层依赖（`webpack_build.aicomponent` 为配套工具）
- **Consumed by**: 所有以 `threejs.aicomponent` 为 Imports 硬依赖的 skill，包括 `camera_controller_3d`、`tray_container`（3D Mesh 模式）、`slide_out_to_tray_animation`（3D 模式）等
- **Integration point**: `src/main.ts` 在 `DOMContentLoaded` 中调用 `StartGame("game-container")`
