decal
This commit is contained in:
@ -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={
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
@ -1,9 +1,5 @@
|
||||
|
||||
|
||||
const DebugPanel = () => {
|
||||
return (
|
||||
<div>DebugPanel</div>
|
||||
)
|
||||
}
|
||||
return <div>DebugPanel</div>;
|
||||
};
|
||||
|
||||
export default DebugPanel
|
||||
export default DebugPanel;
|
||||
|
@ -1,94 +1,108 @@
|
||||
import { useState } from "react";
|
||||
import { AddIcon, DeleteIcon } from "@chakra-ui/icons";
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalOverlay,
|
||||
Image,
|
||||
IconButton
|
||||
Button,
|
||||
Modal,
|
||||
ModalBody,
|
||||
ModalContent,
|
||||
ModalOverlay,
|
||||
Image,
|
||||
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 = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const decals = useModelStore((state) => state.decals);
|
||||
const setDecals = useModelStore((state) => state.setDecals);
|
||||
const setActiveDecal = useModelStore((state) => state.setActiveDecal);
|
||||
const activeDecal = useModelStore((state) => state.activeDecal);
|
||||
return (
|
||||
<>
|
||||
<ul>
|
||||
{decals
|
||||
.filter((el) => el.type === StickerType.logo)
|
||||
.map((decal) => (
|
||||
<li
|
||||
onClick={() => {
|
||||
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" : ""
|
||||
}`}
|
||||
>
|
||||
<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))
|
||||
}} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<div className="flex justify-center mt-4">
|
||||
<Button
|
||||
colorScheme="messenger"
|
||||
onClick={() => {
|
||||
pickFile({
|
||||
accept: ["image/png"],
|
||||
}).then((files) => {
|
||||
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)
|
||||
});
|
||||
}}
|
||||
leftIcon={<AddIcon />}
|
||||
>
|
||||
添加LOGO
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
isOpen={open}
|
||||
onClose={() => {
|
||||
setOpen(() => false);
|
||||
}}
|
||||
const [open, setOpen] = useState(false);
|
||||
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 (
|
||||
<>
|
||||
<ul>
|
||||
{decals
|
||||
.filter((el) => el.type === StickerType.logo)
|
||||
.map((decal) => (
|
||||
<li
|
||||
onClick={() => {
|
||||
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"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalBody>
|
||||
<div className="flex justify-center p-4">
|
||||
<div
|
||||
onClick={() => { }}
|
||||
className={
|
||||
"flex aspect-square w-24 cursor-pointer items-center justify-center rounded-lg border-2 border-gray-300"
|
||||
}
|
||||
>
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
<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="mt-4 flex justify-center">
|
||||
<Button
|
||||
colorScheme="messenger"
|
||||
onClick={() => {
|
||||
pickFile({
|
||||
accept: ["image/png"],
|
||||
}).then((files) => {
|
||||
if (!files.length) return;
|
||||
const id = uuidv4();
|
||||
setDecals([
|
||||
...decals,
|
||||
{
|
||||
id,
|
||||
url: URL.createObjectURL(files[0]),
|
||||
postion: new Vector3(0, 0, 0),
|
||||
type: StickerType.logo,
|
||||
scale: activeModel ? 1.5 : 0.05,
|
||||
},
|
||||
]);
|
||||
setActiveDecal(id);
|
||||
});
|
||||
}}
|
||||
leftIcon={<AddIcon />}
|
||||
>
|
||||
添加LOGO
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Modal
|
||||
isOpen={open}
|
||||
onClose={() => {
|
||||
setOpen(() => false);
|
||||
}}
|
||||
>
|
||||
<ModalOverlay />
|
||||
<ModalContent>
|
||||
<ModalBody>
|
||||
<div className="flex justify-center p-4">
|
||||
<div
|
||||
onClick={() => {}}
|
||||
className={
|
||||
"flex aspect-square w-24 cursor-pointer items-center justify-center rounded-lg border-2 border-gray-300"
|
||||
}
|
||||
>
|
||||
<AddIcon />
|
||||
</div>
|
||||
</div>
|
||||
</ModalBody>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default LogoPanel;
|
||||
|
@ -26,8 +26,9 @@ const RightPanel = () => {
|
||||
onClick={() => {
|
||||
setActiveModel(index);
|
||||
}}
|
||||
className={`${activeModel === index ? "border-2 border-green-300" : ""
|
||||
} flex aspect-square items-center justify-center overflow-hidden rounded-xl bg-blue-100`}
|
||||
className={`${
|
||||
activeModel === index ? "border-2 border-green-300" : ""
|
||||
} flex aspect-square items-center justify-center overflow-hidden rounded-xl bg-blue-100`}
|
||||
>
|
||||
<img
|
||||
className="h-full w-full object-cover"
|
||||
|
@ -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 和文字标签
|
||||
@ -8,51 +8,66 @@ import { useState } from "react";
|
||||
* @returns
|
||||
*/
|
||||
const Sticker = ({ decal }: { decal: DecalSticker }) => {
|
||||
const sticker = useTexture(decal.url);
|
||||
const [scale, setScale] = useState(0.05);
|
||||
const setDecalDragging = useModelStore((state) => state.setDecalDragging);
|
||||
const setActiveDecal = useModelStore((state) => state.setActiveDecal);
|
||||
return (
|
||||
<Decal
|
||||
position={decal.postion}
|
||||
scale={scale}
|
||||
onPointerDown={(ev) => {
|
||||
ev.stopPropagation();
|
||||
setActiveDecal(decal.id)
|
||||
setDecalDragging(true);
|
||||
}}
|
||||
onPointerUp={(ev) => {
|
||||
ev.stopPropagation();
|
||||
setDecalDragging(false);
|
||||
}}
|
||||
// onWheel={(ev) => {
|
||||
// ev.stopPropagation()
|
||||
// setDecalDragging(true)
|
||||
// console.log(ev);
|
||||
|
||||
// // setScale((state) => state += 1)
|
||||
// }}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
rotation={Math.PI}
|
||||
>
|
||||
<meshPhysicalMaterial
|
||||
transparent
|
||||
polygonOffset
|
||||
polygonOffsetFactor={-10}
|
||||
map={sticker}
|
||||
map-flipY={false}
|
||||
map-anisotropy={16}
|
||||
iridescence={1}
|
||||
iridescenceIOR={1}
|
||||
iridescenceThicknessRange={[0, 1400]}
|
||||
roughness={1}
|
||||
clearcoat={0.5}
|
||||
metalness={0.75}
|
||||
toneMapped={false}
|
||||
/>
|
||||
</Decal>
|
||||
);
|
||||
const sticker = useTexture(decal.url);
|
||||
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={decal.scale}
|
||||
onPointerDown={(ev) => {
|
||||
ev.stopPropagation();
|
||||
setActiveDecal(decal.id);
|
||||
setDecalDragging(true);
|
||||
}}
|
||||
onPointerUp={(ev) => {
|
||||
ev.stopPropagation();
|
||||
setDecalDragging(false);
|
||||
}}
|
||||
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
|
||||
polygonOffset
|
||||
polygonOffsetFactor={-10}
|
||||
map={sticker}
|
||||
map-flipY={false}
|
||||
map-anisotropy={16}
|
||||
iridescence={1}
|
||||
iridescenceIOR={1}
|
||||
iridescenceThicknessRange={[0, 1400]}
|
||||
roughness={1}
|
||||
clearcoat={0.5}
|
||||
metalness={0.75}
|
||||
toneMapped={false}
|
||||
/>
|
||||
</Decal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Sticker;
|
||||
|
@ -1,11 +1,11 @@
|
||||
import useModelStore, { StickerType } from "@/store/useModelStore";
|
||||
import { AddIcon, DeleteIcon, EditIcon } from "@chakra-ui/icons";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Input,
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
IconButton,
|
||||
Input,
|
||||
} from "@chakra-ui/react";
|
||||
import { useState } from "react";
|
||||
import { fabric } from "fabric";
|
||||
@ -13,126 +13,131 @@ import { Vector3 } from "three";
|
||||
import { v4 } from "uuid";
|
||||
|
||||
const TextLabelPanel = () => {
|
||||
const decals = useModelStore((state) => state.decals);
|
||||
const setDecals = useModelStore((state) => state.setDecals);
|
||||
const setActiveDecal = useModelStore((state) => state.setActiveDecal);
|
||||
const activeDecal = useModelStore((state) => state.activeDecal);
|
||||
const [editing, setEditing] = useState(false);
|
||||
const [text, setText] = useState("");
|
||||
const [color, setColor] = useState("#ffffff");
|
||||
return (
|
||||
<>
|
||||
{editing ? (
|
||||
<div className={"rounded bg-white p-4"}>
|
||||
<h2 className="font-bold">添加文字</h2>
|
||||
<FormControl>
|
||||
<FormLabel>文字</FormLabel>
|
||||
<Input
|
||||
value={text}
|
||||
onInput={(ev) => {
|
||||
setText(() => (ev.target as HTMLInputElement).value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>文字颜色</FormLabel>
|
||||
<Input
|
||||
type="color"
|
||||
value={color}
|
||||
onInput={(ev) => {
|
||||
setColor(() => (ev.target as HTMLInputElement).value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className={"mt-2 flex justify-center"}>
|
||||
<Button
|
||||
colorScheme="messenger"
|
||||
size={"sm"}
|
||||
onClick={() => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const fabricText = new fabric.Text(text, {
|
||||
fill: color,
|
||||
// fontFamily: 'sans-serif'
|
||||
});
|
||||
const fabricCanvas = new fabric.Canvas(canvas, {
|
||||
width: fabricText.width,
|
||||
height: fabricText.height,
|
||||
});
|
||||
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("");
|
||||
const [color, setColor] = useState("#ffffff");
|
||||
return (
|
||||
<>
|
||||
{editing ? (
|
||||
<div className={"rounded bg-white p-4"}>
|
||||
<h2 className="font-bold">添加文字</h2>
|
||||
<FormControl>
|
||||
<FormLabel>文字</FormLabel>
|
||||
<Input
|
||||
value={text}
|
||||
onInput={(ev) => {
|
||||
setText(() => (ev.target as HTMLInputElement).value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormControl>
|
||||
<FormLabel>文字颜色</FormLabel>
|
||||
<Input
|
||||
type="color"
|
||||
value={color}
|
||||
onInput={(ev) => {
|
||||
setColor(() => (ev.target as HTMLInputElement).value);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className={"mt-2 flex justify-center"}>
|
||||
<Button
|
||||
colorScheme="messenger"
|
||||
size={"sm"}
|
||||
onClick={() => {
|
||||
const canvas = document.createElement("canvas");
|
||||
const fabricText = new fabric.Text(text, {
|
||||
fill: color,
|
||||
// fontFamily: 'sans-serif'
|
||||
});
|
||||
const fabricCanvas = new fabric.Canvas(canvas, {
|
||||
width: fabricText.width,
|
||||
height: fabricText.height,
|
||||
});
|
||||
|
||||
fabricCanvas.add(fabricText);
|
||||
const id = v4();
|
||||
setActiveDecal(id);
|
||||
setDecals([
|
||||
...decals,
|
||||
{
|
||||
id,
|
||||
postion: new Vector3(0, 0, 0),
|
||||
text,
|
||||
url: fabricCanvas.toDataURL(),
|
||||
type: StickerType.text,
|
||||
},
|
||||
]);
|
||||
setText(() => "");
|
||||
setColor(() => "#ffffff");
|
||||
setEditing(() => false);
|
||||
}}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ul>
|
||||
{decals
|
||||
.filter((el) => el.type === StickerType.text)
|
||||
.map((text) => (
|
||||
<li
|
||||
onClick={() => {
|
||||
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" : ""
|
||||
}`}
|
||||
>
|
||||
<span>{text.text}</span>
|
||||
<div className={"right"}>
|
||||
<IconButton
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
}}
|
||||
size={"xs"}
|
||||
icon={<EditIcon />}
|
||||
aria-label={""}
|
||||
/>
|
||||
<IconButton
|
||||
className={"ml-1"}
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
setDecals(decals.filter(el => el.id !== text.id))
|
||||
}}
|
||||
size={"xs"}
|
||||
icon={<DeleteIcon />}
|
||||
aria-label={""}
|
||||
colorScheme="red"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<div className="mt-4 flex justify-center">
|
||||
<Button
|
||||
colorScheme="messenger"
|
||||
onClick={() => {
|
||||
setEditing(() => true);
|
||||
fabricCanvas.add(fabricText);
|
||||
const id = v4();
|
||||
setActiveDecal(id);
|
||||
setDecals([
|
||||
...decals,
|
||||
{
|
||||
id,
|
||||
postion: new Vector3(0, 0, 0),
|
||||
text,
|
||||
url: fabricCanvas.toDataURL(),
|
||||
type: StickerType.text,
|
||||
scale: activeModel ? 1.5 : 0.05,
|
||||
},
|
||||
]);
|
||||
setText(() => "");
|
||||
setColor(() => "#ffffff");
|
||||
setEditing(() => false);
|
||||
}}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<ul>
|
||||
{decals
|
||||
.filter((el) => el.type === StickerType.text)
|
||||
.map((text) => (
|
||||
<li
|
||||
onClick={() => {
|
||||
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"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<span>{text.text}</span>
|
||||
<div className={"right"}>
|
||||
<IconButton
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
}}
|
||||
leftIcon={<AddIcon />}
|
||||
>
|
||||
添加文字
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
size={"xs"}
|
||||
icon={<EditIcon />}
|
||||
aria-label={""}
|
||||
/>
|
||||
<IconButton
|
||||
className={"ml-1"}
|
||||
onClick={(ev) => {
|
||||
ev.stopPropagation();
|
||||
setDecals(decals.filter((el) => el.id !== text.id));
|
||||
}}
|
||||
size={"xs"}
|
||||
icon={<DeleteIcon />}
|
||||
aria-label={""}
|
||||
colorScheme="red"
|
||||
/>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<div className="mt-4 flex justify-center">
|
||||
<Button
|
||||
colorScheme="messenger"
|
||||
onClick={() => {
|
||||
setEditing(() => true);
|
||||
}}
|
||||
leftIcon={<AddIcon />}
|
||||
>
|
||||
添加文字
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default TextLabelPanel;
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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
|
||||
// })),
|
||||
})),
|
||||
);
|
||||
|
||||
|
Reference in New Issue
Block a user