mirror of
https://github.com/exogen/t2-model-skinner.git
synced 2026-01-20 03:34:47 +00:00
add URL param for linking to skins
This commit is contained in:
parent
248b969d26
commit
96d1bc66e1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1 +0,0 @@
|
|||
self.__BUILD_MANIFEST=function(s){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[s,"static/chunks/e21e5bbe-b28e0079b469d4e8.js","static/chunks/ebc70433-4eccd1cb3af29a3e.js","static/chunks/6eb5140f-31a2b2da7903b885.js","static/chunks/5d416436-3c60fd013e24a5af.js","static/chunks/85d7bc83-1ca530d7d3f44153.js","static/chunks/3a17f596-9aeae038dfa51955.js","static/chunks/f580fadb-2911e2fbf64aae5a.js","static/chunks/515-13ff0773d41722ae.js","static/chunks/pages/index-28c32b1427b3bfd0.js"],"/_error":["static/chunks/pages/_error-54b9fcf45cb5bc62.js"],"/gallery":[s,"static/chunks/737a5600-aea383aaa2061cc6.js","static/chunks/918-3c6747f76df39072.js","static/css/922e89893536f2f9.css","static/chunks/pages/gallery-af1406fdc1af13f5.js"],sortedPages:["/","/_app","/_error","/gallery"]}}("static/chunks/cb355538-e538db8a1761f402.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
||||
|
|
@ -0,0 +1 @@
|
|||
self.__BUILD_MANIFEST=function(s){return{__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/":[s,"static/chunks/e21e5bbe-b28e0079b469d4e8.js","static/chunks/ebc70433-4eccd1cb3af29a3e.js","static/chunks/6eb5140f-31a2b2da7903b885.js","static/chunks/5d416436-3c60fd013e24a5af.js","static/chunks/85d7bc83-1ca530d7d3f44153.js","static/chunks/3a17f596-9aeae038dfa51955.js","static/chunks/f580fadb-2911e2fbf64aae5a.js","static/chunks/51-4264bf751d1f46b3.js","static/chunks/pages/index-f0171d44afbb06ea.js"],"/_error":["static/chunks/pages/_error-54b9fcf45cb5bc62.js"],"/gallery":[s,"static/chunks/737a5600-aea383aaa2061cc6.js","static/chunks/918-3c6747f76df39072.js","static/css/922e89893536f2f9.css","static/chunks/pages/gallery-eae2514110a1c5b0.js"],sortedPages:["/","/_app","/_error","/gallery"]}}("static/chunks/cb355538-e538db8a1761f402.js"),self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();
|
||||
File diff suppressed because one or more lines are too long
1
docs/_next/static/chunks/51-4264bf751d1f46b3.js.map
Normal file
1
docs/_next/static/chunks/51-4264bf751d1f46b3.js.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
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
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-f0171d44afbb06ea.js
Normal file
2
docs/_next/static/chunks/pages/index-f0171d44afbb06ea.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]={id:e,loaded:!1,exports:{}},t=!0;try{c[e].call(_.exports,_,_.exports,__webpack_require__),t=!1}finally{t&&delete o[e]}return _.loaded=!0,_.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:"10c119307e239c98",737:"bc4a70b34221e8c8",767:"0dd6b240996f3455",848:"fc0fe21cdc2e6431"})[e]+".js"},__webpack_require__.miniCssF=function(e){return"static/css/"+({214:"922e89893536f2f9",888:"2bfb0b67e396f94e"})[e]+".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__.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},__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-6a8605187f718663.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]={id:e,loaded:!1,exports:{}},t=!0;try{c[e].call(_.exports,_,_.exports,__webpack_require__),t=!1}finally{t&&delete o[e]}return _.loaded=!0,_.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:"10c119307e239c98",737:"bc4a70b34221e8c8",767:"0dd6b240996f3455",848:"fc0fe21cdc2e6431"})[e]+".js"},__webpack_require__.miniCssF=function(e){return"static/css/"+({214:"922e89893536f2f9",888:"811f0a5e25f9ca76"})[e]+".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__.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},__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-8dba4ecbbea48736.js.map
|
||||
File diff suppressed because one or more lines are too long
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/css/811f0a5e25f9ca76.css
Normal file
2
docs/_next/static/css/811f0a5e25f9ca76.css
Normal file
File diff suppressed because one or more lines are too long
1
docs/_next/static/css/811f0a5e25f9ca76.css.map
Normal file
1
docs/_next/static/css/811f0a5e25f9ca76.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
File diff suppressed because one or more lines are too long
|
|
@ -10,6 +10,8 @@ export default function CanvasToggle() {
|
|||
frameCount,
|
||||
selectedFrameIndex,
|
||||
setSelectedFrameIndex,
|
||||
sizeMultiplier,
|
||||
setSizeMultiplier,
|
||||
} = useTools();
|
||||
|
||||
return (
|
||||
|
|
@ -36,6 +38,35 @@ export default function CanvasToggle() {
|
|||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="CanvasToggle" hidden>
|
||||
<button
|
||||
type="button"
|
||||
data-selected={sizeMultiplier === 1 ? "" : undefined}
|
||||
onClick={() => {
|
||||
setSizeMultiplier(1);
|
||||
}}
|
||||
>
|
||||
1×
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-selected={sizeMultiplier === 2 ? "" : undefined}
|
||||
onClick={() => {
|
||||
setSizeMultiplier(2);
|
||||
}}
|
||||
>
|
||||
2×
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-selected={sizeMultiplier === 4 ? "" : undefined}
|
||||
onClick={() => {
|
||||
setSizeMultiplier(4);
|
||||
}}
|
||||
>
|
||||
4×
|
||||
</button>
|
||||
</div>
|
||||
{hasAnimation ? (
|
||||
<div className="FrameSelector">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import type { MaterialDefinition } from "./Material";
|
|||
import useWarrior from "./useWarrior";
|
||||
import useImageWorker from "./useImageWorker";
|
||||
import useImageLoader from "./useImageLoader";
|
||||
import useTools from "./useTools";
|
||||
|
||||
const defaultTextureSize = [512, 512] as [number, number];
|
||||
|
||||
|
|
@ -26,11 +27,12 @@ export default function ColorCanvas({
|
|||
const [noAlphaImageUrl, setNoAlphaImageUrl] = useState<string | null>(null);
|
||||
const { removeAlphaFromArrayBuffer } = useImageWorker();
|
||||
const { loadImage } = useImageLoader();
|
||||
const { sizeMultiplier } = useTools();
|
||||
|
||||
const textureSize = useMemo(
|
||||
() => materialDef.size ?? defaultTextureSize,
|
||||
[materialDef]
|
||||
);
|
||||
const textureSize = useMemo<[number, number]>(() => {
|
||||
const [width, height] = materialDef.size ?? defaultTextureSize;
|
||||
return [width * sizeMultiplier, height * sizeMultiplier];
|
||||
}, [materialDef.size, sizeMultiplier]);
|
||||
|
||||
const handleChange = useCallback<CanvasProps["onChange"]>(
|
||||
async (canvas) => {
|
||||
|
|
@ -86,7 +88,7 @@ export default function ColorCanvas({
|
|||
loadImage,
|
||||
]);
|
||||
|
||||
const canvasId = `${materialDef.name}:color:${frameIndex}`;
|
||||
const canvasId = `${materialDef.name}:color:${frameIndex}:${sizeMultiplier}`;
|
||||
|
||||
return textureSize ? (
|
||||
<Canvas
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import type { MaterialDefinition } from "./Material";
|
|||
import useSkin from "./useSkin";
|
||||
import useWarrior from "./useWarrior";
|
||||
import useImageLoader from "./useImageLoader";
|
||||
import useTools from "./useTools";
|
||||
|
||||
const defaultTextureSize = [512, 512] as [number, number];
|
||||
|
||||
|
|
@ -30,11 +31,12 @@ export default function MetallicCanvas({
|
|||
convertArrayBufferAlphaToGrayscale,
|
||||
} = useImageWorker();
|
||||
const { loadImage } = useImageLoader();
|
||||
const { sizeMultiplier } = useTools();
|
||||
|
||||
const textureSize = useMemo(
|
||||
() => materialDef.size ?? defaultTextureSize,
|
||||
[materialDef]
|
||||
);
|
||||
const textureSize = useMemo<[number, number]>(() => {
|
||||
const [width, height] = materialDef.size ?? defaultTextureSize;
|
||||
return [width * sizeMultiplier, height * sizeMultiplier];
|
||||
}, [materialDef.size, sizeMultiplier]);
|
||||
|
||||
const handleChange = useCallback<CanvasProps["onChange"]>(
|
||||
async (canvas) => {
|
||||
|
|
@ -111,7 +113,7 @@ export default function MetallicCanvas({
|
|||
loadImage,
|
||||
]);
|
||||
|
||||
const canvasId = `${materialDef.name}:metallic:${frameIndex}`;
|
||||
const canvasId = `${materialDef.name}:metallic:${frameIndex}:${sizeMultiplier}`;
|
||||
|
||||
return textureSize ? (
|
||||
<Canvas
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ const { publicRuntimeConfig } = getConfig();
|
|||
|
||||
const { materials } = publicRuntimeConfig;
|
||||
|
||||
const defaultTextureSize = [512, 512] as [number, number];
|
||||
|
||||
function lockObject(object: fabric.Object) {
|
||||
object.lockMovementX = true;
|
||||
object.lockMovementY = true;
|
||||
|
|
@ -47,6 +49,7 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
const { actualModel, selectedModelType } = useWarrior();
|
||||
const [selectedMaterialIndex, setSelectedMaterialIndex] = useState(0);
|
||||
const [selectedFrameIndex, setSelectedFrameIndex] = useState(0);
|
||||
const [sizeMultiplier, setSizeMultiplier] = useState<number>(1);
|
||||
const materialDefs: MaterialDefinition[] = materials[actualModel];
|
||||
const materialDef = materialDefs[selectedMaterialIndex] ?? null;
|
||||
const frameCount = materialDef.frameCount ?? 1;
|
||||
|
|
@ -55,10 +58,10 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
boolean[]
|
||||
>([]);
|
||||
|
||||
const textureSize: [number, number] = useMemo(
|
||||
() => materialDef.size ?? [512, 512],
|
||||
[materialDef]
|
||||
);
|
||||
const textureSize: [number, number] = useMemo(() => {
|
||||
const [width, height] = materialDef.size ?? defaultTextureSize;
|
||||
return [width * sizeMultiplier, height * sizeMultiplier];
|
||||
}, [materialDef.size, sizeMultiplier]);
|
||||
|
||||
const hasMetallic = !(
|
||||
materialDef.metallicFactor === 0 && materialDef.roughnessFactor === 1
|
||||
|
|
@ -74,6 +77,21 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
setSelectedFrameIndex(0);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const savedSizeMultiplier = localStorage.getItem("sizeMultiplier");
|
||||
switch (savedSizeMultiplier) {
|
||||
case "1":
|
||||
case "2":
|
||||
case "4":
|
||||
setSizeMultiplier(+savedSizeMultiplier);
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
// Probably blocked. That's okay.
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedExportMaterials(
|
||||
materialDefs.map((material) =>
|
||||
|
|
@ -100,10 +118,10 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
);
|
||||
|
||||
const activeCanvas = materialDef
|
||||
? `${materialDef.name}:${activeCanvasType}:${selectedFrameIndex}`
|
||||
? `${materialDef.name}:${activeCanvasType}:${selectedFrameIndex}:${sizeMultiplier}`
|
||||
: null;
|
||||
const metallicCanvasId = materialDef
|
||||
? `${materialDef.name}:metallic:${selectedFrameIndex}`
|
||||
? `${materialDef.name}:metallic:${selectedFrameIndex}:${sizeMultiplier}`
|
||||
: null;
|
||||
const { canvases } = useCanvas();
|
||||
const { canvas, notifyChange, undo, redo, canUndo, canRedo } =
|
||||
|
|
@ -412,11 +430,19 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
const frames = new Array(frameCount).fill(null);
|
||||
return frames.map(async (_, frameIndex) => {
|
||||
const colorCanvas =
|
||||
canvases[`${materialDef.name}:color:${frameIndex}`]?.canvas;
|
||||
canvases[
|
||||
`${materialDef.name}:color:${frameIndex}:${sizeMultiplier}`
|
||||
]?.canvas;
|
||||
const metallicCanvas =
|
||||
canvases[`${materialDef.name}:metallic:${frameIndex}`]?.canvas;
|
||||
canvases[
|
||||
`${materialDef.name}:metallic:${frameIndex}:${sizeMultiplier}`
|
||||
]?.canvas;
|
||||
|
||||
const textureSize = materialDef.size ?? [512, 512];
|
||||
const baseTextureSize = materialDef.size ?? defaultTextureSize;
|
||||
const textureSize = [
|
||||
baseTextureSize[0] * sizeMultiplier,
|
||||
baseTextureSize[1] * sizeMultiplier,
|
||||
];
|
||||
let outputImageUrl;
|
||||
|
||||
const colorImageUrl = colorCanvas.toDataURL({
|
||||
|
|
@ -500,15 +526,17 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
(match, a, b) => (a || b).toUpperCase()
|
||||
);
|
||||
let zipFileName = "";
|
||||
const multiplierString =
|
||||
sizeMultiplier > 1 ? `-@${sizeMultiplier}x` : "";
|
||||
switch (selectedModelType) {
|
||||
case "player":
|
||||
zipFileName = `zPlayerSkin-${name}.vl2`;
|
||||
zipFileName = `zPlayerSkin-${name}${multiplierString}.vl2`;
|
||||
break;
|
||||
case "weapon":
|
||||
zipFileName = `zWeapon${camelCaseName}-${name}.vl2`;
|
||||
zipFileName = `zWeapon${camelCaseName}-${name}${multiplierString}.vl2`;
|
||||
break;
|
||||
case "vehicle":
|
||||
zipFileName = `z${camelCaseName}-${name}.vl2`;
|
||||
zipFileName = `z${camelCaseName}-${name}${multiplierString}.vl2`;
|
||||
break;
|
||||
}
|
||||
await saveZipFile(zip, zipFileName);
|
||||
|
|
@ -517,13 +545,14 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
return;
|
||||
},
|
||||
[
|
||||
actualModel,
|
||||
canvasPadding,
|
||||
canvases,
|
||||
combineColorAndAlphaImageUrls,
|
||||
materialDefs,
|
||||
selectedModelType,
|
||||
selectedExportMaterials,
|
||||
canvases,
|
||||
sizeMultiplier,
|
||||
canvasPadding,
|
||||
selectedModelType,
|
||||
combineColorAndAlphaImageUrls,
|
||||
actualModel,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -572,6 +601,8 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
setSelectedFrameIndex,
|
||||
hasAnimation,
|
||||
frameCount,
|
||||
sizeMultiplier,
|
||||
setSizeMultiplier,
|
||||
selectedExportMaterials,
|
||||
setSelectedExportMaterials,
|
||||
}),
|
||||
|
|
@ -583,14 +614,14 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
brushColor,
|
||||
brushSize,
|
||||
hueRotate,
|
||||
saturation,
|
||||
brightness,
|
||||
contrast,
|
||||
layerMode,
|
||||
setHueRotate,
|
||||
saturation,
|
||||
setSaturation,
|
||||
brightness,
|
||||
setBrightness,
|
||||
contrast,
|
||||
setContrast,
|
||||
layerMode,
|
||||
selectedObjects,
|
||||
lockSelection,
|
||||
unlockSelection,
|
||||
|
|
@ -611,6 +642,8 @@ export default function ToolsProvider({ children }: { children: ReactNode }) {
|
|||
selectedFrameIndex,
|
||||
hasAnimation,
|
||||
frameCount,
|
||||
sizeMultiplier,
|
||||
setSizeMultiplier,
|
||||
selectedExportMaterials,
|
||||
]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -4,9 +4,10 @@ import useSettings from "./useSettings";
|
|||
import { WarriorContext } from "./useWarrior";
|
||||
import type { MaterialDefinition } from "./Material";
|
||||
import type { Skin } from "./importUtils";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const { materials, modelDefaults } = publicRuntimeConfig;
|
||||
const { materials, modelDefaults, defaultSkins } = publicRuntimeConfig;
|
||||
const baseSkinPath = `https://exogen.github.io/t2-skins/skins`;
|
||||
|
||||
let IMPORTED_SKINS: Map<string, Map<string | null, Skin>> = new Map();
|
||||
|
|
@ -38,6 +39,55 @@ function getFrameNames(frameZeroFile: string, frameCount: number) {
|
|||
}
|
||||
}
|
||||
|
||||
function modelToType(model: string) {
|
||||
switch (model) {
|
||||
case "lmale":
|
||||
case "mmale":
|
||||
case "hmale":
|
||||
case "lfemale":
|
||||
case "mfemale":
|
||||
case "hfemale":
|
||||
case "lbioderm":
|
||||
case "mbioderm":
|
||||
case "hbioderm":
|
||||
return "player";
|
||||
case "disc":
|
||||
case "chaingun":
|
||||
case "grenade_launcher":
|
||||
case "sniper":
|
||||
case "plasmathrower":
|
||||
case "energy":
|
||||
case "shocklance":
|
||||
case "elf":
|
||||
case "missile":
|
||||
case "mortar":
|
||||
case "repair":
|
||||
case "targeting":
|
||||
case "mine":
|
||||
return "weapon";
|
||||
case "vehicle_grav_scout":
|
||||
case "vehicle_grav_tank":
|
||||
case "vehicle_land_mpbbase":
|
||||
case "vehicle_air_scout":
|
||||
case "vehicle_air_bomber":
|
||||
case "vehicle_air_hapc":
|
||||
return "vehicle";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function skinToType(actualModel: string, skinName: string) {
|
||||
const defaultSkin = modelDefaults[actualModel];
|
||||
if (skinName === defaultSkin) {
|
||||
return "default";
|
||||
} else if (defaultSkins[actualModel]?.includes(skinName)) {
|
||||
return "default";
|
||||
} else {
|
||||
return "custom";
|
||||
}
|
||||
}
|
||||
|
||||
export function getSkinImageUrls({
|
||||
basePath,
|
||||
actualModel,
|
||||
|
|
@ -127,32 +177,8 @@ function getModelUrl(
|
|||
}
|
||||
}
|
||||
|
||||
// const queryParamSeparator = ".";
|
||||
|
||||
// function parseQuerySelection(searchParams: URLSearchParams) {
|
||||
// const modelWithTypeFromUrl = searchParams.get("m");
|
||||
// const skinWithTypeFromUrl = searchParams.get("s");
|
||||
// let selectedModel;
|
||||
// let selectedModelType;
|
||||
// if (typeof modelWithTypeFromUrl === "string") {
|
||||
// [selectedModelType, selectedModel] =
|
||||
// modelWithTypeFromUrl.split(queryParamSeparator);
|
||||
// }
|
||||
// let selectedSkin;
|
||||
// let selectedSkinType;
|
||||
// if (typeof skinWithTypeFromUrl === "string") {
|
||||
// [selectedSkinType, selectedSkin] =
|
||||
// skinWithTypeFromUrl.split(queryParamSeparator);
|
||||
// }
|
||||
// return {
|
||||
// selectedModel: selectedModel || null,
|
||||
// selectedModelType: selectedModelType || null,
|
||||
// selectedSkin: selectedSkin || null,
|
||||
// selectedSkinType: selectedSkinType || null,
|
||||
// };
|
||||
// }
|
||||
|
||||
export default function WarriorProvider({ children }: { children: ReactNode }) {
|
||||
const router = useRouter();
|
||||
const [selectedModel, setSelectedModel] = useState<string>("lmale");
|
||||
const [selectedModelType, setSelectedModelType] = useState("player");
|
||||
const [selectedSkin, setSelectedSkin] = useState<string | null>(
|
||||
|
|
@ -250,6 +276,57 @@ export default function WarriorProvider({ children }: { children: ReactNode }) {
|
|||
addImportedSkins,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
const modelName = router.query.m;
|
||||
if (typeof modelName === "string") {
|
||||
const modelType = modelToType(modelName);
|
||||
const actualModel = modelName === "hfemale" ? "hmale" : modelName;
|
||||
if (modelType) {
|
||||
setSelectedModel(modelName);
|
||||
setSelectedModelType(modelType);
|
||||
const skinPath = router.query.s;
|
||||
if (typeof skinPath === "string") {
|
||||
const skinType = skinToType(actualModel, skinPath);
|
||||
setSelectedSkin(skinPath);
|
||||
setSelectedSkinType(skinType);
|
||||
console.log("set model and skin from route:", modelName, skinPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady]);
|
||||
|
||||
const { replace: replaceRoute } = router;
|
||||
|
||||
useEffect(() => {
|
||||
if (router.isReady) {
|
||||
if (router.query.m !== selectedModel || router.query.s !== selectedSkin) {
|
||||
console.log(
|
||||
"router is ready. query params do not match, replacing:",
|
||||
selectedModel,
|
||||
selectedSkin
|
||||
);
|
||||
replaceRoute(
|
||||
{
|
||||
pathname: router.pathname,
|
||||
query: { ...router.query, m: selectedModel, s: selectedSkin },
|
||||
},
|
||||
undefined,
|
||||
{ shallow: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [
|
||||
replaceRoute,
|
||||
router.isReady,
|
||||
router.pathname,
|
||||
router.query,
|
||||
selectedSkin,
|
||||
selectedModel,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedSkin) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@ import { BsFillGrid3X3GapFill } from "react-icons/bs";
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import useTools from "./useTools";
|
||||
import { importMultipleFilesToModels, modelToModelType } from "./importUtils";
|
||||
import useManifest from "./useManifest";
|
||||
|
||||
const { publicRuntimeConfig } = getConfig();
|
||||
const { defaultSkins, modelDefaults /*materials*/ } = publicRuntimeConfig;
|
||||
const { defaultSkins, modelDefaults } = publicRuntimeConfig;
|
||||
|
||||
const baseManifestPath = `https://exogen.github.io/t2-skins`;
|
||||
const defaultCustomSkins = {};
|
||||
|
||||
const emptyMap = new Map();
|
||||
|
|
@ -40,6 +40,7 @@ export default function WarriorSelector() {
|
|||
null
|
||||
);
|
||||
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
||||
const [manifest, isManifestLoaded] = useManifest();
|
||||
|
||||
const importedSkinsForModel = importedSkins.get(actualModel) ?? emptyMap;
|
||||
|
||||
|
|
@ -48,33 +49,11 @@ export default function WarriorSelector() {
|
|||
).filter((skin) => skin.isComplete);
|
||||
|
||||
useEffect(() => {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
let ignore = false;
|
||||
|
||||
const loadCustomSkins = async () => {
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(`${baseManifestPath}/skins.json`, { signal });
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
if (!ignore) {
|
||||
const json = await res.json();
|
||||
if (!ignore) {
|
||||
setCustomSkins(json.customSkins ?? {});
|
||||
setNewSkins(json.newSkins ?? {});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
loadCustomSkins();
|
||||
|
||||
return () => {
|
||||
ignore = true;
|
||||
controller.abort();
|
||||
};
|
||||
}, []);
|
||||
if (isManifestLoaded) {
|
||||
setCustomSkins(manifest.customSkins);
|
||||
setNewSkins(manifest.newSkins);
|
||||
}
|
||||
}, [manifest, isManifestLoaded]);
|
||||
|
||||
let skinSelectValue = selectedSkin ?? "";
|
||||
if (selectedSkin && selectedSkinSection) {
|
||||
|
|
|
|||
|
|
@ -158,7 +158,11 @@ export default function GalleryPage() {
|
|||
)}.${skinModel}.webp`;
|
||||
|
||||
return (
|
||||
<div key={`${skinName}:${skinModel}`} className={styles.Skin}>
|
||||
<Link
|
||||
key={`${skinName}:${skinModel}`}
|
||||
className={styles.Skin}
|
||||
href={`/?m=${skinModel}&s=${encodeURIComponent(skinName)}`}
|
||||
>
|
||||
<img
|
||||
className={styles.Preview}
|
||||
loading="lazy"
|
||||
|
|
@ -168,7 +172,7 @@ export default function GalleryPage() {
|
|||
alt={skinName}
|
||||
/>
|
||||
<div className={styles.Name}>{skinName}</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -335,6 +335,10 @@ select {
|
|||
border-bottom-left-radius: 4px;
|
||||
}
|
||||
|
||||
.CanvasToggle button:not(:last-child) {
|
||||
border-right-color: rgb(54, 135, 123);
|
||||
}
|
||||
|
||||
.CanvasToggle button:last-child {
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
|
|
|
|||
|
|
@ -52,6 +52,10 @@ interface ToolsContextValue {
|
|||
hasMetallic: boolean;
|
||||
hasAnimation: boolean;
|
||||
frameCount: number;
|
||||
sizeMultiplier: number;
|
||||
setSizeMultiplier: (
|
||||
sizeMultiplier: number | ((sizeMultiplier: number) => number)
|
||||
) => void;
|
||||
selectedExportMaterials: boolean[];
|
||||
setSelectedExportMaterials: (
|
||||
selectedExportMaterials:
|
||||
|
|
|
|||
Loading…
Reference in a new issue