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 (
<ChakraProvider>
<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 /> */}
{modelLoading !== 0 && modelLoading !== 100 && (
<div
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 = () => {
return (
<div>DebugPanel</div>
)
}
return <div>DebugPanel</div>;
};
export default DebugPanel
export default DebugPanel;

View File

@ -7,11 +7,11 @@ import {
ModalContent,
ModalOverlay,
Image,
IconButton
IconButton,
} from "@chakra-ui/react";
import { pickFile } from "@/lib/upload";
import useModelStore, { StickerType } from "@/store/useModelStore";
import { v4 as uuidv4 } from 'uuid'
import { v4 as uuidv4 } from "uuid";
import { Vector3 } from "three";
const LogoPanel = () => {
@ -19,6 +19,7 @@ const LogoPanel = () => {
const decals = useModelStore((state) => state.decals);
const setDecals = useModelStore((state) => state.setDecals);
const setActiveDecal = useModelStore((state) => state.setActiveDecal);
const activeModel = useModelStore((state) => state.activeModel);
const activeDecal = useModelStore((state) => state.activeDecal);
return (
<>
@ -28,35 +29,48 @@ const LogoPanel = () => {
.map((decal) => (
<li
onClick={() => {
setActiveDecal(decal.id)
setActiveDecal(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} />
<IconButton icon={<DeleteIcon />} aria-label={""} colorScheme="red" size={"xs"} onClick={() => {
setDecals(decals.filter(el => el.id !== decal.id))
}} />
<Image className={"h-12 w-12 object-contain"} src={decal.url} />
<IconButton
icon={<DeleteIcon />}
aria-label={""}
colorScheme="red"
size={"xs"}
onClick={() => {
setDecals(decals.filter((el) => el.id !== decal.id));
}}
/>
</li>
))}
</ul>
<div className="flex justify-center mt-4">
<div className="mt-4 flex justify-center">
<Button
colorScheme="messenger"
onClick={() => {
pickFile({
accept: ["image/png"],
}).then((files) => {
if (!files.length) return
const id = uuidv4()
setDecals([...decals, {
if (!files.length) return;
const id = uuidv4();
setDecals([
...decals,
{
id,
url: URL.createObjectURL(files[0]),
postion: new Vector3(0, 0, 0),
type: StickerType.logo
}])
setActiveDecal(id)
type: StickerType.logo,
scale: activeModel ? 1.5 : 0.05,
},
]);
setActiveDecal(id);
});
}}
leftIcon={<AddIcon />}

View File

@ -26,7 +26,8 @@ const RightPanel = () => {
onClick={() => {
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`}
>
<img

View File

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

View File

@ -16,6 +16,7 @@ const TextLabelPanel = () => {
const decals = useModelStore((state) => state.decals);
const setDecals = useModelStore((state) => state.setDecals);
const setActiveDecal = useModelStore((state) => state.setActiveDecal);
const activeModel = useModelStore((state) => state.activeModel);
const activeDecal = useModelStore((state) => state.activeDecal);
const [editing, setEditing] = useState(false);
const [text, setText] = useState("");
@ -70,6 +71,7 @@ const TextLabelPanel = () => {
text,
url: fabricCanvas.toDataURL(),
type: StickerType.text,
scale: activeModel ? 1.5 : 0.05,
},
]);
setText(() => "");
@ -91,7 +93,10 @@ const TextLabelPanel = () => {
setActiveDecal(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>
@ -108,7 +113,7 @@ const TextLabelPanel = () => {
className={"ml-1"}
onClick={(ev) => {
ev.stopPropagation();
setDecals(decals.filter(el => el.id !== text.id))
setDecals(decals.filter((el) => el.id !== text.id));
}}
size={"xs"}
icon={<DeleteIcon />}

View File

@ -6,7 +6,7 @@ import { models } from "@/constant/models.ts";
import { useGLTF } from "@react-three/drei/core/useGLTF";
import { textures } from "@/constant/textures.ts";
import Sticker from "./Sticker";
import { useWhyDidYouUpdate } from "ahooks";
import { useState, useEffect } from "react";
const CapModel = () => {
const activeModel = useModelStore((state) => state.activeModel);
@ -14,9 +14,33 @@ const CapModel = () => {
const decalDragging = useModelStore((state) => state.decalDragging);
const activeDecal = useModelStore((state) => state.activeDecal);
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);
return (
<>
@ -46,6 +70,7 @@ const CapModel = () => {
key={keyName}
onDoubleClick={(ev) => {
ev.stopPropagation();
if (activeDecal) {
const { x, y, z } = ev.point;
const pos = new Vector3(
@ -53,6 +78,18 @@ const CapModel = () => {
y / 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);
}
}}
@ -80,13 +117,11 @@ const CapModel = () => {
}
})}
</group>
<OrbitControls enabled={!decalDragging} />
<OrbitControls enabled={!decalDragging} enableZoom={!isAltDown} />
</>
);
};
export const Stickers = () => {
const decals = useModelStore((state) => state.decals);
@ -96,19 +131,19 @@ export const Stickers = () => {
<Sticker decal={decal} key={decal.id} />
))}
</>
)
}
);
};
const ThreeScene = () => {
// const hdr = useTexture("/venice_sunset_1k.hdr")
return (
<Canvas>
<ambientLight></ambientLight>
<pointLight position={[10, 10, 10]}></pointLight>
<perspectiveCamera />
<CapModel />
<Environment preset={"city"} background={false} />
<axesHelper />
<Environment files={"/venice_sunset_1k.hdr"} background={false} />
</Canvas>
);
};

View File

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