@realsee/vreo 0.2.0-alpha.13 → 0.2.0-alpha.16

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.
Files changed (82) hide show
  1. package/README.md +13 -2
  2. package/docs/assets/search.js +1 -1
  3. package/docs/classes/Player.AudioLike.html +2 -2
  4. package/docs/classes/Player.Controller.html +3 -3
  5. package/docs/classes/Player.Player-1.html +3 -3
  6. package/docs/classes/Player.VideoAgentMesh.html +11 -11
  7. package/docs/classes/Player.VideoAgentScene.html +1 -1
  8. package/docs/demo/assets/{AppReact.388465e9.js → AppReact.2978a2aa.js} +1 -1
  9. package/docs/demo/assets/default.0c3b245d.js +64 -0
  10. package/docs/demo/assets/{default.0350c336.css → default.70243c83.css} +1 -1
  11. package/docs/demo/assets/dynamic.37c058ac.js +1 -0
  12. package/docs/demo/assets/{examples.02abaf01.js → examples.4104efd4.js} +2 -2
  13. package/docs/demo/assets/{index.485f13f7.js → index.863d264e.js} +1 -1
  14. package/docs/demo/assets/{main.0b6b41ab.js → main.f8c52f62.js} +1 -1
  15. package/docs/demo/assets/{react.b14dadc7.js → react.98c8f46f.js} +2 -2
  16. package/docs/demo/assets/{vendor.d3e0753c.js → vendor.f4c5cb32.js} +67 -68
  17. package/docs/demo/examples.html +4 -4
  18. package/docs/demo/index-react-dynamic.html +6 -6
  19. package/docs/demo/index-react.html +6 -6
  20. package/docs/demo/index.html +5 -5
  21. package/docs/enums/Player.InfoPanelStyleEnum.html +1 -1
  22. package/docs/enums/Player.InfoPanelTypeEnum.html +1 -1
  23. package/docs/enums/Player.PanoEffectEnum.html +1 -1
  24. package/docs/enums/Player.PanoTagEnum.html +1 -1
  25. package/docs/enums/Player.PanoTagStyleEnum.html +2 -2
  26. package/docs/enums/Player.VreoKeyframeEnum.html +12 -12
  27. package/docs/enums/fivePlugins.CameraMovementEffect.html +1 -1
  28. package/docs/enums/fivePlugins.Rotation.html +1 -1
  29. package/docs/index.html +10 -3
  30. package/docs/interfaces/Player.CustomVreoKeyframeProps.html +1 -1
  31. package/docs/interfaces/Player.PlayerConfigs.html +2 -2
  32. package/docs/interfaces/Player.Vertex.html +1 -1
  33. package/docs/interfaces/Player.VideoAgentMeshOptions.html +8 -1
  34. package/docs/interfaces/Player.VreoKeyframe.html +1 -1
  35. package/docs/interfaces/Player.VreoUnit.html +1 -1
  36. package/docs/interfaces/Player.VreoVideo.html +1 -1
  37. package/docs/interfaces/fivePlugins.CSS3DRenderPluginExportType.html +4 -0
  38. package/docs/interfaces/fivePlugins.CameraMovementOptsCallback.html +1 -1
  39. package/docs/interfaces/fivePlugins.CameraMovementPluginExportType.html +2 -2
  40. package/docs/interfaces/fivePlugins.ModelTVVideoPluginData.html +1 -1
  41. package/docs/interfaces/fivePlugins.ModelTVVideoPluginExportType.html +1 -1
  42. package/docs/interfaces/fivePlugins.ModelTVVideoPluginParameterType.html +1 -1
  43. package/docs/interfaces/fivePlugins.Vector3Position.html +3 -0
  44. package/docs/interfaces/react.VreoActionCallbacks.html +6 -6
  45. package/docs/interfaces/react.VreoProviderProps.html +1 -1
  46. package/docs/modules/Player.html +17 -15
  47. package/docs/modules/custom.html +1 -1
  48. package/docs/modules/fivePlugins.html +5 -2
  49. package/docs/modules/react.html +1 -1
  50. package/lib/Player/custom/SpatialScenePanel/index.js +2 -2
  51. package/lib/Player/modules/VideoAgent/VideoAgentMesh.d.ts +12 -0
  52. package/lib/Player/modules/VideoAgent/VideoAgentMesh.js +26 -10
  53. package/lib/Player/modules/VideoAgent/VideoAgentScene.d.ts +1 -0
  54. package/lib/Player/modules/VideoAgent/VideoAgentScene.js +1 -2
  55. package/lib/Player/modules/keyframes/PanoTextLabel/index.d.ts +3 -0
  56. package/lib/Player/modules/keyframes/PanoTextLabel/index.js +96 -43
  57. package/lib/fivePlugins/CSS3DRenderPlugin/centerPoint.d.ts +2 -0
  58. package/lib/fivePlugins/CSS3DRenderPlugin/centerPoint.js +12 -0
  59. package/lib/fivePlugins/CSS3DRenderPlugin/createResizeObserver.d.ts +7 -0
  60. package/lib/fivePlugins/CSS3DRenderPlugin/createResizeObserver.js +32 -0
  61. package/lib/fivePlugins/CSS3DRenderPlugin/evenNumber.d.ts +3 -0
  62. package/lib/fivePlugins/CSS3DRenderPlugin/evenNumber.js +11 -0
  63. package/lib/fivePlugins/CSS3DRenderPlugin/index.d.ts +56 -0
  64. package/lib/fivePlugins/CSS3DRenderPlugin/index.js +275 -0
  65. package/lib/fivePlugins/CSS3DRenderPlugin/transformPositionToVector3.d.ts +7 -0
  66. package/lib/fivePlugins/CSS3DRenderPlugin/transformPositionToVector3.js +18 -0
  67. package/lib/typings/VreoUnit.d.ts +11 -0
  68. package/package.json +1 -1
  69. package/resources/Player/custom/SpatialScenePanel/index.tsx +4 -4
  70. package/resources/Player/index.tsx +1 -1
  71. package/resources/Player/modules/VideoAgent/VideoAgentMesh.ts +17 -2
  72. package/resources/Player/modules/VideoAgent/VideoAgentScene.ts +2 -4
  73. package/resources/Player/modules/keyframes/PanoTextLabel/index.tsx +87 -34
  74. package/resources/fivePlugins/CSS3DRenderPlugin/centerPoint.ts +5 -0
  75. package/resources/fivePlugins/CSS3DRenderPlugin/createResizeObserver.ts +17 -0
  76. package/resources/fivePlugins/CSS3DRenderPlugin/evenNumber.ts +4 -0
  77. package/resources/fivePlugins/CSS3DRenderPlugin/index.ts +254 -0
  78. package/resources/fivePlugins/CSS3DRenderPlugin/transformPositionToVector3.ts +5 -0
  79. package/resources/typings/VreoUnit.ts +13 -1
  80. package/stylesheets/default.css +5 -1
  81. package/docs/demo/assets/default.f03f1cdd.js +0 -64
  82. package/docs/demo/assets/dynamic.3f7479e4.js +0 -1
@@ -1,10 +1,9 @@
1
1
  import * as React from 'react'
2
2
  import ReactDOM from 'react-dom'
3
3
  import { Five } from '@realsee/five'
4
- import * as THREE from 'three'
5
- import { Object3D, Raycaster, Vector3 } from 'three'
6
- import { tweenProgress } from '@realsee/dnalogel/shared-utils/animationFrame/BetterTween'
7
- import { CSS3DRenderPlugin, CSS3DRenderPluginExportType } from '@realsee/dnalogel/plugins/CSS3DRenderPlugin'
4
+ import { Raycaster, Vector3 } from 'three'
5
+ import { tweenProgress } from '../../../shared-utils/animationFrame/BetterTween'
6
+ import { CSS3DRenderPlugin, CSS3DRenderPluginExportType } from '../../../fivePlugins/CSS3DRenderPlugin'
8
7
 
9
8
  import { VreoKeyframe, VreoKeyframeEnum } from '../../../typings/VreoUnit'
10
9
  import { CustomVreoKeyframeProps } from '../../typings'
@@ -33,6 +32,7 @@ import { CustomVreoKeyframeProps } from '../../typings'
33
32
  // "temperature": "27",
34
33
  // "title": "沉浸式回家"
35
34
  // }
35
+
36
36
  export interface SpatialScenePanelData {
37
37
  customType: 'SpatialScenePanel'
38
38
  title: string
@@ -41,7 +41,7 @@ export class Player extends Subscribe<VreoKeyframeEvent> {
41
41
 
42
42
  ReactDOM.render(
43
43
  <ControllerContext.Provider value={this.controller}>
44
- <App></App>
44
+ <App />
45
45
  <Drawer />
46
46
  <PopUp />
47
47
  {this.configs.customKeyframes &&
@@ -64,8 +64,20 @@ const cacheInstance: {
64
64
  } = {}
65
65
 
66
66
  export interface VideoAgentMeshOptions {
67
+ /**
68
+ * 自定义视频实例。
69
+ */
67
70
  videoInstance?: HTMLVideoElement
71
+ /**
72
+ * 自定义音频实例。
73
+ */
68
74
  audioInstance?: HTMLAudioElement
75
+ /**
76
+ * 是否开启音视频预载能力。
77
+ *
78
+ * @description 开启预载能力后会通过 `Fetch/XHR` 方式将音视频文件下载下载转成二进制 [`Blob`](https://developer.mozilla.org/zh-CN/docs/Web/API/Blob) 提供给多媒体实例。这样的好处是 **能够保障音视频播放过程中不卡顿**。 但在部分 iOS 系统中会存在兼容问题——播 25s 之后会静音(`muted` 为 `false`,但是没有声音)。
79
+ */
80
+ preload?: boolean
69
81
  }
70
82
 
71
83
  /**
@@ -108,7 +120,9 @@ export class VideoAgentMesh extends THREE.Mesh {
108
120
  height: number,
109
121
  widthSegments: number,
110
122
  heightSegments: number,
111
- options: VideoAgentMeshOptions = {},
123
+ options: VideoAgentMeshOptions = {
124
+ preload: true
125
+ },
112
126
  ) {
113
127
  if (!options.videoInstance) {
114
128
  if (cacheInstance.videoInstance) {
@@ -200,7 +214,8 @@ export class VideoAgentMesh extends THREE.Mesh {
200
214
 
201
215
  const uniforms = (this.material as THREE.ShaderMaterial).uniforms
202
216
  this.videoInstance.muted = true
203
- this.videoInstance.src = await URL.createObjectURL((await Preloader.blob(this.videoUrl)) as unknown as Blob)
217
+
218
+ this.videoInstance.src = this.options.preload ? await URL.createObjectURL((await Preloader.blob(this.videoUrl)) as unknown as Blob) : this.videoUrl
204
219
  this.videoInstance.setAttribute('data-src', this.videoUrl)
205
220
 
206
221
  const onStart = () => {
@@ -9,7 +9,7 @@ export class VideoAgentScene {
9
9
  renderer = new THREE.WebGLRenderer({ alpha: true })
10
10
  container: HTMLElement
11
11
 
12
- constructor(container: HTMLElement, needRender = true, options: { videoInstance?: HTMLVideoElement, audioInstance?: HTMLAudioElement } = {}) {
12
+ constructor(container: HTMLElement, needRender = true, options: { videoInstance?: HTMLVideoElement, audioInstance?: HTMLAudioElement, preload?: boolean } = {}) {
13
13
  this.container = container
14
14
  this.camera.position.set(0, 0, 10)
15
15
  this.camera.lookAt(0, 0, 0)
@@ -33,13 +33,11 @@ export class VideoAgentScene {
33
33
  run = () => {
34
34
  const domElement = this.renderer.domElement
35
35
 
36
- // console.log('renderder', this.videoAgentMesh.paused, this.videoAgentMesh.freeze)
37
-
38
36
  // 视频开始 1s 不渲染:规避某些设备黑屏闪烁问题。视频前 2s 是最好是静默的。
39
37
  if (!(this.videoAgentMesh.paused || this.videoAgentMesh.freeze) && this.videoAgentMesh.currentTime > 1000) {
40
38
  this.renderer.render(this.scene, this.camera)
41
39
  }
42
-
40
+
43
41
  // 兼容非视频场景
44
42
  if (!this.videoAgentMesh.videoUrl?.endsWith('.mp4')) {
45
43
  if (domElement.style.display !== 'none') domElement.style.display = 'none'
@@ -1,21 +1,66 @@
1
- import classNames from 'classnames'
2
1
  import * as React from 'react'
3
- import { Vector3 } from 'three'
2
+ import * as THREE from 'three'
3
+ import ReactDOM from 'react-dom'
4
+ import { Vector3, Quaternion } from 'three'
5
+
6
+ import { useController, useFiveInstance } from '../../../hooks'
7
+ import { CSS3DRenderPlugin } from '../../../../fivePlugins/CSS3DRenderPlugin'
4
8
  import { PanoTextLabelData, VreoKeyframe, VreoKeyframeEnum } from '../../../../typings/VreoUnit'
5
- import { useController, useFiveProject2d } from '../../../hooks'
6
9
 
7
- interface PanoTextLabelState {
8
- text: string
9
- left: number
10
- top: number
11
- fontSize: number
10
+ /**
11
+ * 通过传入中心点确定矩形框架容器的四个点:
12
+ * 1. 已知矩形长宽和中心点
13
+ * 2. 借助planeGeometry生成垂直相机视角的平面上的四个点
14
+ * 3. 通过向量相加,平行四边形法则获取到移动position后的面上四个点
15
+ */
16
+ const calcPoints = (
17
+ centerPoint: Vector3,
18
+ normal: THREE.Vector3 | undefined,
19
+ wrapperLength: number,
20
+ wrapperWidth: number
21
+ ) => {
22
+ // 375px 对应 1m
23
+ const length = wrapperLength / 375
24
+ const width = wrapperWidth / 375
25
+
26
+ const geometry = new THREE.PlaneGeometry(length, width)
27
+ if (normal) {
28
+ const lookPoint = new THREE.Vector3(normal.x + centerPoint.x, normal.y + centerPoint.y, normal.z + centerPoint.z)
29
+ geometry.lookAt(lookPoint)
30
+ }
31
+ // const pointgeometry = new THREE.Geometry()
32
+ // pointgeometry.vertices.push(cameraPosition)
33
+ // const pointmaterial = new THREE.PointsMaterial({
34
+ // color: 0xffffff,
35
+ // size: 0.4,
36
+ // })
37
+ // _point = new THREE.Points(pointgeometry, pointmaterial)
38
+ // console.log(cameraPosition)
39
+
40
+ const material = new THREE.MeshBasicMaterial({ color: 0xffff00, side: THREE.DoubleSide })
41
+ const plane = new THREE.Mesh(geometry, material)
42
+ const v0 = centerPoint.clone()
43
+ plane.position.copy(v0)
44
+
45
+ // 计算传入的四个点的坐标
46
+ const [v1, v2, v3, v4] = plane.geometry.vertices
47
+ const downLeft = v0.clone().add(v4)
48
+ const downRight = v0.clone().add(v3)
49
+ const upLeft = v0.clone().add(v2)
50
+ const upRight = v0.clone().add(v1)
51
+
52
+ // 插件使用要求:矩形四个点位数据,顺序为**必须**为左下、右下、右上、左上
53
+ return [downLeft, downRight, upRight, upLeft]
12
54
  }
13
55
 
56
+ /**
57
+ * 空间文本标签。
58
+ */
14
59
  export function PanoTextLabel() {
15
- const [state, setState] = React.useState<PanoTextLabelState | null>(null)
16
60
  const timeoutRef = React.useRef<NodeJS.Timeout | null>()
17
61
  const controller = useController()
18
- const project2d = useFiveProject2d()
62
+ const five = useFiveInstance()
63
+ const ref = React.useRef<ReturnType<typeof CSS3DRenderPlugin>>()
19
64
 
20
65
  React.useEffect(() => {
21
66
  if (controller.configs?.keyframeMap.PanoTextLabel === false) {
@@ -24,19 +69,43 @@ export function PanoTextLabel() {
24
69
  const callback = (keyframe: VreoKeyframe) => {
25
70
  const { start, end, data } = keyframe
26
71
  const panoTextLabelData = data as PanoTextLabelData
72
+ // 增加无文本情况的判断
73
+ if (data.text === '') return
74
+
27
75
  const { x, y, z } = panoTextLabelData.vertex
76
+ const centerPoint = new Vector3(x, y, z)
77
+ const normal = new Vector3(panoTextLabelData.normal?.x, panoTextLabelData.normal?.y, panoTextLabelData.normal?.z)
78
+ // 生成传入的四个点,将文本框最大宽度定在200px,最高高度在30px
79
+ const wrapperLength = 200
80
+ const wrapperWidth = 30
81
+ const points = calcPoints(centerPoint, panoTextLabelData.normal && normal, wrapperLength, wrapperWidth)
28
82
 
29
- const res = project2d(new Vector3(x, y, z))
30
- if (!res) return
31
- const { x: left, y: top } = res
83
+ if (!ref.current) ref.current = CSS3DRenderPlugin(five)
84
+ const container = ref.current.create3DDomContainer(points)
85
+ const css3DObject = container?.css3DObject
86
+ // 将获取的mesh进行旋转操作,根据欧拉角去进行mesh的变换
87
+ if (data.quaternion) {
88
+ const quaternion = new Quaternion(data.quaternion.x, data.quaternion.y, data.quaternion.z, data.quaternion.w)
89
+ css3DObject?.quaternion.copy(quaternion)
90
+ }
91
+ if (container?.container)
92
+ ReactDOM.render(
93
+ <div className="PanoTextLabel PanoTextLabel--notHidden">
94
+ <div className="PanoTextLabel-wrapper">
95
+ <div style={{ fontSize: `${data.fontSize || 16}px` }} className="PanoText-innerText">
96
+ {data.text || ''}
97
+ </div>
98
+ </div>
99
+ </div>,
100
+ container?.container
101
+ )
32
102
 
33
- // Object.assign(window, { $PanoTextLabel: { setState } })
34
- setState({ left, top, text: data.text || '', fontSize: data.fontSize || 16 })
35
- timeoutRef.current = setTimeout(() => setState(null), end - start)
103
+ timeoutRef.current = setTimeout(() => {
104
+ ref.current?.disposeAll()
105
+ }, end - start)
36
106
  }
37
107
  controller.on(VreoKeyframeEnum.PanoTextLabel, callback)
38
108
 
39
- // Object.assign(window, { $panoTextLabel: { setState } })
40
109
  return () => {
41
110
  controller.off(VreoKeyframeEnum.PanoTextLabel, callback)
42
111
  if (timeoutRef.current) {
@@ -45,21 +114,5 @@ export function PanoTextLabel() {
45
114
  }
46
115
  }, [controller])
47
116
 
48
- return (
49
- <div
50
- className={classNames('PanoTextLabel', {
51
- 'PanoTextLabel--notHidden': state,
52
- })}
53
- style={{
54
- left: (state?.left || 0) + 'px',
55
- top: (state?.top || 0) + 'px',
56
- }}
57
- >
58
- <div className="PanoTextLabel-wrapper">
59
- <div style={{ fontSize: `${state?.fontSize || 16}px` }} className="PanoText-innerText">
60
- {state?.text || ''}
61
- </div>
62
- </div>
63
- </div>
64
- )
117
+ return <></>
65
118
  }
@@ -0,0 +1,5 @@
1
+ import { Vector3 } from 'three'
2
+
3
+ export default function centerPoint(point1: Vector3, point2: Vector3): Vector3 {
4
+ return new Vector3((point1.x + point2.x) / 2, (point1.y + point2.y) / 2, (point1.z + point2.z) / 2)
5
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @description: 简陋的 ResizeObserver polyfill
3
+ */
4
+ export default function createResizeObserver(func: () => any, element?: HTMLElement) {
5
+ if (!element || typeof ResizeObserver === 'undefined') {
6
+ return {
7
+ observe: () => window.addEventListener('resize', func),
8
+ unobserve: () => window.removeEventListener('resize', func),
9
+ }
10
+ } else {
11
+ const observer = new ResizeObserver(func)
12
+ return {
13
+ observe: () => observer.observe(element),
14
+ unobserve: () => observer.unobserve(element),
15
+ }
16
+ }
17
+ }
@@ -0,0 +1,4 @@
1
+ export default function evenNumber(num: number, config?: { smaller?: boolean }): number {
2
+ const roundNum = Math.round(num)
3
+ return roundNum % 2 === 0 ? roundNum : roundNum + Number(config?.smaller ? -1 : 1)
4
+ }
@@ -0,0 +1,254 @@
1
+ import { FivePlugin } from '@realsee/five'
2
+ import * as THREE from 'three'
3
+ import { Scene, Vector3 } from 'three'
4
+ import { CSS3DObject, CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer'
5
+ import evenNumber from './evenNumber'
6
+ import centerPoint from './centerPoint'
7
+ import transformPositionToVector3 from './transformPositionToVector3'
8
+ import createResizeObserver from './createResizeObserver'
9
+
10
+ /**
11
+ * 三维向量
12
+ */
13
+ export interface Vector3Position {
14
+ x: number
15
+ y: number
16
+ z: number
17
+ }
18
+
19
+ export type CSS3DRenderPluginParameterType = undefined
20
+ export interface CSS3DRenderPluginExportType {
21
+ /**
22
+ * @description: 根据传入的四个点位创建一个3d dom容器,可以通过ReactDom.render()的方式将react组件放到容器中
23
+ * @param {Five} five
24
+ * @param {Vector3[] | Vector3Position[]} points: 矩形四个点坐标
25
+ * @param {number} config.ratio: 1px对应多少米,默认为 0.002,即1px对应2mm
26
+ * @param {number} config.dpr: dpr, 默认为1
27
+ * @param {HTMLElement} config.container: HTMLElement
28
+ * @param {number} config.autoRender: 默认为true,如果需要手动render则传false
29
+ * @param {'front' | 'behind'} config.mode: 两种模式
30
+ * @param {HTMLElement} config.behindFiveContainer: 如果config.mode为'behind',需要传入容器,并确保此容器位five下方,且中间没有其他dom元素遮挡
31
+ * @return {{ container: HTMLElement; dispose: () => void }}: container: dom容器, dispose: 清理函数
32
+ */
33
+ create3DDomContainer: (
34
+ points: Vector3[] | Vector3Position[],
35
+ config?: { ratio?: number; dpr?: number; autoRender?: boolean; container?: HTMLElement; [key: string]: any } & (
36
+ | { mode?: 'front' }
37
+ | { mode: 'behind'; behindFiveContainer: HTMLElement }
38
+ ),
39
+ ) => { container: HTMLElement; dispose: () => void; css3DObject: CSS3DObject; render?: () => void } | void
40
+
41
+ /**
42
+ * @description: 销毁所有的渲染内容
43
+ * @return {void}
44
+ */
45
+ disposeAll: () => void
46
+ }
47
+
48
+ type Create3DDomContainerInterface = CSS3DRenderPluginExportType['create3DDomContainer']
49
+ type Create3DDomContainerConfigInterface = NonNullable<Parameters<Create3DDomContainerInterface>[1]>
50
+ interface CSS3DRenderPluginState {
51
+ disposeCallbacks: (() => boolean)[]
52
+ frontMode?: {
53
+ scene: Scene
54
+ css3DRenderer: CSS3DRenderer
55
+ }
56
+ behindMode?: {
57
+ scene: Scene
58
+ css3DRenderer: CSS3DRenderer
59
+ }
60
+ }
61
+
62
+ /**
63
+ * **CSS3DRenderPlugin** 提供矩形区域(三维空间四个坐标点),你可以在此矩形区域中渲染 DOM 内容。
64
+ * 实现原理参考[CSS3DRenderer](https://threejs.org/docs/index.html?q=CSS3D#examples/en/renderers/CSS3DRenderer)。
65
+ *
66
+ * @returns
67
+ */
68
+ export const CSS3DRenderPlugin: FivePlugin<CSS3DRenderPluginParameterType, CSS3DRenderPluginExportType> = (five) => {
69
+ const state: CSS3DRenderPluginState = { disposeCallbacks: [] }
70
+ const create3DDomContainer = (
71
+ ...params: Parameters<Create3DDomContainerInterface>
72
+ ): ReturnType<Create3DDomContainerInterface> => {
73
+ const points = params[0].map((point) => (point instanceof Vector3 ? point : transformPositionToVector3(point)))
74
+ const config = params[1]
75
+ if (points?.length < 4) return console.error('points must be equal or greater than than 4')
76
+ if (!five?.model?.loaded) return console.error('five.model.loaded is: ', five?.model?.loaded)
77
+ const fiveElement = five.getElement()
78
+ if (!fiveElement) return console.error('five.getElement() is ' + fiveElement)
79
+
80
+ const disposers: any[] = []
81
+ const ratio = config?.ratio || 0.00216
82
+ if (ratio <= 0.00215) console.warn('if you need click css3DElement on safari, ratio must be greater than 0.00215')
83
+ const dpr = config?.dpr || 1
84
+ const mode = config?.mode || 'front'
85
+ const autoRender = config?.autoRender ?? true
86
+ const behindFiveContainer = config?.behindFiveContainer || fiveElement.parentElement || document.body
87
+ const container = config?.container || document.createElement('div')
88
+ container.classList.add('__Dnalogel-plugin--CSS3DRenderPlugin')
89
+
90
+ // 获取css3DObject, 如果mode为behind的话,一起获取mesh
91
+ const { css3DObject, mesh } = createObject(points, { ratio, dpr, container, mode })
92
+
93
+ // ======= INIT START =======
94
+ let resizeObserver:
95
+ | {
96
+ observe?: () => void
97
+ unobserve?: () => void
98
+ }
99
+ | undefined
100
+ const frontModeUnInited = mode === 'front' && !state.frontMode
101
+ const behindModeUnInited = mode === 'behind' && !state.behindMode
102
+ const inited = !(frontModeUnInited || behindModeUnInited)
103
+ let stopRenderFlag = false
104
+ let requestAnimationFrameId: number | null = null
105
+
106
+ if (behindModeUnInited) {
107
+ state.behindMode = { scene: new Scene(), css3DRenderer: new CSS3DRenderer() }
108
+ }
109
+ if (frontModeUnInited) {
110
+ state.frontMode = { scene: new Scene(), css3DRenderer: new CSS3DRenderer() }
111
+ }
112
+ const { scene, css3DRenderer } = mode === 'behind' ? state.behindMode! : state.frontMode!
113
+
114
+ const render = (() => {
115
+ if (!inited) {
116
+ const css3DRendererSetSize = () => {
117
+ // 这里evenNumber策略是遇到奇数会加1,不知道为啥会触发滚动条显示,导致fiveElement变小,fiveElement变小又触发了这里的resize,宽高变小,滚动条消失,然后又触发resize。。。无限循环
118
+ // 为了规避上面这种情况,evenNumber设置了一个参数,遇到奇数可选择减一
119
+ const rendererWidth = evenNumber(fiveElement.clientWidth, { smaller: true })
120
+ const rendererHeight = evenNumber(fiveElement.clientHeight, { smaller: true })
121
+ css3DRenderer.setSize(rendererWidth, rendererHeight)
122
+ }
123
+ css3DRendererSetSize()
124
+ resizeObserver = createResizeObserver(css3DRendererSetSize, fiveElement)
125
+ css3DRenderer.domElement.style.position = 'absolute'
126
+ css3DRenderer.domElement.style.top = '0'
127
+ css3DRenderer.domElement.style.userSelect = 'none'
128
+ css3DRenderer.domElement.style.pointerEvents = 'none'
129
+
130
+ const renderEveryFrame = () => {
131
+ if (stopRenderFlag) return
132
+ requestAnimationFrameId = requestAnimationFrame(renderEveryFrame)
133
+ css3DRenderer.render(scene!, five.camera)
134
+ }
135
+ return () => {
136
+ scene.add(css3DObject)
137
+ resizeObserver?.observe?.()
138
+ renderEveryFrame()
139
+ if (mode === 'behind' && mesh) {
140
+ five.scene.add(mesh)
141
+ disposers.push(() => mesh && five.scene.remove(mesh))
142
+ }
143
+ }
144
+ } else {
145
+ return () => {
146
+ scene.add(css3DObject)
147
+ }
148
+ }
149
+ })()
150
+ if (frontModeUnInited) {
151
+ const wrapper = fiveElement.parentElement || document.body
152
+ wrapper.appendChild(css3DRenderer.domElement)
153
+ }
154
+ if (behindModeUnInited) {
155
+ const wrapper = behindFiveContainer
156
+ wrapper.appendChild(css3DRenderer.domElement)
157
+ }
158
+ // ======= INIT END =======
159
+
160
+ const dispose = () => {
161
+ stopRenderFlag = true
162
+ disposers.forEach((d) => d?.())
163
+ scene.remove(css3DObject)
164
+ if (typeof requestAnimationFrameId === 'number') cancelAnimationFrame(requestAnimationFrameId)
165
+ if (scene.children.length === 0) {
166
+ css3DRenderer.domElement.remove()
167
+ if (mode === 'front') state.frontMode = undefined
168
+ if (mode === 'behind') state.behindMode = undefined
169
+ if (!state.behindMode && !state.frontMode) {
170
+ resizeObserver?.unobserve?.()
171
+ }
172
+ }
173
+ return true
174
+ }
175
+ const idx = state.disposeCallbacks.findIndex((item) => item === dispose)
176
+ if (idx !== -1) {
177
+ state.disposeCallbacks.splice(idx, 1)
178
+ } else {
179
+ state.disposeCallbacks.push(dispose)
180
+ }
181
+
182
+ if (autoRender) render()
183
+ return { container, dispose, css3DObject, render: autoRender ? undefined : render }
184
+ }
185
+
186
+ const createObject = (
187
+ points: Vector3[],
188
+ config: Required<Pick<Create3DDomContainerConfigInterface, 'ratio' | 'dpr' | 'container'>> &
189
+ Pick<Create3DDomContainerConfigInterface, 'mode'>,
190
+ ) => {
191
+ const { ratio, dpr, container: element, mode } = config
192
+ const planeWidth = points[0].distanceTo(points[1])
193
+ const planeHeight = points[1].distanceTo(points[2])
194
+ const domWidthPx = evenNumber((planeWidth / ratio) * dpr)
195
+ const domHeightPx = evenNumber((planeHeight / ratio) * dpr)
196
+
197
+ const css3DObject = new CSS3DObject(element)
198
+ css3DObject.scale.set(ratio, ratio, ratio)
199
+ element.style.width = domWidthPx + 'px'
200
+ element.style.height = domHeightPx + 'px'
201
+ element.style.pointerEvents = 'none'
202
+
203
+ const centerPosition = centerPoint(points[0], points[2])
204
+
205
+ const vector01 = points[1].clone().sub(points[0]) // 点0 -> 点1 的向量
206
+ const vector12 = points[2].clone().sub(points[1]) // 点1 -> 点2 的向量
207
+
208
+ // 旋转
209
+ const rotateXAngle = new Vector3(0, 1, 0).angleTo(new Vector3(0, vector12.y, vector12.z))
210
+ const rotateYAngle = new Vector3(1, 0, 0).angleTo(new Vector3(vector01.x, 0, vector01.z))
211
+ const rotateZAngle = vector01.angleTo(new Vector3(vector01.x, 0, vector01.z))
212
+ /**
213
+ * [0,1,0] => [0,0,1] 为rolate Worldx正方向
214
+ * [0,0,1] => [0,1,0] 为rolate Worldy正方向
215
+ * [0,1,1] => [-1,0,0] 为rolate Worldz正方向
216
+ */
217
+ const rotateX = (vector12.z > 0 ? -1 : 1) * rotateXAngle
218
+ const rotateY = (vector01.z < 0 ? 1 : -1) * rotateYAngle
219
+ const rotateZ = ((vector01.x > 0 && vector01.y < 0) || (vector01.x < 0 && vector01.y > 0) ? -1 : 1) * rotateZAngle
220
+
221
+ css3DObject.rotateOnWorldAxis(new Vector3(1, 0, 0), rotateX) // x
222
+ css3DObject.rotateOnWorldAxis(new Vector3(0, 1, 0), rotateY) // y
223
+ css3DObject.rotateOnWorldAxis(new Vector3(0, 0, 1), rotateZ) // z
224
+
225
+ css3DObject.position.set(centerPosition.x, centerPosition.y, centerPosition.z)
226
+
227
+ let mesh: THREE.Mesh<THREE.PlaneGeometry, THREE.MeshBasicMaterial> | undefined
228
+ if (mode === 'behind') {
229
+ const material = new THREE.MeshBasicMaterial({
230
+ opacity: 0,
231
+ transparent: false,
232
+ side: THREE.DoubleSide,
233
+ })
234
+ const geometry = new THREE.PlaneGeometry(domWidthPx, domHeightPx)
235
+ mesh = new THREE.Mesh(geometry, material)
236
+ mesh.name = 'CSS3DRenderPlugin-mesh'
237
+ mesh.position.copy(css3DObject.position)
238
+ mesh.rotation.copy(css3DObject.rotation)
239
+ mesh.scale.copy(css3DObject.scale)
240
+ }
241
+
242
+ return { css3DObject, mesh }
243
+ }
244
+
245
+ return {
246
+ create3DDomContainer,
247
+ disposeAll: () => {
248
+ state.disposeCallbacks.forEach((d) => d?.())
249
+ state.disposeCallbacks = []
250
+ },
251
+ }
252
+ }
253
+
254
+ export default CSS3DRenderPlugin
@@ -0,0 +1,5 @@
1
+ import { Vector3 } from 'three'
2
+
3
+ const transformPositionToVector3 = ({ x, y, z }: { x: number; y: number; z: number }) => new Vector3(x, y, z)
4
+
5
+ export default transformPositionToVector3
@@ -1,4 +1,4 @@
1
- import { Mode, MovePanoOptions, Pose } from '@realsee/five'
1
+ import { Mode, Pose } from '@realsee/five'
2
2
  import { CameraMovementEffect, Rotation } from '../fivePlugins/CameraMovementPlugin/typings'
3
3
 
4
4
  export enum VreoKeyframeEnum {
@@ -191,12 +191,24 @@ export type ModelVideoData = {
191
191
  matrixWorld?: number[]
192
192
  }
193
193
 
194
+ /**
195
+ * 四元数
196
+ */
197
+ export type Quaternion = {
198
+ x: number,
199
+ y: number,
200
+ z: number,
201
+ w: number
202
+ }
203
+
194
204
  /**
195
205
  * 全景文本标签
196
206
  */
197
207
  export type PanoTextLabelData = {
198
208
  text: string
199
209
  vertex: Vertex
210
+ normal?: Vertex
211
+ quaternion?: Quaternion
200
212
  fontSize?: number
201
213
  }
202
214
 
@@ -430,7 +430,11 @@
430
430
  transform-style: preserve-3d;
431
431
  pointer-events: none;
432
432
  z-index: 999999;
433
- transform: translate(-50%, -50%);
433
+ }
434
+ .__Dnalogel-plugin--CSS3DRenderPlugin {
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: center;
434
438
  }
435
439
 
436
440
  @keyframes straightPopPTL {