u-space 0.0.30 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/getTransferables-C97JPUVf.cjs +1 -0
- package/dist/getTransferables-DrzSmiCR.js +43 -0
- package/dist/index.cjs +3 -3
- package/dist/index.js +1549 -1026
- package/dist/modelDecodeProtocol-D1Apm9iG.cjs +1 -0
- package/dist/modelDecodeProtocol-wDB7ksYw.js +56 -0
- package/dist/plugins/u-manager/index.cjs +1 -1
- package/dist/plugins/u-manager/index.js +73 -53
- package/dist/src/loaders/ModelDecodeWorkerClient.d.ts +31 -0
- package/dist/src/loaders/ModelLoaderManager.d.ts +15 -2
- package/dist/src/loaders/buildDecodedGltfModel.d.ts +4 -0
- package/dist/src/loaders/index.d.ts +1 -0
- package/dist/src/objects/Model.d.ts +1 -0
- package/dist/src/worker/decodeGltfModel.d.ts +10 -0
- package/dist/src/worker/getTransferables.d.ts +1 -0
- package/dist/src/worker/index.d.ts +1 -0
- package/dist/src/worker/installWorkerImageLoader.d.ts +3 -0
- package/dist/src/worker/model-decoder.d.ts +13 -0
- package/dist/src/worker/modelDecodeProtocol.d.ts +205 -0
- package/dist/src/worker/workerImageUtils.d.ts +8 -0
- package/dist/worker/index.cjs +1 -1
- package/dist/worker/index.js +8 -6
- package/dist/worker/model-decoder.cjs +1 -0
- package/dist/worker/model-decoder.js +339 -0
- package/dist/worker/runtime.cjs +1 -1
- package/dist/worker/runtime.js +202 -208
- package/dist/workerImageUtils-lmOMyTQN.cjs +1 -0
- package/dist/workerImageUtils-mQD3TQ1N.js +105 -0
- package/docs/api-effects.md +66 -2
- package/docs/api-viewer.md +45 -1
- package/docs/changelog.md +26 -0
- package/docs/examples-guide.md +31 -11
- package/docs/getting-started.md +97 -93
- package/docs/index.md +2 -2
- package/docs/mcp.md +16 -3
- package/docs/release.md +5 -3
- package/package.json +9 -1
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
const s = {
|
|
2
|
+
colorSpaceConversion: "none",
|
|
3
|
+
premultiplyAlpha: "none"
|
|
4
|
+
};
|
|
5
|
+
function o(n, t) {
|
|
6
|
+
return n[t] | n[t + 1] << 8 | n[t + 2] << 16;
|
|
7
|
+
}
|
|
8
|
+
function c(n, t) {
|
|
9
|
+
return n[t] | n[t + 1] << 8;
|
|
10
|
+
}
|
|
11
|
+
function d(n, t) {
|
|
12
|
+
return n[t] | n[t + 1] << 8 | n[t + 2] << 16 | n[t + 3] << 24;
|
|
13
|
+
}
|
|
14
|
+
function f(n) {
|
|
15
|
+
return n === 192 || n === 193 || n === 194 || n === 195 || n === 197 || n === 198 || n === 199 || n === 201 || n === 202 || n === 203 || n === 205 || n === 206 || n === 207;
|
|
16
|
+
}
|
|
17
|
+
async function l(n, t, e) {
|
|
18
|
+
return new Uint8Array(await n.slice(t, t + e).arrayBuffer());
|
|
19
|
+
}
|
|
20
|
+
async function w(n) {
|
|
21
|
+
let t = 2;
|
|
22
|
+
for (; t + 4 <= n.size; ) {
|
|
23
|
+
const e = await l(n, t, Math.min(16, n.size - t));
|
|
24
|
+
let r = 0;
|
|
25
|
+
for (; r < e.length && e[r] === 255; ) r++;
|
|
26
|
+
if (r === 0 || r >= e.length) return null;
|
|
27
|
+
const h = e[r], i = r + 1, a = t + i;
|
|
28
|
+
if (h === 216 || h === 1 || h >= 208 && h <= 215) {
|
|
29
|
+
t = a;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (h === 217 || h === 218 || i + 1 >= e.length) return null;
|
|
33
|
+
const u = e[i] << 8 | e[i + 1];
|
|
34
|
+
if (u < 2) return null;
|
|
35
|
+
if (f(h))
|
|
36
|
+
return i + 6 >= e.length ? null : {
|
|
37
|
+
width: e[i + 5] << 8 | e[i + 6],
|
|
38
|
+
height: e[i + 3] << 8 | e[i + 4]
|
|
39
|
+
};
|
|
40
|
+
t = a + u;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
async function p(n) {
|
|
45
|
+
const t = await l(n, 0, Math.min(32, n.size));
|
|
46
|
+
if (t.length < 10) return null;
|
|
47
|
+
if (t[0] === 255 && t[1] === 216) return w(n);
|
|
48
|
+
if (t.length >= 24 && t[0] === 137 && t[1] === 80 && t[2] === 78 && t[3] === 71) {
|
|
49
|
+
const i = new DataView(t.buffer, t.byteOffset, t.byteLength);
|
|
50
|
+
return { width: i.getUint32(16), height: i.getUint32(20) };
|
|
51
|
+
}
|
|
52
|
+
if (t[0] === 71 && t[1] === 73 && t[2] === 70 && t[3] === 56) return { width: c(t, 6), height: c(t, 8) };
|
|
53
|
+
if (t.length >= 30 && t[0] === 82 && t[1] === 73 && t[2] === 70 && t[3] === 70 && t[8] === 87 && t[9] === 69 && t[10] === 66 && t[11] === 80) {
|
|
54
|
+
const i = String.fromCharCode(t[12], t[13], t[14], t[15]);
|
|
55
|
+
if (i === "VP8X")
|
|
56
|
+
return { width: o(t, 24) + 1, height: o(t, 27) + 1 };
|
|
57
|
+
if (i === "VP8 " && t[23] === 157 && t[24] === 1 && t[25] === 42)
|
|
58
|
+
return {
|
|
59
|
+
width: c(t, 26) & 16383,
|
|
60
|
+
height: c(t, 28) & 16383
|
|
61
|
+
};
|
|
62
|
+
if (i === "VP8L" && t[20] === 47) {
|
|
63
|
+
const a = d(t, 21);
|
|
64
|
+
return { width: (a & 16383) + 1, height: (a >>> 14 & 16383) + 1 };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
function g(n, t, e = 8192) {
|
|
70
|
+
if (n <= e && t <= e) return null;
|
|
71
|
+
const r = e / Math.max(n, t);
|
|
72
|
+
return {
|
|
73
|
+
width: Math.max(1, Math.round(n * r)),
|
|
74
|
+
height: Math.max(1, Math.round(t * r))
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async function m(n, t = {}) {
|
|
78
|
+
const e = {
|
|
79
|
+
...s,
|
|
80
|
+
...t,
|
|
81
|
+
colorSpaceConversion: "none"
|
|
82
|
+
}, r = await p(n), h = r ? g(r.width, r.height) : null;
|
|
83
|
+
if (h)
|
|
84
|
+
return createImageBitmap(n, {
|
|
85
|
+
...e,
|
|
86
|
+
resizeWidth: h.width,
|
|
87
|
+
resizeHeight: h.height,
|
|
88
|
+
resizeQuality: "high"
|
|
89
|
+
});
|
|
90
|
+
const i = await createImageBitmap(n, e), a = g(i.width, i.height);
|
|
91
|
+
if (!a) return i;
|
|
92
|
+
try {
|
|
93
|
+
return await createImageBitmap(i, {
|
|
94
|
+
...s,
|
|
95
|
+
resizeWidth: a.width,
|
|
96
|
+
resizeHeight: a.height,
|
|
97
|
+
resizeQuality: "high"
|
|
98
|
+
});
|
|
99
|
+
} finally {
|
|
100
|
+
i.close();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export {
|
|
104
|
+
m as c
|
|
105
|
+
};
|
package/docs/api-effects.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Effects API
|
|
2
2
|
|
|
3
|
-
`u-space` 提供了两个基于 Three.js 着色语言(TSL/WebGPU 节点)的静态特效工具:`MaterialEffects` 用于为对象应用高亮、呼吸等材质特效,`TSLEffects`
|
|
3
|
+
`u-space` 提供了两个基于 Three.js 着色语言(TSL/WebGPU 节点)的静态特效工具:`MaterialEffects` 用于为对象应用高亮、呼吸等材质特效,`TSLEffects` 用于生成动态颜色节点模式和可组合的 Outline 输出效果。
|
|
4
4
|
|
|
5
5
|
## `MaterialEffects`
|
|
6
6
|
|
|
@@ -123,7 +123,71 @@ await MaterialEffects.fadeIn(myModel, { duration: 500 });
|
|
|
123
123
|
|
|
124
124
|
## `TSLEffects`
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
静态工厂类。`flow()`、`breathe()` 和 `fluid()` 返回可赋给 `NodeMaterial.colorNode` 的 TSL 颜色节点;`outline()` 返回接入 `RenderPipeline` 的输出效果。持续动画需要 `viewer.frameloop = 'always'`。
|
|
127
|
+
|
|
128
|
+
### `TSLEffects.outline(options?)`
|
|
129
|
+
|
|
130
|
+
创建基于 Three.js `OutlineNode` 的屏幕空间描边效果。它不会修改对象材质,而是通过 `RenderPipeline.addOutputEffect()` 叠加在默认渲染结果上,因此可以与内置 Bloom、SSGI、SSR 以及其他 output effects 组合。
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import { TSLEffects } from 'u-space';
|
|
134
|
+
|
|
135
|
+
const outline = TSLEffects.outline({
|
|
136
|
+
selectedObjects: [model],
|
|
137
|
+
edgeStrength: 3,
|
|
138
|
+
edgeThickness: 1.5,
|
|
139
|
+
edgeGlow: 0,
|
|
140
|
+
visibleEdgeColor: 0x00e5ff,
|
|
141
|
+
hiddenEdgeColor: 0xff4d6d,
|
|
142
|
+
downSampleRatio: 2,
|
|
143
|
+
instanceBoundsProxy: true,
|
|
144
|
+
instanceBoundsPadding: 0.05,
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
viewer.renderPipeline.addOutputEffect(outline);
|
|
148
|
+
|
|
149
|
+
// 替换当前描边对象,不需要重建后处理链。
|
|
150
|
+
outline.setSelectedObjects([anotherObject]);
|
|
151
|
+
|
|
152
|
+
// 运行时更新样式与 InstanceObject bounds proxy 参数。
|
|
153
|
+
outline.update({ edgeStrength: 4, edgeGlow: 0.25 });
|
|
154
|
+
viewer.render();
|
|
155
|
+
|
|
156
|
+
// 销毁前先从 RenderPipeline 移除。
|
|
157
|
+
viewer.renderPipeline.removeOutputEffect(outline);
|
|
158
|
+
outline.dispose();
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
#### `TSLOutlineOptions`
|
|
162
|
+
|
|
163
|
+
| 属性 | 类型 | 默认值 | 说明 |
|
|
164
|
+
| :--- | :--- | :--- | :--- |
|
|
165
|
+
| `selectedObjects` | `readonly Object3D[]` | `[]` | 初始描边对象。普通 Mesh、Group 与核心 `InstanceObject` 均可传入。 |
|
|
166
|
+
| `edgeStrength` | `number` | `3` | 边缘颜色强度。 |
|
|
167
|
+
| `edgeThickness` | `number` | `1` | 边缘厚度。 |
|
|
168
|
+
| `edgeGlow` | `number` | `0` | 描边向外扩散的发光强度。 |
|
|
169
|
+
| `visibleEdgeColor` | `ColorRepresentation` | `0xffffff` | 可见边缘颜色。 |
|
|
170
|
+
| `hiddenEdgeColor` | `ColorRepresentation` | `0x4e3636` | 被遮挡边缘颜色。 |
|
|
171
|
+
| `downSampleRatio` | `number` | `2` | Outline pass 降采样比例;非有限值或 `<= 0` 时恢复为 `2`。值越大成本越低,但边缘精度也越低。 |
|
|
172
|
+
| `instanceBoundsProxy` | `boolean` | `true` | `InstanceObject` 没有可直接描边的渲染子树时,是否用世界包围盒代理生成轮廓。 |
|
|
173
|
+
| `instanceBoundsPadding` | `number` | `0` | bounds proxy 每个方向额外扩张的世界单位。 |
|
|
174
|
+
| `instanceBoundsMinSize` | `number` | `0.001` | bounds proxy 每个轴向的最小尺寸,避免退化包围盒无法描边。 |
|
|
175
|
+
|
|
176
|
+
#### 返回的 `TSLOutlineEffect`
|
|
177
|
+
|
|
178
|
+
| 成员 | 说明 |
|
|
179
|
+
| :--- | :--- |
|
|
180
|
+
| `selectedObjects` | 当前逻辑选择对象的只读快照。 |
|
|
181
|
+
| `outlineNode` | 当前内部 `OutlineNode`;在 RenderPipeline 首次构建前为 `null`,Scene 或 Camera 变化后可能被替换。 |
|
|
182
|
+
| `setSelectedObjects(objects)` | 替换当前选择并返回自身,便于链式调用。空数组会清除描边。 |
|
|
183
|
+
| `update(options)` | 更新除 `selectedObjects` 外的全部样式和 bounds proxy 选项,并返回自身。 |
|
|
184
|
+
| `dispose()` | 释放内部 OutlineNode、代理 Geometry/Material 和 dirty 订阅。调用前应先 `removeOutputEffect()`。 |
|
|
185
|
+
|
|
186
|
+
对于 `InstanceObject`,效果会优先描边 `getInstanceRenderObject()` 返回的可渲染对象;如果逻辑对象自身包含 Mesh/Sprite,则直接使用该对象;只有两者都不可用时才根据 `getInstanceBoundingBox()` 创建不可见 bounds proxy。代理会监听 `onInstanceRenderDirty()`,在实例显隐、变换或包围盒变化后同步,而且不会参与拾取、颜色输出或阴影。
|
|
187
|
+
|
|
188
|
+
> 如果业务设置了自定义 `viewer.renderPipeline.setOutputComposer()`,RenderPipeline 会按自定义 composer 语义跳过所有 output effects,Outline 也不会显示。更新选择或参数后,`frameloop = 'demand'` 的 Viewer 需要调用 `viewer.render()` 或 `viewer.invalidate()` 请求新帧。
|
|
189
|
+
|
|
190
|
+
[在线示例](https://u-space-phi.vercel.app/examples/test_outline.html)
|
|
127
191
|
|
|
128
192
|
### `TSLEffects.flow(parameters?)`
|
|
129
193
|
|
package/docs/api-viewer.md
CHANGED
|
@@ -273,7 +273,7 @@ viewer.dispose();
|
|
|
273
273
|
|
|
274
274
|
## OffscreenCanvas Worker(实验性)
|
|
275
275
|
|
|
276
|
-
`u-space/worker` 提供一个独立入口,可将 `Viewer`、Three.js
|
|
276
|
+
`u-space/worker` 提供一个独立入口,可将 `Viewer`、Three.js 场景、相机控制和 WebGPU 渲染全部放入 dedicated render Worker。主线程只负责持有可见 canvas、转发输入事件与尺寸,并接收状态或渲染统计。模型默认仍在 render Worker 内加载;大型静态 glTF/SBMX 场景还可以 opt-in 第二个 decode Worker,让网络读取、SBMX 字节还原、JSON/base64 解析和图片解码不阻塞相机与已有场景的渲染。
|
|
277
277
|
|
|
278
278
|
Worker runtime 入口仅提供 ESM,需使用 `{ type: 'module' }` 创建 Worker。构建工具也必须保留 ES module 输出;Vite 项目需配置 `worker: { format: 'es' }`,否则 IIFE Worker 无法编译 top-level await。
|
|
279
279
|
|
|
@@ -304,6 +304,50 @@ await host.init();
|
|
|
304
304
|
|
|
305
305
|
`await host.init()` 只等待 Worker 创建 `Viewer`、完成 `await viewer.init()` 并发送 `initialized`,此时 WebGPU renderer 和内置 command 已可用,但 HDR、业务模型、editable batch、pipeline 预热和业务首帧可能仍在继续。`initialized` 事件与 `host.init()` resolve 表示同一个边界;`ready` 是业务层边界,只会在 Worker 代码显式调用一次 `worker.ready(detail)` 后触发。因此应在 `host.init()` 之前注册 `ready` / `status` / `stats` 等监听,业务 command 则通常在 `ready` 后调用。
|
|
306
306
|
|
|
307
|
+
### Worker 超大贴图保护
|
|
308
|
+
|
|
309
|
+
`createWorkerViewer()` 会在创建 `Viewer` 前安装 Worker 图片加载兼容层,同时覆盖 Three.js `ImageLoader` 和 glTF 常用的 `ImageBitmapLoader`。JPEG、PNG、GIF、WebP 在解码前读取编码尺寸;任一边超过 WebGPU 默认可移植上限 `8192` 时,会在第一次 `createImageBitmap()` 解码时按原宽高比缩小。无法预读尺寸的格式会在首次解码后检查,超限时生成缩放后的 bitmap,并立即 `close()` 临时原图。
|
|
310
|
+
|
|
311
|
+
该处理保留 `ImageBitmapLoader.setOptions()`、Three.js Cache、request headers、credentials 和 Loader/LoadingManager abort 语义,也不会修改磁盘或服务端源贴图。它避免在默认 `maxTextureDimension2D = 8192` 的 WebGPU device 上触发 `Texture size`,以及随后连续出现的 `Invalid TextureView`、`Invalid BindGroup` 和 `Invalid CommandBuffer`。即使 adapter 支持 `16384`,runtime 也不会强制申请更高 limit,以保持不同 GPU 的可移植性并限制超大贴图的显存占用。
|
|
312
|
+
|
|
313
|
+
自动缩放只覆盖经 `ImageLoader` / `ImageBitmapLoader` 加载的普通图片;KTX2 等压缩纹理由各自 loader 处理,业务仍应在资产管线中确保其尺寸不超过目标设备 limit。
|
|
314
|
+
|
|
315
|
+
### 独立模型 Decode Worker
|
|
316
|
+
|
|
317
|
+
在拥有 Three.js/WebGPU 的 render Worker 中,把一个专用 Worker 交给 `ModelLoaderManager`:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
import { ModelLoaderManager } from 'u-space';
|
|
321
|
+
import { createWorkerViewer } from 'u-space/worker/runtime';
|
|
322
|
+
|
|
323
|
+
const worker = await createWorkerViewer();
|
|
324
|
+
const modelDecodeWorker = new Worker(
|
|
325
|
+
new URL('./model.decode.worker.ts', import.meta.url),
|
|
326
|
+
{ type: 'module' },
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const disposeModelDecodeWorker = ModelLoaderManager.setDecodeWorker(modelDecodeWorker);
|
|
330
|
+
worker.onDispose(disposeModelDecodeWorker);
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
专用 decode Worker 入口不创建 `Viewer`,只安装解码协议:
|
|
334
|
+
|
|
335
|
+
```typescript
|
|
336
|
+
import { installModelDecodeWorker } from 'u-space/worker/model-decoder';
|
|
337
|
+
|
|
338
|
+
installModelDecodeWorker();
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
启用后,`ModelLoaderManager.loadAsync()` 会把受支持的静态 `.gltf`、`.glb` 和 `.sbmx` 请求排入 decode Worker。decode Worker 负责 fetch/Cache Storage、SBMX nibble swap、glTF JSON/base64/buffer 解析、普通图片解码和超大图片等比缩放;完成后以 transferable `ArrayBuffer` / `ImageBitmap` 把纯数据包发送给 render Worker。render Worker 才创建 `BufferGeometry`、`MeshStandardMaterial`、`Texture` 和 `Object3D`,因此 Three.js 对象和 GPU 资源仍只有一个 owner。
|
|
342
|
+
|
|
343
|
+
协议采用串行队列和 ACK backpressure:render Worker 重建完当前模型后才允许发送下一个大数据包,避免消息队列同时堆积多个模型副本。相同图片会按 SHA-256 内容身份复用已转移的 `ImageBitmap`,保持与 Three.js loader cache 一致的材质签名和 editable batching 数量;身份索引使用有界 LRU,不会随不同贴图持续增长。传输层直接使用浏览器 structured clone/transferable,没有额外二进制序列化依赖。
|
|
344
|
+
|
|
345
|
+
外部 buffer/贴图 URL 会回到 render Worker 经 `LoadingManager.resolveURL()` 处理,并完整触发 `itemStart` / `itemEnd` / `itemError`。模型请求的 headers 与 credentials 只会传给同源依赖;URL modifier 把依赖改写到其他 origin 时会自动去掉敏感 headers 并使用 `credentials: 'omit'`,避免授权信息泄漏给资产文件中声明的第三方地址。
|
|
346
|
+
|
|
347
|
+
当前快速路径只接受无 animation、skin、morph target、camera、sparse accessor、glTF extension/压缩扩展且 primitive mode 为 triangles 的 glTF 2 静态模型;节点图还必须满足无重复 child、无多父节点、无环及安全深度限制。其他受支持但不适合快速路径的模型会自动回退现有 `GLTFLoader` / `SBMXLoader`,不会降级功能;decode Worker 致命错误也会摘除失效 client 并回退当前请求。`ModelLoaderManager` 拥有传入的 Worker;再次调用 `setDecodeWorker()` 或传入 `null` 会 dispose 并 terminate 旧 Worker。`setDecodeWorker(worker)` 返回与该实例绑定的 disposer,建议交给 render runtime 的 `onDispose()`,避免旧清理回调误终止后来替换的 Worker。AbortSignal 同时覆盖 decode Worker 快速路径、普通 loader 顶层 fetch 和 glTF/SBMX 外部资源加载。
|
|
348
|
+
|
|
349
|
+
这个能力也可以在主线程的普通 `Viewer` 中启用,但它的主要收益是在 Offscreen render Worker 中把模型解码进一步拆开,使场景已可交互时仍能继续流式装载。它不减少网络体积、最终 GPU 内存或 draw calls;这些仍由资源压缩、instancing、editable batching、LOD 和裁剪解决。
|
|
350
|
+
|
|
307
351
|
### `OffscreenViewerHost`
|
|
308
352
|
|
|
309
353
|
构造选项:
|
package/docs/changelog.md
CHANGED
|
@@ -2,8 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
## 未发布
|
|
4
4
|
|
|
5
|
+
## 0.0.32
|
|
6
|
+
|
|
7
|
+
### 修复与优化
|
|
8
|
+
|
|
9
|
+
- **Offscreen 在线示例场景源** — 保留本地 `examples/scenes/飞龙吾悦广场` 及其匹配密钥;GitHub 发布流程会在 Vercel 构建前拉取真实 Git LFS 对象,构建脚本也会拒绝仅含 pointer 的场景。超过 Vercel 单文件上限的场景资源会在部署产物中拆成 90 MiB 分片,由 render/decode Worker 流式还原,既避免把 LFS pointer 当作 JSON,也不替换原场景。
|
|
10
|
+
|
|
11
|
+
### 文档
|
|
12
|
+
|
|
13
|
+
- **TSLEffects Outline** — 补齐 `TSLEffects.outline()`、`TSLOutlineOptions`、`TSLOutlineEffect`、RenderPipeline output-effect 接入、`InstanceObject` 渲染对象/bounds proxy 策略、资源释放、`test_outline.html` 在线示例及 MCP/LLM 检索入口。该效果最早随 `0.0.30` 发布,本次补全此前遗漏的文档发布面。
|
|
14
|
+
|
|
15
|
+
## 0.0.31
|
|
16
|
+
|
|
17
|
+
### 新增
|
|
18
|
+
|
|
19
|
+
- **嵌套模型 Decode Worker** — 新增 `u-space/worker/model-decoder` 和 `ModelLoaderManager.setDecodeWorker()`;静态 glTF/GLB/SBMX 可在 render Worker 之外完成 fetch/cache、SBMX 还原、JSON/base64/buffer 与图片解码,再以 transferable `ArrayBuffer` / `ImageBitmap` 交回 Three.js owner。协议使用单包 ACK backpressure、AbortSignal 和贴图内容身份复用,复杂 glTF 特性自动回退原 loader;Offscreen UManager 示例默认启用,并支持 `?modelDecodeWorker=off` A/B。
|
|
20
|
+
|
|
21
|
+
### 修复与优化
|
|
22
|
+
|
|
23
|
+
- **Offscreen 在线示例** — Vercel 发布现在通过专用 Vite 配置构建 `examples/offscreen/test_umanager2_offscreen.html`,输出可直接运行的主线程 JavaScript、ES module Worker 和 Draco/Basis WASM 资源,不再让生产页面直接加载 TypeScript;Worker 的 HDR URL 同时兼容本地 `/textures/` 与线上 `/examples/textures/` 路径。
|
|
24
|
+
- **Offscreen Worker 超大贴图** — Worker runtime 同时覆盖 Three.js `ImageLoader` 与 `ImageBitmapLoader`;JPEG/PNG/GIF/WebP 任一边超过 WebGPU 默认 `8192` 上限时,在首次 `createImageBitmap()` 解码时等比缩小并保留 loader options/cache/abort 语义,避免 `Texture size` 继续级联为 `Invalid TextureView`、`Invalid BindGroup` 和 `Invalid CommandBuffer`。未知格式在解码后缩放并释放临时原图,源资产不会被修改。
|
|
25
|
+
- **Offscreen UManager 设备调试** — 增加上一个/下一个设备、飞向、高亮和清除命令;语义楼层、墙体、空间、门窗和设备默认全部显示,设备切换、飞向和清除不再改变其他对象显隐。
|
|
26
|
+
- **模型 Decode Worker 稳定性与安全性** — 外部 buffer/贴图恢复 `LoadingManager.resolveURL()` 和完整 item 生命周期,跨域依赖不再继承模型请求的授权 headers/credentials;增加 glTF 节点图规模、深度、环、多父节点与重复 child 校验,并把贴图内容身份缓存改为有界 LRU。decode Worker 崩溃会自动摘除并回退当前请求,`setDecodeWorker()` 返回实例绑定 disposer,AbortSignal 也覆盖普通 loader 路径;失败的 `Model` 内存缓存 Promise 会被清除以允许重试。
|
|
27
|
+
|
|
28
|
+
## 0.0.30
|
|
29
|
+
|
|
5
30
|
### 新增
|
|
6
31
|
|
|
32
|
+
- **TSLEffects Outline** — 新增 `TSLEffects.outline()` 可组合 output effect,支持普通 `Object3D` 与核心 `InstanceObject` 的可见/隐藏边缘描边、运行时选择和样式更新,以及在实例没有直接可描边渲染子树时使用世界包围盒代理;`examples/test_outline.html` 提供交互示例。
|
|
7
33
|
- **AO 示例** — 新增 `examples/test_ao.html`,通过 SSGI 的 `giIntensity: 0` 默认开启纯 AO,并提供遮蔽强度、半径和厚度实时调节。
|
|
8
34
|
- **RenderPipeline SSR** — 新增 `enableSSR()`、`updateSSR()` 和 `disableSSR()`,封装 Stochastic SSR、Temporal Reproject、Recurrent Denoise、velocity 以及双向历史反馈;提供完整 SSR 与降噪参数配置。
|
|
9
35
|
- **Viewer** — 新增可选 `pixelRatio` 构造参数,省略时继续使用 `Math.min(window.devicePixelRatio, 1.5)`。
|
package/docs/examples-guide.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 运行方式与版本
|
|
6
6
|
|
|
7
|
-
示例统一通过 `examples/importmap.js` 注入 Import Map。本地通过 `localhost`、`127.0.0.1`、`0.0.0.0` 或 `192.168.x.x` 访问时会加载仓库里的 `../dist/` 构建产物;在线部署或非本地域名访问时会从 jsDelivr 加载当前发布版本 `u-space@0.0.
|
|
7
|
+
示例统一通过 `examples/importmap.js` 注入 Import Map。本地通过 `localhost`、`127.0.0.1`、`0.0.0.0` 或 `192.168.x.x` 访问时会加载仓库里的 `../dist/` 构建产物;在线部署或非本地域名访问时会从 jsDelivr 加载当前发布版本 `u-space@0.0.32`。Vercel 文档部署会继续把 `__VERSION__` 占位符替换为 `package.json` 中的版本号,源码里的 `0.0.32` 作为直接托管 `examples/` 时的 fallback。
|
|
8
8
|
|
|
9
9
|
插件示例可以在页面加载 `importmap.js` 前通过 `window.__IMPORTS__` 声明额外依赖。将 `u-space/plugins/<name>` 的值设为 `true` 时,`importmap.js` 会自动在本地和 CDN 路径之间切换。
|
|
10
10
|
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
|
|
103
103
|
## 8. `test_umanager2_offscreen.html`:OffscreenCanvas Worker + editable batching
|
|
104
104
|
|
|
105
|
-
该示例是 `test_umanager2.html` 的 Worker 版本。完整 `Viewer
|
|
105
|
+
该示例是 `test_umanager2.html` 的 Worker 版本。完整 `Viewer`、Three.js 对象重建、editable batch 构建、相机控制和 WebGPU 渲染运行在 dedicated render Worker;主线程只持有可见 canvas、转发输入/resize,并显示 Worker 返回的状态与统计。静态 SBMX/glTF 的 fetch、字节/JSON/base64 解析和图片解码继续拆到 render Worker 创建的第二个 decode Worker,因此加载新模型时已有 3D 场景仍可响应输入。
|
|
106
106
|
|
|
107
107
|
通过专用 Vite 配置启动:
|
|
108
108
|
|
|
@@ -110,12 +110,18 @@
|
|
|
110
110
|
pnpm example:offscreen
|
|
111
111
|
```
|
|
112
112
|
|
|
113
|
-
然后访问 `http://127.0.0.1:5510/offscreen/test_umanager2_offscreen.html`。该示例的 HTML、主线程入口、Worker 入口和专用 Vite 配置集中在 `examples/offscreen/`
|
|
113
|
+
然后访问 `http://127.0.0.1:5510/offscreen/test_umanager2_offscreen.html`。该示例的 HTML、主线程入口、Worker 入口和专用 Vite 配置集中在 `examples/offscreen/` 目录。可用 `pnpm build:example:offscreen` 验证生产构建。
|
|
114
|
+
|
|
115
|
+
[在线演示](https://u-space-phi.vercel.app/examples/offscreen/test_umanager2_offscreen.html)由一键发布流程单独打包:HTML 加载生成的 JavaScript,Worker 保持 ES module 格式,Draco/Basis WASM 等资源写入 `/examples/assets/`。线上页面不直接执行源码 `.ts`,HDR 则复用 `/examples/textures/puresky_1k.hdr`。
|
|
114
116
|
|
|
115
117
|
### 核心要点:
|
|
116
118
|
|
|
117
119
|
- 主线程使用 `OffscreenViewerHost` 调用 `transferControlToOffscreen()`,以 structured-clone 消息转发 pointer、wheel、resize 和业务 command。
|
|
118
120
|
- Worker 在模块顶层通过 `const worker = await createWorkerViewer()` 创建真正的 `Viewer`,随后以顺序式代码加载 HDR、`SemanticLoader` 和 `SceneLoader`,无需把整个入口包进 setup callback。
|
|
121
|
+
- render Worker 通过 `ModelLoaderManager.setDecodeWorker()` 启用 `test_umanager2_offscreen.decode.worker.ts`,并把返回的实例绑定 disposer 注册到 runtime 清理;decode Worker 从 `u-space/worker/model-decoder` 调用 `installModelDecodeWorker()`,以 transferable `ArrayBuffer` / `ImageBitmap` 返回静态 glTF 数据,Three.js/GPU 对象仍只在 render Worker 创建。外部 buffer/贴图继续经过 render Worker 的 LoadingManager URL modifier 与加载统计,跨域依赖不会继承模型请求的敏感 headers/credentials。
|
|
122
|
+
- 解码队列一次只允许一个待确认数据包;render Worker 重建后发送 ACK 再取下一项,防止 1GB 级场景同时堆积多个模型副本。相同图片通过有界 LRU 内容指纹复用 bitmap 身份,避免把原本 25 个 editable material batches 拆成大量重复批次,同时不永久持有所有历史贴图。
|
|
123
|
+
- animation、skin、morph、camera、sparse accessor、glTF extension/压缩扩展和非 triangle primitive 不进入快速路径,会自动使用现有 loader。访问 `?modelDecodeWorker=off` 可关闭嵌套 Worker,与原始 loader 做画面、批次和加载响应 A/B 对比;默认开启。
|
|
124
|
+
- Worker runtime 同时接管 Three.js `ImageLoader` 与 `ImageBitmapLoader`:JPEG/PNG/GIF/WebP 任一边超过 WebGPU 默认 `8192` 上限时,会在第一次 `createImageBitmap()` 解码时等比缩小,保留 loader options/cache/abort 语义且不修改源文件,避免 `Texture size` 引发的 WebGPU validation 级联错误。
|
|
119
125
|
- `await host.init()` / `initialized` 只表示 Worker 内 `viewer.init()` 完成;HDR、模型、editable batch、pipeline 预热和业务首帧完成后,Worker 才显式调用一次 `worker.ready(detail)`。主线程应先注册 `ready` 监听,再执行 `host.init()`,并在 `ready` 后调用业务 command。
|
|
120
126
|
- `SceneLoader.setEditableBatching()` 在 Worker 中完成 Geometry 克隆、变换烘焙、合并和 TSL 状态纹理创建;透明、蒙皮、morph、自定义 shader 等不支持子集保持 fallback。
|
|
121
127
|
- 首帧前关闭 controls invalidation 并等待 `renderer.compileAsync()`,预热 WebGPU 合批管线后再显示完整画面。
|
|
@@ -218,7 +224,21 @@ pnpm example:offscreen
|
|
|
218
224
|
- 使用 `MaterialEffects.fadeOut(object)` / `fadeIn(object)` 执行淡入淡出动画。
|
|
219
225
|
- 使用 `MaterialEffects.removeHighlightColor(object)` 恢复原始材质状态。
|
|
220
226
|
|
|
221
|
-
## 16. `
|
|
227
|
+
## 16. `test_outline.html`:TSLEffects Outline 描边
|
|
228
|
+
|
|
229
|
+
[在线演示](https://u-space-phi.vercel.app/examples/test_outline.html)
|
|
230
|
+
|
|
231
|
+
该示例演示 `TSLEffects.outline()` 作为 RenderPipeline output effect 对普通 Mesh、Group 和核心 `InstanceObject` 进行屏幕空间描边。
|
|
232
|
+
|
|
233
|
+
### 核心要点:
|
|
234
|
+
|
|
235
|
+
- 使用 `viewer.renderPipeline.addOutputEffect(outline)` 叠加 Outline,不修改原始材质,也不占用业务侧 `setOutputComposer()`。
|
|
236
|
+
- 使用 `outline.setSelectedObjects()` 在 Box、Sphere、`InstanceObject`、全部对象和空选择之间切换。
|
|
237
|
+
- 使用 `outline.update()` 实时调整强度、厚度、Glow 和 `instanceBoundsPadding`。
|
|
238
|
+
- `InstanceObject` 有实际渲染对象时直接描边;没有可渲染子树时可通过 `getInstanceBoundingBox()` 自动生成不可见 bounds proxy。
|
|
239
|
+
- 销毁时先调用 `removeOutputEffect(outline)`,再调用 `outline.dispose()` 释放 OutlineNode 与代理资源。
|
|
240
|
+
|
|
241
|
+
## 17. `test_selection.html`:Selection 选择系统
|
|
222
242
|
|
|
223
243
|
该示例演示了对象选择系统的单选和框选功能。
|
|
224
244
|
|
|
@@ -229,7 +249,7 @@ pnpm example:offscreen
|
|
|
229
249
|
- 使用 `selection.enableBoxSelection()` 开启鼠标框选模式。
|
|
230
250
|
- 框选模式下可配合 `viewer.controls.lock()` 避免框选与相机旋转冲突。
|
|
231
251
|
|
|
232
|
-
##
|
|
252
|
+
## 18. `test_measure.html`:MeasureTool 测量工具
|
|
233
253
|
|
|
234
254
|
该示例演示了 3D 场景中的距离、面积和角度测量。
|
|
235
255
|
|
|
@@ -240,7 +260,7 @@ pnpm example:offscreen
|
|
|
240
260
|
- 使用 `measureTool.measureAngle(pointA, vertex, pointC)` 测量夹角。
|
|
241
261
|
- 测量结果以可视化线条渲染到场景中,返回的 `MeasureResult` 包含数值和单位。
|
|
242
262
|
|
|
243
|
-
##
|
|
263
|
+
## 19. `test_annotation.html`:AnnotationManager 标注管理
|
|
244
264
|
|
|
245
265
|
该示例演示了 3D 标注的创建、更新和管理,配合 `CSSRenderer` 实现 HTML 浮动标签。
|
|
246
266
|
|
|
@@ -252,7 +272,7 @@ pnpm example:offscreen
|
|
|
252
272
|
- 使用 `updateContent()` 动态更新标注内容,`updatePosition()` 移动标注位置。
|
|
253
273
|
- 使用 `hideAll()` / `showAll()` 批量控制标注显隐。
|
|
254
274
|
|
|
255
|
-
##
|
|
275
|
+
## 20. `test_clipping.html`:ClippingTool 剖切工具
|
|
256
276
|
|
|
257
277
|
该示例演示了剖切面的创建和实时调整。
|
|
258
278
|
|
|
@@ -263,7 +283,7 @@ pnpm example:offscreen
|
|
|
263
283
|
- 使用滑块通过 `clippingTool.setPlaneConstant()` 实时调整剖切位置。
|
|
264
284
|
- 使用 `showHelper()` / `hideHelper()` 切换剖切面可视化辅助。
|
|
265
285
|
|
|
266
|
-
##
|
|
286
|
+
## 21. `test_lights.html`:LightManager 灯光管理
|
|
267
287
|
|
|
268
288
|
该示例演示了灯光管理器的灯光预设和 Helper 可视化功能。
|
|
269
289
|
|
|
@@ -274,7 +294,7 @@ pnpm example:offscreen
|
|
|
274
294
|
- 使用 `showAllHelpers()` / `hideAllHelpers()` 显示或隐藏灯光辅助可视化。
|
|
275
295
|
- 每种预设会自动清除旧灯光并创建新的灯光组合。
|
|
276
296
|
|
|
277
|
-
##
|
|
297
|
+
## 22. `test_fire.html`:WebGPU 体积火焰
|
|
278
298
|
|
|
279
299
|
该示例演示了 `u-space/plugins/fire` 的 `FireEffect` 插件,封装 Three.js 官方体积火焰示例,在独立 volumetric pass 中模拟火焰和烟雾后叠加到主场景。
|
|
280
300
|
|
|
@@ -286,7 +306,7 @@ pnpm example:offscreen
|
|
|
286
306
|
- 通过 `fire.update()` 实时调整 `fireIntensity`、`smokeLifespan`、`emitterRadius` 等参数。
|
|
287
307
|
- 插件使用 RenderPipeline output effect 接入,不占用业务侧的 `setOutputComposer()`。
|
|
288
308
|
|
|
289
|
-
##
|
|
309
|
+
## 23. `test_ssr.html`:SSR 时空降噪
|
|
290
310
|
|
|
291
311
|
[在线演示](https://u-space-phi.vercel.app/examples/test_ssr.html)
|
|
292
312
|
|
|
@@ -304,7 +324,7 @@ pnpm example:offscreen
|
|
|
304
324
|
- `maxDistance` 从默认值 `0.4` 开始,保留滑块用于观察追踪距离对质量和性能的影响。
|
|
305
325
|
- 移动物体继续生成 velocity,但不投射动态阴影;2048² 方向光阴影只更新一次。
|
|
306
326
|
|
|
307
|
-
##
|
|
327
|
+
## 24. `test_ao.html`:环境光遮蔽
|
|
308
328
|
|
|
309
329
|
[在线演示](https://u-space-phi.vercel.app/examples/test_ao.html)
|
|
310
330
|
|
package/docs/getting-started.md
CHANGED
|
@@ -1,123 +1,127 @@
|
|
|
1
1
|
# 快速上手 `u-space`
|
|
2
2
|
|
|
3
|
-
`u-space` 是一个基于 Three.js 的 WebGPU 3D
|
|
3
|
+
`u-space` 是一个基于 Three.js 的 WebGPU 3D 引擎。本指南使用 React、TypeScript 和 Vite 搭建一个基础应用。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 创建项目
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
### 1. 初始化 React + Vite
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
pnpm
|
|
10
|
+
pnpm create vite u-space-demo --template react-ts
|
|
11
|
+
cd u-space-demo
|
|
12
|
+
pnpm install
|
|
11
13
|
```
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
### 2. 安装 `u-space`
|
|
14
16
|
|
|
15
|
-
|
|
17
|
+
```bash
|
|
18
|
+
pnpm add u-space three camera-controls
|
|
19
|
+
pnpm add -D @types/three
|
|
20
|
+
```
|
|
16
21
|
|
|
17
|
-
|
|
22
|
+
Vite 会直接解析 npm 包及 `three/webgpu`,不需要额外配置模块映射。
|
|
18
23
|
|
|
19
|
-
|
|
24
|
+
## 编写场景组件
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
<!DOCTYPE html>
|
|
23
|
-
<html lang="zh">
|
|
24
|
-
<head>
|
|
25
|
-
<meta charset="UTF-8" />
|
|
26
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
27
|
-
<title>u-space App</title>
|
|
28
|
-
</head>
|
|
29
|
-
<body style="margin: 0">
|
|
30
|
-
<div id="app" style="width: 100vw; height: 100vh"></div>
|
|
31
|
-
<!-- 脚本放这里 -->
|
|
32
|
-
</body>
|
|
33
|
-
</html>
|
|
34
|
-
```
|
|
26
|
+
### 1. 替换 `src/App.tsx`
|
|
35
27
|
|
|
36
|
-
|
|
28
|
+
下面的组件会初始化 `Viewer`、添加一个可交互的立方体,并在 React 组件卸载时释放资源。
|
|
37
29
|
|
|
38
|
-
|
|
30
|
+
```tsx
|
|
31
|
+
import { useEffect, useRef } from 'react';
|
|
32
|
+
import { BoxGeometry, Color, GridHelper, MeshBasicMaterial } from 'three/webgpu';
|
|
33
|
+
import { BaseMesh, Viewer } from 'u-space';
|
|
39
34
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
35
|
+
export default function App() {
|
|
36
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
const container = containerRef.current;
|
|
40
|
+
if (!container) return;
|
|
41
|
+
|
|
42
|
+
let viewer: Viewer | undefined;
|
|
43
|
+
let cancelled = false;
|
|
44
|
+
|
|
45
|
+
async function setup() {
|
|
46
|
+
const nextViewer = new Viewer({
|
|
47
|
+
el: container,
|
|
48
|
+
rendererOptions: { forceWebGL: false },
|
|
49
|
+
});
|
|
50
|
+
await nextViewer.init();
|
|
51
|
+
|
|
52
|
+
// 兼容 React StrictMode 在开发环境中的重复挂载检查
|
|
53
|
+
if (cancelled) {
|
|
54
|
+
nextViewer.dispose();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
nextViewer.scene.background = new Color(0x666666);
|
|
59
|
+
nextViewer.scene.add(new GridHelper(10, 10));
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
const material = new MeshBasicMaterial({ color: 0xff0000 });
|
|
62
|
+
const box = new BaseMesh(new BoxGeometry(1, 1, 1), material);
|
|
63
|
+
box.position.set(0, 0.5, 0);
|
|
64
|
+
nextViewer.scene.add(box);
|
|
57
65
|
|
|
58
|
-
|
|
59
|
-
<script type="module">
|
|
60
|
-
import { Color, GridHelper, Mesh, BoxGeometry, MeshBasicMaterial } from 'three/webgpu';
|
|
61
|
-
import { Viewer } from 'u-space';
|
|
66
|
+
nextViewer.interactionManager.pointerMoveEventsEnabled = true;
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
box.addEventListener('click', ({ event }) => {
|
|
69
|
+
console.log('点击位置:', event.intersect?.point);
|
|
70
|
+
material.color.set(Math.random() * 0xffffff);
|
|
71
|
+
void nextViewer.render();
|
|
72
|
+
});
|
|
64
73
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
rendererOptions: { forceWebGL: false }, // 优先使用 WebGPU
|
|
69
|
-
});
|
|
70
|
-
await viewer.init();
|
|
74
|
+
box.addEventListener('pointerenter', () => {
|
|
75
|
+
document.body.style.cursor = 'pointer';
|
|
76
|
+
});
|
|
71
77
|
|
|
72
|
-
|
|
73
|
-
|
|
78
|
+
box.addEventListener('pointerleave', () => {
|
|
79
|
+
document.body.style.cursor = 'default';
|
|
80
|
+
});
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
viewer = nextViewer;
|
|
83
|
+
void viewer.render();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
void setup();
|
|
78
87
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
88
|
+
return () => {
|
|
89
|
+
cancelled = true;
|
|
90
|
+
document.body.style.cursor = 'default';
|
|
91
|
+
viewer?.dispose();
|
|
92
|
+
};
|
|
93
|
+
}, []);
|
|
85
94
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
</script>
|
|
95
|
+
return <div ref={containerRef} className="viewer" />;
|
|
96
|
+
}
|
|
89
97
|
```
|
|
90
98
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
document.body.style.cursor = 'default';
|
|
115
|
-
material.color.set(0xff0000);
|
|
116
|
-
viewer.render();
|
|
117
|
-
});
|
|
99
|
+
`BaseMesh` 已包含 `u-space` 交互事件类型,因此可以直接监听 `click`、`pointerenter` 和 `pointerleave`。
|
|
100
|
+
|
|
101
|
+
### 2. 替换 `src/index.css`
|
|
102
|
+
|
|
103
|
+
```css
|
|
104
|
+
html,
|
|
105
|
+
body,
|
|
106
|
+
#root,
|
|
107
|
+
.viewer {
|
|
108
|
+
width: 100%;
|
|
109
|
+
height: 100%;
|
|
110
|
+
margin: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
body {
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### 3. 启动开发服务器
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
pnpm dev
|
|
118
122
|
```
|
|
119
123
|
|
|
120
|
-
|
|
124
|
+
打开 Vite 输出的本地地址,即可看到并操作 `u-space` 场景。
|
|
121
125
|
|
|
122
126
|
## 版本信息
|
|
123
127
|
|
|
@@ -125,7 +129,7 @@ box.addEventListener('pointerleave', (e) => {
|
|
|
125
129
|
|
|
126
130
|
```typescript
|
|
127
131
|
import { version } from 'u-space';
|
|
128
|
-
console.log(version); // e.g. '0.0.
|
|
132
|
+
console.log(version); // e.g. '0.0.32'
|
|
129
133
|
|
|
130
134
|
// 也可以通过全局变量访问
|
|
131
135
|
console.log(window.__USPACE__.version);
|