三维重建(临云镜)SDK 使用文档
1. 背景介绍
三维重建(TDSR)SDK 主要为三维重建(TDSR)提供了平台生成的三维模型的:模型播放能力、 与模型进行互动的能力、以及标签标注能力。整个 SDK 分为 PanoramaScenePlayer 和 PanoramaSceneEdtior 两部分:
- PanoramaScenePlayer 提供了基本模型加载,模型交互(如场景漫游,飞入标签,多视图 切换,事件监听等能力)
- PanoramaSceneEditor 继承了 PanoramaScenePlayer 对象,该对象除了提供模型加载, 模型交互,还提供了标签编辑的能力,可以跟进 API 进行标签创建、标签删除、标签修 改等操作。
该 SDK 与框架无关,您可在 React 框架中使用,也可嵌入到 Angular\Vue 等任何框架中 使用。本文档将以嵌入 React 的方式演示如何利用该 SDK 加载三维模型以及如何与三维模 型进行交互。
2. 关键术语
- 模型: 用户将全景照片上传到三维重建(TDSR)平台,利用平台的模型重建能力创造出来的 模型;用户可以通过 3D 交互的方式查看该模型的结构。也可以飞入到模型中以全景视角 产看场景。
- 场景: 场景跟模型是一一对应的
- 子场景: 与平台子场景含义相同,一张全景图即一个子场景,用户可以在不同子场景进行 漫游。
- 三维模式:传说中的“上帝视角“,在该模式下,用户可以较清晰看到模型结构,以及各个 子场景之间的关联关系
- 全景模式: 子场景模型,可通过鼠标点击/SDK 提供的 API 的操作实现场景间切换从而实 现场景漫游
- 标签:SDK 允许在编辑模式下进行图像的标注,标注后会一标签的形势存在,目前支持标 注模式有图片,图像,文本,链接。
3. 快速上手
3.1 PanoramaScenePlayer 快速上手
PanoramaScenePlayer 相关,详见第 5 章
-
初始化 PlayerService 对象, 该对象封装了前后端通信的能力,提供与后端接口交互 API,详见章节 4。
const service = new PanoramaScenePlayerService(); // service.registerFetch(utils.mock) // NOTICE: 本地mock测试,线上不要使用 service.registerHook({ // 详见章节4 async request(method, url, data, header) { const prefix = '//localhost/aoding/demo'; return [`${prefix}${url}`, data, header]; }, async response(result, context) { return { code: result.code, message: result.message, data: result.data, }; }, });
-
初始化播放器对象
const player = new PanoramaScenePlayer(document, { service: service, // PlayerService对象 mode: '1', // mode: 1 | 2, 参数详见5.1 });
-
模型加载
await player.load(token); // TODO: 请求获取模型数据接口,并加载模型
-
设置监听事件,详见 5.3 节
player.registerSceneHook({ onOrbitRotate: (quaternion) => { console.log('Current Quaternion', quaternion); }, onChangePanorama: (panoId) => { console.log('Current Panorama', panoId); }, });
-
基于 React 的完整代码如下:
import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { PanoramaScenePlayer, PanoramaScenePlayerService, utils, DefaultSceneData, } from 'alibabacloud-tdsr-js-sdk'; import axios from 'axios'; /** * 加载模型 * @param {string} token * @param {HTMLDivElement} target */ function usePlayer(token, target) { const [myPlayer, setMyPlayer] = React.useState(null); const init = async () => { const service = new PanoramaScenePlayerService(); service.registerHook({ // 详见章节4 async request(method, url, data, header) { const prefix = '//localhost/aoding/demo'; return [`${prefix}${url}`, data, header]; }, async response(result, context) { return { code: result.code, message: result.message, data: result.data, }; }, }); const player = new PanoramaScenePlayer(target.current, { service: service, mode: '1', // 如果想预览未发布的Tag, mode设置为2,mode参数详见5.1 }); await player.load(token); // 请求接口&加载模型 // 设置监听事件,详见5.3节 player.registerSceneHook({ onOrbitRotate: (quaternion) => { console.log('Current Quaternion', quaternion); }, onChangePanorama: (panoId) => { console.log('Current Panorama', panoId); }, }); return player; }; React.useEffect(() => { init().then((p) => setMyPlayer(p)); }, [token]); return myPlayer; } function Player(props) { // token 即 listMainScenes 接口中获取的 previewToken const token = '58b51071d0a446318607443bd2135d74'; const target = React.useRef(); const player = usePlayer(token, target); const style = { width: '100%', height: 450, position: 'relative', }; return <div ref={target} style={style} />; } ReactDOM.render(<Player />, mountNode);
3.2 PanoramaSceneEditor 快速上手
PanoramaSceneEditor 相关,详见第 6 章
-
初始化 PanoramaSceneEditorService 对象, 该对象封装了前后端通信的能力,提供与 后端接口交互 API,详见下一章节。
const service = new PanoramaSceneEditorService(); service.registerFetch(utils.mock); // NOTICE: 本地mock测试,线上不要使用
-
初始化播放器对象
const editor = new PanoramaSceneEditor(document, { service, autoSave: false, });
-
模型加载
await editor.load(token);
-
设置监听事件,详见 5.3 节
editor.registerTagHook({ onSelected: (tag) => { console.log('tag', tag); }, });
-
基于 React 的完整代码如下:
import React, { useCallback, useState, useEffect } from 'react'; import { PanoramaSceneEditor, PanoramaSceneEditorService, } from 'alibabacloud-tdsr-js-sdk'; /** * 加载模型 * @param {string} token * @param {HTMLDivElement} target */ function useEditor(token, target) { const [editor, setEditor] = useState(null); const init = async () => { const service = new PanoramaSceneEditorService(); // service.registerFetch(utils.mock) // NOTICE: 本地mock测试,线上不要使用 service.registerHook({ async request(method, url, data, header) { const prefix = '//localhost/aoding/demo'; // 域名需更换为业务方自己服务器域名 return [`${prefix}${url}`, data, header]; }, async response(result, context) { return { code: result.code, message: result.message, data: result.data, }; }, }); const editor = new PanoramaSceneEditor(target.current, { service, autoSave: false, // 是否开启标签自动保存 }); await editor.load(token); // 请求接口&加载模型 editor.registerTagHook({ // 标签创建必要 onCreating: () => { // tag参数,详见6.4.1 const tag = { type: 'TEXT', config: { backgroundColor: '#000', content: '444', title: '1231', }, }; return tag; }, // 标签创建完成 onCreated: (tag) => { console.log(tag); // editor.updateTag(tag.id, tag) }, }); return editor; }; useEffect(() => { init().then((editor) => { setEditor(editor); }); }, [token]); return editor; } function Editor(props) { // token 即 listMainScenes 接口中获取的 previewToken const token = 'f9d19ae0541f4131bb787d8a4521d46d'; const target = React.useRef(); const editor = useEditor(token, target); // 添加标签 const addTag = useCallback(() => { if (!editor) return; // tips: 添加标签,需配合onCreating事件使用 editor.setState(2); // tips: 改为2 即 EDIT态,可鼠标右键创建标签 // editor.createTagInView({ x: 1, y: 1 }) // 在指定位置添加标签 }, [editor]); // 切换至3D模型,DollHouse: 3D模型,Panorama: 全景模式 const go3D = useCallback(() => { if (!editor) return; editor.changeView('DollHouse'); }, [editor]); // 保存标签 const save = useCallback(() => { if (!editor) return; // save后的标签,可在mode为Preview时展示 editor.save(); }, [editor]); // 发布标签 const publish = useCallback(() => { if (!editor) return; // save后的标签,可在mode为Online时展示 editor.publish(); }, [editor]); return ( <div> <div ref={target} style={{ width: '100%', height: 450, position: 'relative', }} /> <button onClick={() => addTag()}>ADD Tag</button> <button onClick={() => go3D()}>go 3D</button> <button onClick={() => save()}>save</button> <button onClick={() => publish()}>publish</button> </div> ); } export default Editor;
4. 通信
SDK 需要依赖后端接口进行模型的加载以及标签的增删改查,考虑到安全性,需要业务
方对依赖接口进行转发,其架构如下图。三维重建服务端把依赖的后端接口通过 Open
API 的方式开放给业务方,业务方可以通过 Open API SDK 方式与这些接口进行交互,并通
过 WEB API 方式转发给 WEB 端的播放器。播放器通过转发后的 API 实现三维重建服务端
通信, 又能保证其安全性,以及各平台兼容性
。
PanoramaScenePlayerService、PanoramaSceneEditorService
对象封装了通信能力,开发者可以通过
const service = new PanoramaSceneEditorService();
和
const service = new PanoramaSceneEditorService();
的方式创建该对象。
-
Mock:为了方便开发者进行本地调试 ,PanoramaScenePlayerService、PanoramaSceneEditorService 提供了 Mock 数据的能 力,开发者在开发前期可以通过如下的方式加载 Mock 数据进行本地开发
-
service.registerFetch:
(replacedFetch: fetch) => viod;
替换底层 fetch 库, 用于 mock 数据service.registerFetch(utils.mock); // NOTICE: 本地mock测试,线上不要使用
-
-
协议适配: 考虑到不同业务方前后端通信协议会有较大差异 ,PanoramaScenePlayerService、PanoramaSceneEditorService 开发了协议转换的能力 ,用户可以通过 registerHook 接口进行协议转换。比如以下 Code 给 URL 增加前缀, 同时对返回结果进行适配
-
service.registerHook:
({request, response}) => viod;
-
参数说明:
-
request 请求拦截器
-
method: GET | POST 请求方式
-
url: string 临云镜后端 SDK 接口地址,接口在前端 SDK 内部调用,详见下 文
服务端接口对应交互API
-
data: object 请求参数
-
header: object 请求头
-
-
response 响应拦截器
-
code: number 状态码 code:0 为成功,否则抛出异常
-
message: string 错误信息
-
data: object 临云镜后端 SDK 返回的 data 数据,具体参数详见临云镜服务端 SDK 接口文档
-
-
-
示例
service.registerHook({ // 请求拦截器 async request(method, url, data, header) { const prefix = '//localhost/biz' return [`${prefix}${url}`, data, header]; }, // 响应拦截器 async response(result, context) { return { code: result.errorCode, message: result.errorMessage, data: result.data } });
-
-
API: 以下 API 均为 SDK 内部已封装集成,无需手动调用(域名需业务方自己通过 registerHook 配置),API 内会自动调用服务端接口。接口参数可参考临云镜服务端 SDK 接口文档。
- PanoramaScenePlayerService > player.load()时,SDK内部调用 - service.getPanoramaConfigData: `(token: string) => response;` 获得全局配置信息,调用接口:`/getWindowConfig` - service.getSceneData: `(token: string) => response;` 获取场景渲染数据,调用接口:`/getSceneData` - service.getTags: `(subSceneUuid: number, previewToken: string, type: number) => response;` 获取所有热点标注数据,调用接口:`/getHotspotTag` - PanoramaSceneEditorService > tips: 继承 PanoramaScenePlayerService 所有 API,无需手动调用,save()&publish()时自动调用 - service.saveTags: `(subSceneUuid: number, tags: ICombinedTag[]) => response;` 保存标签。tips: ICombinedTag为标签数据,调用接口:`/saveHotspotTag` - service.publish: `(subSceneUuid: number) => response;` 发布标签,调用接口:`/publishHotspot`
5. Panorama Scene Player
PanoramaScenePlayer 对象提供了基本模型加载,模型交互(如场景漫游,飞入标签,多视 图切换,事件监听等能力)
5.1 Constructor
constructor(target: HTMLDivElement, protected config: IPanoramaScenePlayerConfig)
- target: Player 的 DOM 容器
- config
- service: 前后端通信对象,见 4. 通信 章节内容
- mode:
- Player 的模式,支持 Preview = '2', Online = '1',
- 其中 Preview 加载已经创建,但是还没有发布的标签和已经发布的标签;Online 只 会加载已经发布的标签
- view: DollHouse(3D 模式)\Panorama(全景模式), 默认为 DollHouse 模式
5.2 Load/Unload
-
加载模型,previewToken 为模型的唯一标示,可以通过 OPEN API 提供的接口获得,详 见后端 SDK
async player.load(previewToken: string)
-
卸载模型
async player.unload()
5.3 注册监听事件
registerSceneHook(hooks); // hooks: {} 一个事件对象
- hooks.onLoad:
() => void;
加载完成事件 - hooks.onChangeView:
(type: EnumPanoramaSceneView) => void;
切换全景/3D 模式 事件 - hooks.onChangePanorama:
(panoId: string) => void;
切换子场景事件 - hooks.onOrbitRotate:
(quanertion: TypeQuaternion) => void;
场景旋转, 目前暂 不支持三维空间旋转
5.4 API
-
player.getTagList()
获得所有标签 -
player.getTagListByPanoramaId(panoId)
获得当前子场景下标签 -
player.getPanoramaList()
获得所有子场景 -
player.changeView(view)
切换视图,支持 DollHouse(3D 模式)\Panorama(全景模 式), -
player.flyToPanoramaById(panoId)
跳转到指定子场景 -
player.hideTag()
隐藏标签 -
player.showTag()
显示标签 -
player.flyToTag(tagId)
跳转到指定标签位置 -
player.getTagClientPosition(tagId)
获得 Tag 的屏幕坐标 -
player.getCurrentPanorama()
获得当前子场景,若在 Dollhouse(3D)模式下,返 回为 undefined -
player.isTagInViewScene(tagId)
判断标签是否当前视口可见 -
player.getRotateQuaternion()
获得当前场景旋转四元组 -
player.setRotateQuaternion(quaternion)
设置当前场景的旋转四元组
6. Panorama Scene Editor
PanoramaSceneEditor 继承了 PanoramaScenePlayer 对象,该对象除了提供模型加载,模 型交互,还提供了标签编辑的能力,可以跟进 API 进行标签创建、标签删除、标签修改等 操作。
6.1 Constructor
constructor(target: HTMLDivElement, protected config: IPanoramaSceneEditorConfig)
- target: Player 的 DOM 容器
- config
- service: 前后端通信对象,见 4. 通信 章节内容
- autoSave: 是否自动保存 TAG, 若 autoSave 为 true, 模型创建、模型取消选中、模 型更新、模型拖拽、
- view: DollHouse(3D 模式)\Panorama(全景模式), 默认为 DollHouse 模式
6.2 Load/UnLoad
-
加载模型,previewToken 为模型的唯一标示,可以通过 OPEN API 提供的接口获得,详 见后端 SDK
async editor.load(previewToken: string)
-
卸载模型
async editor.unload()
6.3 注册监听事件
registerTagHook(hooks); // hooks: {} 一个事件对象
- hooks.onClick:
(tag: ICombinedTag) => void;
点击标签事件 - hooks.onSelected:
(tag: ICombinedTag) => boolean;
标签选中事件 - hooks.onWillUnselected:
(tag: ICombinedTag) => boolean;
标签取消选中事件 - hooks.onCreating:
() => ICombinedTag;
创建标签事件 - hooks.onCreated:
(tag: ICombinedTag) => void;
标签创建完成事件 - hooks.onUpdatePositions:
(tag: ICombinedTagWithScenePosition[]) => void;
标 签位置更新事件 - hooks.onDragStart:
(tag: ICombinedTag) => void;
标签拖拽事件 - hooks.onDragStop:
(tag: ICombinedTag) => void;
标签拖拽事件 - hooks.onDragging:
(tag: ICombinedTag) => void;
标签拖拽事件
6.4 API
PanoramaSceneEditor 继承了 PanoramaScenePlayer 所有 API 除此之外 ,PanoramaSceneEditor 还提供了跟标签编辑有关的 API
- 6.4.1 创建标签
创建标签目前只能在全景模型下创建,标签创建必须通过 onCreating 事件。
自定义创建 tips(参考 3.2 示例): 1.通过 onCreating 创建默认标签。2.通过 onCreated 拿到标签 Id。3. 通过
editor.updateTag(tagId, tag)
方法自定义标签内容
-
通过注册 onCreating 事件回填标签内容
-
tag 参数说明:
-
type: tag 类型支持 IMAGE | TEXT | VIDEO
tips: VIDEO 类型的暂时无法播放!如果有播放需求,需业务方前端额外添加弹 出层,视频在弹出框中播放
-
config: tag 配置
-
formSelectImgType: 'default'(默认) | 'point'(小标) | 'mural'(壁纸); 图片 类型,仅 IMAGE 支持
-
title: string 标题
-
content: string 内容
-
backgroundColor: string 背景色
-
images: string[] 图片 CDN 地址
-
video: string 视频 CDN 地址
-
-
tag 示例:
-
图片:
{ type: 'IMAGE', config: { backgroundColor: '#000', // 背景色 images: [ // 图片cdn地址 'http://s.alicdn.com/@lyj/pano_src/100070-82766/hotspot/image/4bbc-06439_LD.png', ], title: '22222', formSelectImgType: 'mural', }, }
-
文本:
{ type: 'TEXT', config: { backgroundColor: '#000', title: '22222', content: '11111' }, }
-
视频:
{ type: 'VIEDO', config: { backgroundColor: '#000', title: '22222', content: '11111', video: '' // 视频cdn地址 }, }
-
-
onCreating 示例:
editor.registerTagHook({ onCreating: () => { const tag = { type: 'IMAGE', config: { backgroundColor: '#000', images: [ 'http://s.alicdn.com/@lyj/pano_src/100070-82766/hotspot/image/4bbc-06439_LD.png', ], title: '60152892558', formSelectImgType: 'mural', }, }; return tag; }, });
-
-
通过
createTagInView(position?: { x: number, y: number })
接口创建标签,默 认在视口中心创建editor.createTagInView();
-
另外也支持通过鼠标点击方式在指定位置创建标签,开启鼠标点击交互能力
/* 播放器状态, state: 1 | 2 默认为: 1 1: ROMA, 默认允许进行场景漫游,场景旋转等操作 2: EDIT, 编辑态,允许用户右键创建标签 */ editor.setState(1) editor.setState(2) const state = editor.getState() // 获得当前播放器状态
-
6.4.2 更新标签
editor.updateTag(tagId, tag);
-
6.4.3 选中标签
editor.selectTag(tagId);
-
6.4.4 删除标签
editor.deleteTag(tagId);
-
6.4.5 保存标签
-
当开启了 autoSave 模式,模型创建、模型取消选中、模型更新、模型拖拽都会触发保 存,否则就需要调用
save()
手动触发保存。 -
保存后的标签可以在 PanoramaScenePlayer 的 Preiview 模式查看,但是不能 Online 模式查看
editor.save();
-
-
6.4.6 发布标签
-
发布后的标签既可以在 PanoramaScenePlayer 的 Preiview 模式查看,又可以在 Online 模式查看
editor.publish();
-
已知问题
- 暂时无法支持天花板打标
- flyToTag 接口会触发页面闪动问题