添加材质切换,调节功能
@@ -1,5 +1,3 @@
 | 
				
			|||||||
# Vue 3 + TypeScript + Vite
 | 
					# 材质编辑器
 | 
				
			||||||
 | 
					
 | 
				
			||||||
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
 | 
					独立实现的WebCAD材质编辑器,提供材质预览,调节和上传功能。
 | 
				
			||||||
 | 
					 | 
				
			||||||
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
 | 
					 | 
				
			||||||
							
								
								
									
										15
									
								
								package.json
									
									
									
									
									
								
							
							
						
						@@ -9,18 +9,21 @@
 | 
				
			|||||||
    "preview": "vite preview"
 | 
					    "preview": "vite preview"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "vue": "^3.5.13",
 | 
					 | 
				
			||||||
    "three": "npm:three-cf@^0.122.9",
 | 
					 | 
				
			||||||
    "js-angusj-clipper": "^1.2.1",
 | 
					 | 
				
			||||||
    "polylabel": "^1.1.0",
 | 
					 | 
				
			||||||
    "@jscad/modeling": "^2.11.0",
 | 
					    "@jscad/modeling": "^2.11.0",
 | 
				
			||||||
 | 
					    "fflate": "^0.8.2",
 | 
				
			||||||
    "flatbush": "^3.3.0",
 | 
					    "flatbush": "^3.3.0",
 | 
				
			||||||
    "xaop": "^2.0.0",
 | 
					    "js-angusj-clipper": "^1.2.1",
 | 
				
			||||||
    "webcad_ue4_api": "http://gitea.cf/cx/webcad-ue4-api/archive/3.20.0.tar.gz"
 | 
					    "pinia": "^3.0.2",
 | 
				
			||||||
 | 
					    "polylabel": "^1.1.0",
 | 
				
			||||||
 | 
					    "three": "npm:three-cf@^0.122.9",
 | 
				
			||||||
 | 
					    "vue": "^3.5.13",
 | 
				
			||||||
 | 
					    "webcad_ue4_api": "http://gitea.cf/cx/webcad-ue4-api/archive/3.20.0.tar.gz",
 | 
				
			||||||
 | 
					    "xaop": "^2.0.0"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@vitejs/plugin-vue": "^5.2.1",
 | 
					    "@vitejs/plugin-vue": "^5.2.1",
 | 
				
			||||||
    "@vue/tsconfig": "^0.7.0",
 | 
					    "@vue/tsconfig": "^0.7.0",
 | 
				
			||||||
 | 
					    "csstype": "^3.1.3",
 | 
				
			||||||
    "typescript": "~5.7.2",
 | 
					    "typescript": "~5.7.2",
 | 
				
			||||||
    "vite": "^6.2.0",
 | 
					    "vite": "^6.2.0",
 | 
				
			||||||
    "vue-tsc": "^2.2.4"
 | 
					    "vue-tsc": "^2.2.4"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										110
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						@@ -11,12 +11,18 @@ importers:
 | 
				
			|||||||
      '@jscad/modeling':
 | 
					      '@jscad/modeling':
 | 
				
			||||||
        specifier: ^2.11.0
 | 
					        specifier: ^2.11.0
 | 
				
			||||||
        version: 2.12.5
 | 
					        version: 2.12.5
 | 
				
			||||||
 | 
					      fflate:
 | 
				
			||||||
 | 
					        specifier: ^0.8.2
 | 
				
			||||||
 | 
					        version: 0.8.2
 | 
				
			||||||
      flatbush:
 | 
					      flatbush:
 | 
				
			||||||
        specifier: ^3.3.0
 | 
					        specifier: ^3.3.0
 | 
				
			||||||
        version: 3.3.1
 | 
					        version: 3.3.1
 | 
				
			||||||
      js-angusj-clipper:
 | 
					      js-angusj-clipper:
 | 
				
			||||||
        specifier: ^1.2.1
 | 
					        specifier: ^1.2.1
 | 
				
			||||||
        version: 1.3.1
 | 
					        version: 1.3.1
 | 
				
			||||||
 | 
					      pinia:
 | 
				
			||||||
 | 
					        specifier: ^3.0.2
 | 
				
			||||||
 | 
					        version: 3.0.2(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
 | 
				
			||||||
      polylabel:
 | 
					      polylabel:
 | 
				
			||||||
        specifier: ^1.1.0
 | 
					        specifier: ^1.1.0
 | 
				
			||||||
        version: 1.1.0
 | 
					        version: 1.1.0
 | 
				
			||||||
@@ -39,6 +45,9 @@ importers:
 | 
				
			|||||||
      '@vue/tsconfig':
 | 
					      '@vue/tsconfig':
 | 
				
			||||||
        specifier: ^0.7.0
 | 
					        specifier: ^0.7.0
 | 
				
			||||||
        version: 0.7.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
 | 
					        version: 0.7.0(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
 | 
				
			||||||
 | 
					      csstype:
 | 
				
			||||||
 | 
					        specifier: ^3.1.3
 | 
				
			||||||
 | 
					        version: 3.1.3
 | 
				
			||||||
      typescript:
 | 
					      typescript:
 | 
				
			||||||
        specifier: ~5.7.2
 | 
					        specifier: ~5.7.2
 | 
				
			||||||
        version: 5.7.3
 | 
					        version: 5.7.3
 | 
				
			||||||
@@ -369,6 +378,15 @@ packages:
 | 
				
			|||||||
  '@vue/compiler-vue2@2.7.16':
 | 
					  '@vue/compiler-vue2@2.7.16':
 | 
				
			||||||
    resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
 | 
					    resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@vue/devtools-api@7.7.2':
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-1syn558KhyN+chO5SjlZIwJ8bV/bQ1nOVTG66t2RbG66ZGekyiYNmRO7X9BJCXQqPsFHlnksqvPhce2qpzxFnA==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@vue/devtools-kit@7.7.2':
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@vue/devtools-shared@7.7.2':
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  '@vue/language-core@2.2.8':
 | 
					  '@vue/language-core@2.2.8':
 | 
				
			||||||
    resolution: {integrity: sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ==}
 | 
					    resolution: {integrity: sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ==}
 | 
				
			||||||
    peerDependencies:
 | 
					    peerDependencies:
 | 
				
			||||||
@@ -411,9 +429,16 @@ packages:
 | 
				
			|||||||
  balanced-match@1.0.2:
 | 
					  balanced-match@1.0.2:
 | 
				
			||||||
    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
 | 
					    resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  birpc@0.2.19:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  brace-expansion@2.0.1:
 | 
					  brace-expansion@2.0.1:
 | 
				
			||||||
    resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
 | 
					    resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  copy-anything@3.0.5:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==}
 | 
				
			||||||
 | 
					    engines: {node: '>=12.13'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  csstype@3.1.3:
 | 
					  csstype@3.1.3:
 | 
				
			||||||
    resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 | 
					    resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -432,6 +457,9 @@ packages:
 | 
				
			|||||||
  estree-walker@2.0.2:
 | 
					  estree-walker@2.0.2:
 | 
				
			||||||
    resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
 | 
					    resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fflate@0.8.2:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  flatbush@3.3.1:
 | 
					  flatbush@3.3.1:
 | 
				
			||||||
    resolution: {integrity: sha512-oKuPbtT+DS2CxH+9Vhbsq8HifmSCuOw+3Cy5zt/vCIrZl5KyengoTHDBLmtpZoBhcwa7/biNjgL1DwdLMJYm1A==}
 | 
					    resolution: {integrity: sha512-oKuPbtT+DS2CxH+9Vhbsq8HifmSCuOw+3Cy5zt/vCIrZl5KyengoTHDBLmtpZoBhcwa7/biNjgL1DwdLMJYm1A==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -447,6 +475,13 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
 | 
					    resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
 | 
				
			||||||
    hasBin: true
 | 
					    hasBin: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  hookable@5.5.3:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  is-what@4.1.16:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==}
 | 
				
			||||||
 | 
					    engines: {node: '>=12.13'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  js-angusj-clipper@1.3.1:
 | 
					  js-angusj-clipper@1.3.1:
 | 
				
			||||||
    resolution: {integrity: sha512-/qru4QXxN/gBbQjL4WaFl296YSM8kh5XKpNuNqfZhJ4t4Hw3KeLc5ERj3XHAeLi6pBrqeh6o9PFZUpS3QThEEQ==}
 | 
					    resolution: {integrity: sha512-/qru4QXxN/gBbQjL4WaFl296YSM8kh5XKpNuNqfZhJ4t4Hw3KeLc5ERj3XHAeLi6pBrqeh6o9PFZUpS3QThEEQ==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -457,6 +492,9 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
 | 
					    resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
 | 
				
			||||||
    engines: {node: '>=16 || 14 >=14.17'}
 | 
					    engines: {node: '>=16 || 14 >=14.17'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mitt@3.0.1:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  muggle-string@0.4.1:
 | 
					  muggle-string@0.4.1:
 | 
				
			||||||
    resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
 | 
					    resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -468,9 +506,21 @@ packages:
 | 
				
			|||||||
  path-browserify@1.0.1:
 | 
					  path-browserify@1.0.1:
 | 
				
			||||||
    resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
 | 
					    resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  perfect-debounce@1.0.0:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  picocolors@1.1.1:
 | 
					  picocolors@1.1.1:
 | 
				
			||||||
    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
 | 
					    resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pinia@3.0.2:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==}
 | 
				
			||||||
 | 
					    peerDependencies:
 | 
				
			||||||
 | 
					      typescript: '>=4.4.4'
 | 
				
			||||||
 | 
					      vue: ^2.7.0 || ^3.5.11
 | 
				
			||||||
 | 
					    peerDependenciesMeta:
 | 
				
			||||||
 | 
					      typescript:
 | 
				
			||||||
 | 
					        optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  polylabel@1.1.0:
 | 
					  polylabel@1.1.0:
 | 
				
			||||||
    resolution: {integrity: sha512-bxaGcA40sL3d6M4hH72Z4NdLqxpXRsCFk8AITYg6x1rn1Ei3izf00UMLklerBZTO49aPA3CYrIwVulx2Bce2pA==}
 | 
					    resolution: {integrity: sha512-bxaGcA40sL3d6M4hH72Z4NdLqxpXRsCFk8AITYg6x1rn1Ei3izf00UMLklerBZTO49aPA3CYrIwVulx2Bce2pA==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -478,6 +528,9 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
 | 
					    resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==}
 | 
				
			||||||
    engines: {node: ^10 || ^12 || >=14}
 | 
					    engines: {node: ^10 || ^12 || >=14}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rfdc@1.4.1:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  rollup@4.39.0:
 | 
					  rollup@4.39.0:
 | 
				
			||||||
    resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==}
 | 
					    resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==}
 | 
				
			||||||
    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
 | 
					    engines: {node: '>=18.0.0', npm: '>=8.0.0'}
 | 
				
			||||||
@@ -487,6 +540,14 @@ packages:
 | 
				
			|||||||
    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
 | 
					    resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
 | 
				
			||||||
    engines: {node: '>=0.10.0'}
 | 
					    engines: {node: '>=0.10.0'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  speakingurl@14.0.1:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
 | 
				
			||||||
 | 
					    engines: {node: '>=0.10.0'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  superjson@2.2.2:
 | 
				
			||||||
 | 
					    resolution: {integrity: sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==}
 | 
				
			||||||
 | 
					    engines: {node: '>=16'}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  three-cf@0.122.9:
 | 
					  three-cf@0.122.9:
 | 
				
			||||||
    resolution: {integrity: sha512-y+bvPYKI0yMNGF2flNsTbqloPiMAL4OKbIbR9QaQLRjNlz15Th+69ZtirU5ZASGXF/vQIvIrv+6OkxdSjiN89Q==}
 | 
					    resolution: {integrity: sha512-y+bvPYKI0yMNGF2flNsTbqloPiMAL4OKbIbR9QaQLRjNlz15Th+69ZtirU5ZASGXF/vQIvIrv+6OkxdSjiN89Q==}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -770,6 +831,24 @@ snapshots:
 | 
				
			|||||||
      de-indent: 1.0.2
 | 
					      de-indent: 1.0.2
 | 
				
			||||||
      he: 1.2.0
 | 
					      he: 1.2.0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@vue/devtools-api@7.7.2':
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      '@vue/devtools-kit': 7.7.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@vue/devtools-kit@7.7.2':
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      '@vue/devtools-shared': 7.7.2
 | 
				
			||||||
 | 
					      birpc: 0.2.19
 | 
				
			||||||
 | 
					      hookable: 5.5.3
 | 
				
			||||||
 | 
					      mitt: 3.0.1
 | 
				
			||||||
 | 
					      perfect-debounce: 1.0.0
 | 
				
			||||||
 | 
					      speakingurl: 14.0.1
 | 
				
			||||||
 | 
					      superjson: 2.2.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  '@vue/devtools-shared@7.7.2':
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      rfdc: 1.4.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  '@vue/language-core@2.2.8(typescript@5.7.3)':
 | 
					  '@vue/language-core@2.2.8(typescript@5.7.3)':
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      '@volar/language-core': 2.4.12
 | 
					      '@volar/language-core': 2.4.12
 | 
				
			||||||
@@ -816,10 +895,16 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  balanced-match@1.0.2: {}
 | 
					  balanced-match@1.0.2: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  birpc@0.2.19: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  brace-expansion@2.0.1:
 | 
					  brace-expansion@2.0.1:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      balanced-match: 1.0.2
 | 
					      balanced-match: 1.0.2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  copy-anything@3.0.5:
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      is-what: 4.1.16
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  csstype@3.1.3: {}
 | 
					  csstype@3.1.3: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  de-indent@1.0.2: {}
 | 
					  de-indent@1.0.2: {}
 | 
				
			||||||
@@ -856,6 +941,8 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  estree-walker@2.0.2: {}
 | 
					  estree-walker@2.0.2: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fflate@0.8.2: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  flatbush@3.3.1:
 | 
					  flatbush@3.3.1:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      flatqueue: 1.2.1
 | 
					      flatqueue: 1.2.1
 | 
				
			||||||
@@ -867,6 +954,10 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  he@1.2.0: {}
 | 
					  he@1.2.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  hookable@5.5.3: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  is-what@4.1.16: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  js-angusj-clipper@1.3.1: {}
 | 
					  js-angusj-clipper@1.3.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  magic-string@0.30.17:
 | 
					  magic-string@0.30.17:
 | 
				
			||||||
@@ -877,14 +968,25 @@ snapshots:
 | 
				
			|||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      brace-expansion: 2.0.1
 | 
					      brace-expansion: 2.0.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  mitt@3.0.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  muggle-string@0.4.1: {}
 | 
					  muggle-string@0.4.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  nanoid@3.3.11: {}
 | 
					  nanoid@3.3.11: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  path-browserify@1.0.1: {}
 | 
					  path-browserify@1.0.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  perfect-debounce@1.0.0: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  picocolors@1.1.1: {}
 | 
					  picocolors@1.1.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  pinia@3.0.2(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3)):
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      '@vue/devtools-api': 7.7.2
 | 
				
			||||||
 | 
					      vue: 3.5.13(typescript@5.7.3)
 | 
				
			||||||
 | 
					    optionalDependencies:
 | 
				
			||||||
 | 
					      typescript: 5.7.3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  polylabel@1.1.0:
 | 
					  polylabel@1.1.0:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      tinyqueue: 2.0.3
 | 
					      tinyqueue: 2.0.3
 | 
				
			||||||
@@ -895,6 +997,8 @@ snapshots:
 | 
				
			|||||||
      picocolors: 1.1.1
 | 
					      picocolors: 1.1.1
 | 
				
			||||||
      source-map-js: 1.2.1
 | 
					      source-map-js: 1.2.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  rfdc@1.4.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  rollup@4.39.0:
 | 
					  rollup@4.39.0:
 | 
				
			||||||
    dependencies:
 | 
					    dependencies:
 | 
				
			||||||
      '@types/estree': 1.0.7
 | 
					      '@types/estree': 1.0.7
 | 
				
			||||||
@@ -923,6 +1027,12 @@ snapshots:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  source-map-js@1.2.1: {}
 | 
					  source-map-js@1.2.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  speakingurl@14.0.1: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  superjson@2.2.2:
 | 
				
			||||||
 | 
					    dependencies:
 | 
				
			||||||
 | 
					      copy-anything: 3.0.5
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  three-cf@0.122.9: {}
 | 
					  three-cf@0.122.9: {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  tinyqueue@2.0.3: {}
 | 
					  tinyqueue@2.0.3: {}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								public/back-gray.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 60 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/back.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 64 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/bottom-gray.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 454 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/bottom.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 438 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/front-gray.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 152 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/front.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 155 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/left-gray.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 78 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/left.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 85 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/right-gray.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 137 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/right.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 140 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/texture1.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 406 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/top-gray.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 7.0 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								public/top.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						| 
		 After Width: | Height: | Size: 8.2 KiB  | 
@@ -1,6 +1,9 @@
 | 
				
			|||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import MaterialView from './components/MaterialView.vue';
 | 
					import MaterialView from './components/MaterialView.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 禁用右键菜单
 | 
				
			||||||
 | 
					document.addEventListener('contextmenu', (e) => e.preventDefault()); 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<template>
 | 
					<template>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										45
									
								
								src/api/Api.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					function GetCurHost() {
 | 
				
			||||||
 | 
					    let searchParams = new URLSearchParams(globalThis.location?.search);
 | 
				
			||||||
 | 
					    if (searchParams.has("server"))
 | 
				
			||||||
 | 
					        return searchParams.get("server");
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					        let hostname = globalThis.location?.hostname;
 | 
				
			||||||
 | 
					        switch (hostname) {
 | 
				
			||||||
 | 
					            case "cfcad.cn":
 | 
				
			||||||
 | 
					            case "www.cfcad.cn":
 | 
				
			||||||
 | 
					                return "https://api.cfcad.cn";
 | 
				
			||||||
 | 
					            case "vip.cfcad.cn":
 | 
				
			||||||
 | 
					                return "https://vapi.cfcad.cn";
 | 
				
			||||||
 | 
					            case "v.cfcad.cn":
 | 
				
			||||||
 | 
					                return "https://vapi.cfcad.cn";
 | 
				
			||||||
 | 
					            case "t.cfcad.cn":
 | 
				
			||||||
 | 
					                return "https://tapi.cfcad.cn:7779";
 | 
				
			||||||
 | 
					            case "tvip.cfcad.cn":
 | 
				
			||||||
 | 
					                return "https://tvapi.cfcad.cn:7779";
 | 
				
			||||||
 | 
					            case "tv.cfcad.cn":
 | 
				
			||||||
 | 
					                return "https://tvapi.cfcad.cn:7779";
 | 
				
			||||||
 | 
					            default:
 | 
				
			||||||
 | 
					                return "https://tapi.cfcad.cn:7779";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const CURRENT_HOST = GetCurHost();
 | 
				
			||||||
 | 
					export const ImgsUrl = {
 | 
				
			||||||
 | 
					    get: CURRENT_HOST + "/CAD-imageList",
 | 
				
			||||||
 | 
					    upload: CURRENT_HOST + "/CAD-imageUpload",
 | 
				
			||||||
 | 
					    delete: CURRENT_HOST + "/CAD-imageDelete",
 | 
				
			||||||
 | 
					    logo: CURRENT_HOST + "/CAD-logoUpload",
 | 
				
			||||||
 | 
					    update: CURRENT_HOST + "/CAD-imageUpdate"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					export const MaterialUrls = {
 | 
				
			||||||
 | 
					    query: CURRENT_HOST + "/CAD-materialList",
 | 
				
			||||||
 | 
					    create: CURRENT_HOST + "/CAD-materialCreate",
 | 
				
			||||||
 | 
					    get: CURRENT_HOST + "/CAD-materialList",
 | 
				
			||||||
 | 
					    detail: CURRENT_HOST + "/CAD-materialDetail",
 | 
				
			||||||
 | 
					    delete: CURRENT_HOST + "/CAD-materialDelete",
 | 
				
			||||||
 | 
					    update: CURRENT_HOST + "/CAD-materialUpdate",
 | 
				
			||||||
 | 
					    move: CURRENT_HOST + "/CAD-materialMove",
 | 
				
			||||||
 | 
					    buy: CURRENT_HOST + "/Materials-openList",
 | 
				
			||||||
 | 
					    publishDetail: CURRENT_HOST + "/CAD-materialPublicDetail",
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										71
									
								
								src/api/Request.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,71 @@
 | 
				
			|||||||
 | 
					import { ImgsUrl } from "./Api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum DirectoryId
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    None = "",
 | 
				
			||||||
 | 
					    FileDir = "1", //图片根目录
 | 
				
			||||||
 | 
					    MaterialDir = "2", //材质根目录
 | 
				
			||||||
 | 
					    ImgDir = "3", //图片根目录
 | 
				
			||||||
 | 
					    ToplineDir = "4", //材质根目录
 | 
				
			||||||
 | 
					    TemplateDir = "5", //模板根目录
 | 
				
			||||||
 | 
					    DrillingDir = "6", //排钻目录
 | 
				
			||||||
 | 
					    KnifePathDir = "7", //刀路目录
 | 
				
			||||||
 | 
					    Frame = "8", //图框目录
 | 
				
			||||||
 | 
					    CeilingContour = "9", //吊顶轮廓
 | 
				
			||||||
 | 
					    HistoryDit = "-1",//历史编辑目录
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export enum RequestStatus
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    NoLogin = 88888,
 | 
				
			||||||
 | 
					    Ok = 0,
 | 
				
			||||||
 | 
					    NoPermission = 102,//没有经过授权,不能登录该账号
 | 
				
			||||||
 | 
					    DeleteWarn1 = 401,
 | 
				
			||||||
 | 
					    DeleteWarn2 = 402,
 | 
				
			||||||
 | 
					    NoBuy = 3298, //未购买cad包月服务错误码
 | 
				
			||||||
 | 
					    NoBuy1 = 3299,//包月服务未充值
 | 
				
			||||||
 | 
					    NoBuy2 = 3300,//包月服务未生效
 | 
				
			||||||
 | 
					    NoBuy3 = 3301,//包月服务已失效
 | 
				
			||||||
 | 
					    NoBuy4 = 3412, //未购买渲染包月服务
 | 
				
			||||||
 | 
					    None = -1, //未知错误
 | 
				
			||||||
 | 
					    OffLine = 44444, //踢下线
 | 
				
			||||||
 | 
					    NoToken = 6600, //酷家乐未授权
 | 
				
			||||||
 | 
					    CreateTempNoLogo = 802, //导入模板时未查询到json文件logo
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					export interface IResponseData
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    err_code: RequestStatus;
 | 
				
			||||||
 | 
					    err_msg: string;
 | 
				
			||||||
 | 
					    [key: string]: any;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function PostJson<T = object>(
 | 
				
			||||||
 | 
					    url: string, body: Exclude<T, BodyInit>,
 | 
				
			||||||
 | 
					    isShowErrMsg = true)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    while (true)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        let res = await Post(url, JSON.stringify(body), isShowErrMsg);
 | 
				
			||||||
 | 
					        return res;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function Post(url: string, body?: BodyInit, isShowErrMsg = true): Promise<IResponseData>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    try
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        let res = await fetch(url, {
 | 
				
			||||||
 | 
					            method: "POST",
 | 
				
			||||||
 | 
					            mode: "cors",
 | 
				
			||||||
 | 
					            credentials: "include",
 | 
				
			||||||
 | 
					            body,
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        let result = await res.json();
 | 
				
			||||||
 | 
					        return result;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    catch (error)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        // ReportError(`请求url错误:${url}`);
 | 
				
			||||||
 | 
					        return { err_code: RequestStatus.None, err_msg: `请求失败,地址:${url}` };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/assets/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					body {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    font-family: 'Noto Sans SC', "Microsoft YaHei", Arial, sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,251 +0,0 @@
 | 
				
			|||||||
import * as THREE from 'three';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Vector3 } from 'three';
 | 
					 | 
				
			||||||
import { Viewer } from './Viewer';
 | 
					 | 
				
			||||||
import { KeyBoard, MouseKey } from './KeyEnum';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
//相机控制状态
 | 
					 | 
				
			||||||
export enum CameraControlState
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    Null = 0, Pan = 1, Rotate = 2, Scale = 3
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class CameraControls
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    m_TouthTypeList = [CameraControlState.Rotate, CameraControlState.Scale, CameraControlState.Pan];
 | 
					 | 
				
			||||||
    m_domElement: HTMLElement;//HTMLDocument
 | 
					 | 
				
			||||||
    //起始点击
 | 
					 | 
				
			||||||
    m_StartClickPoint: THREE.Vector3 = new THREE.Vector3();
 | 
					 | 
				
			||||||
    m_EndClickPoint: THREE.Vector3 = new THREE.Vector3();
 | 
					 | 
				
			||||||
    m_DollyStart: THREE.Vector2 = new THREE.Vector2();
 | 
					 | 
				
			||||||
    m_DollyEnd: THREE.Vector2 = new THREE.Vector2();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    m_KeyDown = new Map<KeyBoard, boolean>();
 | 
					 | 
				
			||||||
    m_MouseDown = new Map<MouseKey, boolean>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //状态
 | 
					 | 
				
			||||||
    m_State: CameraControlState = CameraControlState.Null;
 | 
					 | 
				
			||||||
    m_Viewer: Viewer;
 | 
					 | 
				
			||||||
    //左键使用旋转
 | 
					 | 
				
			||||||
    m_LeftUseRotate: boolean = true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    constructor(viewer: Viewer)
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this.m_Viewer = viewer;
 | 
					 | 
				
			||||||
        this.m_domElement = viewer.Renderer.domElement.parentElement;
 | 
					 | 
				
			||||||
        this.RegisterEvent();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    RegisterEvent()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if (this.m_domElement)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            this.m_domElement.addEventListener("mousedown", this.onMouseDown, false)
 | 
					 | 
				
			||||||
            this.m_domElement.addEventListener("mousemove", this.onMouseMove, false)
 | 
					 | 
				
			||||||
            this.m_domElement.addEventListener("mouseup", this.onMouseUp, false)
 | 
					 | 
				
			||||||
            window.addEventListener("keydown", this.onKeyDown, false);
 | 
					 | 
				
			||||||
            window.addEventListener("keyup", this.onKeyUp, false);
 | 
					 | 
				
			||||||
            this.m_domElement.addEventListener('wheel', this.onMouseWheel, false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            this.m_domElement.addEventListener('touchstart', this.onTouchStart, false);
 | 
					 | 
				
			||||||
            this.m_domElement.addEventListener('touchend', this.onTouchEnd, false);
 | 
					 | 
				
			||||||
            this.m_domElement.addEventListener('touchmove', this.onTouchMove, false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            window.addEventListener("blur", this.onBlur, false);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 窗体失去焦点时.
 | 
					 | 
				
			||||||
     * @memberof CameraControls
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    onBlur = () =>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this.m_KeyDown.clear();
 | 
					 | 
				
			||||||
        this.m_MouseDown.clear();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //触屏开始事件
 | 
					 | 
				
			||||||
    onTouchStart = (event: TouchEvent) =>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this.m_Viewer.UpdateLockTarget();
 | 
					 | 
				
			||||||
        this.m_StartClickPoint.set(event.touches[0].pageX, event.touches[0].pageY, 0);
 | 
					 | 
				
			||||||
        if (event.touches.length < 4)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            if (event.touches.length == 2)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                var dx = event.touches[0].pageX - event.touches[1].pageX;
 | 
					 | 
				
			||||||
                var dy = event.touches[0].pageY - event.touches[1].pageY;
 | 
					 | 
				
			||||||
                var distance = Math.sqrt(dx * dx + dy * dy);
 | 
					 | 
				
			||||||
                this.m_DollyStart.set(0, distance);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            this.m_State = this.m_TouthTypeList[event.touches.length - 1];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    onTouchEnd = (event: TouchEvent) =>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this.m_State = CameraControlState.Null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    onTouchMove = (event: TouchEvent) =>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        event.preventDefault();
 | 
					 | 
				
			||||||
        event.stopPropagation();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.m_EndClickPoint.set(event.touches[0].pageX, event.touches[0].pageY, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let vec = this.m_EndClickPoint.clone().sub(this.m_StartClickPoint);
 | 
					 | 
				
			||||||
        switch (this.m_State)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            case CameraControlState.Pan:
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    this.m_Viewer.Pan(vec);
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            case CameraControlState.Scale:
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    var dx = event.touches[0].pageX - event.touches[1].pageX;
 | 
					 | 
				
			||||||
                    var dy = event.touches[0].pageY - event.touches[1].pageY;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    var distance = Math.sqrt(dx * dx + dy * dy);
 | 
					 | 
				
			||||||
                    this.m_DollyEnd.set(0, distance);
 | 
					 | 
				
			||||||
                    if (distance > this.m_DollyStart.y)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        this.m_Viewer.Zoom(0.95);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    else
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        this.m_Viewer.Zoom(1.05)
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    this.m_DollyStart.copy(this.m_DollyEnd);
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            case CameraControlState.Rotate:
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    this.m_Viewer.Rotate(vec.multiplyScalar(2));
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        this.m_StartClickPoint.copy(this.m_EndClickPoint);
 | 
					 | 
				
			||||||
        this.m_Viewer.UpdateRender();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    beginRotate()
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this.m_State = CameraControlState.Rotate;
 | 
					 | 
				
			||||||
        this.m_Viewer.UpdateLockTarget();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //最后一次按中键的时间
 | 
					 | 
				
			||||||
    lastMiddleClickTime = 0;
 | 
					 | 
				
			||||||
    //鼠标
 | 
					 | 
				
			||||||
    onMouseDown = (event: MouseEvent) =>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        event.preventDefault();
 | 
					 | 
				
			||||||
        let key: MouseKey = event.button;
 | 
					 | 
				
			||||||
        this.m_MouseDown.set(key, true);
 | 
					 | 
				
			||||||
        this.m_StartClickPoint.set(event.offsetX, event.offsetY, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        switch (key)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            case MouseKey.Left:
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    if (this.m_LeftUseRotate)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        this.beginRotate();
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            case MouseKey.Middle:
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    let curTime = Date.now();
 | 
					 | 
				
			||||||
                    let t = curTime - this.lastMiddleClickTime;
 | 
					 | 
				
			||||||
                    this.lastMiddleClickTime = curTime;
 | 
					 | 
				
			||||||
                    if (t < 350)
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        this.m_Viewer.ZoomAll();
 | 
					 | 
				
			||||||
                        return;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    if (this.m_KeyDown.get(KeyBoard.Control))
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        this.beginRotate();
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    else
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        this.m_State = CameraControlState.Pan;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            case MouseKey.Right:
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    onMouseUp = (event: MouseEvent) =>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        event.preventDefault();
 | 
					 | 
				
			||||||
        this.m_State = CameraControlState.Null;
 | 
					 | 
				
			||||||
        this.m_MouseDown.set(event.button, false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    onMouseMove = (event: MouseEvent) =>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        event.preventDefault();
 | 
					 | 
				
			||||||
        this.m_EndClickPoint.set(event.offsetX, event.offsetY, 0);
 | 
					 | 
				
			||||||
        let changeVec = this.m_EndClickPoint.clone().sub(this.m_StartClickPoint);
 | 
					 | 
				
			||||||
        this.m_StartClickPoint.copy(this.m_EndClickPoint);
 | 
					 | 
				
			||||||
        if (
 | 
					 | 
				
			||||||
            (this.m_LeftUseRotate ||
 | 
					 | 
				
			||||||
                (this.m_KeyDown.get(KeyBoard.Control))
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            && this.m_State == CameraControlState.Rotate
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            this.m_Viewer.Rotate(changeVec);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        switch (this.m_State)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            case CameraControlState.Pan:
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    this.m_Viewer.Pan(changeVec);
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            case CameraControlState.Rotate:
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            case CameraControlState.Scale:
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 鼠标滚轮事件
 | 
					 | 
				
			||||||
     *
 | 
					 | 
				
			||||||
     * @memberof CameraControls
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    onMouseWheel = (event: WheelEvent) =>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        event.stopPropagation();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let pt = new THREE.Vector3(event.offsetX, event.offsetY, 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.m_Viewer.ScreenToWorld(pt, new Vector3().setFromMatrixColumn(this.m_Viewer.m_Camera.Camera.matrixWorld, 2));
 | 
					 | 
				
			||||||
        if (event.deltaY < 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            this.m_Viewer.Zoom(0.6, pt);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else if (event.deltaY > 0)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            this.m_Viewer.Zoom(1.4, pt);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    //按键
 | 
					 | 
				
			||||||
    onKeyDown = (event: KeyboardEvent) =>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this.m_KeyDown.set(event.keyCode, true);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    onKeyUp = (event: KeyboardEvent) =>
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this.m_KeyDown.set(event.keyCode, false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,11 +1,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { AmbientLight, BoxBufferGeometry, BufferGeometry, ConeBufferGeometry, CubeRefractionMapping, CubeTextureLoader, Geometry, Mesh, MeshPhysicalMaterial, Object3D, SphereBufferGeometry, sRGBEncoding, Texture, TorusBufferGeometry, TorusKnotBufferGeometry } from 'three';
 | 
					import { AmbientLight, BoxBufferGeometry, BufferGeometry, Color, ConeBufferGeometry, CubeRefractionMapping, CubeTextureLoader, FrontSide, Geometry, LinearEncoding, Mesh, MeshPhysicalMaterial, Object3D, SphereBufferGeometry, sRGBEncoding, Texture, TextureLoader, TorusBufferGeometry, TorusKnotBufferGeometry, Vector3 } from 'three';
 | 
				
			||||||
import { Singleton } from './Singleton';
 | 
					import { Singleton } from './Singleton';
 | 
				
			||||||
import { Viewer } from './Viewer';
 | 
					import { Viewer } from './Viewer';
 | 
				
			||||||
import { PMREMGenerator3 } from './PMREMGenerator2';
 | 
					import { PMREMGenerator3 } from './PMREMGenerator2';
 | 
				
			||||||
import type { PhysicalMaterialRecord, TextureTableRecord } from 'webcad_ue4_api';
 | 
					import type { PhysicalMaterialRecord, TextureTableRecord } from 'webcad_ue4_api';
 | 
				
			||||||
import { MaterialEditorCamerControl } from './MaterialMouseControl';
 | 
					import { MaterialEditorCameraControl } from './MaterialMouseControl';
 | 
				
			||||||
import { ref } from 'vue';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function textureRenderUpdate(textureRecord:TextureTableRecord){
 | 
					async function textureRenderUpdate(textureRecord:TextureTableRecord){
 | 
				
			||||||
    const texture = textureRecord['texture'] as Texture;
 | 
					    const texture = textureRecord['texture'] as Texture;
 | 
				
			||||||
@@ -29,9 +28,11 @@ async function textureRenderUpdate(textureRecord:TextureTableRecord){
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
export class MaterialEditor extends Singleton
 | 
					export class MaterialEditor extends Singleton
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 | 
					    private _pointerLocked = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Geometrys: Map<string, Geometry | BufferGeometry>;
 | 
					    Geometrys: Map<string, Geometry | BufferGeometry>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    CurGeometryName = ref("球");
 | 
					    CurGeometryName = "球";
 | 
				
			||||||
    Canvas: HTMLElement;
 | 
					    Canvas: HTMLElement;
 | 
				
			||||||
    ShowObject: Object3D;
 | 
					    ShowObject: Object3D;
 | 
				
			||||||
    ShowMesh: Mesh;
 | 
					    ShowMesh: Mesh;
 | 
				
			||||||
@@ -68,14 +69,21 @@ export class MaterialEditor extends Singleton
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            this.Viewer = new Viewer(this.Canvas);
 | 
					            this.Viewer = new Viewer(this.Canvas);
 | 
				
			||||||
            // this.Viewer.PreViewer.Cursor.CursorObject.visible = false;
 | 
					            // this.Viewer.PreViewer.Cursor.CursorObject.visible = false;
 | 
				
			||||||
            // this.Viewer.CameraCtrl.CameraType = CameraType.PerspectiveCamera;
 | 
					 | 
				
			||||||
            // this.Viewer.UsePass = false;
 | 
					            // this.Viewer.UsePass = false;
 | 
				
			||||||
            this.initScene();
 | 
					            this.initScene();
 | 
				
			||||||
            new MaterialEditorCamerControl(this.Viewer);
 | 
					            new MaterialEditorCameraControl(this.Viewer, this.ShowObject.position);
 | 
				
			||||||
 | 
					            this.Viewer.ZoomAll();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // 初始化相机位置到观察物体的正后方
 | 
				
			||||||
 | 
					            // CameraUpdate中的Rotate参数类型并非标准的欧拉角。。。
 | 
				
			||||||
 | 
					            this.Viewer.RotateAround(new Vector3(0, -90 * 8, 0), this.ShowObject.position);
 | 
				
			||||||
 | 
					            this.Viewer.Pan(new Vector3(0, 0, -1500));
 | 
				
			||||||
 | 
					            this.Viewer.UpdateRender();
 | 
				
			||||||
 | 
					            this.Viewer.Fov = 90;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            this.Canvas.appendChild(this.Viewer.Renderer.domElement);
 | 
					            this.Canvas.appendChild(document);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -83,28 +91,18 @@ export class MaterialEditor extends Singleton
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        this.Canvas = canvas;
 | 
					        this.Canvas = canvas;
 | 
				
			||||||
        this.initViewer();
 | 
					        this.initViewer();
 | 
				
			||||||
        this.CurGeometryName.value = "球";
 | 
					        this.CurGeometryName = "球";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    initScene()
 | 
					    initScene()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        let scene = this.Viewer.Scene;
 | 
					        let scene = this.Viewer.Scene;
 | 
				
			||||||
        this.ShowObject = new Object3D();
 | 
					        this.ShowObject = new Object3D();
 | 
				
			||||||
        let geo = this.Geometrys.get(this.CurGeometryName.value);
 | 
					        let geo = this.Geometrys.get(this.CurGeometryName);
 | 
				
			||||||
        this.ShowMesh = new Mesh(geo, this._MeshMaterial);
 | 
					        this.ShowMesh = new Mesh(geo, this._MeshMaterial);
 | 
				
			||||||
        this.ShowMesh.scale.set(1000, 1000, 1000);
 | 
					        this.ShowMesh.scale.set(1000, 1000, 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.ShowObject.add(this.ShowMesh);
 | 
					        this.ShowObject.add(this.ShowMesh);
 | 
				
			||||||
        scene.add(this.ShowObject);
 | 
					        scene.add(this.ShowObject);
 | 
				
			||||||
        // let remove = autorun(() =>
 | 
					 | 
				
			||||||
        // {
 | 
					 | 
				
			||||||
        //     let geo = this.Geometrys.get(this.CurGeometryName.get());
 | 
					 | 
				
			||||||
        //     if (geo)
 | 
					 | 
				
			||||||
        //     {
 | 
					 | 
				
			||||||
        //         this.ShowMesh.geometry = geo;
 | 
					 | 
				
			||||||
        //         this.Viewer.UpdateRender();
 | 
					 | 
				
			||||||
        //     }
 | 
					 | 
				
			||||||
        // });
 | 
					 | 
				
			||||||
        // end(this as MaterialEditor, this.dispose, remove);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        //环境光
 | 
					        //环境光
 | 
				
			||||||
        let ambient = new AmbientLight();
 | 
					        let ambient = new AmbientLight();
 | 
				
			||||||
@@ -119,14 +117,15 @@ export class MaterialEditor extends Singleton
 | 
				
			|||||||
        if (this.metaTexture) return this.metaTexture;
 | 
					        if (this.metaTexture) return this.metaTexture;
 | 
				
			||||||
        if (this.metaPromise) return this.metaPromise;
 | 
					        if (this.metaPromise) return this.metaPromise;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new Promise((res, rej) =>
 | 
					        return new Promise(async (res, rej) =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            let urls = ['right.webp', 'left.webp', 'top.webp', 'bottom.webp', 'front.webp', 'back.webp'];
 | 
					            let urls = ['right.webp', 'left.webp', 'top.webp', 'bottom.webp', 'front.webp', 'back.webp'];
 | 
				
			||||||
            new CubeTextureLoader().setPath('https://cdn.cfcad.cn/t/house/')
 | 
					            new CubeTextureLoader().setPath('./')
 | 
				
			||||||
                .load(urls, (t) =>
 | 
					                .load(urls, (t) =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    t.encoding = sRGBEncoding;
 | 
					                    t.encoding = sRGBEncoding;
 | 
				
			||||||
                    t.mapping = CubeRefractionMapping;
 | 
					                    t.mapping = CubeRefractionMapping;
 | 
				
			||||||
 | 
					                    t.encoding = LinearEncoding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    let pmremGenerator = new PMREMGenerator3(this.Viewer.Renderer);
 | 
					                    let pmremGenerator = new PMREMGenerator3(this.Viewer.Renderer);
 | 
				
			||||||
                    let ldrCubeRenderTarget = pmremGenerator.fromCubemap(t);
 | 
					                    let ldrCubeRenderTarget = pmremGenerator.fromCubemap(t);
 | 
				
			||||||
@@ -135,8 +134,6 @@ export class MaterialEditor extends Singleton
 | 
				
			|||||||
                    res(this.metaTexture);
 | 
					                    res(this.metaTexture);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    this.Viewer.Scene.background = this.metaTexture;
 | 
					                    this.Viewer.Scene.background = this.metaTexture;
 | 
				
			||||||
                    // this.Viewer.Scene.background = new Color(255,0,0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    this.Viewer.UpdateRender();
 | 
					                    this.Viewer.UpdateRender();
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
@@ -151,12 +148,14 @@ export class MaterialEditor extends Singleton
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        this.exrPromise = new Promise<Texture>((res, rej) =>
 | 
					        this.exrPromise = new Promise<Texture>((res, rej) =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            let urls = ['right.webp', 'left.webp', 'top.webp', 'bottom.webp', 'front.webp', 'back.webp'];
 | 
					            let urls = ['right-gray.webp', 'left-gray.webp', 'top-gray.webp', 'bottom-gray.webp', 'front-gray.webp', 'back-gray.webp'];
 | 
				
			||||||
            new CubeTextureLoader().setPath('https://cdn.cfcad.cn/t/house_gray/')
 | 
					            new CubeTextureLoader().setPath('./')
 | 
				
			||||||
                .load(urls, (t) =>
 | 
					                .load(urls, (t) =>
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    t.encoding = sRGBEncoding;
 | 
					                    t.encoding = sRGBEncoding;
 | 
				
			||||||
                    t.mapping = CubeRefractionMapping;
 | 
					                    t.mapping = CubeRefractionMapping;
 | 
				
			||||||
 | 
					                    t.encoding = LinearEncoding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    let pmremGenerator = new PMREMGenerator3(this.Viewer.Renderer);
 | 
					                    let pmremGenerator = new PMREMGenerator3(this.Viewer.Renderer);
 | 
				
			||||||
                    let target = pmremGenerator.fromCubemap(t);
 | 
					                    let target = pmremGenerator.fromCubemap(t);
 | 
				
			||||||
                    this.exrTexture = target.texture;
 | 
					                    this.exrTexture = target.texture;
 | 
				
			||||||
@@ -198,21 +197,6 @@ export class MaterialEditor extends Singleton
 | 
				
			|||||||
    async Update()
 | 
					    async Update()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        let mat = this.ShowMesh.material as MeshPhysicalMaterial;
 | 
					        let mat = this.ShowMesh.material as MeshPhysicalMaterial;
 | 
				
			||||||
        if (this.Material.map && this.Material.map.Object && this.Material.useMap)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            let texture = this.Material.map.Object as TextureTableRecord;
 | 
					 | 
				
			||||||
            await textureRenderUpdate(texture);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (this.Material.bumpMap && this.Material.bumpMap.Object)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            let texture = this.Material.bumpMap.Object as TextureTableRecord;
 | 
					 | 
				
			||||||
            await textureRenderUpdate(texture);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if (this.Material.roughnessMap && this.Material.roughnessMap.Object)
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            let texture = this.Material.roughnessMap.Object as TextureTableRecord;
 | 
					 | 
				
			||||||
            await textureRenderUpdate(texture);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        mat.needsUpdate = true;
 | 
					        mat.needsUpdate = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this._MeshMaterial.copy(this.Material.Material);
 | 
					        this._MeshMaterial.copy(this.Material.Material);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,24 +4,25 @@ import type { Viewer } from './Viewer';
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * 材质编辑器的场景鼠标控制.
 | 
					 * 材质编辑器的场景鼠标控制.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
export class MaterialEditorCamerControl
 | 
					export class MaterialEditorCameraControl {
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    private Viewer: Viewer;
 | 
					    private Viewer: Viewer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //State.
 | 
					    private _movement: Vector3 = new Vector3();
 | 
				
			||||||
    private _MouseIsDown: boolean = false;
 | 
					    private _MouseIsDown: boolean = false;
 | 
				
			||||||
    private _StartPoint: Vector3 = new Vector3();
 | 
					 | 
				
			||||||
    private _EndPoint = new Vector3();
 | 
					 | 
				
			||||||
    private pointId: number;
 | 
					    private pointId: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(view: Viewer)
 | 
					    private _target: Vector3 | null = null;
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this.Viewer = view;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get Target() { return this._target; }
 | 
				
			||||||
 | 
					    set Target(val: Vector3 | null) { this._target = val; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(view: Viewer, target: Vector3 | null = null) {
 | 
				
			||||||
 | 
					        this.Viewer = view;
 | 
				
			||||||
 | 
					        this.Target = target;
 | 
				
			||||||
        this.initMouseControl();
 | 
					        this.initMouseControl();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    initMouseControl()
 | 
					
 | 
				
			||||||
    {
 | 
					    initMouseControl() {
 | 
				
			||||||
        let el = this.Viewer.Renderer.domElement;
 | 
					        let el = this.Viewer.Renderer.domElement;
 | 
				
			||||||
        el.addEventListener("pointerdown", (e) => { this.pointId = e.pointerId; }, false);
 | 
					        el.addEventListener("pointerdown", (e) => { this.pointId = e.pointerId; }, false);
 | 
				
			||||||
        el.addEventListener("mousedown", this.onMouseDown, false);
 | 
					        el.addEventListener("mousedown", this.onMouseDown, false);
 | 
				
			||||||
@@ -29,28 +30,23 @@ export class MaterialEditorCamerControl
 | 
				
			|||||||
        el.addEventListener("mouseup", this.onMouseUp, false);
 | 
					        el.addEventListener("mouseup", this.onMouseUp, false);
 | 
				
			||||||
        el.addEventListener('wheel', this.onMouseWheel, false);
 | 
					        el.addEventListener('wheel', this.onMouseWheel, false);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    onMouseDown = (event: MouseEvent) =>
 | 
					    onMouseDown = (event: MouseEvent) => {
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this.requestPointerLock();
 | 
					        this.requestPointerLock();
 | 
				
			||||||
        this._MouseIsDown = true;
 | 
					        this._MouseIsDown = true;
 | 
				
			||||||
        this._StartPoint.set(event.offsetX, event.offsetY, 0);
 | 
					 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    onMouseUp = (event: MouseEvent) =>
 | 
					    onMouseUp = (event: MouseEvent) => {
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        this._MouseIsDown = false;
 | 
					        this._MouseIsDown = false;
 | 
				
			||||||
        this.exitPointerLock();
 | 
					        this.exitPointerLock();
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    onMouseMove = (event: MouseEvent) =>
 | 
					    onMouseMove = (event: MouseEvent) => {
 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        event.preventDefault();
 | 
					        event.preventDefault();
 | 
				
			||||||
        if (this._MouseIsDown)
 | 
					        if (this._MouseIsDown) {
 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            this._EndPoint.set(event.offsetX, event.offsetY, 0);
 | 
					 | 
				
			||||||
            let changeVec: Vector3 = new Vector3();
 | 
					 | 
				
			||||||
            changeVec.subVectors(this._EndPoint, this._StartPoint);
 | 
					 | 
				
			||||||
            this._StartPoint.copy(this._EndPoint);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.Viewer.Rotate(changeVec);
 | 
					            this._movement.set(event.movementX, event.movementY, 0);
 | 
				
			||||||
 | 
					            if (this.Target) {
 | 
				
			||||||
 | 
					                this.Viewer.RotateAround(this._movement, this.Target);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            else this.Viewer.Rotate(this._movement);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
    onMouseWheel = (event: WheelEvent) =>
 | 
					    onMouseWheel = (event: WheelEvent) =>
 | 
				
			||||||
@@ -65,21 +61,25 @@ export class MaterialEditorCamerControl
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    requestPointerLock()
 | 
					    requestPointerLock() {
 | 
				
			||||||
    {
 | 
					        const dom = this.Viewer.Renderer.domElement;
 | 
				
			||||||
        if (this.Viewer.Renderer.domElement.setPointerCapture)
 | 
					        if (dom.setPointerCapture)
 | 
				
			||||||
            this.Viewer.Renderer.domElement.setPointerCapture(this.pointId);
 | 
					            dom.setPointerCapture(this.pointId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (dom.requestPointerLock)
 | 
				
			||||||
 | 
					            dom.requestPointerLock();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    exitPointerLock()
 | 
					    exitPointerLock() {
 | 
				
			||||||
    {
 | 
					        const dom = this.Viewer.Renderer.domElement;
 | 
				
			||||||
        if (this.Viewer.Renderer.domElement.releasePointerCapture && this.pointId !== undefined)
 | 
					        if (dom.releasePointerCapture && this.pointId !== undefined) {
 | 
				
			||||||
        {
 | 
					            try {
 | 
				
			||||||
            try
 | 
					                dom.releasePointerCapture(this.pointId);
 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                this.Viewer.Renderer.domElement.releasePointerCapture(this.pointId);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (error) { }
 | 
					            catch (error) { }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (document.exitPointerLock)
 | 
				
			||||||
 | 
					            document.exitPointerLock();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										94
									
								
								src/common/MaterialRenderer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					import { AmbientLight, BoxBufferGeometry, Camera, Material, Mesh, MeshPhysicalMaterial, OrthographicCamera, PointLight, Scene, Sprite, SpriteMaterial, WebGLRenderer } from 'three';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class MaterialRenderer
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    sprite: Sprite;
 | 
				
			||||||
 | 
					    sphere: Mesh;
 | 
				
			||||||
 | 
					    scene: Scene;
 | 
				
			||||||
 | 
					    camera: Camera;
 | 
				
			||||||
 | 
					    canvas: HTMLCanvasElement;
 | 
				
			||||||
 | 
					    renderer: WebGLRenderer;
 | 
				
			||||||
 | 
					    constructor()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        //Renderer
 | 
				
			||||||
 | 
					        this.renderer = new WebGLRenderer({ alpha: true, antialias: true });
 | 
				
			||||||
 | 
					        this.renderer.setSize(300, 300);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Canvas
 | 
				
			||||||
 | 
					        this.canvas = this.renderer.domElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Camera
 | 
				
			||||||
 | 
					        this.camera = new OrthographicCamera(-1, 1, 1, -1, -1, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Scene
 | 
				
			||||||
 | 
					        this.scene = new Scene();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Sphere
 | 
				
			||||||
 | 
					        this.sphere = new Mesh(new BoxBufferGeometry(2, 2, 2));
 | 
				
			||||||
 | 
					        this.scene.add(this.sphere);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Sprite
 | 
				
			||||||
 | 
					        this.sprite = new Sprite();
 | 
				
			||||||
 | 
					        this.sprite.scale.set(2, 2, 1);
 | 
				
			||||||
 | 
					        this.scene.add(this.sprite);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Ambient light
 | 
				
			||||||
 | 
					        var ambient = new AmbientLight();
 | 
				
			||||||
 | 
					        this.scene.add(ambient);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Pontual light
 | 
				
			||||||
 | 
					        var point = new PointLight();
 | 
				
			||||||
 | 
					        point.position.set(-0.5, 1, 1.5);
 | 
				
			||||||
 | 
					        this.scene.add(point);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    //Set render size
 | 
				
			||||||
 | 
					    setSize(x: number, y: number)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.renderer.setSize(x, y);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    update(material: Material)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        //Set material
 | 
				
			||||||
 | 
					        if (material instanceof SpriteMaterial)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            this.sphere.visible = false;
 | 
				
			||||||
 | 
					            this.sprite.visible = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.sprite.material = material;
 | 
				
			||||||
 | 
					            this.camera.position.set(0, 0, 0.5);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            this.sprite.visible = false;
 | 
				
			||||||
 | 
					            this.sphere.visible = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            this.sphere.material = material as MeshPhysicalMaterial;
 | 
				
			||||||
 | 
					            this.camera.position.set(0, 0, 1.5);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.camera.updateMatrix();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        //Render
 | 
				
			||||||
 | 
					        this.renderer.render(this.scene, this.camera);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    getBlob(material: Material): Promise<Blob>
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.update(material);
 | 
				
			||||||
 | 
					        return new Promise(res =>
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            this.canvas.toBlob(b => res(b));
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    render(material: Material)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.update(material);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return this.canvas.toDataURL();
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const materialRenderer = new MaterialRenderer();
 | 
				
			||||||
							
								
								
									
										22
									
								
								src/common/MaterialSerializer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					import { CADFiler, Database, DuplicateRecordCloning, PhysicalMaterialRecord } from "webcad_ue4_api";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function MaterialOut(material: PhysicalMaterialRecord): string
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    let db = new Database();
 | 
				
			||||||
 | 
					    debugger;
 | 
				
			||||||
 | 
					    db.WblockCloneObejcts(
 | 
				
			||||||
 | 
					        [material],
 | 
				
			||||||
 | 
					        db.MaterialTable,
 | 
				
			||||||
 | 
					        new Map(),
 | 
				
			||||||
 | 
					        DuplicateRecordCloning.Ignore
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return JSON.stringify(db.FileWrite().Data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function MaterialIn(fileData: Object[]): PhysicalMaterialRecord
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    let f = new CADFiler(fileData);
 | 
				
			||||||
 | 
					    let db = new Database().FileRead(f);
 | 
				
			||||||
 | 
					    db.hm.Enable = false;
 | 
				
			||||||
 | 
					    return db.MaterialTable.Symbols.entries().next().value[1];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,13 +1,11 @@
 | 
				
			|||||||
import { Raycaster, Scene, Vector3, WebGLRenderer, type WebGLRendererParameters } from "three";
 | 
					import { Raycaster, Scene, Vector3, WebGLRenderer, type Vector, type WebGLRendererParameters } from "three";
 | 
				
			||||||
import { cZeroVec, GetBox, GetBoxArr } from "./GeUtils";
 | 
					import { cZeroVec, GetBox, GetBoxArr } from "./GeUtils";
 | 
				
			||||||
import { PlaneExt } from "./PlaneExt";
 | 
					import { PlaneExt } from "./PlaneExt";
 | 
				
			||||||
import { CameraUpdate } from "webcad_ue4_api";
 | 
					import { CameraType, CameraUpdate } from "webcad_ue4_api";
 | 
				
			||||||
import { CameraControls } from "./CameraControls";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class Viewer {
 | 
					export class Viewer {
 | 
				
			||||||
    m_LookTarget: any;
 | 
					    m_LookTarget: any;
 | 
				
			||||||
    m_Camera: CameraUpdate = new CameraUpdate();
 | 
					    m_Camera: CameraUpdate = new CameraUpdate();
 | 
				
			||||||
    CameraCtrl: CameraControls;
 | 
					 | 
				
			||||||
    protected NeedUpdate: boolean = true;
 | 
					    protected NeedUpdate: boolean = true;
 | 
				
			||||||
    Renderer: WebGLRenderer;//渲染器  //暂时只用这个类型
 | 
					    Renderer: WebGLRenderer;//渲染器  //暂时只用这个类型
 | 
				
			||||||
    m_DomEl: HTMLElement;    //画布容器
 | 
					    m_DomEl: HTMLElement;    //画布容器
 | 
				
			||||||
@@ -21,6 +19,15 @@ export class Viewer {
 | 
				
			|||||||
        return this._Scene;
 | 
					        return this._Scene;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    get Fov() {
 | 
				
			||||||
 | 
					        return this.m_Camera.Fov;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    set Fov(val: number) {
 | 
				
			||||||
 | 
					        this.m_Camera.Fov = val;
 | 
				
			||||||
 | 
					        this.NeedUpdate = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    UpdateRender()
 | 
					    UpdateRender()
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        this.NeedUpdate = true;
 | 
					        this.NeedUpdate = true;
 | 
				
			||||||
@@ -37,10 +44,10 @@ export class Viewer {
 | 
				
			|||||||
        this.initRender(canvasContainer);
 | 
					        this.initRender(canvasContainer);
 | 
				
			||||||
        this.OnSize();
 | 
					        this.OnSize();
 | 
				
			||||||
        this.StartRender();
 | 
					        this.StartRender();
 | 
				
			||||||
        this.CameraCtrl = new CameraControls(this);
 | 
					 | 
				
			||||||
        window.addEventListener("resize", () => {
 | 
					        window.addEventListener("resize", () => {
 | 
				
			||||||
            this.OnSize();
 | 
					            this.OnSize();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					        this.InitCamera();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //初始化render
 | 
					    //初始化render
 | 
				
			||||||
@@ -73,6 +80,11 @@ export class Viewer {
 | 
				
			|||||||
        this.OnSize();
 | 
					        this.OnSize();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    InitCamera() {
 | 
				
			||||||
 | 
					        this.m_Camera.CameraType = CameraType.PerspectiveCamera;
 | 
				
			||||||
 | 
					        this.m_Camera.Near = 0.001;
 | 
				
			||||||
 | 
					        this.m_Camera.Camera.far = 1000000000;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    OnSize = (width?: number, height?: number) => {
 | 
					    OnSize = (width?: number, height?: number) => {
 | 
				
			||||||
        this._Width = width ? width : this.m_DomEl.clientWidth;
 | 
					        this._Width = width ? width : this.m_DomEl.clientWidth;
 | 
				
			||||||
        this._Height = height ? height : this.m_DomEl.clientHeight;
 | 
					        this._Height = height ? height : this.m_DomEl.clientHeight;
 | 
				
			||||||
@@ -91,6 +103,7 @@ export class Viewer {
 | 
				
			|||||||
        requestAnimationFrame(this.StartRender);
 | 
					        requestAnimationFrame(this.StartRender);
 | 
				
			||||||
        if (this._Scene != null && this.NeedUpdate) {
 | 
					        if (this._Scene != null && this.NeedUpdate) {
 | 
				
			||||||
            this.Render();
 | 
					            this.Render();
 | 
				
			||||||
 | 
					            this.m_Camera.Update();
 | 
				
			||||||
            this.NeedUpdate = false;
 | 
					            this.NeedUpdate = false;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
@@ -139,6 +152,10 @@ export class Viewer {
 | 
				
			|||||||
        this.m_Camera.Rotate(mouseMove, this.m_LookTarget);
 | 
					        this.m_Camera.Rotate(mouseMove, this.m_LookTarget);
 | 
				
			||||||
        this.NeedUpdate = true;
 | 
					        this.NeedUpdate = true;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    RotateAround(movement: Vector3, center: Vector3) {
 | 
				
			||||||
 | 
					        this.m_Camera.Rotate(movement, center);
 | 
				
			||||||
 | 
					        this.NeedUpdate = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    Pan(mouseMove: Vector3) {
 | 
					    Pan(mouseMove: Vector3) {
 | 
				
			||||||
        this.m_Camera.Pan(mouseMove);
 | 
					        this.m_Camera.Pan(mouseMove);
 | 
				
			||||||
        this.NeedUpdate = true;
 | 
					        this.NeedUpdate = true;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										42
									
								
								src/components/CfFlex.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div class="cf-flex" :style="{
 | 
				
			||||||
 | 
					        alignItems: props.align,
 | 
				
			||||||
 | 
					        justifyContent: props.justify,
 | 
				
			||||||
 | 
					        flexDirection: flexDirection,
 | 
				
			||||||
 | 
					        flexWrap: props.wrap ? 'wrap' : 'nowrap',
 | 
				
			||||||
 | 
					        gap: props.gap
 | 
				
			||||||
 | 
					    }">
 | 
				
			||||||
 | 
					        <slot></slot>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang='ts'>
 | 
				
			||||||
 | 
					import { type Property } from 'csstype';
 | 
				
			||||||
 | 
					import { computed } from 'vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const props = withDefaults(defineProps<{
 | 
				
			||||||
 | 
					    align?: Property.AlignItems;
 | 
				
			||||||
 | 
					    justify?: Property.JustifyContent;
 | 
				
			||||||
 | 
					    vertical?: boolean;
 | 
				
			||||||
 | 
					    wrap?: boolean;
 | 
				
			||||||
 | 
					    gap?: string;
 | 
				
			||||||
 | 
					    reverse?: boolean;
 | 
				
			||||||
 | 
					}>(), {
 | 
				
			||||||
 | 
					    justify: 'normal',
 | 
				
			||||||
 | 
					    vertical: false,
 | 
				
			||||||
 | 
					    wrap: true
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const flexDirection = computed(() =>
 | 
				
			||||||
 | 
					    props.vertical ?
 | 
				
			||||||
 | 
					        props.reverse ? 'column-reverse' : 'column'
 | 
				
			||||||
 | 
					        : props.reverse ? 'row-reverse' : 'row')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					.cf-flex
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										293
									
								
								src/components/MaterialAdjuster.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,293 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					    <div vertical class="material-adjuster">
 | 
				
			||||||
 | 
					        <div class="adjust-section">
 | 
				
			||||||
 | 
					            <h3>模型预览</h3>
 | 
				
			||||||
 | 
					            <label>选择模型样式</label>
 | 
				
			||||||
 | 
					            <select v-model="CurrGeometry">
 | 
				
			||||||
 | 
					                <option v-for="name in Geometries" :value="name">{{ name }}</option>
 | 
				
			||||||
 | 
					            </select>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="adjust-section" v-if="debugMode">
 | 
				
			||||||
 | 
					            <h3>纹理选择(DEBUG)</h3>
 | 
				
			||||||
 | 
					            <label>纹理链接</label>
 | 
				
			||||||
 | 
					            <input v-model.trim="textureLink" type="text" placeholder="在此键入纹理图像的URL..." />
 | 
				
			||||||
 | 
					            <button class="btn-primary" @click="scene.ChangeTextureAsync(textureLink)"
 | 
				
			||||||
 | 
					                style="margin-left: 1em;">应用</button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <div class="adjust-section">
 | 
				
			||||||
 | 
					            <h3>材质调节</h3>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <label>金属度</label>
 | 
				
			||||||
 | 
					            <CfFlex gap="1em">
 | 
				
			||||||
 | 
					                <input v-model="model.metallic" type="range" min="0" max="1" step="0.01" />
 | 
				
			||||||
 | 
					                <span>{{ model.metallic }}</span>
 | 
				
			||||||
 | 
					            </CfFlex>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <label>粗糙度</label>
 | 
				
			||||||
 | 
					            <CfFlex gap="1em">
 | 
				
			||||||
 | 
					                <input v-model="model.roughness" type="range" min="0" max="1" step="0.01" />
 | 
				
			||||||
 | 
					                <span>{{ model.roughness }}</span>
 | 
				
			||||||
 | 
					            </CfFlex>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <label>法线强度</label>
 | 
				
			||||||
 | 
					            <CfFlex gap="1em">
 | 
				
			||||||
 | 
					                <input v-model="model.normalScale" type="range" min="0" max="1" step="0.01" />
 | 
				
			||||||
 | 
					                <span>{{ model.normalScale }}</span>
 | 
				
			||||||
 | 
					            </CfFlex>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <label>高光</label>
 | 
				
			||||||
 | 
					            <CfFlex gap="1em">
 | 
				
			||||||
 | 
					                <input v-model="model.emissiveIntensity" type="range" min="0" max="1" step="0.01" />
 | 
				
			||||||
 | 
					                <span>{{ model.emissiveIntensity }}</span>
 | 
				
			||||||
 | 
					            </CfFlex>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="adjust-section">
 | 
				
			||||||
 | 
					            <h3>纹理调节</h3>
 | 
				
			||||||
 | 
					            <label>启用纹理</label>
 | 
				
			||||||
 | 
					            <input type="checkbox" v-model="enableTexture" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <label>平铺U</label>
 | 
				
			||||||
 | 
					            <select v-model="textureAdjustment.wrapS">
 | 
				
			||||||
 | 
					                <option value="0">镜像平铺</option>
 | 
				
			||||||
 | 
					                <option value="1">平铺</option>
 | 
				
			||||||
 | 
					                <option value="2">展开</option>
 | 
				
			||||||
 | 
					            </select>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <label>平铺V</label>
 | 
				
			||||||
 | 
					            <select v-model="textureAdjustment.wrapT">
 | 
				
			||||||
 | 
					                <option value="0">镜像平铺</option>
 | 
				
			||||||
 | 
					                <option value="1">平铺</option>
 | 
				
			||||||
 | 
					                <option value="2">展开</option>
 | 
				
			||||||
 | 
					            </select>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <label>旋转</label>
 | 
				
			||||||
 | 
					            <input v-model="textureAdjustment.rotation" type="number" step="0.01" min="0" max="359" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <label>偏移</label>
 | 
				
			||||||
 | 
					                <span style="margin-right: 0.5em;">X</span>
 | 
				
			||||||
 | 
					                <input v-model="textureAdjustment.moveX" type="number" step="0.01" style="max-width: 60px;" />
 | 
				
			||||||
 | 
					                 
 | 
				
			||||||
 | 
					                 
 | 
				
			||||||
 | 
					                <span style="margin-right: 0.5em;">Y</span>
 | 
				
			||||||
 | 
					                <input v-model="textureAdjustment.moveY" type="number" step="0.01" style="max-width: 60px;" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <div>
 | 
				
			||||||
 | 
					                <label>尺寸</label>
 | 
				
			||||||
 | 
					                <span style="margin-right: 0.5em;">长</span>
 | 
				
			||||||
 | 
					                <input v-model="textureAdjustment.repeatX" type="number" step="0.01" style="max-width: 60px;" />
 | 
				
			||||||
 | 
					                 
 | 
				
			||||||
 | 
					                 
 | 
				
			||||||
 | 
					                <span style="margin-right: 0.5em;">宽</span>
 | 
				
			||||||
 | 
					                <input v-model="textureAdjustment.repeatY" type="number" step="0.01" style="max-width: 60px;" />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div class="adjust-section">
 | 
				
			||||||
 | 
					            <h3>操作</h3>
 | 
				
			||||||
 | 
					            <fieldset v-if="debugMode" style="margin: 1em 0;">
 | 
				
			||||||
 | 
					                <legend>DEBUG</legend>
 | 
				
			||||||
 | 
					                <label>上传路径ID</label>
 | 
				
			||||||
 | 
					                <input v-model="materialRequest.dirId" type="text" placeholder="材质路径ID" />
 | 
				
			||||||
 | 
					            </fieldset>
 | 
				
			||||||
 | 
					            <label>材质名</label>
 | 
				
			||||||
 | 
					            <input v-model="materialRequest.materialName" type="text" placeholder="材质名" />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            <CfFlex gap="1em">
 | 
				
			||||||
 | 
					                <button class="btn-success" style="min-width: 110px;" @click="HandleUpload">上传</button>
 | 
				
			||||||
 | 
					                <button class="btn-danger" style="min-width: 110px;" @click="HandleCancel">取消</button>
 | 
				
			||||||
 | 
					            </CfFlex>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script setup lang='ts'>
 | 
				
			||||||
 | 
					import { ref, reactive, watch } from "vue"
 | 
				
			||||||
 | 
					import { useScene, type TextureAdjustment } from "../stores/sceneStore";
 | 
				
			||||||
 | 
					import { storeToRefs } from "pinia";
 | 
				
			||||||
 | 
					import CfFlex from "./CfFlex.vue";
 | 
				
			||||||
 | 
					import { DirectoryId } from "../api/Request";
 | 
				
			||||||
 | 
					import { IsNullOrWhitespace } from "../helpers/helper.string";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const scene = useScene();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const debugMode = ref(true);
 | 
				
			||||||
 | 
					const { CurrGeometry, Geometries, Material } = storeToRefs(scene);
 | 
				
			||||||
 | 
					const enableTexture = ref(Material.value.useMap);
 | 
				
			||||||
 | 
					const textureLink = ref('');
 | 
				
			||||||
 | 
					const textureAdjustment = reactive<TextureAdjustment>({
 | 
				
			||||||
 | 
					    wrapS: 0,
 | 
				
			||||||
 | 
					    wrapT: 0,
 | 
				
			||||||
 | 
					    rotation: 0,
 | 
				
			||||||
 | 
					    repeatX: 1,
 | 
				
			||||||
 | 
					    repeatY: 1,
 | 
				
			||||||
 | 
					    moveX: 0,
 | 
				
			||||||
 | 
					    moveY: 0
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					const uploading = ref(false);
 | 
				
			||||||
 | 
					const model = reactive({
 | 
				
			||||||
 | 
					    metallic: Material.value.matalness,
 | 
				
			||||||
 | 
					    roughness: Material.value.roughness,
 | 
				
			||||||
 | 
					    normalScale: Material.value.bumpScale,
 | 
				
			||||||
 | 
					    emissiveIntensity: Material.value.specular
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					const materialRequest = reactive({
 | 
				
			||||||
 | 
					    dirId: DirectoryId.MaterialDir, // 正常来说是2
 | 
				
			||||||
 | 
					    materialName: ''
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(model, async (val) => {
 | 
				
			||||||
 | 
					    await UpdateMaterial();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(enableTexture, async (val) => {
 | 
				
			||||||
 | 
					    await EnableTexture(val);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					watch(textureAdjustment, async (val) => {
 | 
				
			||||||
 | 
					    UpdateTexture();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function UpdateMaterial() {
 | 
				
			||||||
 | 
					    Material.value.matalness = model.metallic;
 | 
				
			||||||
 | 
					    Material.value.roughness = model.roughness;
 | 
				
			||||||
 | 
					    Material.value.bumpScale = model.normalScale;
 | 
				
			||||||
 | 
					    Material.value.specular = model.emissiveIntensity;
 | 
				
			||||||
 | 
					    // Material.value.side = DoubleSide;
 | 
				
			||||||
 | 
					    await scene.UpdateMaterialAsync();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function UpdateTexture() {
 | 
				
			||||||
 | 
					    scene.UpdateTexture(textureAdjustment);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function EnableTexture(enable: boolean) {
 | 
				
			||||||
 | 
					    Material.value.useMap = enable;
 | 
				
			||||||
 | 
					    await scene.UpdateMaterialAsync();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function HandleUpload() {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        if (IsNullOrWhitespace(materialRequest.materialName)) {
 | 
				
			||||||
 | 
					            alert('材质名称不可为空');
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        uploading.value = true;
 | 
				
			||||||
 | 
					        await scene.UploadMaterialAsync(materialRequest);
 | 
				
			||||||
 | 
					        uploading.value = false;
 | 
				
			||||||
 | 
					        alert("上传成功");
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					        console.error(error);
 | 
				
			||||||
 | 
					        alert("上传材质出错,请检查网络连接或联系管理员");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function HandleCancel() {
 | 
				
			||||||
 | 
					    // TODO: 触发取消事件
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					.material-adjuster
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    padding: 1rem;
 | 
				
			||||||
 | 
					    overflow-y: auto;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.adjust-section
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    border-radius: 8px;
 | 
				
			||||||
 | 
					    border: 1px solid #ccc;
 | 
				
			||||||
 | 
					    padding: 1rem;
 | 
				
			||||||
 | 
					    margin-bottom: 1rem;
 | 
				
			||||||
 | 
					    box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
 | 
				
			||||||
 | 
					    background-color: white;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h3
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    margin: 0 0 0.5rem 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					label
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    display: block;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					select
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    width: 220px;
 | 
				
			||||||
 | 
					    padding: 4px;
 | 
				
			||||||
 | 
					    margin: 0.5rem 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input[type="range"]
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    width: 220px;
 | 
				
			||||||
 | 
					    margin: 0.5rem 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input[type="text"]
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    width: 220px;
 | 
				
			||||||
 | 
					    padding: 4px;
 | 
				
			||||||
 | 
					    margin: 0.5rem 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input[type="number"]
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    width: 220px;
 | 
				
			||||||
 | 
					    padding: 4px;
 | 
				
			||||||
 | 
					    margin: 0.5rem 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-success
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    background-color: #4caf50;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    padding: 0.5rem 1rem;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    border-radius: 4px;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-success:hover
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    background-color: #3e8e41;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-danger
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    background-color: #f44336;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    padding: 0.5rem 1rem;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    border-radius: 4px;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-danger:hover
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    background-color: #da190b;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    background-color: #2196f3;
 | 
				
			||||||
 | 
					    color: white;
 | 
				
			||||||
 | 
					    padding: 0.5rem 1rem;
 | 
				
			||||||
 | 
					    border: none;
 | 
				
			||||||
 | 
					    border-radius: 4px;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.btn-primary:hover
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    background-color: #0a7ed2;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -1,41 +1,32 @@
 | 
				
			|||||||
<template>
 | 
					<template>
 | 
				
			||||||
    <div ref="container" style="width: 800px;height: 800px;"></div>
 | 
					    <CfFlex class="material-view">
 | 
				
			||||||
    {{ CurGeometryName }}
 | 
					        <div ref="container" style="width: 100%; height: 100%; flex: 3; box-sizing: border-box;" />
 | 
				
			||||||
    <div v-for="geo in geometries">
 | 
					        <MaterialAdjuster style="flex: 1;overflow-y: auto; width: 100%; height: 100%; box-sizing: border-box;" />
 | 
				
			||||||
        <button @click="changeGeometry(geo[0])">{{ geo[0] }}</button>
 | 
					    </CfFlex>
 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
</template>
 | 
					</template>
 | 
				
			||||||
<script setup lang="ts">
 | 
					<script setup lang="ts">
 | 
				
			||||||
import { onMounted, useTemplateRef } from 'vue';
 | 
					import { onMounted, useTemplateRef } from 'vue';
 | 
				
			||||||
import { MaterialEditor } from '../common/MaterialEditor';
 | 
					import MaterialAdjuster from './MaterialAdjuster.vue';
 | 
				
			||||||
import { PhysicalMaterialRecord } from 'webcad_ue4_api';
 | 
					import { useScene } from '../stores/sceneStore';
 | 
				
			||||||
 | 
					import CfFlex from './CfFlex.vue';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const scene = useScene();
 | 
				
			||||||
const container = useTemplateRef<HTMLElement>('container');
 | 
					const container = useTemplateRef<HTMLElement>('container');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
let editor:MaterialEditor = MaterialEditor.GetInstance();
 | 
					 | 
				
			||||||
const geometries = editor.Geometrys;
 | 
					 | 
				
			||||||
const material = new PhysicalMaterialRecord();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const CurGeometryName = editor.CurGeometryName;
 | 
					 | 
				
			||||||
onMounted(() => {
 | 
					onMounted(() => {
 | 
				
			||||||
    editor.SetViewer(container.value);
 | 
					    scene.Initial(container.value);
 | 
				
			||||||
    editor.setMaterial(material);
 | 
					});
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const view = editor.Viewer;
 | 
					 | 
				
			||||||
    view.OnSize(800, 800);
 | 
					 | 
				
			||||||
    view.ZoomAll();
 | 
					 | 
				
			||||||
    view.Zoom(2.1);
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function changeGeometry(geoName:string) {
 | 
					 | 
				
			||||||
    CurGeometryName.value = geoName;
 | 
					 | 
				
			||||||
    let geo = editor.Geometrys.get(CurGeometryName.value);
 | 
					 | 
				
			||||||
    if (geo) {
 | 
					 | 
				
			||||||
        editor.ShowMesh.geometry = geo;
 | 
					 | 
				
			||||||
        editor.Viewer.UpdateRender();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped>
 | 
				
			||||||
 | 
					.material-view
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100vh;
 | 
				
			||||||
 | 
					    box-sizing: border-box;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    overflow: hidden;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
							
								
								
									
										3
									
								
								src/helpers/MathHelper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					const clamp = (val, min, max) => Math.min(Math.max(val, min), max);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default { clamp };
 | 
				
			||||||
							
								
								
									
										155
									
								
								src/helpers/helper.compression.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,155 @@
 | 
				
			|||||||
 | 
					import * as fflate from 'fflate'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 使用deflate算法压缩文件
 | 
				
			||||||
 | 
					 * @param data 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function Deflate(data: Uint8Array | string): Uint8Array {
 | 
				
			||||||
 | 
					    if (typeof data === 'string')
 | 
				
			||||||
 | 
					        data = StrToU8(data);
 | 
				
			||||||
 | 
					    return fflate.deflateSync(data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 异步地使用deflate算法压缩文件
 | 
				
			||||||
 | 
					 * @param data 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function DeflateAsync(data: Uint8Array | string): Promise<Uint8Array> {
 | 
				
			||||||
 | 
					    if (typeof data === 'string')
 | 
				
			||||||
 | 
					        data = StrToU8(data);
 | 
				
			||||||
 | 
					    return new Promise<Uint8Array>((resolve, reject) => {
 | 
				
			||||||
 | 
					        const termiante = fflate.deflate(data as Uint8Array, (err, result) => {
 | 
				
			||||||
 | 
					            if (err)
 | 
				
			||||||
 | 
					                reject(err.message);
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                resolve(result);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 使用Zlib压缩文件
 | 
				
			||||||
 | 
					 * @param data 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function Zlib(data: Uint8Array | string): Uint8Array {
 | 
				
			||||||
 | 
					    if (typeof data === "string")
 | 
				
			||||||
 | 
					        data = fflate.strToU8(data);
 | 
				
			||||||
 | 
					    return fflate.zlibSync(data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 异步地使用Zlib压缩文件
 | 
				
			||||||
 | 
					 * @param data 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function ZlibAsync(data: Uint8Array | string): Promise<Uint8Array> {
 | 
				
			||||||
 | 
					    if (typeof data === 'string')
 | 
				
			||||||
 | 
					        data = StrToU8(data);
 | 
				
			||||||
 | 
					    return new Promise<Uint8Array>((resolve, reject) => {
 | 
				
			||||||
 | 
					        const terminate = fflate.zlib(data as Uint8Array, (err, result) => {
 | 
				
			||||||
 | 
					            if (err)
 | 
				
			||||||
 | 
					                reject(err.message);
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                resolve(result);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 创建zip文件
 | 
				
			||||||
 | 
					 * @param data Zip格式对象
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function Zip(data: fflate.Zippable): Uint8Array {
 | 
				
			||||||
 | 
					    return fflate.zipSync(data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 异步地创建zip文件
 | 
				
			||||||
 | 
					 * @param data Zip格式对象
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function ZipAsync(data: fflate.Zippable): Promise<Uint8Array> {
 | 
				
			||||||
 | 
					    return new Promise<Uint8Array>((resolve, reject) => {
 | 
				
			||||||
 | 
					        const terminate = fflate.zip(data, (err, result) => {
 | 
				
			||||||
 | 
					            if (err)
 | 
				
			||||||
 | 
					                reject(err.message);
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                resolve(result);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 解压Zip文件
 | 
				
			||||||
 | 
					 * @param data 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function Unzip(data: Uint8Array): fflate.Unzipped {
 | 
				
			||||||
 | 
					    return fflate.unzipSync(data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 异步地解压Zip文件
 | 
				
			||||||
 | 
					 * @param data 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function UnzipAsync(data: Uint8Array): Promise<fflate.Unzipped> {
 | 
				
			||||||
 | 
					    return new Promise<fflate.Unzipped>((resolve, reject) => {
 | 
				
			||||||
 | 
					        const terminate = fflate.unzip(data, (err, result) => {
 | 
				
			||||||
 | 
					            if (err)
 | 
				
			||||||
 | 
					                reject(err.message);
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                resolve(result);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 解压缩文件,支持GZip,Zlib或DEFLATE数据
 | 
				
			||||||
 | 
					 * @param data 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function Decompress(data: Uint8Array): string | undefined {
 | 
				
			||||||
 | 
					    // decompressSync 存在eof问题
 | 
				
			||||||
 | 
					    let stringData: string | undefined = undefined;
 | 
				
			||||||
 | 
					    const utfDecode = new fflate.DecodeUTF8((data) => {
 | 
				
			||||||
 | 
					        stringData = data;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const dcmpStrm = new fflate.Decompress((chunk, final) => {
 | 
				
			||||||
 | 
					        utfDecode.push(chunk, final);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					        dcmpStrm.push(data);
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					        console.log(error)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return stringData;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 异步地解压缩文件,支持GZip,Zlib或DEFLATE数据
 | 
				
			||||||
 | 
					 * @param data 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export async function DecompressAsync(data: Uint8Array, size: number | undefined = undefined): Promise<Uint8Array> {
 | 
				
			||||||
 | 
					    return new Promise<Uint8Array>((resolve, reject) => {
 | 
				
			||||||
 | 
					        const terminate = fflate.decompress(data, { size: size }, (err, result) => {
 | 
				
			||||||
 | 
					            if (err)
 | 
				
			||||||
 | 
					                reject(err.message);
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                resolve(result);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 将字符串转换为8位无符号整型数组
 | 
				
			||||||
 | 
					 * @param str 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function StrToU8(str: string): Uint8Array {
 | 
				
			||||||
 | 
					    return fflate.strToU8(str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 将8位无符号整形数组转换为字符串
 | 
				
			||||||
 | 
					 * @param u8arr 
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					export function U8ToStr(u8arr: Uint8Array): string {
 | 
				
			||||||
 | 
					    return fflate.strFromU8(u8arr);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								src/helpers/helper.imageLoader.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					import { ImageLoader } from "three";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let loader = new ImageLoader();
 | 
				
			||||||
 | 
					export async function LoadImageFromUrl(url: string): Promise<HTMLImageElement>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    return new Promise<HTMLImageElement>(async (res, rej) =>
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!globalThis.document)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            res(undefined);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        loader.load(url,
 | 
				
			||||||
 | 
					            img => res(img), e => { },
 | 
				
			||||||
 | 
					            err => res(undefined)
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										7
									
								
								src/helpers/helper.string.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					export function IsNullOrEmpty(str?: string | null) {
 | 
				
			||||||
 | 
					    return str === null || str === undefined || str === "";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function IsNullOrWhitespace(str?: string | null) {
 | 
				
			||||||
 | 
					    return str === null || str === undefined || str.trim() === "";
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,6 +1,12 @@
 | 
				
			|||||||
import { createApp } from 'vue'
 | 
					import { createApp } from 'vue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import App from './App.vue'
 | 
					import App from './App.vue'
 | 
				
			||||||
 | 
					import './assets/main.css'
 | 
				
			||||||
 | 
					import { createPinia } from 'pinia';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
createApp(App).mount('#app')
 | 
					const pinia = createPinia();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					createApp(App)
 | 
				
			||||||
 | 
					    .use(pinia)
 | 
				
			||||||
 | 
					    .mount('#app')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										150
									
								
								src/stores/sceneStore.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						@@ -0,0 +1,150 @@
 | 
				
			|||||||
 | 
					import { defineStore } from "pinia";
 | 
				
			||||||
 | 
					import { computed, ref, watch } from "vue";
 | 
				
			||||||
 | 
					import { MaterialEditor } from "../common/MaterialEditor";
 | 
				
			||||||
 | 
					import { Database, ObjectId, PhysicalMaterialRecord, TextureTableRecord } from "webcad_ue4_api";
 | 
				
			||||||
 | 
					import { LoadImageFromUrl } from "../helpers/helper.imageLoader";
 | 
				
			||||||
 | 
					import { Texture } from "three";
 | 
				
			||||||
 | 
					import { materialRenderer } from "../common/MaterialRenderer";
 | 
				
			||||||
 | 
					import { ImgsUrl, MaterialUrls } from "../api/Api";
 | 
				
			||||||
 | 
					import { Post, PostJson, RequestStatus } from "../api/Request";
 | 
				
			||||||
 | 
					import { MaterialOut } from "../common/MaterialSerializer";
 | 
				
			||||||
 | 
					import { DeflateAsync } from "../helpers/helper.compression";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useScene = defineStore('scene', () => {
 | 
				
			||||||
 | 
					    let _editor: MaterialEditor;
 | 
				
			||||||
 | 
					    const _currGeometry = ref<string>('球');
 | 
				
			||||||
 | 
					    const _currTexture = ref<Texture>();
 | 
				
			||||||
 | 
					    const CurrGeometry = computed({
 | 
				
			||||||
 | 
					        get: () => _currGeometry.value,
 | 
				
			||||||
 | 
					        set: (val: string) => ChangeGeometry(val)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    const CurrTexture = computed<Texture>(() => _currTexture.value);
 | 
				
			||||||
 | 
					    const Geometries = ref<string[]>([]);
 | 
				
			||||||
 | 
					    const Material = ref<PhysicalMaterialRecord>(new PhysicalMaterialRecord());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function Initial(canvas: HTMLElement) {
 | 
				
			||||||
 | 
					        if (_editor) {
 | 
				
			||||||
 | 
					            console.warn("SceneStore has already been initialized");
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 为Material配置一个ObjectId,否则其无法被序列化
 | 
				
			||||||
 | 
					        Material.value.objectId = new ObjectId(undefined, undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _editor = MaterialEditor.GetInstance();
 | 
				
			||||||
 | 
					        Geometries.value = Array.from(_editor.Geometrys.keys());
 | 
				
			||||||
 | 
					        _currGeometry.value = _editor.CurGeometryName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _editor.SetViewer(canvas);
 | 
				
			||||||
 | 
					        _editor.setMaterial((Material.value) as PhysicalMaterialRecord);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const view = _editor.Viewer;
 | 
				
			||||||
 | 
					        window.onresize = () => {
 | 
				
			||||||
 | 
					            view.OnSize(canvas.clientWidth, canvas.clientHeight);
 | 
				
			||||||
 | 
					            view.UpdateRender();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function ChangeGeometry(geoName: string) {
 | 
				
			||||||
 | 
					        _currGeometry.value = geoName;
 | 
				
			||||||
 | 
					        let geo = _editor.Geometrys.get(_currGeometry.value);
 | 
				
			||||||
 | 
					        if (geo) {
 | 
				
			||||||
 | 
					            _editor.ShowMesh.geometry = geo;
 | 
				
			||||||
 | 
					            _editor.Viewer.UpdateRender();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function Update() {
 | 
				
			||||||
 | 
					        _editor.Viewer.UpdateRender();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async function UpdateMaterialAsync() {
 | 
				
			||||||
 | 
					        await Material.value.Update();
 | 
				
			||||||
 | 
					        await _editor.Update();
 | 
				
			||||||
 | 
					        Update();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async function ChangeTextureAsync(url: string) {
 | 
				
			||||||
 | 
					        const record = new TextureTableRecord();
 | 
				
			||||||
 | 
					        record.objectId = new ObjectId(undefined, record);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const img = await LoadImageFromUrl(url);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        _currTexture.value = record['texture'] as Texture;
 | 
				
			||||||
 | 
					        _currTexture.value.image = img;
 | 
				
			||||||
 | 
					        Material.value.map = record.Id;
 | 
				
			||||||
 | 
					        _currTexture.value.needsUpdate = true;
 | 
				
			||||||
 | 
					        await UpdateMaterialAsync();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    function UpdateTexture(adjustment: TextureAdjustment) {
 | 
				
			||||||
 | 
					        const texture = _currTexture.value;
 | 
				
			||||||
 | 
					        texture.wrapS = adjustment.wrapS;
 | 
				
			||||||
 | 
					        texture.wrapT = adjustment.wrapT;
 | 
				
			||||||
 | 
					        texture.anisotropy = 16;
 | 
				
			||||||
 | 
					        texture.rotation = adjustment.rotation;
 | 
				
			||||||
 | 
					        texture.repeat.set(adjustment.repeatX, adjustment.repeatY);
 | 
				
			||||||
 | 
					        texture.offset.set(adjustment.moveX, adjustment.moveY);
 | 
				
			||||||
 | 
					        texture.needsUpdate = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Update();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    interface UploadMaterialRequest {
 | 
				
			||||||
 | 
					        dirId: string;
 | 
				
			||||||
 | 
					        materialName: string;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    async function UploadMaterialAsync(request: UploadMaterialRequest) {
 | 
				
			||||||
 | 
					        const logoPath = await HandleUpdateLogo();
 | 
				
			||||||
 | 
					        const matJson = MaterialOut(Material.value as PhysicalMaterialRecord);
 | 
				
			||||||
 | 
					        const data = await PostJson(MaterialUrls.create, {
 | 
				
			||||||
 | 
					            dir_id: request,
 | 
				
			||||||
 | 
					            name: request.materialName,
 | 
				
			||||||
 | 
					            logo: logoPath,
 | 
				
			||||||
 | 
					            // jsonString -> Deflate -> BinaryString -> Base64
 | 
				
			||||||
 | 
					            file: btoa(String.fromCharCode(...await DeflateAsync(matJson))),
 | 
				
			||||||
 | 
					            zip_type: 'gzip',
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        if (data.err_code !== RequestStatus.Ok) {
 | 
				
			||||||
 | 
					            throw new Error(data.err_msg);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async function HandleUpdateLogo() {
 | 
				
			||||||
 | 
					        const blob = await materialRenderer.getBlob(Material.value.Material);
 | 
				
			||||||
 | 
					        const file = new File([blob], "blob.png", { type: blob.type });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const formData = new FormData();
 | 
				
			||||||
 | 
					        formData.append("file", file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let data = await Post(ImgsUrl.logo, formData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let logoPath = "";
 | 
				
			||||||
 | 
					        if (data.err_code === RequestStatus.Ok) {
 | 
				
			||||||
 | 
					            logoPath = data.images.path;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return logoPath;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        CurrGeometry,
 | 
				
			||||||
 | 
					        Geometries,
 | 
				
			||||||
 | 
					        Material,
 | 
				
			||||||
 | 
					        Initial,
 | 
				
			||||||
 | 
					        Update,
 | 
				
			||||||
 | 
					        UpdateMaterialAsync,
 | 
				
			||||||
 | 
					        ChangeTextureAsync,
 | 
				
			||||||
 | 
					        UpdateTexture,
 | 
				
			||||||
 | 
					        UploadMaterialAsync,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type TextureAdjustment = {
 | 
				
			||||||
 | 
					    wrapS: number,
 | 
				
			||||||
 | 
					    wrapT: number,
 | 
				
			||||||
 | 
					    rotation: number,
 | 
				
			||||||
 | 
					    repeatX: number,
 | 
				
			||||||
 | 
					    repeatY: number,
 | 
				
			||||||
 | 
					    moveX: number,
 | 
				
			||||||
 | 
					    moveY: number
 | 
				
			||||||
 | 
					}
 | 
				
			||||||