mirror of
https://github.com/exogen/t2-model-skinner.git
synced 2026-01-19 19:24:44 +00:00
Add support for animated textures
This commit is contained in:
parent
12295fe980
commit
dd55f3b728
|
|
@ -47,13 +47,11 @@ export async function getSkinConfig() {
|
|||
models.map((name) => globby(`./public/textures/*.${name}.png`))
|
||||
),
|
||||
Promise.all(
|
||||
models.map((name) =>
|
||||
globby(path.join(T2_SKINS_PATH, `docs/skins/*.${name}.png`))
|
||||
)
|
||||
models.map((name) => globby(`${T2_SKINS_PATH}/docs/skins/*.${name}.png`))
|
||||
),
|
||||
Promise.all(
|
||||
weaponModels.map((name) =>
|
||||
globby(path.join(T2_SKINS_PATH, `docs/skins/*/weapon_${name}.png`))
|
||||
globby(`${T2_SKINS_PATH}/docs/skins/*/weapon_${name}.png`)
|
||||
)
|
||||
),
|
||||
]);
|
||||
|
|
@ -240,6 +238,8 @@ export async function getSkinConfig() {
|
|||
alphaMode: "BLEND",
|
||||
metallicFactor: 0,
|
||||
roughnessFactor: 1,
|
||||
frameCount: 6,
|
||||
frameTimings: [21, 1, 1, 1, 1, 1],
|
||||
},
|
||||
],
|
||||
chaingun: [{ label: "Chaingun", name: "weapon_chaingun" }],
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
|
@ -1,2 +0,0 @@
|
|||
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[445],{9583:function(t,a,c){c.d(a,{AMf:function(){return FaTrashAlt},D5B:function(){return FaUnlock},hJX:function(){return FaGithub},kUi:function(){return FaLock}});var n=c(8357);function FaGithub(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 496 512"},child:[{tag:"path",attr:{d:"M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"}}]})(t)}function FaLock(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 448 512"},child:[{tag:"path",attr:{d:"M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z"}}]})(t)}function FaTrashAlt(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 448 512"},child:[{tag:"path",attr:{d:"M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"}}]})(t)}function FaUnlock(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 448 512"},child:[{tag:"path",attr:{d:"M400 256H152V152.9c0-39.6 31.7-72.5 71.3-72.9 40-.4 72.7 32.1 72.7 72v16c0 13.3 10.7 24 24 24h32c13.3 0 24-10.7 24-24v-16C376 68 307.5-.3 223.5 0 139.5.3 72 69.5 72 153.5V256H48c-26.5 0-48 21.5-48 48v160c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48z"}}]})(t)}}}]);
|
||||
//# sourceMappingURL=1bfc9850-93cf1d387cb4594a.js.map
|
||||
File diff suppressed because one or more lines are too long
2
docs/_next/static/chunks/1bfc9850-b4ceccea4b74407c.js
Normal file
2
docs/_next/static/chunks/1bfc9850-b4ceccea4b74407c.js
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[445],{9583:function(t,a,c){c.d(a,{AMf:function(){return FaTrashAlt},D5B:function(){return FaUnlock},Dli:function(){return FaChevronRight},bUI:function(){return FaChevronLeft},hJX:function(){return FaGithub},kUi:function(){return FaLock}});var n=c(8357);function FaGithub(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 496 512"},child:[{tag:"path",attr:{d:"M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"}}]})(t)}function FaChevronLeft(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 320 512"},child:[{tag:"path",attr:{d:"M34.52 239.03L228.87 44.69c9.37-9.37 24.57-9.37 33.94 0l22.67 22.67c9.36 9.36 9.37 24.52.04 33.9L131.49 256l154.02 154.75c9.34 9.38 9.32 24.54-.04 33.9l-22.67 22.67c-9.37 9.37-24.57 9.37-33.94 0L34.52 272.97c-9.37-9.37-9.37-24.57 0-33.94z"}}]})(t)}function FaChevronRight(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 320 512"},child:[{tag:"path",attr:{d:"M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"}}]})(t)}function FaLock(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 448 512"},child:[{tag:"path",attr:{d:"M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z"}}]})(t)}function FaTrashAlt(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 448 512"},child:[{tag:"path",attr:{d:"M32 464a48 48 0 0 0 48 48h288a48 48 0 0 0 48-48V128H32zm272-256a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zm-96 0a16 16 0 0 1 32 0v224a16 16 0 0 1-32 0zM432 32H312l-9.4-18.7A24 24 0 0 0 281.1 0H166.8a23.72 23.72 0 0 0-21.4 13.3L136 32H16A16 16 0 0 0 0 48v32a16 16 0 0 0 16 16h416a16 16 0 0 0 16-16V48a16 16 0 0 0-16-16z"}}]})(t)}function FaUnlock(t){return(0,n.w_)({tag:"svg",attr:{viewBox:"0 0 448 512"},child:[{tag:"path",attr:{d:"M400 256H152V152.9c0-39.6 31.7-72.5 71.3-72.9 40-.4 72.7 32.1 72.7 72v16c0 13.3 10.7 24 24 24h32c13.3 0 24-10.7 24-24v-16C376 68 307.5-.3 223.5 0 139.5.3 72 69.5 72 153.5V256H48c-26.5 0-48 21.5-48 48v160c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V304c0-26.5-21.5-48-48-48z"}}]})(t)}}}]);
|
||||
//# sourceMappingURL=1bfc9850-b4ceccea4b74407c.js.map
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"static/chunks/767.5a1b83173dac696e.js","mappings":"+QAGO,SAASA,cACdC,CAAiD,EAEjD,IAAMC,EAAM,GAAIC,CAAAA,GAAAA,EAChB,IAAK,IAAMC,KAAQH,EACjBC,EAAIE,IAAI,CAAC,kBAA4BC,MAAA,CAAVD,EAAKE,IAAI,EAAIF,EAAKG,IAAI,EAEnD,OAAOL,CACT,CAEO,eAAeM,YAAYN,CAAU,CAAEI,CAAY,EACxD,IAAMG,EAAO,MAAMP,EAAIQ,aAAa,CAAC,CAAEC,KAAM,MAAO,GACpDC,CAAAA,EAAAA,EAAAA,MAAAA,EAAOH,EAAMH,EACf,CAEO,SAASO,YAAYC,CAAgB,CAAER,CAAY,EACxDM,CAAAA,EAAAA,EAAAA,MAAAA,EAAOE,EAAUR,EACnB","sources":["webpack://_N_E/./src/exportUtils.ts","webpack://_N_E/<anon>"],"sourcesContent":["import JSZip from \"jszip\";\nimport { saveAs } from \"file-saver\";\n\nexport function createZipFile(\n files: Array<{ name: string; data: ArrayBuffer }>\n) {\n const zip = new JSZip();\n for (const file of files) {\n zip.file(`textures/skins/${file.name}`, file.data);\n }\n return zip;\n}\n\nexport async function saveZipFile(zip: JSZip, name: string) {\n const blob = await zip.generateAsync({ type: \"blob\" });\n saveAs(blob, name);\n}\n\nexport function savePngFile(imageUrl: string, name: string) {\n saveAs(imageUrl, name);\n}\n"],"names":["createZipFile","files","zip","JSZip","file","concat","name","data","saveZipFile","blob","generateAsync","type","saveAs","savePngFile","imageUrl"],"sourceRoot":""}
|
||||
{"version":3,"file":"static/chunks/767.5a1b83173dac696e.js","mappings":"+QAGO,SAASA,cACdC,CAAiD,EAEjD,IAAMC,EAAM,GAAIC,CAAAA,GAAAA,EAChB,IAAK,IAAMC,KAAQH,EACjBC,EAAIE,IAAI,CAAC,kBAA4BC,MAAA,CAAVD,EAAKE,IAAI,EAAIF,EAAKG,IAAI,EAEnD,OAAOL,CACT,CAEO,eAAeM,YAAYN,CAAU,CAAEI,CAAY,EACxD,IAAMG,EAAO,MAAMP,EAAIQ,aAAa,CAAC,CAAEC,KAAM,MAAO,GACpDC,CAAAA,EAAAA,EAAAA,MAAAA,EAAOH,EAAMH,EACf,CAEO,SAASO,YAAYC,CAAgB,CAAER,CAAY,EACxDM,CAAAA,EAAAA,EAAAA,MAAAA,EAAOE,EAAUR,EACnB","sources":["webpack://_N_E/./src/exportUtils.ts","webpack://_N_E/<anon>"],"sourcesContent":["import JSZip from \"jszip\";\r\nimport { saveAs } from \"file-saver\";\r\n\r\nexport function createZipFile(\r\n files: Array<{ name: string; data: ArrayBuffer }>\r\n) {\r\n const zip = new JSZip();\r\n for (const file of files) {\r\n zip.file(`textures/skins/${file.name}`, file.data);\r\n }\r\n return zip;\r\n}\r\n\r\nexport async function saveZipFile(zip: JSZip, name: string) {\r\n const blob = await zip.generateAsync({ type: \"blob\" });\r\n saveAs(blob, name);\r\n}\r\n\r\nexport function savePngFile(imageUrl: string, name: string) {\r\n saveAs(imageUrl, name);\r\n}\r\n"],"names":["createZipFile","files","zip","JSZip","file","concat","name","data","saveZipFile","blob","generateAsync","type","saveAs","savePngFile","imageUrl"],"sourceRoot":""}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
{"version":3,"file":"static/chunks/pages/_app-3fa88258652f2cb2.js","mappings":"oFAAAA,EAAAC,OAAA,CAAAC,EAAA,4BCCA,CAAAC,OAAAC,QAAA,CAAAD,OAAAC,QAAA,MAAAC,IAAA,EACA,QACA,WACA,OAAeH,EAAQ,IACvB,EACA,kFCFAI,EAAA,QAAeC","sources":["webpack://_N_E/./node_modules/next/app.js","webpack://_N_E/?b089","webpack://_N_E/./src/pages/_app.ts","webpack://_N_E/./node_modules/rc-slider/assets/index.css","webpack://_N_E/./src/styles/global.css","webpack://_N_E/<anon>"],"sourcesContent":["module.exports = require('./dist/pages/_app')\n","\n (window.__NEXT_P = window.__NEXT_P || []).push([\n \"/_app\",\n function () {\n return require(\"private-next-pages/_app.ts\");\n }\n ]);\n if(module.hot) {\n module.hot.dispose(function () {\n window.__NEXT_P.push([\"/_app\"])\n });\n }\n ","import App from \"next/app\";\nimport \"../styles/global.css\";\nimport \"rc-slider/assets/index.css\";\n\nexport default App;\n","// extracted by mini-css-extract-plugin","// extracted by mini-css-extract-plugin"],"names":["module","exports","__webpack_require__","window","__NEXT_P","push","__webpack_exports__","App"],"sourceRoot":""}
|
||||
{"version":3,"file":"static/chunks/pages/_app-3fa88258652f2cb2.js","mappings":"oFAAAA,EAAAC,OAAA,CAAAC,EAAA,4BCCA,CAAAC,OAAAC,QAAA,CAAAD,OAAAC,QAAA,MAAAC,IAAA,EACA,QACA,WACA,OAAeH,EAAQ,IACvB,EACA,kFCFAI,EAAA,QAAeC","sources":["webpack://_N_E/./node_modules/next/app.js","webpack://_N_E/?b089","webpack://_N_E/./src/pages/_app.ts","webpack://_N_E/./node_modules/rc-slider/assets/index.css","webpack://_N_E/./src/styles/global.css","webpack://_N_E/<anon>"],"sourcesContent":["module.exports = require('./dist/pages/_app')\n","\n (window.__NEXT_P = window.__NEXT_P || []).push([\n \"/_app\",\n function () {\n return require(\"private-next-pages/_app.ts\");\n }\n ]);\n if(module.hot) {\n module.hot.dispose(function () {\n window.__NEXT_P.push([\"/_app\"])\n });\n }\n ","import App from \"next/app\";\r\nimport \"../styles/global.css\";\r\nimport \"rc-slider/assets/index.css\";\r\n\r\nexport default App;\r\n","// extracted by mini-css-extract-plugin","// extracted by mini-css-extract-plugin"],"names":["module","exports","__webpack_require__","window","__NEXT_P","push","__webpack_exports__","App"],"sourceRoot":""}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
docs/_next/static/chunks/pages/index-e955a68f1762f24b.js
Normal file
2
docs/_next/static/chunks/pages/index-e955a68f1762f24b.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,2 +1,2 @@
|
|||
!function(){"use strict";var e,r,_,t,n,i,u,c={},o={};function __webpack_require__(e){var r=o[e];if(void 0!==r)return r.exports;var _=o[e]={exports:{}},t=!0;try{c[e].call(_.exports,_,_.exports,__webpack_require__),t=!1}finally{t&&delete o[e]}return _.exports}__webpack_require__.m=c,e=[],__webpack_require__.O=function(r,_,t,n){if(_){n=n||0;for(var i=e.length;i>0&&e[i-1][2]>n;i--)e[i]=e[i-1];e[i]=[_,t,n];return}for(var u=1/0,i=0;i<e.length;i++){for(var _=e[i][0],t=e[i][1],n=e[i][2],c=!0,o=0;o<_.length;o++)u>=n&&Object.keys(__webpack_require__.O).every(function(e){return __webpack_require__.O[e](_[o])})?_.splice(o--,1):(c=!1,n<u&&(u=n));if(c){e.splice(i--,1);var a=t()}}return a},__webpack_require__.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(r,{a:r}),r},__webpack_require__.d=function(e,r){for(var _ in r)__webpack_require__.o(r,_)&&!__webpack_require__.o(e,_)&&Object.defineProperty(e,_,{enumerable:!0,get:r[_]})},__webpack_require__.f={},__webpack_require__.e=function(e){return Promise.all(Object.keys(__webpack_require__.f).reduce(function(r,_){return __webpack_require__.f[_](e,r),r},[]))},__webpack_require__.u=function(e){return"static/chunks/"+(737===e?"fb7d5399":e)+"."+({250:"a985869c089b2fd3",354:"c8f476539d33c65e",737:"bc4a70b34221e8c8",767:"5a1b83173dac696e",848:"fc0fe21cdc2e6431"})[e]+".js"},__webpack_require__.miniCssF=function(e){return"static/css/dc34af488e4eed17.css"},__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r={},_="_N_E:",__webpack_require__.l=function(e,t,n,i){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var u,c,o=document.getElementsByTagName("script"),a=0;a<o.length;a++){var p=o[a];if(p.getAttribute("src")==e||p.getAttribute("data-webpack")==_+n){u=p;break}}u||(c=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,__webpack_require__.nc&&u.setAttribute("nonce",__webpack_require__.nc),u.setAttribute("data-webpack",_+n),u.src=__webpack_require__.tu(e)),r[e]=[t];var onScriptComplete=function(_,t){u.onerror=u.onload=null,clearTimeout(b);var n=r[e];if(delete r[e],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(function(e){return e(t)}),_)return _(t)},b=setTimeout(onScriptComplete.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=onScriptComplete.bind(null,u.onerror),u.onload=onScriptComplete.bind(null,u.onload),c&&document.head.appendChild(u)},__webpack_require__.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.tt=function(){return void 0===t&&(t={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(t=trustedTypes.createPolicy("nextjs#bundler",t))),t},__webpack_require__.tu=function(e){return __webpack_require__.tt().createScriptURL(e)},__webpack_require__.p="/t2-model-skinner/_next/",n={272:0},__webpack_require__.f.j=function(e,r){var _=__webpack_require__.o(n,e)?n[e]:void 0;if(0!==_){if(_)r.push(_[2]);else if(272!=e){var t=new Promise(function(r,t){_=n[e]=[r,t]});r.push(_[2]=t);var i=__webpack_require__.p+__webpack_require__.u(e),u=Error();__webpack_require__.l(i,function(r){if(__webpack_require__.o(n,e)&&(0!==(_=n[e])&&(n[e]=void 0),_)){var t=r&&("load"===r.type?"missing":r.type),i=r&&r.target&&r.target.src;u.message="Loading chunk "+e+" failed.\n("+t+": "+i+")",u.name="ChunkLoadError",u.type=t,u.request=i,_[1](u)}},"chunk-"+e,e)}else n[e]=0}},__webpack_require__.O.j=function(e){return 0===n[e]},i=function(e,r){var _,t,i=r[0],u=r[1],c=r[2],o=0;if(i.some(function(e){return 0!==n[e]})){for(_ in u)__webpack_require__.o(u,_)&&(__webpack_require__.m[_]=u[_]);if(c)var a=c(__webpack_require__)}for(e&&e(r);o<i.length;o++)t=i[o],__webpack_require__.o(n,t)&&n[t]&&n[t][0](),n[t]=0;return __webpack_require__.O(a)},(u=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(i.bind(null,0)),u.push=i.bind(null,u.push.bind(u))}();
|
||||
//# sourceMappingURL=webpack-87a6ffe25f8aa4fa.js.map
|
||||
!function(){"use strict";var e,r,_,t,n,i,u,c={},o={};function __webpack_require__(e){var r=o[e];if(void 0!==r)return r.exports;var _=o[e]={exports:{}},t=!0;try{c[e].call(_.exports,_,_.exports,__webpack_require__),t=!1}finally{t&&delete o[e]}return _.exports}__webpack_require__.m=c,e=[],__webpack_require__.O=function(r,_,t,n){if(_){n=n||0;for(var i=e.length;i>0&&e[i-1][2]>n;i--)e[i]=e[i-1];e[i]=[_,t,n];return}for(var u=1/0,i=0;i<e.length;i++){for(var _=e[i][0],t=e[i][1],n=e[i][2],c=!0,o=0;o<_.length;o++)u>=n&&Object.keys(__webpack_require__.O).every(function(e){return __webpack_require__.O[e](_[o])})?_.splice(o--,1):(c=!1,n<u&&(u=n));if(c){e.splice(i--,1);var a=t()}}return a},__webpack_require__.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return __webpack_require__.d(r,{a:r}),r},__webpack_require__.d=function(e,r){for(var _ in r)__webpack_require__.o(r,_)&&!__webpack_require__.o(e,_)&&Object.defineProperty(e,_,{enumerable:!0,get:r[_]})},__webpack_require__.f={},__webpack_require__.e=function(e){return Promise.all(Object.keys(__webpack_require__.f).reduce(function(r,_){return __webpack_require__.f[_](e,r),r},[]))},__webpack_require__.u=function(e){return"static/chunks/"+(737===e?"fb7d5399":e)+"."+({250:"a985869c089b2fd3",354:"c8f476539d33c65e",737:"bc4a70b34221e8c8",767:"5a1b83173dac696e",848:"fc0fe21cdc2e6431"})[e]+".js"},__webpack_require__.miniCssF=function(e){return"static/css/8f9c54e3d59c6be4.css"},__webpack_require__.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||Function("return this")()}catch(e){if("object"==typeof window)return window}}(),__webpack_require__.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r={},_="_N_E:",__webpack_require__.l=function(e,t,n,i){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var u,c,o=document.getElementsByTagName("script"),a=0;a<o.length;a++){var p=o[a];if(p.getAttribute("src")==e||p.getAttribute("data-webpack")==_+n){u=p;break}}u||(c=!0,(u=document.createElement("script")).charset="utf-8",u.timeout=120,__webpack_require__.nc&&u.setAttribute("nonce",__webpack_require__.nc),u.setAttribute("data-webpack",_+n),u.src=__webpack_require__.tu(e)),r[e]=[t];var onScriptComplete=function(_,t){u.onerror=u.onload=null,clearTimeout(f);var n=r[e];if(delete r[e],u.parentNode&&u.parentNode.removeChild(u),n&&n.forEach(function(e){return e(t)}),_)return _(t)},f=setTimeout(onScriptComplete.bind(null,void 0,{type:"timeout",target:u}),12e4);u.onerror=onScriptComplete.bind(null,u.onerror),u.onload=onScriptComplete.bind(null,u.onload),c&&document.head.appendChild(u)},__webpack_require__.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},__webpack_require__.tt=function(){return void 0===t&&(t={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(t=trustedTypes.createPolicy("nextjs#bundler",t))),t},__webpack_require__.tu=function(e){return __webpack_require__.tt().createScriptURL(e)},__webpack_require__.p="/t2-model-skinner/_next/",n={272:0},__webpack_require__.f.j=function(e,r){var _=__webpack_require__.o(n,e)?n[e]:void 0;if(0!==_){if(_)r.push(_[2]);else if(272!=e){var t=new Promise(function(r,t){_=n[e]=[r,t]});r.push(_[2]=t);var i=__webpack_require__.p+__webpack_require__.u(e),u=Error();__webpack_require__.l(i,function(r){if(__webpack_require__.o(n,e)&&(0!==(_=n[e])&&(n[e]=void 0),_)){var t=r&&("load"===r.type?"missing":r.type),i=r&&r.target&&r.target.src;u.message="Loading chunk "+e+" failed.\n("+t+": "+i+")",u.name="ChunkLoadError",u.type=t,u.request=i,_[1](u)}},"chunk-"+e,e)}else n[e]=0}},__webpack_require__.O.j=function(e){return 0===n[e]},i=function(e,r){var _,t,i=r[0],u=r[1],c=r[2],o=0;if(i.some(function(e){return 0!==n[e]})){for(_ in u)__webpack_require__.o(u,_)&&(__webpack_require__.m[_]=u[_]);if(c)var a=c(__webpack_require__)}for(e&&e(r);o<i.length;o++)t=i[o],__webpack_require__.o(n,t)&&n[t]&&n[t][0](),n[t]=0;return __webpack_require__.O(a)},(u=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(i.bind(null,0)),u.push=i.bind(null,u.push.bind(u))}();
|
||||
//# sourceMappingURL=webpack-0d2be6fe958413db.js.map
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
docs/_next/static/css/8f9c54e3d59c6be4.css.map
Normal file
1
docs/_next/static/css/8f9c54e3d59c6be4.css.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +1 @@
|
|||
self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/78e521c3-3739cc27b3254d35.js","static/chunks/95b64a6e-a0ff77d56afeed48.js","static/chunks/31664189-69d752d1129a4958.js","static/chunks/545f34e4-3e66c340444ca8b2.js","static/chunks/1bfc9850-93cf1d387cb4594a.js","static/chunks/d7eeaac4-d223ea230e13423c.js","static/chunks/f580fadb-2911e2fbf64aae5a.js","static/chunks/50-a8a6240a880bd3e0.js","static/chunks/pages/index-3a28f1c89241d3c8.js"],"/_error":["static/chunks/pages/_error-54b9fcf45cb5bc62.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
||||
self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":["static/chunks/78e521c3-3739cc27b3254d35.js","static/chunks/95b64a6e-a0ff77d56afeed48.js","static/chunks/31664189-69d752d1129a4958.js","static/chunks/545f34e4-3e66c340444ca8b2.js","static/chunks/1bfc9850-b4ceccea4b74407c.js","static/chunks/d7eeaac4-d223ea230e13423c.js","static/chunks/f580fadb-2911e2fbf64aae5a.js","static/chunks/50-a8a6240a880bd3e0.js","static/chunks/pages/index-e955a68f1762f24b.js"],"/_error":["static/chunks/pages/_error-54b9fcf45cb5bc62.js"],sortedPages:["/","/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -1,30 +1,64 @@
|
|||
import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
|
||||
import useTools from "./useTools";
|
||||
|
||||
export default function CanvasToggle() {
|
||||
const { activeCanvasType, setActiveCanvasType, hasMetallic } = useTools();
|
||||
const {
|
||||
activeCanvasType,
|
||||
setActiveCanvasType,
|
||||
hasMetallic,
|
||||
hasAnimation,
|
||||
frameCount,
|
||||
selectedFrameIndex,
|
||||
setSelectedFrameIndex,
|
||||
} = useTools();
|
||||
|
||||
return (
|
||||
<div className="CanvasToggle">
|
||||
<button
|
||||
type="button"
|
||||
data-selected={activeCanvasType === "color" ? "" : undefined}
|
||||
onClick={() => {
|
||||
setActiveCanvasType("color");
|
||||
}}
|
||||
>
|
||||
Color
|
||||
</button>
|
||||
{hasMetallic ? (
|
||||
<>
|
||||
<div className="CanvasToggle">
|
||||
<button
|
||||
type="button"
|
||||
data-selected={activeCanvasType === "metallic" ? "" : undefined}
|
||||
data-selected={activeCanvasType === "color" ? "" : undefined}
|
||||
onClick={() => {
|
||||
setActiveCanvasType("metallic");
|
||||
setActiveCanvasType("color");
|
||||
}}
|
||||
>
|
||||
Metallic
|
||||
Color
|
||||
</button>
|
||||
{hasMetallic ? (
|
||||
<button
|
||||
type="button"
|
||||
data-selected={activeCanvasType === "metallic" ? "" : undefined}
|
||||
onClick={() => {
|
||||
setActiveCanvasType("metallic");
|
||||
}}
|
||||
>
|
||||
Metallic
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
{hasAnimation ? (
|
||||
<div className="FrameSelector">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedFrameIndex((index) => (index - 1) % frameCount);
|
||||
}}
|
||||
>
|
||||
<FaChevronLeft />
|
||||
</button>
|
||||
<span className="FrameInfo">
|
||||
{selectedFrameIndex + 1} / {frameCount}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSelectedFrameIndex((index) => (index + 1) % frameCount);
|
||||
}}
|
||||
>
|
||||
<FaChevronRight />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,16 @@ const defaultTextureSize = [512, 512] as [number, number];
|
|||
|
||||
export default function ColorCanvas({
|
||||
materialDef,
|
||||
frameIndex = 0,
|
||||
}: {
|
||||
materialDef: MaterialDefinition;
|
||||
frameIndex: number;
|
||||
}) {
|
||||
const { skinImageUrls, defaultSkinImageUrls } = useWarrior();
|
||||
const skinImageUrl = skinImageUrls[materialDef.file ?? materialDef.name];
|
||||
const skinImageUrl =
|
||||
skinImageUrls[materialDef.file ?? materialDef.name]?.[frameIndex];
|
||||
const defaultSkinImageUrl =
|
||||
defaultSkinImageUrls[materialDef.file ?? materialDef.name];
|
||||
defaultSkinImageUrls[materialDef.file ?? materialDef.name]?.[frameIndex];
|
||||
const { setColorImageUrl } = useSkin();
|
||||
const { canvasPadding } = useSettings();
|
||||
const [noAlphaImageUrl, setNoAlphaImageUrl] = useState<string | null>(null);
|
||||
|
|
@ -37,9 +40,13 @@ export default function ColorCanvas({
|
|||
width: textureSize[0],
|
||||
height: textureSize[1],
|
||||
});
|
||||
setColorImageUrl(materialDef.file ?? materialDef.name, imageUrl);
|
||||
setColorImageUrl(
|
||||
materialDef.file ?? materialDef.name,
|
||||
imageUrl,
|
||||
frameIndex
|
||||
);
|
||||
},
|
||||
[textureSize, canvasPadding, setColorImageUrl, materialDef]
|
||||
[textureSize, canvasPadding, setColorImageUrl, materialDef, frameIndex]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
@ -79,7 +86,7 @@ export default function ColorCanvas({
|
|||
loadImage,
|
||||
]);
|
||||
|
||||
const canvasId = `${materialDef.name}:color`;
|
||||
const canvasId = `${materialDef.name}:color:${frameIndex}`;
|
||||
|
||||
return textureSize ? (
|
||||
<Canvas
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ export type MaterialDefinition = {
|
|||
emissiveTexture?: boolean;
|
||||
metallicFactor?: number;
|
||||
roughnessFactor?: number;
|
||||
frameCount?: number;
|
||||
frameTimings?: number[];
|
||||
};
|
||||
|
||||
function useTexture({
|
||||
|
|
@ -39,13 +41,14 @@ function useTexture({
|
|||
material: ModelMaterial;
|
||||
materialDef?: MaterialDefinition;
|
||||
textureType: "baseColorTexture" | "metallicRoughnessTexture";
|
||||
imageUrl?: string;
|
||||
imageUrl?: string[];
|
||||
}) {
|
||||
const { modelViewer } = useModelViewer();
|
||||
const { basePath } = useSettings();
|
||||
|
||||
useEffect(() => {
|
||||
let stale = false;
|
||||
let animationFrame: ReturnType<typeof requestAnimationFrame>;
|
||||
|
||||
const updateTexture = async () => {
|
||||
if (!materialDef || materialDef.hidden) {
|
||||
|
|
@ -64,8 +67,11 @@ function useTexture({
|
|||
emissiveTexture = false,
|
||||
metallicFactor = 1,
|
||||
roughnessFactor = 1,
|
||||
frameCount = 1,
|
||||
frameTimings,
|
||||
} = materialDef;
|
||||
let textureUrl = imageUrl ?? `${basePath}/white.png`;
|
||||
let textureUrls =
|
||||
imageUrl ?? new Array(frameCount).fill(`${basePath}/white.png`);
|
||||
switch (textureType) {
|
||||
case "baseColorTexture":
|
||||
if (baseColorFactor) {
|
||||
|
|
@ -85,15 +91,32 @@ function useTexture({
|
|||
material.pbrMetallicRoughness.setMetallicFactor(metallicFactor);
|
||||
material.pbrMetallicRoughness.setRoughnessFactor(roughnessFactor);
|
||||
if (metallicFactor === 0 && roughnessFactor === 1) {
|
||||
textureUrl = `${basePath}/green.png`;
|
||||
textureUrls = new Array(frameCount).fill(`${basePath}/green.png`);
|
||||
}
|
||||
}
|
||||
const texture = await modelViewer.createTexture(textureUrl);
|
||||
const textures = await Promise.all(
|
||||
textureUrls.map((textureUrl) => modelViewer.createTexture(textureUrl))
|
||||
);
|
||||
if (!stale) {
|
||||
material.pbrMetallicRoughness[textureType].setTexture(texture);
|
||||
if (textureType === "baseColorTexture" && emissiveTexture) {
|
||||
material.emissiveTexture.setTexture(texture);
|
||||
}
|
||||
let frameIndex = 0;
|
||||
let frameProgress = 0;
|
||||
const frame: FrameRequestCallback = (timestamp) => {
|
||||
const frameTiming = frameTimings?.[frameIndex] ?? 1;
|
||||
const texture = textures[frameIndex];
|
||||
material.pbrMetallicRoughness[textureType].setTexture(texture);
|
||||
if (textureType === "baseColorTexture" && emissiveTexture) {
|
||||
material.emissiveTexture.setTexture(texture);
|
||||
}
|
||||
frameProgress += 1;
|
||||
if (frameCount > 1) {
|
||||
if (frameProgress >= frameTiming) {
|
||||
frameIndex = (frameIndex + 1) % frameCount;
|
||||
frameProgress = 0;
|
||||
}
|
||||
animationFrame = requestAnimationFrame(frame);
|
||||
}
|
||||
};
|
||||
animationFrame = requestAnimationFrame(frame);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -102,6 +125,7 @@ function useTexture({
|
|||
|
||||
return () => {
|
||||
stale = true;
|
||||
cancelAnimationFrame(animationFrame);
|
||||
};
|
||||
}, [basePath, modelViewer, material, materialDef, textureType, imageUrl]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,26 @@ export default function MaterialCanvases() {
|
|||
const hasMetallic = !(
|
||||
materialDef.metallicFactor === 0 && materialDef.roughnessFactor === 1
|
||||
);
|
||||
const frameCount = materialDef.frameCount ?? 1;
|
||||
const frames = new Array(frameCount).fill(null);
|
||||
return (
|
||||
<React.Fragment key={`${actualModel}-${materialDef.name}`}>
|
||||
<ColorCanvas materialDef={materialDef} />
|
||||
{hasMetallic ? <MetallicCanvas materialDef={materialDef} /> : null}
|
||||
{frames.map((_, i) => (
|
||||
<ColorCanvas
|
||||
materialDef={materialDef}
|
||||
frameIndex={i}
|
||||
key={`color:${i}`}
|
||||
/>
|
||||
))}
|
||||
{hasMetallic
|
||||
? frames.map((_, i) => (
|
||||
<MetallicCanvas
|
||||
materialDef={materialDef}
|
||||
frameIndex={i}
|
||||
key={`metallic:${i}`}
|
||||
/>
|
||||
))
|
||||
: null}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -11,13 +11,16 @@ const defaultTextureSize = [512, 512] as [number, number];
|
|||
|
||||
export default function MetallicCanvas({
|
||||
materialDef,
|
||||
frameIndex = 0,
|
||||
}: {
|
||||
materialDef: MaterialDefinition;
|
||||
frameIndex: number;
|
||||
}) {
|
||||
const { skinImageUrls, defaultSkinImageUrls } = useWarrior();
|
||||
const skinImageUrl = skinImageUrls[materialDef.file ?? materialDef.name];
|
||||
const skinImageUrl =
|
||||
skinImageUrls[materialDef.file ?? materialDef.name]?.[frameIndex];
|
||||
const defaultSkinImageUrl =
|
||||
defaultSkinImageUrls[materialDef.file ?? materialDef.name];
|
||||
defaultSkinImageUrls[materialDef.file ?? materialDef.name]?.[frameIndex];
|
||||
const { setMetallicImageUrl } = useSkin();
|
||||
const { canvasPadding } = useSettings();
|
||||
const [alphaImageUrl, setAlphaImageUrl] = useState<string | null>(null);
|
||||
|
|
@ -53,7 +56,8 @@ export default function MetallicCanvas({
|
|||
if (runningChangeHandlers.current === 0) {
|
||||
setMetallicImageUrl(
|
||||
materialDef.file ?? materialDef.name,
|
||||
outputImageUrl
|
||||
outputImageUrl,
|
||||
frameIndex
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -63,6 +67,7 @@ export default function MetallicCanvas({
|
|||
setMetallicImageUrl,
|
||||
convertGrayscaleImageUrlToMetallicRoughness,
|
||||
materialDef,
|
||||
frameIndex,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -106,7 +111,7 @@ export default function MetallicCanvas({
|
|||
loadImage,
|
||||
]);
|
||||
|
||||
const canvasId = `${materialDef.name}:metallic`;
|
||||
const canvasId = `${materialDef.name}:metallic:${frameIndex}`;
|
||||
|
||||
return textureSize ? (
|
||||
<Canvas
|
||||
|
|
|
|||
|
|
@ -14,24 +14,40 @@ export default function SkinProvider({ children }: { children: ReactNode }) {
|
|||
};
|
||||
});
|
||||
},
|
||||
setColorImageUrl(materialFile: string, colorImageUrl: string) {
|
||||
setColorImageUrl(
|
||||
materialFile: string,
|
||||
colorImageUrl: string,
|
||||
frameIndex: number
|
||||
) {
|
||||
setMaterialSkins((materialSkins) => {
|
||||
const newColorImageUrl = Array.from(
|
||||
materialSkins[materialFile]?.colorImageUrl ?? []
|
||||
);
|
||||
newColorImageUrl.splice(frameIndex, 1, colorImageUrl);
|
||||
return {
|
||||
...materialSkins,
|
||||
[materialFile]: {
|
||||
...materialSkins[materialFile],
|
||||
colorImageUrl,
|
||||
colorImageUrl: newColorImageUrl,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
setMetallicImageUrl(materialFile: string, metallicImageUrl: string) {
|
||||
setMetallicImageUrl(
|
||||
materialFile: string,
|
||||
metallicImageUrl: string,
|
||||
frameIndex: number
|
||||
) {
|
||||
setMaterialSkins((materialSkins) => {
|
||||
const newMetallicImageUrl = Array.from(
|
||||
materialSkins[materialFile]?.metallicImageUrl ?? []
|
||||
);
|
||||
newMetallicImageUrl.splice(frameIndex, 1, metallicImageUrl);
|
||||
return {
|
||||
...materialSkins,
|
||||
[materialFile]: {
|
||||
...materialSkins[materialFile],
|
||||
metallicImageUrl,
|
||||
metallicImageUrl: newMetallicImageUrl,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -46,11 +62,11 @@ export default function SkinProvider({ children }: { children: ReactNode }) {
|
|||
getSkinImages(materialFile: string) {
|
||||
return materialSkins[materialFile];
|
||||
},
|
||||
getColorImageUrl(materialFile: string) {
|
||||
return materialSkins[materialFile].colorImageUrl;
|
||||
getColorImageUrl(materialFile: string, frameIndex: number) {
|
||||
return materialSkins[materialFile].colorImageUrl?.[frameIndex];
|
||||
},
|
||||
getMetallicImageUrl(materialFile: string) {
|
||||
return materialSkins[materialFile].metallicImageUrl;
|
||||
getMetallicImageUrl(materialFile: string, frameIndex: number) {
|
||||
return materialSkins[materialFile].metallicImageUrl?.[frameIndex];
|
||||
},
|
||||
...setters,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -45,9 +45,13 @@ type ObjectFilters = {
|
|||
export default function ToolsProvider({ children }: { children: ReactNode }) {
|
||||
const { actualModel, selectedModelType } = useWarrior();
|
||||
const [selectedMaterialIndex, setSelectedMaterialIndex] = useState(0);
|
||||
const [selectedFrameIndex, setSelectedFrameIndex] = useState(0);
|
||||
const materialDefs = materials[actualModel];
|
||||
const materialDef = materialDefs[selectedMaterialIndex] ?? null;
|
||||
|
||||
const frameCount = materialDef.frameCount ?? 1;
|
||||
const hasAnimation = frameCount > 1;
|
||||
|
||||
const textureSize = useMemo(
|
||||
() => materialDef.size ?? [512, 512],
|
||||
[materialDef]
|
||||
|
|
@ -63,6 +67,14 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
setActiveCanvasType("color");
|
||||
}
|
||||
|
||||
if (selectedFrameIndex >= frameCount) {
|
||||
setSelectedFrameIndex(0);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedFrameIndex(0);
|
||||
}, [materialDef]);
|
||||
|
||||
const [backgroundColor, setBackgroundColor] = useState("magenta");
|
||||
const [lockedObjects, setLockedObjects] = useState(
|
||||
() => new Set<fabric.Object>()
|
||||
|
|
@ -77,9 +89,11 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
);
|
||||
|
||||
const activeCanvas = materialDef
|
||||
? `${materialDef.name}:${activeCanvasType}`
|
||||
? `${materialDef.name}:${activeCanvasType}:${selectedFrameIndex}`
|
||||
: null;
|
||||
const metallicCanvasId = materialDef
|
||||
? `${materialDef.name}:metallic:${selectedFrameIndex}`
|
||||
: null;
|
||||
const metallicCanvasId = materialDef ? `${materialDef.name}:metallic` : null;
|
||||
const { canvases } = useCanvas();
|
||||
const { canvas, notifyChange, undo, redo, canUndo, canRedo } =
|
||||
useCanvas(activeCanvas);
|
||||
|
|
@ -348,54 +362,73 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
!materialDef.hidden &&
|
||||
materialDef.selectable !== false
|
||||
)
|
||||
.map(async (materialDef: MaterialDefinition) => {
|
||||
const colorCanvas = canvases[`${materialDef.name}:color`]?.canvas;
|
||||
const metallicCanvas =
|
||||
canvases[`${materialDef.name}:metallic`]?.canvas;
|
||||
.map((materialDef: MaterialDefinition) => {
|
||||
const frameCount = materialDef.frameCount ?? 1;
|
||||
const frames = new Array(frameCount).fill(null);
|
||||
return frames.map(async (_, frameIndex) => {
|
||||
const colorCanvas =
|
||||
canvases[`${materialDef.name}:color:${frameIndex}`]?.canvas;
|
||||
const metallicCanvas =
|
||||
canvases[`${materialDef.name}:metallic:${frameIndex}`]?.canvas;
|
||||
|
||||
const textureSize = materialDef.size ?? [512, 512];
|
||||
let outputImageUrl;
|
||||
const textureSize = materialDef.size ?? [512, 512];
|
||||
let outputImageUrl;
|
||||
|
||||
const colorImageUrl = colorCanvas.toDataURL({
|
||||
top: canvasPadding,
|
||||
left: canvasPadding,
|
||||
width: textureSize[0],
|
||||
height: textureSize[1],
|
||||
});
|
||||
|
||||
if (metallicCanvas) {
|
||||
const metallicImageUrl = metallicCanvas.toDataURL({
|
||||
const colorImageUrl = colorCanvas.toDataURL({
|
||||
top: canvasPadding,
|
||||
left: canvasPadding,
|
||||
width: textureSize[0],
|
||||
height: textureSize[1],
|
||||
});
|
||||
outputImageUrl = await combineColorAndAlphaImageUrls({
|
||||
colorImageUrl,
|
||||
metallicImageUrl,
|
||||
});
|
||||
} else {
|
||||
outputImageUrl = colorImageUrl;
|
||||
}
|
||||
|
||||
let filename;
|
||||
switch (selectedModelType) {
|
||||
case "player":
|
||||
filename = `${name}.${actualModel}.png`;
|
||||
break;
|
||||
case "weapon":
|
||||
case "vehicle":
|
||||
if (materialDef) {
|
||||
filename = `${materialDef.file ?? materialDef.name}.png`;
|
||||
} else if (selectedModelType === "weapon") {
|
||||
filename = `weapon_${actualModel}.png`;
|
||||
} else {
|
||||
filename = `${actualModel}.png`;
|
||||
}
|
||||
}
|
||||
if (metallicCanvas) {
|
||||
const metallicImageUrl = metallicCanvas.toDataURL({
|
||||
top: canvasPadding,
|
||||
left: canvasPadding,
|
||||
width: textureSize[0],
|
||||
height: textureSize[1],
|
||||
});
|
||||
outputImageUrl = await combineColorAndAlphaImageUrls({
|
||||
colorImageUrl,
|
||||
metallicImageUrl,
|
||||
});
|
||||
} else {
|
||||
outputImageUrl = colorImageUrl;
|
||||
}
|
||||
|
||||
return { imageUrl: outputImageUrl, filename };
|
||||
let filename;
|
||||
switch (selectedModelType) {
|
||||
case "player":
|
||||
filename = `${name}.${actualModel}.png`;
|
||||
break;
|
||||
case "weapon":
|
||||
case "vehicle":
|
||||
if (materialDef) {
|
||||
const frameZeroFile = materialDef.file ?? materialDef.name;
|
||||
if (frameCount > 1) {
|
||||
const match = frameZeroFile.match(/^(.+)(\d\d)$/);
|
||||
if (match) {
|
||||
const baseName = match[1];
|
||||
filename = `${baseName}${frameIndex
|
||||
.toString()
|
||||
.padStart(2, "0")}.png`;
|
||||
} else {
|
||||
throw new Error("Unexpected animation filename");
|
||||
}
|
||||
} else {
|
||||
filename = `${frameZeroFile}.png`;
|
||||
}
|
||||
} else if (selectedModelType === "weapon") {
|
||||
filename = `weapon_${actualModel}.png`;
|
||||
} else {
|
||||
filename = `${actualModel}.png`;
|
||||
}
|
||||
}
|
||||
|
||||
return { imageUrl: outputImageUrl, filename };
|
||||
});
|
||||
})
|
||||
.flat()
|
||||
);
|
||||
|
||||
switch (format) {
|
||||
|
|
@ -484,6 +517,10 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
setSelectedMaterialIndex,
|
||||
textureSize,
|
||||
hasMetallic,
|
||||
selectedFrameIndex,
|
||||
setSelectedFrameIndex,
|
||||
hasAnimation,
|
||||
frameCount,
|
||||
}),
|
||||
[
|
||||
activeCanvas,
|
||||
|
|
@ -516,6 +553,9 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
selectedMaterialIndex,
|
||||
textureSize,
|
||||
hasMetallic,
|
||||
selectedFrameIndex,
|
||||
hasAnimation,
|
||||
frameCount,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,20 @@ const { publicRuntimeConfig } = getConfig();
|
|||
const { materials, modelDefaults } = publicRuntimeConfig;
|
||||
const baseSkinPath = `https://exogen.github.io/t2-skins/skins`;
|
||||
|
||||
function getFrameNames(frameZeroFile: string, frameCount: number) {
|
||||
if (frameCount < 2) {
|
||||
return [frameZeroFile];
|
||||
}
|
||||
const match = frameZeroFile.match(/^(.+)(\d\d)$/);
|
||||
if (match) {
|
||||
const baseName = match[1];
|
||||
const frames = new Array(frameCount).fill(null);
|
||||
return frames.map((_, i) => `${baseName}${i.toString().padStart(2, "0")}`);
|
||||
} else {
|
||||
throw new Error("Did not match expected frame format");
|
||||
}
|
||||
}
|
||||
|
||||
export function getSkinImageUrls({
|
||||
basePath,
|
||||
actualModel,
|
||||
|
|
@ -20,43 +34,48 @@ export function getSkinImageUrls({
|
|||
selectedModelType: string;
|
||||
selectedSkin: string | null;
|
||||
selectedSkinType: string | null;
|
||||
}): Record<string, string> {
|
||||
}): Record<string, string[]> {
|
||||
const materialDefs = materials[actualModel];
|
||||
switch (selectedModelType) {
|
||||
case "player":
|
||||
switch (selectedSkinType) {
|
||||
case "default":
|
||||
return {
|
||||
base: `${basePath}/textures/${selectedSkin}.${actualModel}.png`,
|
||||
base: [`${basePath}/textures/${selectedSkin}.${actualModel}.png`],
|
||||
};
|
||||
case "custom":
|
||||
return { base: `${baseSkinPath}/${selectedSkin}.${actualModel}.png` };
|
||||
return {
|
||||
base: [`${baseSkinPath}/${selectedSkin}.${actualModel}.png`],
|
||||
};
|
||||
}
|
||||
break;
|
||||
case "weapon":
|
||||
case "vehicle":
|
||||
return materialDefs.reduce(
|
||||
(
|
||||
skinImageUrls: Record<string, string>,
|
||||
skinImageUrls: Record<string, string[]>,
|
||||
materialDef: MaterialDefinition
|
||||
) => {
|
||||
if (materialDef) {
|
||||
const frameCount = materialDef.frameCount ?? 1;
|
||||
switch (selectedSkinType) {
|
||||
case "default":
|
||||
if (materialDef.hasDefault !== false) {
|
||||
skinImageUrls[
|
||||
materialDef.file ?? materialDef.name
|
||||
] = `${basePath}/textures/${
|
||||
materialDef.file ?? materialDef.name
|
||||
}.png`;
|
||||
skinImageUrls[materialDef.file ?? materialDef.name] =
|
||||
getFrameNames(
|
||||
materialDef.file ?? materialDef.name,
|
||||
frameCount
|
||||
).map((name) => `${basePath}/textures/${name}.png`);
|
||||
}
|
||||
break;
|
||||
case "custom":
|
||||
skinImageUrls[
|
||||
materialDef.file ?? materialDef.name
|
||||
] = `${baseSkinPath}/${selectedSkin}/${
|
||||
materialDef.file ?? materialDef.name
|
||||
}.png`;
|
||||
skinImageUrls[materialDef.file ?? materialDef.name] =
|
||||
getFrameNames(
|
||||
materialDef.file ?? materialDef.name,
|
||||
frameCount
|
||||
).map(
|
||||
(name) => `${baseSkinPath}/${selectedSkin}/${name}.png`
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -127,7 +146,7 @@ export default function WarriorProvider({ children }: { children: ReactNode }) {
|
|||
selectedAnimation
|
||||
);
|
||||
|
||||
const [skinImageUrls, setSkinImageUrls] = useState<Record<string, string>>(
|
||||
const [skinImageUrls, setSkinImageUrls] = useState<Record<string, string[]>>(
|
||||
() =>
|
||||
getSkinImageUrls({
|
||||
basePath,
|
||||
|
|
|
|||
|
|
@ -184,7 +184,7 @@ export default function WarriorSelector() {
|
|||
});
|
||||
setSelectedSkin(null);
|
||||
setSkinImageUrls({
|
||||
[materialDef.file ?? materialDef.name]: imageUrl,
|
||||
[materialDef.file ?? materialDef.name]: [imageUrl],
|
||||
});
|
||||
}}
|
||||
type="file"
|
||||
|
|
|
|||
|
|
@ -488,3 +488,34 @@ h6 {
|
|||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.FrameSelector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-right: auto;
|
||||
border-radius: 5px;
|
||||
padding: 2px 0;
|
||||
background: rgb(74, 193, 171);
|
||||
color: rgb(5, 69, 76);
|
||||
}
|
||||
|
||||
.FrameSelector button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 2px 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.FrameSelector button:disabled {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.FrameInfo {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useContext } from "react";
|
||||
|
||||
export type SkinImages = {
|
||||
colorImageUrl?: string;
|
||||
metallicImageUrl?: string;
|
||||
colorImageUrl?: string[];
|
||||
metallicImageUrl?: string[];
|
||||
};
|
||||
|
||||
export type MaterialSkins = Record<string, SkinImages>;
|
||||
|
|
@ -11,10 +11,24 @@ interface SkinContextValue {
|
|||
materialSkins: MaterialSkins;
|
||||
getSkinImages: (materialFile: string) => SkinImages;
|
||||
setSkinImages: (materialFile: string, skinImages: SkinImages) => void;
|
||||
getColorImageUrl: (materialFile: string) => string | undefined;
|
||||
setColorImageUrl: (materialFile: string, colorImageUrl: string) => void;
|
||||
getMetallicImageUrl: (materialFile: string) => string | undefined;
|
||||
setMetallicImageUrl: (materialFile: string, colorImageUrl: string) => void;
|
||||
getColorImageUrl: (
|
||||
materialFile: string,
|
||||
frameIndex: number
|
||||
) => string | undefined;
|
||||
setColorImageUrl: (
|
||||
materialFile: string,
|
||||
colorImageUrl: string,
|
||||
frameIndex: number
|
||||
) => void;
|
||||
getMetallicImageUrl: (
|
||||
materialFile: string,
|
||||
frameIndex: number
|
||||
) => string | undefined;
|
||||
setMetallicImageUrl: (
|
||||
materialFile: string,
|
||||
metallicImageUrl: string,
|
||||
frameIndex: number
|
||||
) => void;
|
||||
}
|
||||
|
||||
const SkinContext = React.createContext<SkinContextValue | null>(null);
|
||||
|
|
|
|||
|
|
@ -41,8 +41,14 @@ interface ToolsContextValue {
|
|||
setBackgroundColor: (backgroundColor: string) => void;
|
||||
selectedMaterialIndex: number;
|
||||
setSelectedMaterialIndex: (materialIndex: number) => void;
|
||||
selectedFrameIndex: number;
|
||||
setSelectedFrameIndex: (
|
||||
frameIndex: number | ((frameIndex: number) => number)
|
||||
) => void;
|
||||
textureSize: [number, number];
|
||||
hasMetallic: boolean;
|
||||
hasAnimation: boolean;
|
||||
frameCount: number;
|
||||
}
|
||||
|
||||
const ToolsContext = React.createContext<ToolsContextValue | null>(null);
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ type WarriorContextValue = {
|
|||
setAnimationPaused: (
|
||||
animationPaused: boolean | ((animationPaused: boolean) => boolean)
|
||||
) => void;
|
||||
skinImageUrls: Record<string, string>;
|
||||
defaultSkinImageUrls: Record<string, string>;
|
||||
skinImageUrls: Record<string, string[]>;
|
||||
defaultSkinImageUrls: Record<string, string[]>;
|
||||
setSkinImageUrls: (
|
||||
value:
|
||||
| Record<string, string>
|
||||
| ((prevSkinImageUrls: Record<string, string>) => Record<string, string>)
|
||||
| Record<string, string[]>
|
||||
| ((
|
||||
prevSkinImageUrls: Record<string, string[]>
|
||||
) => Record<string, string[]>)
|
||||
) => void;
|
||||
selectedSkinType: string | null;
|
||||
setSelectedSkinType: (selectedSkinType: string | null) => void;
|
||||
|
|
|
|||
Loading…
Reference in a new issue