This commit is contained in:
2023-12-21 17:29:10 +08:00
parent ec316824b7
commit 18fd617a01
9 changed files with 367 additions and 348 deletions

View File

@ -9,14 +9,12 @@ function App() {
return ( return (
<ChakraProvider> <ChakraProvider>
<div className={"relative flex h-screen w-screen"}> <div className={"relative flex h-screen w-screen"}>
{/*<ThreeDesigner />*/}
<div className={"flex-[5] shrink-0"}> <div className={"flex-[5] shrink-0"}>
<ThreeScene /> <ThreeScene />
</div> </div>
<div className={"flex-[2] shrink-0 bg-blue-50 p-4"}> <div className={"flex-[2] shrink-0 bg-blue-50 p-4"}>
<RightPanel /> <RightPanel />
</div> </div>
{/* <AreaIndicator /> */}
{modelLoading !== 0 && modelLoading !== 100 && ( {modelLoading !== 0 && modelLoading !== 100 && (
<div <div
className={ className={

View File

@ -1,65 +0,0 @@
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

@ -1,9 +1,5 @@
const DebugPanel = () => { const DebugPanel = () => {
return ( return <div>DebugPanel</div>;
<div>DebugPanel</div> };
)
}
export default DebugPanel export default DebugPanel;

View File

@ -7,11 +7,11 @@ import {
ModalContent, ModalContent,
ModalOverlay, ModalOverlay,
Image, Image,
IconButton IconButton,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { pickFile } from "@/lib/upload"; import { pickFile } from "@/lib/upload";
import useModelStore, { StickerType } from "@/store/useModelStore"; import useModelStore, { StickerType } from "@/store/useModelStore";
import { v4 as uuidv4 } from 'uuid' import { v4 as uuidv4 } from "uuid";
import { Vector3 } from "three"; import { Vector3 } from "three";
const LogoPanel = () => { const LogoPanel = () => {
@ -19,6 +19,7 @@ const LogoPanel = () => {
const decals = useModelStore((state) => state.decals); const decals = useModelStore((state) => state.decals);
const setDecals = useModelStore((state) => state.setDecals); const setDecals = useModelStore((state) => state.setDecals);
const setActiveDecal = useModelStore((state) => state.setActiveDecal); const setActiveDecal = useModelStore((state) => state.setActiveDecal);
const activeModel = useModelStore((state) => state.activeModel);
const activeDecal = useModelStore((state) => state.activeDecal); const activeDecal = useModelStore((state) => state.activeDecal);
return ( return (
<> <>
@ -28,35 +29,48 @@ const LogoPanel = () => {
.map((decal) => ( .map((decal) => (
<li <li
onClick={() => { onClick={() => {
setActiveDecal(decal.id) setActiveDecal(decal.id);
}} }}
key={decal.id} key={decal.id}
className={`mb-2 flex h-12 w-full cursor-pointer items-center justify-between rounded bg-white px-2 shadow ${decal.id === activeDecal ? "border-2 border-green-100 bg-blue-200" : "" className={`mb-2 flex h-12 w-full cursor-pointer items-center justify-between rounded bg-white px-2 shadow ${
decal.id === activeDecal
? "border-2 border-green-100 bg-blue-200"
: ""
}`} }`}
> >
<Image className={"w-12 h-12 object-contain"} src={decal.url} /> <Image className={"h-12 w-12 object-contain"} src={decal.url} />
<IconButton icon={<DeleteIcon />} aria-label={""} colorScheme="red" size={"xs"} onClick={() => { <IconButton
setDecals(decals.filter(el => el.id !== decal.id)) icon={<DeleteIcon />}
}} /> aria-label={""}
colorScheme="red"
size={"xs"}
onClick={() => {
setDecals(decals.filter((el) => el.id !== decal.id));
}}
/>
</li> </li>
))} ))}
</ul> </ul>
<div className="flex justify-center mt-4"> <div className="mt-4 flex justify-center">
<Button <Button
colorScheme="messenger" colorScheme="messenger"
onClick={() => { onClick={() => {
pickFile({ pickFile({
accept: ["image/png"], accept: ["image/png"],
}).then((files) => { }).then((files) => {
if (!files.length) return if (!files.length) return;
const id = uuidv4() const id = uuidv4();
setDecals([...decals, { setDecals([
...decals,
{
id, id,
url: URL.createObjectURL(files[0]), url: URL.createObjectURL(files[0]),
postion: new Vector3(0, 0, 0), postion: new Vector3(0, 0, 0),
type: StickerType.logo type: StickerType.logo,
}]) scale: activeModel ? 1.5 : 0.05,
setActiveDecal(id) },
]);
setActiveDecal(id);
}); });
}} }}
leftIcon={<AddIcon />} leftIcon={<AddIcon />}
@ -76,7 +90,7 @@ const LogoPanel = () => {
<ModalBody> <ModalBody>
<div className="flex justify-center p-4"> <div className="flex justify-center p-4">
<div <div
onClick={() => { }} onClick={() => {}}
className={ className={
"flex aspect-square w-24 cursor-pointer items-center justify-center rounded-lg border-2 border-gray-300" "flex aspect-square w-24 cursor-pointer items-center justify-center rounded-lg border-2 border-gray-300"
} }

View File

@ -26,7 +26,8 @@ const RightPanel = () => {
onClick={() => { onClick={() => {
setActiveModel(index); setActiveModel(index);
}} }}
className={`${activeModel === index ? "border-2 border-green-300" : "" className={`${
activeModel === index ? "border-2 border-green-300" : ""
} flex aspect-square items-center justify-center overflow-hidden rounded-xl bg-blue-100`} } flex aspect-square items-center justify-center overflow-hidden rounded-xl bg-blue-100`}
> >
<img <img

View File

@ -1,6 +1,6 @@
import useModelStore, { DecalSticker } from "@/store/useModelStore"; import useModelStore, { DecalSticker } from "@/store/useModelStore";
import { Decal, useTexture } from "@react-three/drei"; import { Decal, useTexture } from "@react-three/drei";
import { useState } from "react"; import { Euler } from "three";
/** /**
* logo 和文字标签 * logo 和文字标签
@ -9,32 +9,47 @@ import { useState } from "react";
*/ */
const Sticker = ({ decal }: { decal: DecalSticker }) => { const Sticker = ({ decal }: { decal: DecalSticker }) => {
const sticker = useTexture(decal.url); const sticker = useTexture(decal.url);
const [scale, setScale] = useState(0.05);
const setDecalDragging = useModelStore((state) => state.setDecalDragging); const setDecalDragging = useModelStore((state) => state.setDecalDragging);
const setDecalScale = useModelStore((state) => state.setDecalScale);
const setActiveDecal = useModelStore((state) => state.setActiveDecal); const setActiveDecal = useModelStore((state) => state.setActiveDecal);
// useInterval(() => {
// setRotation(state => state += 0.001 * Math.PI)
// }, 10)
return ( return (
<Decal <Decal
debug={true}
position={decal.postion} position={decal.postion}
scale={scale} scale={decal.scale}
onPointerDown={(ev) => { onPointerDown={(ev) => {
ev.stopPropagation(); ev.stopPropagation();
setActiveDecal(decal.id) setActiveDecal(decal.id);
setDecalDragging(true); setDecalDragging(true);
}} }}
onPointerUp={(ev) => { onPointerUp={(ev) => {
ev.stopPropagation(); ev.stopPropagation();
setDecalDragging(false); setDecalDragging(false);
}} }}
// onWheel={(ev) => { onPointerEnter={(_ev) => {
// ev.stopPropagation() // ev.ctrlKey
// setDecalDragging(true) }}
// console.log(ev); onWheel={(ev) => {
ev.stopPropagation();
// // setScale((state) => state += 1) // setDecalDragging(true);
// }} // setDecals()
if (!ev.altKey) return;
if (ev.deltaY > 0) {
// setScale(state => state += 0.01)
setDecalScale(decal.id, (decal.scale -= 0.01));
} else if (ev.deltaY < 0) {
setDecalScale(decal.id, (decal.scale += 0.01));
// setScale(state => state -= 0.01)
}
}}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error // @ts-expect-error
rotation={Math.PI} rotation={Math.PI}
// rotation={new Euler(Math.PI / 2, 0, 0)}
// rotation={[Math.PI * 1, Math.PI * 1, Math.PI * 1]}
> >
<meshPhysicalMaterial <meshPhysicalMaterial
transparent transparent

View File

@ -16,6 +16,7 @@ const TextLabelPanel = () => {
const decals = useModelStore((state) => state.decals); const decals = useModelStore((state) => state.decals);
const setDecals = useModelStore((state) => state.setDecals); const setDecals = useModelStore((state) => state.setDecals);
const setActiveDecal = useModelStore((state) => state.setActiveDecal); const setActiveDecal = useModelStore((state) => state.setActiveDecal);
const activeModel = useModelStore((state) => state.activeModel);
const activeDecal = useModelStore((state) => state.activeDecal); const activeDecal = useModelStore((state) => state.activeDecal);
const [editing, setEditing] = useState(false); const [editing, setEditing] = useState(false);
const [text, setText] = useState(""); const [text, setText] = useState("");
@ -70,6 +71,7 @@ const TextLabelPanel = () => {
text, text,
url: fabricCanvas.toDataURL(), url: fabricCanvas.toDataURL(),
type: StickerType.text, type: StickerType.text,
scale: activeModel ? 1.5 : 0.05,
}, },
]); ]);
setText(() => ""); setText(() => "");
@ -91,7 +93,10 @@ const TextLabelPanel = () => {
setActiveDecal(text.id); setActiveDecal(text.id);
}} }}
key={text.id} key={text.id}
className={`mb-2 flex h-12 w-full cursor-pointer items-center justify-between rounded bg-white px-2 shadow ${activeDecal === text.id ? "border-2 border-green-100 bg-blue-200" : "" className={`mb-2 flex h-12 w-full cursor-pointer items-center justify-between rounded bg-white px-2 shadow ${
activeDecal === text.id
? "border-2 border-green-100 bg-blue-200"
: ""
}`} }`}
> >
<span>{text.text}</span> <span>{text.text}</span>
@ -108,7 +113,7 @@ const TextLabelPanel = () => {
className={"ml-1"} className={"ml-1"}
onClick={(ev) => { onClick={(ev) => {
ev.stopPropagation(); ev.stopPropagation();
setDecals(decals.filter(el => el.id !== text.id)) setDecals(decals.filter((el) => el.id !== text.id));
}} }}
size={"xs"} size={"xs"}
icon={<DeleteIcon />} icon={<DeleteIcon />}

View File

@ -6,7 +6,7 @@ import { models } from "@/constant/models.ts";
import { useGLTF } from "@react-three/drei/core/useGLTF"; import { useGLTF } from "@react-three/drei/core/useGLTF";
import { textures } from "@/constant/textures.ts"; import { textures } from "@/constant/textures.ts";
import Sticker from "./Sticker"; import Sticker from "./Sticker";
import { useWhyDidYouUpdate } from "ahooks"; import { useState, useEffect } from "react";
const CapModel = () => { const CapModel = () => {
const activeModel = useModelStore((state) => state.activeModel); const activeModel = useModelStore((state) => state.activeModel);
@ -14,9 +14,33 @@ const CapModel = () => {
const decalDragging = useModelStore((state) => state.decalDragging); const decalDragging = useModelStore((state) => state.decalDragging);
const activeDecal = useModelStore((state) => state.activeDecal); const activeDecal = useModelStore((state) => state.activeDecal);
const setDecalPositon = useModelStore((state) => state.setDecalPositon); const setDecalPositon = useModelStore((state) => state.setDecalPositon);
useWhyDidYouUpdate('useWhyDidYouUpdateComponent', {
activeDecal, decalDragging, activeTextures, activeModel const [isAltDown, setIsAltDown] = useState(false);
});
useEffect(() => {
function handleKeyDown(event: any) {
if (event.key === "Alt") {
setIsAltDown(true);
}
}
function handleKeyUp(event: any) {
if (event.key === "Alt") {
setIsAltDown(false);
}
}
// 监听键盘按下和释放事件
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
// 组件卸载时移除事件监听器
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp);
};
}, []);
const { nodes } = useGLTF(models[activeModel].path); const { nodes } = useGLTF(models[activeModel].path);
return ( return (
<> <>
@ -46,6 +70,7 @@ const CapModel = () => {
key={keyName} key={keyName}
onDoubleClick={(ev) => { onDoubleClick={(ev) => {
ev.stopPropagation(); ev.stopPropagation();
if (activeDecal) { if (activeDecal) {
const { x, y, z } = ev.point; const { x, y, z } = ev.point;
const pos = new Vector3( const pos = new Vector3(
@ -53,6 +78,18 @@ const CapModel = () => {
y / models[activeModel].scale, y / models[activeModel].scale,
z / models[activeModel].scale, z / models[activeModel].scale,
); );
// console.log(ev.point.transformDirection(ev.intersections[0].object.matrixWorld));
const normal = ev.face?.normal;
// normal?.transformDirection(ev.intersections[0].object.matrixWorld)
// normal?.applyQuaternion(ev.object.quaternion)
normal?.normalize();
console.log(normal);
console.log(normal?.angleTo(new Vector3(0, 1, 0)));
// const y_a = normal?.angleTo(new Vector3(0, 1, 0))
// const x_a = normal?.angleTo(new Vector3(1, 0, 0))
// const z_a = normal?.angleTo(new Vector3(0, 0, 1))
// console.log(x_a, y_a, z_a);
setDecalPositon(activeDecal, pos); setDecalPositon(activeDecal, pos);
} }
}} }}
@ -80,13 +117,11 @@ const CapModel = () => {
} }
})} })}
</group> </group>
<OrbitControls enabled={!decalDragging} /> <OrbitControls enabled={!decalDragging} enableZoom={!isAltDown} />
</> </>
); );
}; };
export const Stickers = () => { export const Stickers = () => {
const decals = useModelStore((state) => state.decals); const decals = useModelStore((state) => state.decals);
@ -96,19 +131,19 @@ export const Stickers = () => {
<Sticker decal={decal} key={decal.id} /> <Sticker decal={decal} key={decal.id} />
))} ))}
</> </>
) );
} };
const ThreeScene = () => { const ThreeScene = () => {
// const hdr = useTexture("/venice_sunset_1k.hdr")
return ( return (
<Canvas> <Canvas>
<ambientLight></ambientLight> <ambientLight></ambientLight>
<pointLight position={[10, 10, 10]}></pointLight> <pointLight position={[10, 10, 10]}></pointLight>
<perspectiveCamera /> <perspectiveCamera />
<CapModel /> <CapModel />
<Environment preset={"city"} background={false} /> <axesHelper />
<Environment files={"/venice_sunset_1k.hdr"} background={false} />
</Canvas> </Canvas>
); );
}; };

View File

@ -2,7 +2,7 @@ import { create } from "zustand";
import { models } from "@/constant/models.ts"; import { models } from "@/constant/models.ts";
import { Vector3 } from "three"; import { Vector3 } from "three";
import { devtools } from "zustand/middleware"; import { devtools } from "zustand/middleware";
import { v4 as uuidv4 } from "uuid"; // import { v4 as uuidv4 } from "uuid";
export enum StickerType { export enum StickerType {
text, text,
logo, logo,
@ -13,6 +13,7 @@ export interface DecalSticker {
postion: Vector3; postion: Vector3;
text?: string; text?: string;
url: string; url: string;
scale: number;
type: StickerType; type: StickerType;
} }
@ -32,10 +33,14 @@ interface ModelState {
decalDragging: boolean; decalDragging: boolean;
setDecalDragging: (enable: boolean) => void; setDecalDragging: (enable: boolean) => void;
decalZooming: boolean;
setDecalZooming: (enable: boolean) => void;
decals: DecalSticker[]; decals: DecalSticker[];
activeDecal?: string; activeDecal?: string;
setDecalPositon: (id: string, postion: Vector3) => void; setDecalPositon: (id: string, postion: Vector3) => void;
setDecals: (decals: DecalSticker[]) => void; setDecals: (decals: DecalSticker[]) => void;
setDecalScale: (id: string, scale: number) => void;
setActiveDecal: (decalId: string) => void; setActiveDecal: (decalId: string) => void;
} }
@ -44,7 +49,18 @@ const useModelStore = create<ModelState>()(
modelLoading: 0, modelLoading: 0,
setModelLoading: (progress: number) => setModelLoading: (progress: number) =>
set(() => ({ modelLoading: progress })), set(() => ({ modelLoading: progress })),
setDecalScale: (id: string, scale: number) =>
set((state) => {
const _decals = state.decals.map((el) => {
if (el.id === id) {
el.scale = scale;
}
return el;
});
return {
decals: _decals,
};
}),
activeModel: 0, activeModel: 0,
setActiveModel: (index: number) => setActiveModel: (index: number) =>
set(() => ({ set(() => ({
@ -71,14 +87,11 @@ const useModelStore = create<ModelState>()(
setDecalDragging: (enable: boolean) => setDecalDragging: (enable: boolean) =>
set(() => ({ decalDragging: enable })), set(() => ({ decalDragging: enable })),
decals: [ // 是否正在缩放sticker
{ decalZooming: false,
id: uuidv4(), setDecalZooming: (enable: boolean) => set(() => ({ decalZooming: enable })),
url: "/textures/archlogo.png",
postion: new Vector3(0, 0, 0), decals: [],
type: StickerType.logo,
},
],
activeDecal: undefined, activeDecal: undefined,
setDecalPositon: (id: string, postion: Vector3) => setDecalPositon: (id: string, postion: Vector3) =>
set((state) => { set((state) => {
@ -100,10 +113,17 @@ const useModelStore = create<ModelState>()(
set(() => ({ set(() => ({
decals, decals,
})), })),
setActiveDecal: (decalId: string) => setActiveDecal: (decalId: string) =>
set(() => ({ set(() => ({
activeDecal: decalId, activeDecal: decalId,
})), })),
// // @ts-ignore
// setDecalScale: (id: string, scale: number) =>
// set(() => ({
// activeArea: 0
// })),
})), })),
); );