znlgis 博客

GIS开发与技术分享 — GDAL · GeoServer · PostGIS · QGIS · OpenLayers · Cesium · FreeCAD · NPOI

第10章:二次开发实战与生态集成

本章把前面所学落到实处:基于 SuperSplat 开源代码做二次开发——新增工具、自定义编辑操作、扩展 UI、对接 iframe 通信、增加语言,并将其集成到自己的产品中。本章基于第 09 章的架构理解,给出可操作的开发范式(请以仓库当前源码为准)。

1. 二次开发的总体思路

由于 SuperSplat 采用事件总线 + 模块化注册架构,二次开发的核心范式是:

  1. 写一个 registerMyFeatureEvents(events, ...) 函数,在其中用 events.function/events.on 注册逻辑;
  2. main.ts 的装配流程中调用它;
  3. 如需界面,在 ui/ 中新增面板/按钮,通过 events.fire/events.invoke 与逻辑通信;
  4. 如需可撤销,把修改封装成 EditOp 交给 EditHistory

牢记“UI 只发事件、逻辑只挂事件”的解耦原则,新增功能就不会侵入既有代码。

2. 实战一:新增一个自定义选择工具

所有工具实现统一接口(src/tools/tool-manager.ts):

interface Tool {
    activate: () => void;
    deactivate: () => void;
}

新增工具的步骤:

  1. src/tools/ 下新建 my-selection.ts,实现一个类,构造时接收 events、场景/相机等依赖,实现 activate()(绑定鼠标事件、显示辅助图形)与 deactivate()(解绑、清理);
  2. activate 的交互回调里,计算被选中的高斯点索引区间,然后通过编辑操作修改 selected 状态位(复用 edit-ops.ts 中的 SelectOp/StateOp 模式),并压入历史;
  3. main.ts 注册:toolManager.register('mySelection', new MySelection(events, ...))
  4. src/shortcut-manager.tsdefaultShortcuts 增加绑定,例如 'tool.mySelection': { keys: ['k'] },并在工具激活逻辑里响应该事件;
  5. 在右侧工具栏(src/ui/right-toolbar.ts)添加按钮,点击时 events.fire('tool.mySelection')

参考实现:rect-selection.ts(2D 屏幕选择)或 sphere-selection.ts(3D 体积选择)是最佳模板。

3. 实战二:自定义可撤销编辑操作

要新增一种“数据编辑”(例如“把选中点的不透明度乘以 0.5”),实现 EditOp 接口:

class ScaleOpacityOp implements EditOp {
    name = 'scaleOpacity';
    // 保存撤销所需的原始数据
    constructor(/* splat, ranges, factor, 备份... */) {}
    do() { /* 修改选中点 opacity,并更新 GPU 数据 */ }
    undo() { /* 用备份恢复 */ }
    destroy() { /* 释放备份 */ }
}

然后通过历史执行:

events.fire('edit.add', new ScaleOpacityOp(/* ... */));

实现要点:

  • do()/undo() 必须严格互逆;备份原始值以便回退;
  • 修改高斯数据后要触发对应的 GPU 数据更新(参考 splat.tsupdateState/相关更新方法);
  • 大批量修改用 IndexRangessrc/index-ranges.ts)按区间操作以提升性能;
  • 组合多个操作可用 MultiOp

这样新功能自动获得 Ctrl+Z/Ctrl+Shift+Z 支持。

4. 实战三:扩展 UI 面板

以新增一个“统计面板”为例:

  1. src/ui/ 下新建 stats-panel.ts,用 PCUI 的 Container/Label 构建;
  2. 通过 events.invoke('selection')、自定义的 events.invoke('splat.count') 等查询数据并显示;
  3. 监听相关事件(如 events.on('edit.changed', refresh))在数据变化时刷新;
  4. src/ui/editor.tsEditorUI 中实例化并挂载到布局;
  5. 所有文案用 localize('panel.stats.xxx') 并在语言文件中补充键值。

PCUI 提供 SliderInputSelectInputBooleanInputColorPickerTextInputButton 等控件,足以构建复杂面板。务必坚持“面板不直接持有业务对象,只经由 events 通信”。

5. 实战四:对接 iframe API(嵌入到你的网站)

SuperSplat 提供了一个最小的 iframe 通信 APIsrc/iframe-api.ts),便于把编辑器嵌入你自己的页面并查询状态。当前内置的查询是“场景是否有未保存改动”:

const IS_SCENE_DIRTY = 'supersplat:is-scene-dirty';

const registerIframeApi = (events: Events) => {
    window.addEventListener('message', (event) => {
        const source = event.source as Window | null;
        if (!source) return;
        if (event.data?.type === IS_SCENE_DIRTY) {
            source.postMessage({
                type: IS_SCENE_DIRTY,
                result: events.invoke('scene.dirty') as boolean
            }, event.origin);
        }
    });
};

在宿主页面中查询:

// 宿主页面
const iframe = document.getElementById('supersplat');
window.addEventListener('message', (e) => {
    if (e.data?.type === 'supersplat:is-scene-dirty') {
        console.log('未保存改动:', e.data.result);
    }
});
iframe.contentWindow.postMessage({ type: 'supersplat:is-scene-dirty' }, '*');

扩展自己的消息类型:在 iframe-api.ts 中仿照 IS_SCENE_DIRTY 增加新的 type,在 message 监听里识别并通过 events.invoke/events.fire 桥接到内部逻辑,再 postMessage 回传结果。例如增加“加载指定 URL 的模型”“触发导出”等命令,即可让宿主页面远程控制编辑器。

6. 实战五:增加一种界面语言

参照 README 的本地化指南:

  1. static/locales/ 下新增 <locale>.json(复制 en.json 后逐项翻译);
  2. src/ui/localization.tssupportedLngs 数组中加入新语言代码;
  3. npm run develop 后用 http://localhost:3000/?lng=<locale> 测试。

localize(key) 会在缺失键时回退到英文(fallbackLng: 'en'),因此可以渐进翻译。注意保持 JSON 结构与键名和英文版完全一致。

7. 实战六:私有化部署与品牌定制

把 SuperSplat 改造成自有产品的常见定制点:

  • 品牌:替换 logo(src/ui/playcanvas-logo.png)、关于弹窗(src/ui/about-popup.ts)、标题与图标(src/index.htmlmanifest.json);
  • 默认配置:调整 src/scene-config.ts 的背景、相机、网格、色调默认值;
  • 裁剪功能:通过删除工具注册/菜单项,精简为面向特定用户的轻量版本;
  • 发布后端src/publish.ts 的上传/发布走 user.apiServer,若要发布到自有服务,需要实现兼容的后端接口(/api/id/upload/*/splats/publish 等)或改造该模块对接你的存储;
  • 构建部署npm run build 产出 dist/,作为纯静态站点部署到任意 CDN / 对象存储 / Nginx 即可。

注意许可证:SuperSplat 采用 MIT 许可证,二次开发与商用需保留版权与许可证声明,并遵守其条款。

8. 生态集成:把成果用起来

二次开发往往是“编辑器 + 查看器”的组合。导出/发布后(第 08 章),在下游消费:

  • PlayCanvas Engine:用 GSplat 组件加载 .ply/.sog,集成进 Web 应用或游戏;
  • SuperSplat Viewerplaycanvas/supersplat-viewer):轻量查看器,发布链接即由它驱动,可自托管;
  • Three.js@mkkellogg/gaussian-splats-3d 加载 .ply/.splat/.ksplat
  • 批处理流水线:用 @playcanvas/splat-transform(与 SuperSplat 共享数据处理内核)做命令行转换/压缩,纳入 CI/自动化管线;
  • iframe 嵌入:把编辑器或查看器嵌入 CMS、电商、数字孪生平台。

一个典型产品形态:用户上传 .ply → 嵌入的 SuperSplat(定制版)清理编辑 → 发布到你的后端 → 前端用 viewer 展示。

9. 开发建议与排错

  • 改动后看不到效果:多半是 Service Worker 缓存,按第 09 章关闭缓存;
  • 事件不触发:检查 function 名称是否重复(重复会抛错)、on/fire 名称是否一致、焦点是否在画布(快捷键仅 document.body 聚焦时生效);
  • 撤销异常:确认 EditOpdo/undo 互逆且正确备份;
  • 性能问题:批量修改用 IndexRanges,避免逐点 JS 循环;调色等用 hasTint 思路做“无改动跳过”;
  • 提交前:跑 npm run lint,遵循 @playcanvas/eslint-config 规范;参与上游贡献时遵循仓库 CONTRIBUTING 流程。

10. 本章小结

本章给出了 SuperSplat 二次开发的完整范式:

  • 遵循“注册函数/事件 + 在 main.ts 装配 + UI 经事件通信”的模式扩展功能;
  • 新增工具实现 Tool 接口并注册到 ToolManager、绑定快捷键与工具栏按钮;
  • 自定义编辑实现 EditOp 接口交给 EditHistory,自动获得撤销/重做;
  • 扩展 UI 用 PCUI 并保持与逻辑解耦;
  • 通过 iframe API(postMessage)让宿主页面查询/控制编辑器,可自行扩展消息类型;
  • 增加语言只需新增 locale JSON 并登记到 supportedLngs
  • 可做品牌定制、私有化部署,并与 PlayCanvas/Three.js/viewer/splat-transform 等生态集成。

至此,本教程从原理、使用到源码与二次开发的完整链路结束。建议结合官方仓库源码与用户手册边读边练,真正把 SuperSplat 变成你工具箱里得心应手的一件利器。


← 上一章 目录