This commit is contained in:
quantulr
2023-12-13 17:31:05 +08:00
parent d0336be4d5
commit dad2f61aaa
12 changed files with 536 additions and 388 deletions

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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 && (

View File

@ -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);

View 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>;
};

View 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;

View File

@ -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);
}} />
</>
);
};

View 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;

View File

@ -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);
});

View File

@ -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>
);
};

View File

@ -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: "右后",

View File

@ -10,5 +10,5 @@ export const textures = [
},
{
path: "/textures/quilt_.jpg",
}
},
];