useThree.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. import * as THREE from 'three';
  2. import { RGBELoader } from 'three/examples/jsm/loaders/RGBELoader.js';
  3. // 导入轨道控制器
  4. import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
  5. import { CSS3DRenderer, CSS3DObject } from 'three/examples/jsm/renderers/CSS3DRenderer'
  6. // import gsap from 'gsap';
  7. import ResourceTracker from '/@/utils/threejs/ResourceTracker';
  8. import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
  9. import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
  10. class UseThree {
  11. canvasContainer: HTMLCanvasElement;
  12. CSSCanvasContainer: HTMLCanvasElement | null = null;
  13. camera: THREE.PerspectiveCamera | null = null;
  14. scene: THREE.Scene | null = null;
  15. renderer: THREE.WebGLRenderer | null = null;
  16. css3dRender: CSS3DRenderer | null = null;
  17. orbitControls: OrbitControls | null = null;
  18. giftLoader: THREE.Object3D | null = null;
  19. animationMixer: THREE.AnimationMixer | null = null;
  20. animationAction: THREE.AnimationAction | null = null;
  21. clock: THREE.Clock = new THREE.Clock(); // 计时器
  22. timeoutId: NodeJS.Timeout | null = null;
  23. animationId: number = 0
  24. resourceTracker:ResourceTracker | null = null
  25. track: any = null
  26. spriteText: THREE.Sprite | null = null
  27. constructor(canvasSelector, cssCanvas?) {
  28. this.resourceTracker = new ResourceTracker()
  29. this.track = this.resourceTracker.track.bind(this.resourceTracker)
  30. this.animationId = 0
  31. this.canvasContainer = document.querySelector(canvasSelector);
  32. //初始化
  33. this.init(cssCanvas);
  34. this.animate();
  35. window.addEventListener('resize', this.resizeRenderer.bind(this));
  36. // 添加滚动事件,鼠标滚动模型执行动画
  37. // window.addEventListener('wheel', this.wheelRenderer.bind(this));
  38. }
  39. init(cssCanvas?) {
  40. // 初始化场景
  41. this.initScene();
  42. // 初始化环境光
  43. this.initLight();
  44. // 初始化相机
  45. this.initCamera();
  46. //初始化渲染器
  47. this.initRenderer();
  48. // 初始化控制器
  49. this.initControles();
  50. if(cssCanvas){
  51. this.initCSSRenderer(cssCanvas)
  52. // this.addTextSprite()
  53. }
  54. // this.setTestPlane()
  55. }
  56. initScene() {
  57. this.scene = this.track(new THREE.Scene());
  58. // const axesHelper = new THREE.AxesHelper(100);
  59. // this.scene?.add(axesHelper);
  60. // const size = 1000;
  61. // const divisions = 10;
  62. // const gridHelper = new THREE.GridHelper( size, divisions );
  63. // this.scene?.add( gridHelper )
  64. }
  65. initLight() {
  66. const light = this.track(new THREE.AmbientLight(0xffffff, 1));
  67. light.position.set(0, 1000, 1000);
  68. (this.scene as THREE.Scene).add(light);
  69. }
  70. initCamera() {
  71. this.camera = this.track(new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 1000));
  72. this.camera?.position.set(0, 0.2, 0.3);
  73. // const helper = new THREE.CameraHelper( this.camera);
  74. // this.scene?.add( helper );
  75. }
  76. initRenderer() {
  77. this.renderer = this.track(new THREE.WebGLRenderer({ antialias: true, alpha:true })) as THREE.WebGLRenderer;
  78. // 设置屏幕像素比
  79. this.renderer?.setPixelRatio(window.devicePixelRatio);
  80. // 设置渲染的尺寸
  81. this.renderer?.setSize(window.innerWidth, window.innerHeight);
  82. // 色调映射
  83. this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
  84. this.renderer.outputEncoding = THREE.sRGBEncoding;
  85. this.renderer.shadowMap.enabled = true
  86. this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
  87. // 曝光程度
  88. this.renderer.toneMappingExposure = 1;
  89. this.canvasContainer.appendChild(this.renderer.domElement);
  90. }
  91. initCSSRenderer(cssCanvas) {
  92. this.CSSCanvasContainer = document.querySelector(cssCanvas);
  93. this.css3dRender = this.track(new CSS3DRenderer()) as CSS3DRenderer
  94. this.css3dRender.setSize((window.innerWidth), (window.innerHeight))
  95. this.CSSCanvasContainer?.appendChild(this.css3dRender.domElement)
  96. this.css3dRender.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera)
  97. this.orbitControls = this.track(new OrbitControls(this.camera as THREE.Camera, this.css3dRender?.domElement)) as OrbitControls;
  98. this.orbitControls.update()
  99. // this.orbitControls.enableZoom = false;
  100. // // to disable rotation
  101. // this.orbitControls.enableRotate = false;
  102. // // to disable pan
  103. // this.orbitControls.enablePan = false;
  104. }
  105. initControles() {
  106. this.orbitControls = this.track(new OrbitControls(this.camera as THREE.Camera, this.renderer?.domElement)) as OrbitControls;
  107. this.orbitControls.update()
  108. // this.orbitControls.enableZoom = false;
  109. // // to disable rotation
  110. // this.orbitControls.enableRotate = false;
  111. // // to disable pan
  112. // this.orbitControls.enablePan = false;
  113. }
  114. setModalCenter(group) {
  115. var box3 = new THREE.Box3()
  116. // 计算层级模型group的包围盒
  117. // 模型group是加载一个三维模型返回的对象,包含多个网格模型
  118. box3.expandByObject(group)
  119. // 计算一个层级模型对应包围盒的几何体中心在世界坐标中的位置
  120. var center = new THREE.Vector3()
  121. box3.getCenter(center)
  122. // console.log('查看几何体中心坐标', center);
  123. // 重新设置模型的位置,使之居中。
  124. group.position.x = group.position.x - center.x
  125. group.position.y = group.position.y - center.y
  126. group.position.z = group.position.z - center.z
  127. };
  128. setModel(modalName) {
  129. const a = new Date().getTime() / 1000
  130. return new Promise(async (resolve, reject) => {
  131. try {
  132. const db = window['CustomDB']
  133. const modalArr = await db.modal.where('modalName').equals(modalName).toArray()
  134. // debugger
  135. if(modalArr.length > 0) {
  136. const modalValue = modalArr[0].modalVal
  137. try {
  138. const gltfLoader = new GLTFLoader()
  139. const dracoLoader = new DRACOLoader();
  140. dracoLoader.setDecoderPath( '/3D/draco/gltf/' );
  141. dracoLoader.setDecoderConfig({ type: "js" }); //使用兼容性强的draco_decoder.js解码器
  142. dracoLoader.preload();
  143. gltfLoader.setDRACOLoader(dracoLoader);
  144. gltfLoader.setPath('/3d/glft/')
  145. gltfLoader.parse(modalValue, '/3d/glft/', (gltf) => {
  146. const object = this.track(gltf.scene.children[0])
  147. this.setModalCenter(object)
  148. object.traverse((obj) => {
  149. if (obj instanceof THREE.Mesh) {
  150. obj.material.emissiveIntensity = 1
  151. obj.material.emissiveMap = obj.material.map
  152. if(obj.name !== "buxiugangse") {
  153. obj.receiveShadow = true
  154. }
  155. obj.castShadow = true
  156. } else if (obj.type === 'Object3D') {
  157. // const text3D = this.addYFText(obj)
  158. // const textCQ3D = this.addCQText(obj)
  159. // gltf.scene.add(text3D)
  160. // gltf.scene.add(textCQ3D)
  161. }
  162. })
  163. this.scene?.add(object);
  164. console.log(object);
  165. resolve(object)
  166. }, (err)=> {
  167. console.log(err);
  168. })
  169. } catch (error) {
  170. console.log(error);
  171. }
  172. }
  173. } catch (error) {
  174. reject('加载模型出错')
  175. }
  176. })
  177. }
  178. setTestPlane() {
  179. const plane = new THREE.Mesh(
  180. new THREE.PlaneGeometry( 100, 100 ),
  181. new THREE.MeshPhongMaterial( { color: 0x999999, specular: 0x101010 } )
  182. );
  183. plane.rotation.x = - Math.PI / 2;
  184. plane.position.y = 0.03;
  185. plane.receiveShadow = true;
  186. // this.scene?.add( plane );
  187. }
  188. /* 自定义材质 */
  189. setCustomMaterial(group){}
  190. /* 场景环境背景 */
  191. setEnvMap(hdr) {
  192. // new RGBELoader().setPath('/public/3D/hdr/').load(hdr + '.jpeg', (texture) => {
  193. // debugger
  194. // texture.mapping = THREE.EquirectangularReflectionMapping;
  195. // (this.scene as THREE.Scene).background = texture;
  196. // (this.scene as THREE.Scene).environment = texture;
  197. // });
  198. new THREE.TextureLoader().setPath('/3D/hdr/').load(hdr + '.jpeg', (texture) => {
  199. texture.mapping = THREE.EquirectangularReflectionMapping;
  200. // (this.scene as THREE.Scene).background = new THREE.Color('#00000000');
  201. (this.scene as THREE.Scene).environment = texture;
  202. });
  203. }
  204. render() {
  205. const delta = this.clock.getElapsedTime();
  206. this.resetLookAt()
  207. this.startMY()
  208. this.renderer?.render(this.scene as THREE.Object3D, this.camera as THREE.Camera);
  209. this.css3dRender?.render(this.scene as THREE.Scene, this.camera as THREE.PerspectiveCamera)
  210. this.animationMixer?.update(delta);
  211. }
  212. /* 实时物体刷新朝向 */
  213. resetLookAt() {
  214. }
  215. /* 漫游 */
  216. startMY() {}
  217. animate() {
  218. // this.renderer?.setAnimationLoop(this.render.bind(this));
  219. if(this.animationId != -1) {
  220. this.render()
  221. this.animationId = requestAnimationFrame(this.animate.bind(this))
  222. }
  223. }
  224. resizeRenderer() {
  225. // 更新相机比例
  226. (this.camera as THREE.PerspectiveCamera).aspect = window.innerWidth / window.innerHeight;
  227. // 刷新相机矩阵
  228. this.camera?.updateProjectionMatrix();
  229. // 设置场景尺寸
  230. this.renderer?.setSize(window.innerWidth, window.innerHeight);
  231. this.css3dRender?.setSize(window.innerWidth, window.innerHeight);
  232. }
  233. wheelRenderer(e: WheelEvent) {
  234. const timeScale = e.deltaY > 0 ? 1 : -1;
  235. this.animationAction?.setEffectiveTimeScale(timeScale);
  236. (this.animationAction as THREE.AnimationAction).paused = false;
  237. this.animationAction?.play();
  238. if (this.timeoutId) {
  239. clearTimeout(this.timeoutId);
  240. }
  241. this.timeoutId = setTimeout(() => {
  242. this.animationAction?.halt(0.5);
  243. }, 500);
  244. }
  245. deleteModal() {
  246. try {
  247. this.resourceTracker && this.resourceTracker.dispose()
  248. this.renderer?.dispose()
  249. this.renderer?.forceContextLoss()
  250. // this.renderer?.domElement = null
  251. this.renderer = null
  252. // this.css3dRender?.domElement = null
  253. this.css3dRender = null;
  254. cancelAnimationFrame(this.animationId);
  255. const gl = this.renderer?.domElement.getContext("webgl");
  256. gl && gl?.getExtension("WEBGL_lose_context")?.loseContext();
  257. const gl1 = this.css3dRender?.domElement.getContext("webgl");
  258. gl1 && gl1?.getExtension("WEBGL_lose_context")?.loseContext();
  259. this.animationId = -1
  260. this.resourceTracker?.untrack(this.resourceTracker.resources)
  261. // this.scene = null
  262. console.log('销毁场景',this.scene);
  263. } catch (error) {
  264. console.log(error);
  265. }
  266. // this.scene?.children.forEach((obj:any) => {
  267. // if(obj.type === 'Group'){
  268. // obj.traverse(function(item:any) {
  269. // if (item.type === 'Mesh') {
  270. // item.geometry.dispose();
  271. // item.material.dispose();
  272. // !!item.clear&&item.clear();
  273. // }
  274. // })
  275. // }else if (obj.material) {
  276. // obj.material.dispose();
  277. // }else if (obj.geometry) {
  278. // obj.geometry.dispose();
  279. // }
  280. // this.scene?.remove(obj)
  281. // !!obj.clear??obj.clear()
  282. // obj = null;
  283. // })
  284. // let gl = this.renderer?.domElement.getContext("webgl");
  285. // gl && gl?.getExtension("WEBGL_lose_context")?.loseContext();
  286. // this.renderer?.forceContextLoss();
  287. // this.renderer?.dispose();
  288. // this.camera = null;
  289. // this.orbitControls = null;
  290. // this.renderer.domElement = null;
  291. // this.renderer = null;
  292. // css3dRender.domElement = null;
  293. // css3dRender = null;
  294. // model3D.innerHTML = '';
  295. // css3D.innerHTML = '';
  296. // model3D = null
  297. // css3D = null
  298. // this.stats = null
  299. // scene = null
  300. THREE.Cache.clear();
  301. console.log('3D环境已清理干净');
  302. }
  303. /* 创建字体精灵 */
  304. addTextSprite(message, parameters) {
  305. if (parameters === undefined) parameters = {};
  306. var fontface = parameters.hasOwnProperty("fontface") ?
  307. parameters["fontface"] : "Arial";
  308. /* 字体大小 */
  309. var fontsize = parameters.hasOwnProperty("fontsize") ?
  310. parameters["fontsize"] : 18;
  311. /* 边框厚度 */
  312. var borderThickness = parameters.hasOwnProperty("borderThickness") ?
  313. parameters["borderThickness"] : 4;
  314. /* 边框颜色 */
  315. var borderColor = parameters.hasOwnProperty("borderColor") ?
  316. parameters["borderColor"] : { r: 0, g: 0, b: 0, a: 1.0 };
  317. /* 背景颜色 */
  318. var backgroundColor = parameters.hasOwnProperty("backgroundColor") ?
  319. parameters["backgroundColor"] : { r: 255, g: 255, b: 255, a: 1.0 };
  320. /* 创建画布 */
  321. var canvas = document.createElement('canvas');
  322. var context = canvas.getContext('2d') as CanvasRenderingContext2D;
  323. // canvas.width = 0
  324. // canvas.height = 200
  325. /* 字体加粗 */
  326. context.font = "Bold " + fontsize + "px " + fontface;
  327. /* 获取文字的大小数据,高度取决于文字的大小 */
  328. var metrics = context.measureText(message);
  329. var textWidth = metrics.width;
  330. /* 背景颜色 */
  331. context.fillStyle = "rgba(" + backgroundColor.r + "," + backgroundColor.g + ","
  332. + backgroundColor.b + "," + backgroundColor.a + ")";
  333. /* 边框的颜色 */
  334. context.strokeStyle = "rgba(" + borderColor.r + "," + borderColor.g + ","
  335. + borderColor.b + "," + borderColor.a + ")";
  336. context.lineWidth = borderThickness;
  337. /* 绘制圆角矩形 */
  338. this.roundRect(context, borderThickness / 2, borderThickness / 2, textWidth + borderThickness, fontsize * 1.4 + borderThickness, 6);
  339. // this.roundRect(context, borderThickness / 2, borderThickness / 2, 10000, 10000, 6);
  340. /* 字体颜色 */
  341. context.fillStyle = "rgba(0, 0, 0, 1.0)";
  342. context.fillText(message, borderThickness, fontsize + borderThickness);
  343. // context.fillRect(borderThickness / 2, borderThickness / 2, 10000, 10000)
  344. /* 画布内容用于纹理贴图 */
  345. var texture = new THREE.Texture(canvas);
  346. texture.needsUpdate = true;
  347. var spriteMaterial = new THREE.SpriteMaterial({ map: texture, transparent:false });
  348. var sprite = new THREE.Sprite(spriteMaterial);
  349. /* 缩放比例 */
  350. sprite.scale.set(10, 5, 1);
  351. return sprite;
  352. }
  353. /* 绘制圆角矩形 */
  354. roundRect(ctx, x, y, w, h, r) {
  355. ctx.beginPath();
  356. ctx.moveTo(x + r, y);
  357. ctx.lineTo(x + w - r, y);
  358. ctx.quadraticCurveTo(x + w, y, x + w, y + r);
  359. ctx.lineTo(x + w, y + h - r);
  360. ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
  361. ctx.lineTo(x + r, y + h);
  362. ctx.quadraticCurveTo(x, y + h, x, y + h - r);
  363. ctx.lineTo(x, y + r);
  364. ctx.quadraticCurveTo(x, y, x + r, y);
  365. ctx.closePath();
  366. ctx.fill();
  367. ctx.stroke();
  368. }
  369. }
  370. export default UseThree;