diff --git a/package.json b/package.json index 8bb9d93..fae26b9 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "axios": "^1.3.4", "driver.js": "^1.3.1", "eslint-plugin-react-hooks": "^4.6.0", + "gsap": "^3.12.5", "localforage": "^1.10.0", "lodash": "^4.17.21", "moment": "^2.29.4", @@ -64,7 +65,8 @@ "react-sortable-hoc": "^2.0.0", "recoil": "^0.7.6", "svg-sprite-loader": "^6.0.11", - "svgo-loader": "^4.0.0" + "svgo-loader": "^4.0.0", + "three": "^0.166.0" }, "devDependencies": { "@babel/core": "^7.20.12", diff --git a/src/assets/vr/back.png b/src/assets/vr/back.png new file mode 100644 index 0000000..cefcbcf Binary files /dev/null and b/src/assets/vr/back.png differ diff --git a/src/assets/vr/bottom.png b/src/assets/vr/bottom.png new file mode 100644 index 0000000..c0f5110 Binary files /dev/null and b/src/assets/vr/bottom.png differ diff --git a/src/assets/vr/front.png b/src/assets/vr/front.png new file mode 100644 index 0000000..c42df34 Binary files /dev/null and b/src/assets/vr/front.png differ diff --git a/src/assets/vr/kitchen.jpg b/src/assets/vr/kitchen.jpg new file mode 100644 index 0000000..2cb0dc1 Binary files /dev/null and b/src/assets/vr/kitchen.jpg differ diff --git a/src/assets/vr/left.png b/src/assets/vr/left.png new file mode 100644 index 0000000..484120e Binary files /dev/null and b/src/assets/vr/left.png differ diff --git a/src/assets/vr/livingRoom.jpg b/src/assets/vr/livingRoom.jpg new file mode 100644 index 0000000..19e02ab Binary files /dev/null and b/src/assets/vr/livingRoom.jpg differ diff --git a/src/assets/vr/right.png b/src/assets/vr/right.png new file mode 100644 index 0000000..7a230b6 Binary files /dev/null and b/src/assets/vr/right.png differ diff --git a/src/assets/vr/tip.png b/src/assets/vr/tip.png new file mode 100644 index 0000000..05b3903 Binary files /dev/null and b/src/assets/vr/tip.png differ diff --git a/src/assets/vr/top.png b/src/assets/vr/top.png new file mode 100644 index 0000000..e54d257 Binary files /dev/null and b/src/assets/vr/top.png differ diff --git a/src/pages/threeVR/index.less b/src/pages/threeVR/index.less new file mode 100644 index 0000000..9f499a9 --- /dev/null +++ b/src/pages/threeVR/index.less @@ -0,0 +1,12 @@ +.three-vr { + position: relative; + width: 100%; + height: 100%; + overflow: hidden; + + .vr-container { + width: 100%; + height: 100%; + overflow: hidden; + } +} diff --git a/src/pages/threeVR/index.tsx b/src/pages/threeVR/index.tsx new file mode 100644 index 0000000..a731f05 --- /dev/null +++ b/src/pages/threeVR/index.tsx @@ -0,0 +1,240 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import gsap from 'gsap' +import React, { useEffect, useRef, useState } from 'react' +import * as THREE from 'three' +import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' +import styles from './index.less' + +/** + * @description: three.js VR 全景看房demo + * @author: KaifengLi + * @version: v1.0.0 + * @Date: 2024-07-02 14:27:18 + */ +const ThreeVR: React.FC = () => { + // VR容器 + const vRContainerRef = useRef(null) + const tooltipBoxRef = useRef(null) + const [tooltopContent, setTooltopContent] = useState({ + title: '', + text: '', + }) + const [sceneExample, setSceneExample] = useState(null) + const [cameraExample, setCameraExample] = useState(null) + const [rendererExample, setRendererExample] = useState(null) + const [controlsExample, setControlsExample] = useState(null) + const [sphereExample, setSphereExample] = useState(null) + const [tipsSpriteList, setTipsSpriteList] = useState([]) + const [sceneChildren, setSceneChildren] = useState([]) + + const [vrSource] = useState([ + { + image: require('@/assets/vr/livingRoom.jpg'), + tipsList: [ + { + position: { x: -200, y: -4, z: -147 }, + content: { + title: '进入厨房', + text: '', + image: 1, + showTip: false, + showTitle: true, + }, + }, + { + position: { x: -100, y: 0, z: -231 }, + content: { + title: '信息点2', + text: '77989', + showTip: true, + showTitle: false, + }, + }, + { + position: { x: 150, y: -50, z: -198 }, + content: { + title: '信息点3', + text: 'qwdcz', + showTip: true, + showTitle: false, + }, + }, + { + position: { x: 210, y: 11, z: -140 }, + content: { + title: '信息点4', + text: '大豆食心虫侦察十大大苏打大大大大大大大', + showTip: true, + showTitle: false, + }, + }, + { + position: { x: 208, y: -12, z: 140 }, + content: { + title: '信息点5', + text: 'eq', + showTip: true, + showTitle: false, + }, + }, + { + position: { x: 86, y: -9, z: 236 }, + content: { + title: '进入房间', + text: '', + showTip: false, + showTitle: true, + }, + }, + ], + }, + { + image: require('@/assets/vr/kitchen.jpg'), + tipsList: [ + { + position: { x: -199, y: -24, z: 145 }, + content: { + title: '进入大厅', + text: '', + image: 0, + showTip: false, + showTitle: true, + }, + }, + ], + }, + ]) + + const handleAddTipsSprite = (index = 0, scene: any) => { + const tipTexture = new THREE.TextureLoader().load(require('@/assets/vr/tip.png')) + const material = new THREE.SpriteMaterial({ map: tipTexture }) + const tips: any[] = [] + vrSource[index].tipsList.forEach((item: any) => { + const sprite = new THREE.Sprite(material) + sprite.scale.set(10, 10, 10) + sprite.position.set(item.position.x, item.position.y, item.position.z) + sprite.content = item.content + tips.push(sprite) + scene.add(sprite) + }) + setTipsSpriteList(tips) + } + + const handleChangeContentAndtips = (index: number, scene: any, camera: any, sphere: any) => { + const children = scene.children.filter((item: any) => String(item.type) !== 'Sprite') + setTipsSpriteList([]) + setSceneChildren(children) + const texture = new THREE.TextureLoader().load(vrSource[index].image) + const sphereMaterial = new THREE.MeshBasicMaterial({ + map: texture, + transparent: true, + opacity: 0, + }) + sphere.material = sphereMaterial + gsap.to(sphereMaterial, { transparent: true, opacity: 1, duration: 2 }) + camera.updateProjectionMatrix() + handleAddTipsSprite(index, scene) + } + + useEffect(() => { + const dom: any = document.getElementById('vrContainer') + // 创建场景 场景能够让你在什么地方、摆放什么东西来交给three.js来渲染,这是你放置物体、灯光和摄像机的地方。 + const scene = new THREE.Scene() + // 对 Color 实例进行遍历将按相应的顺序生成它的分量 (r, g, b)。 + scene.background = new THREE.Color(0x101010) + // 这一摄像机使用perspective projection(透视投影)来进行投影。 + // 这一投影模式被用来模拟人眼所看到的景象,它是3D场景的渲染中使用得最普遍的投影模式。 + // fov — 摄像机视锥体垂直视野角度 aspect — 摄像机视锥体长宽比 near — 摄像机视锥体近端面 far — 摄像机视锥体远端面 + const camera = new THREE.PerspectiveCamera(45, dom.clientWidth / dom.clientHeight, 0.1, 1000) + // x, y, z + // 相机在Three.js三维坐标系中的位置, 根据需要设置相机位置具体值 + camera.position.set(50, 0, 50) + + // Orbit controls(轨道控制器)可以使得相机围绕目标进行轨道运动。 + // 要使用这一功能,就像在/examples(示例)目录中的所有文件一样, 您必须在HTML中包含这个文件。 + const controls = new OrbitControls(camera, dom) + // 你能够将相机向内移动多少(仅适用于PerspectiveCamera),其默认值为0。 + controls.minDistance = 1 + // 你能够将相机向外移动多少(仅适用于PerspectiveCamera),其默认值为Infinity。 + controls.maxDistance = 100 + // 启用或禁用摄像机平移,默认为true。 + controls.enablePan = false + + // const geometry = new THREE.BoxGeometry(10, 10, 10) + // const picList = ['left', 'right', 'top', 'bottom', 'front', 'back'] + // const boxMaterials: any = [] + // picList.forEach(item => { + // const texture = new THREE.TextureLoader().load(require(`@/assets/vr/${item}.png`)) + // boxMaterials.push(new THREE.MeshBasicMaterial({ map: texture })) + // }) + + // 球缓冲几何体 一个用于生成球体的类。 + // radius — 球体半径,默认为1。 + // widthSegments — 水平分段数(沿着经线分段),最小值为3,默认值为32。 + // heightSegments — 垂直分段数(沿着纬线分段),最小值为2,默认值为16。 + // phiStart — 指定水平(经线)起始角度,默认值为0。。 + // phiLength — 指定水平(经线)扫描角度的大小,默认值为 Math.PI * 2。 + // thetaStart — 指定垂直(纬线)起始角度,默认值为0。 + // thetaLength — 指定垂直(纬线)扫描角度大小,默认值为 Math.PI。 + const sphereGeometry = new THREE.SphereGeometry(16, 50, 50) + // 加载texture的一个类。 内部使用ImageLoader来加载文件。 + // load ( url : String, onLoad : Function, onProgress : Function, onError : Function ) + // url — 文件的URL或者路径,也可以为 Data URI. + // onLoad — 加载完成时将调用。回调参数为将要加载的texture. + // onProgress — 将在加载过程中进行调用。参数为XMLHttpRequest实例,实例包含total和loaded字节。 + // onError — 在加载错误时被调用。 + const texture = new THREE.TextureLoader().load(vrSource[0].image) + // 基础网格材质 一个以简单着色(平面或线框)方式来绘制几何体的材质。 这种材质不受光照的影响。 + // .map 颜色贴图。可以选择包括一个alpha通道,通常与.transparent 或.alphaTest。默认为null。 + const sphereMaterial = new THREE.MeshBasicMaterial({ map: texture }) + + // 表示基于以三角形为polygon mesh(多边形网格)的物体的类 + // geometry —— (可选)BufferGeometry的实例,默认值是一个新的BufferGeometry。 + // material —— (可选)一个Material,或是一个包含有Material的数组,默认是一个新的 + const cube = new THREE.Mesh(sphereGeometry, sphereMaterial) + sphereGeometry.scale(16, 16, -16) + // cube.geometry.scale(10, 10, -10) + scene.add(cube) + handleAddTipsSprite(0, scene) + + // WebGL Render 用WebGL渲染出你精心制作的场景。 + // parameters: (可选) 该对象的属性定义了渲染器的行为。 + // 也可以完全不传参数。在所有情况下,当缺少参数时,它将采用合理的默认值。 + const renderer = new THREE.WebGLRenderer() + renderer.setSize(dom?.offsetWidth, dom?.offsetHeight) + dom?.appendChild(renderer.domElement) + + controls.update() + function animate() { + requestAnimationFrame(animate) + + renderer.render(scene, camera) + } + animate() + + // 监听窗口变化 + window.addEventListener( + 'resize', + () => { + camera.aspect = dom.clientWidth / dom.clientHeight + camera.updateProjectionMatrix() + renderer.setSize(dom.clientWidth, dom.clientHeight) + }, + false, + ) + }, []) + + return ( +
+
+
+
+
标题:{tooltopContent.title}
+
说明:{tooltopContent.text}
+
+
+
+ ) +} + +export default ThreeVR diff --git a/src/routers/modules/iconDashboard.tsx b/src/routers/modules/iconDashboard.tsx index 5430920..fcc1da5 100644 --- a/src/routers/modules/iconDashboard.tsx +++ b/src/routers/modules/iconDashboard.tsx @@ -8,7 +8,12 @@ const iconRouter: Array = [ { element: , path: '/icon', - single: true, + meta: { + title: 'Demo', + key: 'Demo', + icon: 'CameraOutlined', + auth: true, + }, children: [ { path: '/icon/index', @@ -20,6 +25,16 @@ const iconRouter: Array = [ auth: true, }, }, + { + path: '/icon/threeVR', + element: lazyLoad(React.lazy(() => import('@/pages/threeVR'))), + meta: { + title: 'ThreeVR', + key: 'ThreeVR', + icon: 'RocketOutlined', + auth: true, + }, + }, ], }, ] diff --git a/src/routers/utils/authRouter.tsx b/src/routers/utils/authRouter.tsx index 161ad86..dc80ea3 100644 --- a/src/routers/utils/authRouter.tsx +++ b/src/routers/utils/authRouter.tsx @@ -30,6 +30,7 @@ const AuthRouter = (props: { children: any }) => { '/dashboard/analysis', '/dataScreen/analysis', '/icon/index', + '/icon/threeVR', ] // 5. Static Router(静态路由,必须配置首页地址,否则不能进首页获取菜单、按钮权限等数据),获取数据的时候会loading,所有配置首页地址也没问题 diff --git a/src/typings/images.d.ts b/src/typings/images.d.ts index ffdc236..9ea65fc 100644 --- a/src/typings/images.d.ts +++ b/src/typings/images.d.ts @@ -7,3 +7,5 @@ declare module '*.bmp' declare module '*.tiff' declare module '*.less' declare module '*.css' +declare module 'three' +declare module 'three/examples/jsm/controls/OrbitControls.js'