right panel

This commit is contained in:
2023-12-15 17:03:07 +08:00
parent dad2f61aaa
commit ca56a184b2
8 changed files with 171 additions and 138 deletions

View File

@ -1,9 +1,5 @@
import "./App.css";
import ThreeDesigner from "@/components/ThreeDesigner.tsx";
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";
@ -20,8 +16,7 @@ function App() {
<div className={"flex-[2] shrink-0 bg-blue-50 p-4"}>
<RightPanel />
</div>
<AreaIndicator />
<TextureSelector />
{/* <AreaIndicator /> */}
{modelLoading !== 0 && modelLoading !== 100 && (
<div
className={

View File

@ -1,99 +1,18 @@
import { models } from "@/constant/models.ts";
import useModelStore from "@/store/useModelStore.ts";
import styles from "./AreaIndicator.module.scss";
import {
Button,
ButtonGroup,
Icon,
IconButton,
Popover,
PopoverArrow,
PopoverBody,
PopoverCloseButton,
PopoverContent,
PopoverFooter,
PopoverHeader,
PopoverTrigger,
} 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);
const setActiveModel = useModelStore((state) => state.setActiveModel);
const activeArea = useModelStore((state) => state.activeArea);
const setActiveArea = useModelStore((state) => state.setActiveArea);
const [showModelPicker, setShowModelPicker] = useState(false);
return (
<div
className={
"absolute left-2 top-1/2 flex -translate-y-1/2 flex-col items-center justify-center rounded bg-red-200 p-2 shadow-lg transition-shadow hover:shadow-2xl"
}
>
<div className={"flex"}>
<div className={"flex flex-col"}>
<div className={"text-center text-[10px]"}></div>
<IconButton
className={"mt-1"}
onClick={() => {
setShowModelPicker(() => true);
}}
isRound={true}
boxShadow={"lg"}
aria-label={"switch"}
icon={<Icon boxSize={8} as={CgArrowsExchange} />}
/>
</div>
<Popover
closeOnBlur={false}
placement={"right"}
onClose={() => {
setShowModelPicker(() => false);
}}
isOpen={showModelPicker}
>
<PopoverTrigger>
<div className={"w-0"}></div>
</PopoverTrigger>
<PopoverContent>
<PopoverHeader fontWeight="semibold"></PopoverHeader>
<PopoverArrow />
<PopoverCloseButton />
<PopoverBody>
<div className={"max-h-48 overflow-y-auto p-1"}>
{models.map((el, index) => (
<div
onClick={() => {
setActiveModel(index);
setShowModelPicker(() => false);
}}
className={`${styles.modelItem} ${
activeModel === index ? "bg-blue-50" : ""
} flex h-10 cursor-pointer items-center rounded px-3 shadow transition-shadow hover:shadow-lg`}
key={el.path}
>
{el.name}
</div>
))}
</div>
</PopoverBody>
<PopoverFooter display="flex" justifyContent="flex-end">
<ButtonGroup size="sm">
<Button
variant="outline"
onClick={() => {
setShowModelPicker(() => false);
}}
>
</Button>
</ButtonGroup>
</PopoverFooter>
</PopoverContent>
</Popover>
</div>
{models[activeModel].mesh.map((item, index) => (
<div
onClick={() => {

View File

@ -0,0 +1,35 @@
import { useState } from "react"
import { AddIcon } from "@chakra-ui/icons"
import { Button, Modal, ModalBody, ModalContent, ModalOverlay } from "@chakra-ui/react"
const LogoPanel = () => {
const [open, setOpen] = useState(false)
return (
<>
<div className="flex justify-center">
<Button colorScheme='messenger' onClick={() => {
setOpen(() => true)
}} leftIcon={<AddIcon />}>LOGO</Button>
</div>
<Modal isOpen={open} onClose={() => {
setOpen(() => false)
}}>
<ModalOverlay />
<ModalContent>
<ModalBody>
<div className="flex justify-center p-4">
<div onClick={() => {
}} className={"w-24 border-2 border-gray-300 rounded-lg aspect-square flex justify-center items-center cursor-pointer"}>
<AddIcon />
</div>
</div>
</ModalBody>
</ModalContent>
</Modal>
</>
)
}
export default LogoPanel

View File

@ -1,9 +1,12 @@
import { Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
import { models } from "@/constant/models.ts";
import useModelStore from "@/store/useModelStore.ts";
import TextureSelectorPanel from "./TextureSelectorPanel";
import LogoPanel from "./LogoPanel";
const RightPanel = () => {
const activeModel = useModelStore(state => state.activeModel);
const setActiveModel = useModelStore(state => state.setActiveModel);
return (
<>
<Tabs variant={"soft-rounded"}>
@ -17,13 +20,21 @@ const RightPanel = () => {
<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>)}
onClick={() => {
setActiveModel(index)
}}
className={`${activeModel === index ? "border-green-300 border-2" : ""} flex justify-center items-center aspect-square bg-blue-100 overflow-hidden rounded-xl`}>
<img className="w-full h-full object-cover" src={models[activeModel].icon} />
</div>)}
</div>
</TabPanel>
<TabPanel>2</TabPanel>
<TabPanel>
<TextureSelectorPanel />
</TabPanel>
<TabPanel>3</TabPanel>
<TabPanel>4</TabPanel>
<TabPanel>
<LogoPanel />
</TabPanel>
</TabPanels>
</Tabs>
</>

View File

@ -0,0 +1,8 @@
const Sticker = () => {
return (
<>
</>
)
}
export default Sticker

View File

@ -0,0 +1,35 @@
import { models } from "@/constant/models"
import { textures } from "@/constant/textures"
import useModelStore from "@/store/useModelStore"
function TextureSelectorPanel() {
const activeTextures = useModelStore(state => state.activeTextures)
const activeArea = useModelStore(state => state.activeArea)
const setActiveArea = useModelStore(state => state.setActiveArea)
const activeModel = useModelStore(state => state.activeModel)
const setActiveTextures = useModelStore(state => state.setActiveTextures)
return (
<>
<h2 className={"text-sm font-bold"}></h2>
<div className="mt-2 grid grid-cols-8 gap-3">
{models[activeModel].mesh.map((mesh, index) => <div onClick={() => {
setActiveArea(index)
}} className={`${activeArea === index ? "border-2 border-green-300" : ""} aspect-square cursor-pointer rounded overflow-hidden`} key={mesh.name}>
<img src={mesh.icon} className="object-cover w-full h-full" />
</div>)}
</div>
<h2 className={"mt-3 text-sm font-bold"}></h2>
<div className="mt-2 grid grid-cols-6 gap-3">
{textures.map((texture, index) => <div key={texture.path}
onClick={() => {
setActiveTextures(activeArea, index)
}}
className={`${activeTextures[activeArea] === index ? "border-2 border-green-300" : ""} rounded overflow-hidden cursor-pointer`}>
<img className={"w-full h-full object-cover"} src={texture.path} />
</div>)}
</div>
</>
)
}
export default TextureSelectorPanel

View File

@ -1,20 +1,36 @@
import { Canvas } from "@react-three/fiber";
import { useState } from "react";
import { useEffect, useRef, 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";
import { fabric } from 'fabric';
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 canvasRef = useRef<string | undefined>()
const [logoUrl, setLogoUrl] = useState<string | undefined>()
const logo = useTexture("/textures/archlogo.png");
useEffect(() => {
const canvas = document.createElement("canvas")
const fabricCanvas = new fabric.Canvas(canvas)
const text = new fabric.Text("archlinux")
fabricCanvas.add(text)
setLogoUrl(() => canvas.toDataURL())
// canvasRef.current = canvas.toDataURL()
return () => {
// canvasRef.current = undefined
fabricCanvas.dispose()
}
})
const { nodes } = useGLTF(models[activeModel].path);
return (
<>
<group scale={models[activeModel].scale}>
{Object.keys(nodes).map((keyName) => {
if (nodes[keyName].type === "Mesh") {
@ -23,7 +39,9 @@ const CapModel = () => {
<mesh
key={keyName}
onClick={(ev) => {
ev.stopPropagation()
const { x, y, z } = ev.point;
const canvas = new fabric.Canvas("canvas")
setPos(new Vector3(x / models[activeModel].scale, y / models[activeModel].scale, z / models[activeModel].scale));
}}
castShadow
@ -36,7 +54,14 @@ const CapModel = () => {
{areaIndex > -1 ?
<meshStandardMaterial map={new TextureLoader().load(textures[activeTextures[areaIndex]].path)} />
: <meshStandardMaterial color="gray" />}
<Decal position={pos} scale={0.05} rotation={Math.PI}>
<Decal position={pos} scale={0.05}
onClick={(ev) => {
ev.stopPropagation()
console.log(ev);
}}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error
rotation={Math.PI}>
<meshPhysicalMaterial
transparent
polygonOffset
@ -58,17 +83,20 @@ const CapModel = () => {
}
})}
</group>
<OrbitControls key={activeModel} />
</>
);
};
const ThreeScene = () => {
// const activeModel = useModelStore(state => state.activeModel)
return (
<Canvas>
<ambientLight></ambientLight>
<pointLight position={[10, 10, 10]}></pointLight>
<perspectiveCamera />
<OrbitControls />
{/* {activeModel > -1 && } */}
<CapModel />
<Environment preset={"city"} background={false} />
</Canvas>

View File

@ -2,6 +2,7 @@ export const models = [
{
name: "版型1",
path: "/models/baseball_cap_3d.glb",
icon: "/icon/baseball_cap_3d.png",
scale: 18,
mesh: [
{
@ -48,6 +49,7 @@ export const models = [
},
{
name: "版型2",
icon: "/icon/baseball_cap_3d_logo.png",
path: "/models/baseball_cap.glb",
scale: 0.02,
mesh: [