diff --git a/package.json b/package.json index 4c62aeb..f300810 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,21 @@ "preview": "vite preview" }, "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", + "fflate": "^0.8.2", "flatbush": "^3.3.0", - "xaop": "^2.0.0", - "webcad_ue4_api": "http://gitea.cf/cx/webcad-ue4-api/archive/3.20.0.tar.gz" + "js-angusj-clipper": "^1.2.1", + "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": { "@vitejs/plugin-vue": "^5.2.1", "@vue/tsconfig": "^0.7.0", + "csstype": "^3.1.3", "typescript": "~5.7.2", "vite": "^6.2.0", "vue-tsc": "^2.2.4" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c60c76b..3ae74d1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,12 +11,18 @@ importers: '@jscad/modeling': specifier: ^2.11.0 version: 2.12.5 + fflate: + specifier: ^0.8.2 + version: 0.8.2 flatbush: specifier: ^3.3.0 version: 3.3.1 js-angusj-clipper: specifier: ^1.2.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: specifier: ^1.1.0 version: 1.1.0 @@ -39,6 +45,9 @@ importers: '@vue/tsconfig': specifier: ^0.7.0 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: specifier: ~5.7.2 version: 5.7.3 @@ -369,6 +378,15 @@ packages: '@vue/compiler-vue2@2.7.16': 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': resolution: {integrity: sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ==} peerDependencies: @@ -411,9 +429,16 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + birpc@0.2.19: + resolution: {integrity: sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==} + brace-expansion@2.0.1: resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + copy-anything@3.0.5: + resolution: {integrity: sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==} + engines: {node: '>=12.13'} + csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -432,6 +457,9 @@ packages: estree-walker@2.0.2: resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + flatbush@3.3.1: resolution: {integrity: sha512-oKuPbtT+DS2CxH+9Vhbsq8HifmSCuOw+3Cy5zt/vCIrZl5KyengoTHDBLmtpZoBhcwa7/biNjgL1DwdLMJYm1A==} @@ -447,6 +475,13 @@ packages: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} 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: resolution: {integrity: sha512-/qru4QXxN/gBbQjL4WaFl296YSM8kh5XKpNuNqfZhJ4t4Hw3KeLc5ERj3XHAeLi6pBrqeh6o9PFZUpS3QThEEQ==} @@ -457,6 +492,9 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} @@ -468,9 +506,21 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + picocolors@1.1.1: 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: resolution: {integrity: sha512-bxaGcA40sL3d6M4hH72Z4NdLqxpXRsCFk8AITYg6x1rn1Ei3izf00UMLklerBZTO49aPA3CYrIwVulx2Bce2pA==} @@ -478,6 +528,9 @@ packages: resolution: {integrity: sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==} engines: {node: ^10 || ^12 || >=14} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rollup@4.39.0: resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -487,6 +540,14 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 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: resolution: {integrity: sha512-y+bvPYKI0yMNGF2flNsTbqloPiMAL4OKbIbR9QaQLRjNlz15Th+69ZtirU5ZASGXF/vQIvIrv+6OkxdSjiN89Q==} @@ -770,6 +831,24 @@ snapshots: de-indent: 1.0.2 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)': dependencies: '@volar/language-core': 2.4.12 @@ -816,10 +895,16 @@ snapshots: balanced-match@1.0.2: {} + birpc@0.2.19: {} + brace-expansion@2.0.1: dependencies: balanced-match: 1.0.2 + copy-anything@3.0.5: + dependencies: + is-what: 4.1.16 + csstype@3.1.3: {} de-indent@1.0.2: {} @@ -856,6 +941,8 @@ snapshots: estree-walker@2.0.2: {} + fflate@0.8.2: {} + flatbush@3.3.1: dependencies: flatqueue: 1.2.1 @@ -867,6 +954,10 @@ snapshots: he@1.2.0: {} + hookable@5.5.3: {} + + is-what@4.1.16: {} + js-angusj-clipper@1.3.1: {} magic-string@0.30.17: @@ -877,14 +968,25 @@ snapshots: dependencies: brace-expansion: 2.0.1 + mitt@3.0.1: {} + muggle-string@0.4.1: {} nanoid@3.3.11: {} path-browserify@1.0.1: {} + perfect-debounce@1.0.0: {} + 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: dependencies: tinyqueue: 2.0.3 @@ -895,6 +997,8 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + rfdc@1.4.1: {} + rollup@4.39.0: dependencies: '@types/estree': 1.0.7 @@ -923,6 +1027,12 @@ snapshots: 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: {} tinyqueue@2.0.3: {} diff --git a/public/back-gray.webp b/public/back-gray.webp new file mode 100644 index 0000000..a3c9fe7 Binary files /dev/null and b/public/back-gray.webp differ diff --git a/public/back.webp b/public/back.webp new file mode 100644 index 0000000..fdcf90f Binary files /dev/null and b/public/back.webp differ diff --git a/public/bottom-gray.webp b/public/bottom-gray.webp new file mode 100644 index 0000000..5836e26 Binary files /dev/null and b/public/bottom-gray.webp differ diff --git a/public/bottom.webp b/public/bottom.webp new file mode 100644 index 0000000..a9fc3ea Binary files /dev/null and b/public/bottom.webp differ diff --git a/public/front-gray.webp b/public/front-gray.webp new file mode 100644 index 0000000..724a1ca Binary files /dev/null and b/public/front-gray.webp differ diff --git a/public/front.webp b/public/front.webp new file mode 100644 index 0000000..fc43e27 Binary files /dev/null and b/public/front.webp differ diff --git a/public/left-gray.webp b/public/left-gray.webp new file mode 100644 index 0000000..61e1801 Binary files /dev/null and b/public/left-gray.webp differ diff --git a/public/left.webp b/public/left.webp new file mode 100644 index 0000000..eca651d Binary files /dev/null and b/public/left.webp differ diff --git a/public/right-gray.webp b/public/right-gray.webp new file mode 100644 index 0000000..f98219a Binary files /dev/null and b/public/right-gray.webp differ diff --git a/public/right.webp b/public/right.webp new file mode 100644 index 0000000..0c444f1 Binary files /dev/null and b/public/right.webp differ diff --git a/public/texture1.png b/public/texture1.png new file mode 100644 index 0000000..42ae8f0 Binary files /dev/null and b/public/texture1.png differ diff --git a/public/top-gray.webp b/public/top-gray.webp new file mode 100644 index 0000000..ac160c1 Binary files /dev/null and b/public/top-gray.webp differ diff --git a/public/top.webp b/public/top.webp new file mode 100644 index 0000000..16825e3 Binary files /dev/null and b/public/top.webp differ diff --git a/src/App.vue b/src/App.vue index 4b70bc0..cd11a2a 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,6 +1,9 @@ diff --git a/src/api/Api.ts b/src/api/Api.ts new file mode 100644 index 0000000..11cb7a2 --- /dev/null +++ b/src/api/Api.ts @@ -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", +}; \ No newline at end of file diff --git a/src/api/Request.ts b/src/api/Request.ts new file mode 100644 index 0000000..498fa87 --- /dev/null +++ b/src/api/Request.ts @@ -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( + url: string, body: Exclude, + 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 +{ + 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}` }; + } +} diff --git a/src/assets/main.css b/src/assets/main.css new file mode 100644 index 0000000..35ab72c --- /dev/null +++ b/src/assets/main.css @@ -0,0 +1,10 @@ +body { + margin: 0; + padding: 0; + font-family: 'Noto Sans SC', "Microsoft YaHei", Arial, sans-serif; +} + +html { + margin: 0; + padding: 0; +} \ No newline at end of file diff --git a/src/common/CameraControls.ts b/src/common/CameraControls.ts deleted file mode 100644 index 88588ff..0000000 --- a/src/common/CameraControls.ts +++ /dev/null @@ -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(); - m_MouseDown = new Map(); - - //状态 - 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); - } -} diff --git a/src/common/MaterialEditor.ts b/src/common/MaterialEditor.ts index c511a95..a15aa3a 100644 --- a/src/common/MaterialEditor.ts +++ b/src/common/MaterialEditor.ts @@ -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 { Viewer } from './Viewer'; import { PMREMGenerator3 } from './PMREMGenerator2'; import type { PhysicalMaterialRecord, TextureTableRecord } from 'webcad_ue4_api'; -import { MaterialEditorCamerControl } from './MaterialMouseControl'; -import { ref } from 'vue'; +import { MaterialEditorCameraControl } from './MaterialMouseControl'; async function textureRenderUpdate(textureRecord:TextureTableRecord){ const texture = textureRecord['texture'] as Texture; @@ -29,9 +28,11 @@ async function textureRenderUpdate(textureRecord:TextureTableRecord){ */ export class MaterialEditor extends Singleton { + private _pointerLocked = false; + Geometrys: Map; - CurGeometryName = ref("球"); + CurGeometryName = "球"; Canvas: HTMLElement; ShowObject: Object3D; ShowMesh: Mesh; @@ -68,14 +69,21 @@ export class MaterialEditor extends Singleton { this.Viewer = new Viewer(this.Canvas); // this.Viewer.PreViewer.Cursor.CursorObject.visible = false; - // this.Viewer.CameraCtrl.CameraType = CameraType.PerspectiveCamera; // this.Viewer.UsePass = false; 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 { - this.Canvas.appendChild(this.Viewer.Renderer.domElement); + this.Canvas.appendChild(document); } } @@ -83,28 +91,18 @@ export class MaterialEditor extends Singleton { this.Canvas = canvas; this.initViewer(); - this.CurGeometryName.value = "球"; + this.CurGeometryName = "球"; } initScene() { let scene = this.Viewer.Scene; 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.scale.set(1000, 1000, 1000); this.ShowObject.add(this.ShowMesh); 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(); @@ -119,14 +117,15 @@ export class MaterialEditor extends Singleton if (this.metaTexture) return this.metaTexture; 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']; - new CubeTextureLoader().setPath('https://cdn.cfcad.cn/t/house/') + new CubeTextureLoader().setPath('./') .load(urls, (t) => { t.encoding = sRGBEncoding; t.mapping = CubeRefractionMapping; + t.encoding = LinearEncoding; let pmremGenerator = new PMREMGenerator3(this.Viewer.Renderer); let ldrCubeRenderTarget = pmremGenerator.fromCubemap(t); @@ -135,8 +134,6 @@ export class MaterialEditor extends Singleton res(this.metaTexture); this.Viewer.Scene.background = this.metaTexture; - // this.Viewer.Scene.background = new Color(255,0,0); - this.Viewer.UpdateRender(); }); }); @@ -151,12 +148,14 @@ export class MaterialEditor extends Singleton this.exrPromise = new Promise((res, rej) => { - let urls = ['right.webp', 'left.webp', 'top.webp', 'bottom.webp', 'front.webp', 'back.webp']; - new CubeTextureLoader().setPath('https://cdn.cfcad.cn/t/house_gray/') + let urls = ['right-gray.webp', 'left-gray.webp', 'top-gray.webp', 'bottom-gray.webp', 'front-gray.webp', 'back-gray.webp']; + new CubeTextureLoader().setPath('./') .load(urls, (t) => { t.encoding = sRGBEncoding; t.mapping = CubeRefractionMapping; + t.encoding = LinearEncoding; + let pmremGenerator = new PMREMGenerator3(this.Viewer.Renderer); let target = pmremGenerator.fromCubemap(t); this.exrTexture = target.texture; @@ -198,21 +197,6 @@ export class MaterialEditor extends Singleton async Update() { 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; this._MeshMaterial.copy(this.Material.Material); diff --git a/src/common/MaterialMouseControl.ts b/src/common/MaterialMouseControl.ts index 002805e..59e34ca 100644 --- a/src/common/MaterialMouseControl.ts +++ b/src/common/MaterialMouseControl.ts @@ -4,24 +4,25 @@ import type { Viewer } from './Viewer'; /** * 材质编辑器的场景鼠标控制. */ -export class MaterialEditorCamerControl -{ +export class MaterialEditorCameraControl { private Viewer: Viewer; - //State. + private _movement: Vector3 = new Vector3(); private _MouseIsDown: boolean = false; - private _StartPoint: Vector3 = new Vector3(); - private _EndPoint = new Vector3(); private pointId: number; - constructor(view: Viewer) - { - this.Viewer = view; + private _target: Vector3 | null = null; + 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(); } - initMouseControl() - { + + initMouseControl() { let el = this.Viewer.Renderer.domElement; el.addEventListener("pointerdown", (e) => { this.pointId = e.pointerId; }, false); el.addEventListener("mousedown", this.onMouseDown, false); @@ -29,28 +30,23 @@ export class MaterialEditorCamerControl el.addEventListener("mouseup", this.onMouseUp, false); el.addEventListener('wheel', this.onMouseWheel, false); } - onMouseDown = (event: MouseEvent) => - { + onMouseDown = (event: MouseEvent) => { this.requestPointerLock(); this._MouseIsDown = true; - this._StartPoint.set(event.offsetX, event.offsetY, 0); }; - onMouseUp = (event: MouseEvent) => - { + onMouseUp = (event: MouseEvent) => { this._MouseIsDown = false; this.exitPointerLock(); }; - onMouseMove = (event: MouseEvent) => - { + onMouseMove = (event: MouseEvent) => { event.preventDefault(); - 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); + if (this._MouseIsDown) { - 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) => @@ -65,21 +61,25 @@ export class MaterialEditorCamerControl } }; - requestPointerLock() - { - if (this.Viewer.Renderer.domElement.setPointerCapture) - this.Viewer.Renderer.domElement.setPointerCapture(this.pointId); + requestPointerLock() { + const dom = this.Viewer.Renderer.domElement; + if (dom.setPointerCapture) + dom.setPointerCapture(this.pointId); + + if (dom.requestPointerLock) + dom.requestPointerLock(); } - exitPointerLock() - { - if (this.Viewer.Renderer.domElement.releasePointerCapture && this.pointId !== undefined) - { - try - { - this.Viewer.Renderer.domElement.releasePointerCapture(this.pointId); + exitPointerLock() { + const dom = this.Viewer.Renderer.domElement; + if (dom.releasePointerCapture && this.pointId !== undefined) { + try { + dom.releasePointerCapture(this.pointId); } catch (error) { } } + + if (document.exitPointerLock) + document.exitPointerLock(); } } diff --git a/src/common/MaterialRenderer.ts b/src/common/MaterialRenderer.ts new file mode 100644 index 0000000..8758456 --- /dev/null +++ b/src/common/MaterialRenderer.ts @@ -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 + { + 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(); \ No newline at end of file diff --git a/src/common/MaterialSerializer.ts b/src/common/MaterialSerializer.ts new file mode 100644 index 0000000..938344d --- /dev/null +++ b/src/common/MaterialSerializer.ts @@ -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]; +} \ No newline at end of file diff --git a/src/common/Viewer.ts b/src/common/Viewer.ts index 8810967..d7de98a 100644 --- a/src/common/Viewer.ts +++ b/src/common/Viewer.ts @@ -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 { PlaneExt } from "./PlaneExt"; -import { CameraUpdate } from "webcad_ue4_api"; -import { CameraControls } from "./CameraControls"; +import { CameraType, CameraUpdate } from "webcad_ue4_api"; export class Viewer { m_LookTarget: any; m_Camera: CameraUpdate = new CameraUpdate(); - CameraCtrl: CameraControls; protected NeedUpdate: boolean = true; Renderer: WebGLRenderer;//渲染器 //暂时只用这个类型 m_DomEl: HTMLElement; //画布容器 @@ -21,6 +19,15 @@ export class Viewer { return this._Scene; } + get Fov() { + return this.m_Camera.Fov; + } + + set Fov(val: number) { + this.m_Camera.Fov = val; + this.NeedUpdate = true; + } + UpdateRender() { this.NeedUpdate = true; @@ -37,10 +44,10 @@ export class Viewer { this.initRender(canvasContainer); this.OnSize(); this.StartRender(); - this.CameraCtrl = new CameraControls(this); window.addEventListener("resize", () => { this.OnSize(); }); + this.InitCamera(); } //初始化render @@ -73,6 +80,11 @@ export class Viewer { 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) => { this._Width = width ? width : this.m_DomEl.clientWidth; this._Height = height ? height : this.m_DomEl.clientHeight; @@ -91,6 +103,7 @@ export class Viewer { requestAnimationFrame(this.StartRender); if (this._Scene != null && this.NeedUpdate) { this.Render(); + this.m_Camera.Update(); this.NeedUpdate = false; } }; @@ -139,6 +152,10 @@ export class Viewer { this.m_Camera.Rotate(mouseMove, this.m_LookTarget); this.NeedUpdate = true; } + RotateAround(movement: Vector3, center: Vector3) { + this.m_Camera.Rotate(movement, center); + this.NeedUpdate = true; + } Pan(mouseMove: Vector3) { this.m_Camera.Pan(mouseMove); this.NeedUpdate = true; diff --git a/src/components/CfFlex.vue b/src/components/CfFlex.vue new file mode 100644 index 0000000..6e3327d --- /dev/null +++ b/src/components/CfFlex.vue @@ -0,0 +1,42 @@ + + + + + + + + + diff --git a/src/components/MaterialAdjuster.vue b/src/components/MaterialAdjuster.vue new file mode 100644 index 0000000..5082d3c --- /dev/null +++ b/src/components/MaterialAdjuster.vue @@ -0,0 +1,293 @@ + + + + 模型预览 + 选择模型样式 + + {{ name }} + + + + 纹理选择(DEBUG) + 纹理链接 + + 应用 + + + 材质调节 + + 金属度 + + + {{ model.metallic }} + + + 粗糙度 + + + {{ model.roughness }} + + + 法线强度 + + + {{ model.normalScale }} + + + 高光 + + + {{ model.emissiveIntensity }} + + + + + 纹理调节 + 启用纹理 + + + 平铺U + + 镜像平铺 + 平铺 + 展开 + + + 平铺V + + 镜像平铺 + 平铺 + 展开 + + + 旋转 + + + + 偏移 + X + +   +   + Y + + + + + 尺寸 + 长 + +   +   + 宽 + + + + + + + 操作 + + DEBUG + 上传路径ID + + + 材质名 + + + + 上传 + 取消 + + + + + + + + \ No newline at end of file diff --git a/src/components/MaterialView.vue b/src/components/MaterialView.vue index e2759e7..9bf2fa3 100644 --- a/src/components/MaterialView.vue +++ b/src/components/MaterialView.vue @@ -1,41 +1,32 @@ - - {{ CurGeometryName }} - - {{ geo[0] }} - + + + + -function changeGeometry(geoName:string) { - CurGeometryName.value = geoName; - let geo = editor.Geometrys.get(CurGeometryName.value); - if (geo) { - editor.ShowMesh.geometry = geo; - editor.Viewer.UpdateRender(); - } + \ No newline at end of file diff --git a/src/helpers/MathHelper.ts b/src/helpers/MathHelper.ts new file mode 100644 index 0000000..229b0b8 --- /dev/null +++ b/src/helpers/MathHelper.ts @@ -0,0 +1,3 @@ +const clamp = (val, min, max) => Math.min(Math.max(val, min), max); + +export default { clamp }; \ No newline at end of file diff --git a/src/helpers/helper.compression.ts b/src/helpers/helper.compression.ts new file mode 100644 index 0000000..3f4612c --- /dev/null +++ b/src/helpers/helper.compression.ts @@ -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 { + if (typeof data === 'string') + data = StrToU8(data); + return new Promise((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 { + if (typeof data === 'string') + data = StrToU8(data); + return new Promise((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 { + return new Promise((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 { + return new Promise((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 { + return new Promise((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); +} diff --git a/src/helpers/helper.imageLoader.ts b/src/helpers/helper.imageLoader.ts new file mode 100644 index 0000000..646db33 --- /dev/null +++ b/src/helpers/helper.imageLoader.ts @@ -0,0 +1,19 @@ +import { ImageLoader } from "three"; + +let loader = new ImageLoader(); +export async function LoadImageFromUrl(url: string): Promise +{ + return new Promise(async (res, rej) => + { + if (!globalThis.document) + { + res(undefined); + return; + }; + + loader.load(url, + img => res(img), e => { }, + err => res(undefined) + ); + }); +} \ No newline at end of file diff --git a/src/helpers/helper.string.ts b/src/helpers/helper.string.ts new file mode 100644 index 0000000..132a97d --- /dev/null +++ b/src/helpers/helper.string.ts @@ -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() === ""; +} diff --git a/src/main.ts b/src/main.ts index bf50ae6..345943b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,12 @@ import { createApp } from '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') diff --git a/src/stores/sceneStore.ts b/src/stores/sceneStore.ts new file mode 100644 index 0000000..290774f --- /dev/null +++ b/src/stores/sceneStore.ts @@ -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('球'); + const _currTexture = ref(); + const CurrGeometry = computed({ + get: () => _currGeometry.value, + set: (val: string) => ChangeGeometry(val) + }) + const CurrTexture = computed(() => _currTexture.value); + const Geometries = ref([]); + const Material = ref(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 +} \ No newline at end of file