update
This commit is contained in:
28
package.json
28
package.json
@ -15,10 +15,10 @@
|
||||
"@chakra-ui/react": "^2.8.2",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@react-three/drei": "^9.90.0",
|
||||
"@react-three/drei": "^9.92.1",
|
||||
"@react-three/fiber": "^8.15.12",
|
||||
"fabric": "^5.3.0",
|
||||
"framer-motion": "^10.16.14",
|
||||
"framer-motion": "^10.16.16",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.12.0",
|
||||
@ -27,23 +27,23 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fabric": "^5.3.6",
|
||||
"@types/node": "^20.10.3",
|
||||
"@types/react": "^18.2.37",
|
||||
"@types/react-dom": "^18.2.15",
|
||||
"@types/node": "^20.10.4",
|
||||
"@types/react": "^18.2.45",
|
||||
"@types/react-dom": "^18.2.17",
|
||||
"@types/three": "^0.159.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
||||
"@typescript-eslint/parser": "^6.10.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.14.0",
|
||||
"@typescript-eslint/parser": "^6.14.0",
|
||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"eslint-plugin-react-refresh": "^0.4.5",
|
||||
"postcss": "^8.4.32",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier-plugin-tailwindcss": "^0.5.7",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-tailwindcss": "^0.5.9",
|
||||
"sass": "^1.69.5",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.0"
|
||||
"tailwindcss": "^3.3.6",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.8"
|
||||
}
|
||||
}
|
||||
|
637
pnpm-lock.yaml
generated
637
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
13
src/App.tsx
13
src/App.tsx
@ -5,14 +5,21 @@ import { ChakraProvider, CircularProgress } from "@chakra-ui/react";
|
||||
import AreaIndicator from "@/components/AreaIndicator.tsx";
|
||||
import TextureSelector from "@/components/TextureSelector.tsx";
|
||||
import useModelStore from "@/store/useModelStore.ts";
|
||||
import ThreeScene from "@/components/ThreeScene.tsx";
|
||||
import RightPanel from "@/components/RightPanel.tsx";
|
||||
|
||||
function App() {
|
||||
const modelLoading = useModelStore((state) => state.modelLoading);
|
||||
return (
|
||||
<ChakraProvider>
|
||||
<div className={"relative h-screen w-screen"}>
|
||||
<ThreeDesigner />
|
||||
{/*<ThreeScene/>*/}
|
||||
<div className={"relative flex h-screen w-screen"}>
|
||||
{/*<ThreeDesigner />*/}
|
||||
<div className={"flex-[5] shrink-0"}>
|
||||
<ThreeScene />
|
||||
</div>
|
||||
<div className={"flex-[2] shrink-0 bg-blue-50 p-4"}>
|
||||
<RightPanel />
|
||||
</div>
|
||||
<AreaIndicator />
|
||||
<TextureSelector />
|
||||
{modelLoading !== 0 && modelLoading !== 100 && (
|
||||
|
@ -17,6 +17,7 @@ import {
|
||||
} from "@chakra-ui/react";
|
||||
import { CgArrowsExchange } from "react-icons/cg";
|
||||
import { useState } from "react";
|
||||
import { DecalGeometry } from "three/addons/geometries/DecalGeometry.js";
|
||||
|
||||
const AreaIndicator = () => {
|
||||
const activeModel = useModelStore((state) => state.activeModel);
|
||||
|
45
src/components/CapMesh.tsx
Normal file
45
src/components/CapMesh.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { Object3D, TextureLoader, Vector3 } from "three";
|
||||
import useModelStore from "@/store/useModelStore.ts";
|
||||
import { useState } from "react";
|
||||
import { models } from "@/constant/models.ts";
|
||||
import { textures } from "@/constant/textures.ts";
|
||||
|
||||
const CapMesh = ({ areaIndex, mesh }: { areaIndex: number, mesh: Object3D }) => {
|
||||
const logo = useTexture("/textures/archlogo.png");
|
||||
const activeModel = useModelStore(state => state.activeModel);
|
||||
const activeTextures = useModelStore(state => state.activeTextures);
|
||||
const [pos, setPos] = useState<Vector3>(new Vector3(0, 0, 0));
|
||||
return <mesh
|
||||
onClick={(ev) => {
|
||||
const { x, y, z } = ev.point;
|
||||
setPos(new Vector3(x / models[activeModel].scale, y / models[activeModel].scale, z / models[activeModel].scale));
|
||||
}}
|
||||
castShadow
|
||||
receiveShadow
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
geometry={mesh.geometry}
|
||||
dispose={null}
|
||||
>
|
||||
{areaIndex > -1 ?
|
||||
<meshStandardMaterial map={new TextureLoader().load(textures[activeTextures[areaIndex]].path)} />
|
||||
: <meshStandardMaterial color="gray" />}
|
||||
<Decal position={pos} scale={0.05} rotation={Math.PI}>
|
||||
<meshPhysicalMaterial
|
||||
transparent
|
||||
polygonOffset
|
||||
polygonOffsetFactor={-10}
|
||||
map={logo}
|
||||
map-flipY={false}
|
||||
map-anisotropy={16}
|
||||
iridescence={1}
|
||||
iridescenceIOR={1}
|
||||
iridescenceThicknessRange={[0, 1400]}
|
||||
roughness={1}
|
||||
clearcoat={0.5}
|
||||
metalness={0.75}
|
||||
toneMapped={false}
|
||||
/>
|
||||
</Decal>
|
||||
</mesh>;
|
||||
};
|
33
src/components/RightPanel.tsx
Normal file
33
src/components/RightPanel.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
|
||||
import { models } from "@/constant/models.ts";
|
||||
import useModelStore from "@/store/useModelStore.ts";
|
||||
|
||||
const RightPanel = () => {
|
||||
const activeModel = useModelStore(state => state.activeModel);
|
||||
return (
|
||||
<>
|
||||
<Tabs variant={"soft-rounded"}>
|
||||
<TabList>
|
||||
<Tab>版型</Tab>
|
||||
<Tab>纹理</Tab>
|
||||
<Tab>文字</Tab>
|
||||
<Tab>LOGO</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<div className={"w-full grid grid-cols-6 gap-4"}>
|
||||
{models.map((model, index) => <div key={model.path}
|
||||
onClick={()=>{}}
|
||||
className={`${activeModel === index ? "border-green-300 border-2" : ""} flex justify-center items-center aspect-square bg-blue-100 rounded-xl`}>{model.name}</div>)}
|
||||
</div>
|
||||
</TabPanel>
|
||||
<TabPanel>2</TabPanel>
|
||||
<TabPanel>3</TabPanel>
|
||||
<TabPanel>4</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RightPanel;
|
@ -2,13 +2,16 @@ import useModelStore from "@/store/useModelStore.ts";
|
||||
import { textures } from "@/constant/textures.ts";
|
||||
// import CanvasTexturesEditor from "@/components/CanvasTexturesEditor.tsx";
|
||||
import { useState } from "react";
|
||||
import { Divider, IconButton } from "@chakra-ui/react";
|
||||
import { AddIcon } from "@chakra-ui/icons";
|
||||
import TexturesEditor from "@/components/TexturesEditor.tsx";
|
||||
|
||||
const TextureSelector = () => {
|
||||
const { activeTextures, setActiveTextures, activeArea } = useModelStore();
|
||||
// eslint-disable-next-line no-empty-pattern
|
||||
const [
|
||||
/*showEditor,*/
|
||||
/*setShowEditor*/
|
||||
showEditor,
|
||||
setShowEditor
|
||||
] = useState(false);
|
||||
return (
|
||||
<>
|
||||
@ -34,19 +37,19 @@ const TextureSelector = () => {
|
||||
<img className={"object-cover"} src={item.path} alt={""} />
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/*<IconButton*/}
|
||||
{/* className={"ml-4"}*/}
|
||||
{/* aria-label={""}*/}
|
||||
{/* icon={<AddIcon />}*/}
|
||||
{/* onClick={() => {*/}
|
||||
{/* // setShowEditor(() => true);*/}
|
||||
{/* }}*/}
|
||||
{/*></IconButton>*/}
|
||||
<Divider orientation="vertical" />
|
||||
<IconButton
|
||||
className={"ml-4"}
|
||||
aria-label={""}
|
||||
icon={<AddIcon />}
|
||||
onClick={() => {
|
||||
setShowEditor(() => true);
|
||||
}}
|
||||
></IconButton>
|
||||
</div>
|
||||
{/*<CanvasTexturesEditor open={showEditor} onClose={() => {*/}
|
||||
{/* setShowEditor(() => false);*/}
|
||||
{/*}} />*/}
|
||||
<TexturesEditor isOpen={showEditor} onClose={() => {
|
||||
setShowEditor(() => false);
|
||||
}} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
18
src/components/TexturesEditor.tsx
Normal file
18
src/components/TexturesEditor.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { Modal, ModalBody, ModalContent, ModalHeader, ModalOverlay } from "@chakra-ui/react";
|
||||
|
||||
const TexturesEditor = ({ isOpen, onClose }: { isOpen: boolean, onClose: () => void }) => {
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose}>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalHeader>
|
||||
添加贴纸
|
||||
</ModalHeader>
|
||||
<ModalBody>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default TexturesEditor;
|
@ -4,10 +4,11 @@ import {
|
||||
Fog,
|
||||
Group,
|
||||
MeshStandardMaterial,
|
||||
PerspectiveCamera, RepeatWrapping,
|
||||
PerspectiveCamera,
|
||||
RepeatWrapping,
|
||||
Scene,
|
||||
TextureLoader,
|
||||
WebGLRenderer
|
||||
WebGLRenderer,
|
||||
} from "three";
|
||||
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
||||
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
|
||||
@ -24,7 +25,7 @@ const loadModel = (
|
||||
path: string;
|
||||
scale: number;
|
||||
},
|
||||
onProgress?: (xhr: ProgressEvent) => void
|
||||
onProgress?: (xhr: ProgressEvent) => void,
|
||||
): Promise<Group> =>
|
||||
new Promise((resolve, reject) => {
|
||||
new GLTFLoader().load(
|
||||
@ -40,7 +41,7 @@ const loadModel = (
|
||||
onProgress,
|
||||
(error) => {
|
||||
reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@ -48,7 +49,7 @@ const initTexture = (
|
||||
model: Group,
|
||||
activeModel: number,
|
||||
activeTextures: number[],
|
||||
activeArea: number
|
||||
activeArea: number,
|
||||
) =>
|
||||
new Promise((resolve) => {
|
||||
model.traverse((_child) => {
|
||||
@ -56,23 +57,23 @@ const initTexture = (
|
||||
// @ts-expect-error
|
||||
if (_child.isMesh) {
|
||||
const ttr = models[activeModel].mesh.find(
|
||||
(el) => el.name === _child.name
|
||||
(el) => el.name === _child.name,
|
||||
);
|
||||
if (ttr) {
|
||||
const texture = new TextureLoader().load(
|
||||
textures[activeTextures[activeArea]].path
|
||||
textures[activeTextures[activeArea]].path,
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
_child.material = new MeshStandardMaterial({
|
||||
map: texture
|
||||
map: texture,
|
||||
});
|
||||
// renderer.render(scene, camera);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
_child.material = new MeshStandardMaterial({
|
||||
color: 0x999999
|
||||
color: 0x999999,
|
||||
});
|
||||
// renderer.render(scene, camera);
|
||||
}
|
||||
@ -106,7 +107,7 @@ const ThreeDesigner = () => {
|
||||
75,
|
||||
window.innerWidth / window.innerHeight,
|
||||
0.1,
|
||||
1000
|
||||
1000,
|
||||
);
|
||||
camera.position.z = 600;
|
||||
|
||||
@ -148,7 +149,7 @@ const ThreeDesigner = () => {
|
||||
threeRef.current = {
|
||||
scene,
|
||||
renderer,
|
||||
camera
|
||||
camera,
|
||||
};
|
||||
}
|
||||
|
||||
@ -177,7 +178,7 @@ const ThreeDesigner = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
child.material = new MeshStandardMaterial({
|
||||
map: texture
|
||||
map: texture,
|
||||
});
|
||||
renderer.render(scene, camera);
|
||||
});
|
||||
|
@ -1,43 +1,76 @@
|
||||
import { Canvas, useLoader } from "@react-three/fiber";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
||||
import { Group } from "three";
|
||||
import { Environment, OrbitControls } from "@react-three/drei";
|
||||
import { Canvas } from "@react-three/fiber";
|
||||
import { useState } from "react";
|
||||
import { TextureLoader, Vector3 } from "three";
|
||||
import { Decal, Environment, OrbitControls, useTexture } from "@react-three/drei";
|
||||
import useModelStore from "@/store/useModelStore.ts";
|
||||
import { models } from "@/constant/models.ts";
|
||||
import { useGLTF } from "@react-three/drei/core/useGLTF";
|
||||
import { textures } from "@/constant/textures.ts";
|
||||
|
||||
const Model = () => {
|
||||
const groupRef = useRef<Group>(null);
|
||||
|
||||
// const capTexture = useLoader(TextureLoader, "/textures/baseball_cap_diffuse.png");
|
||||
|
||||
// 使用GLTFLoader加载gltf模型
|
||||
const { scene } = useLoader(GLTFLoader, "/models/baseball_cap_3d.glb");
|
||||
useEffect(() => {
|
||||
scene.traverse((child) => {
|
||||
if (child.type == "Mesh") {
|
||||
// console.log(child);
|
||||
// child.material = capTexture;
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
const CapModel = () => {
|
||||
const activeModel = useModelStore(state => state.activeModel);
|
||||
// const activeArea = useModelStore(state => state.activeArea);
|
||||
const activeTextures = useModelStore(state => state.activeTextures);
|
||||
const [pos, setPos] = useState<Vector3>(new Vector3(0, 0, 0));
|
||||
const logo = useTexture("/textures/archlogo.png");
|
||||
const { nodes } = useGLTF(models[activeModel].path);
|
||||
return (
|
||||
<group ref={groupRef} scale={10}>
|
||||
<OrbitControls />
|
||||
{/*<meshStandardMaterial map={capTexture2} />*/}
|
||||
<primitive object={scene} />
|
||||
<group scale={models[activeModel].scale}>
|
||||
{Object.keys(nodes).map((keyName) => {
|
||||
if (nodes[keyName].type === "Mesh") {
|
||||
const areaIndex = models[activeModel].mesh.findIndex(el => el.name === nodes[keyName].name);
|
||||
return (
|
||||
<mesh
|
||||
key={keyName}
|
||||
onClick={(ev) => {
|
||||
const { x, y, z } = ev.point;
|
||||
setPos(new Vector3(x / models[activeModel].scale, y / models[activeModel].scale, z / models[activeModel].scale));
|
||||
}}
|
||||
castShadow
|
||||
receiveShadow
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
geometry={nodes[keyName].geometry}
|
||||
dispose={null}
|
||||
>
|
||||
{areaIndex > -1 ?
|
||||
<meshStandardMaterial map={new TextureLoader().load(textures[activeTextures[areaIndex]].path)} />
|
||||
: <meshStandardMaterial color="gray" />}
|
||||
<Decal position={pos} scale={0.05} rotation={Math.PI}>
|
||||
<meshPhysicalMaterial
|
||||
transparent
|
||||
polygonOffset
|
||||
polygonOffsetFactor={-10}
|
||||
map={logo}
|
||||
map-flipY={false}
|
||||
map-anisotropy={16}
|
||||
iridescence={1}
|
||||
iridescenceIOR={1}
|
||||
iridescenceThicknessRange={[0, 1400]}
|
||||
roughness={1}
|
||||
clearcoat={0.5}
|
||||
metalness={0.75}
|
||||
toneMapped={false}
|
||||
/>
|
||||
</Decal>
|
||||
</mesh>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</group>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const ThreeScene = () => {
|
||||
return (
|
||||
<Canvas>
|
||||
<ambientLight></ambientLight>
|
||||
<pointLight position={[10, 10, 10]}></pointLight>
|
||||
{/*<camera />*/}
|
||||
<perspectiveCamera />
|
||||
<OrbitControls />
|
||||
<Model />
|
||||
<Environment preset={"city"} background />
|
||||
<CapModel />
|
||||
<Environment preset={"city"} background={false} />
|
||||
</Canvas>
|
||||
);
|
||||
};
|
||||
|
@ -2,7 +2,7 @@ export const models = [
|
||||
{
|
||||
name: "版型1",
|
||||
path: "/models/baseball_cap_3d.glb",
|
||||
scale: 20,
|
||||
scale: 18,
|
||||
mesh: [
|
||||
{
|
||||
name: "Pattern2D_2289809_Node",
|
||||
@ -39,7 +39,7 @@ export const models = [
|
||||
icon: "/icon/baseball_cap_3d_lb.png",
|
||||
label: "左后",
|
||||
},
|
||||
{
|
||||
{
|
||||
name: "Pattern2D_19144_Node",
|
||||
icon: "/icon/baseball_cap_3d_rb.png",
|
||||
label: "右后",
|
||||
|
@ -10,5 +10,5 @@ export const textures = [
|
||||
},
|
||||
{
|
||||
path: "/textures/quilt_.jpg",
|
||||
}
|
||||
},
|
||||
];
|
||||
|
Reference in New Issue
Block a user