3d요소 디자인 변경
This commit is contained in:
parent
8a318ea741
commit
9b337496b8
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { Canvas, useThree } from "@react-three/fiber";
|
||||
import { OrbitControls, Grid, Box } from "@react-three/drei";
|
||||
import { OrbitControls, Grid, Box, Text } from "@react-three/drei";
|
||||
import { Suspense, useRef, useState, useEffect } from "react";
|
||||
import * as THREE from "three";
|
||||
|
||||
|
|
@ -68,16 +68,19 @@ function MaterialBox({
|
|||
}) {
|
||||
const meshRef = useRef<THREE.Mesh>(null);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [isValidPosition, setIsValidPosition] = useState(true); // 배치 가능 여부 (시각 피드백용)
|
||||
const dragStartPos = useRef<{ x: number; y: number; z: number }>({ x: 0, y: 0, z: 0 });
|
||||
const mouseStartPos = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||
const { camera, gl } = useThree();
|
||||
|
||||
// 특정 좌표에 요소를 배치할 수 있는지 확인하고, 필요하면 Y 위치를 조정
|
||||
const checkCollisionAndAdjustY = (x: number, y: number, z: number): { hasCollision: boolean; adjustedY: number } => {
|
||||
const palletHeight = 0.3; // 팔레트 높이
|
||||
const palletGap = 0.05; // 팔레트와 박스 사이 간격
|
||||
|
||||
const mySize = placement.size_x || gridSize; // 내 크기 (5)
|
||||
const myHalfSize = mySize / 2; // 2.5
|
||||
const mySizeY = placement.size_y || gridSize; // 내 높이 (5)
|
||||
const mySizeY = placement.size_y || gridSize; // 박스 높이 (5)
|
||||
const myTotalHeight = mySizeY + palletHeight + palletGap; // 팔레트 포함한 전체 높이
|
||||
|
||||
let maxYBelow = gridSize / 2; // 기본 바닥 높이 (2.5)
|
||||
|
||||
|
|
@ -89,24 +92,33 @@ function MaterialBox({
|
|||
|
||||
const pSize = p.size_x || gridSize; // 상대방 크기 (5)
|
||||
const pHalfSize = pSize / 2; // 2.5
|
||||
const pSizeY = p.size_y || gridSize; // 상대방 높이 (5)
|
||||
const pSizeY = p.size_y || gridSize; // 상대방 박스 높이 (5)
|
||||
const pTotalHeight = pSizeY + palletHeight + palletGap; // 상대방 팔레트 포함 전체 높이
|
||||
|
||||
// XZ 평면에서 겹치는지 확인
|
||||
const isXZOverlapping =
|
||||
Math.abs(x - p.position_x) < myHalfSize + pHalfSize && // X축 겹침
|
||||
Math.abs(z - p.position_z) < myHalfSize + pHalfSize; // Z축 겹침
|
||||
// 1단계: 넓은 범위로 겹침 감지 (살짝만 가까이 가도 감지)
|
||||
const detectionMargin = 0.5; // 감지 범위 확장 (0.5 유닛)
|
||||
const isNearby =
|
||||
Math.abs(x - p.position_x) < myHalfSize + pHalfSize + detectionMargin && // X축 근접
|
||||
Math.abs(z - p.position_z) < myHalfSize + pHalfSize + detectionMargin; // Z축 근접
|
||||
|
||||
if (isXZOverlapping) {
|
||||
// 같은 XZ 위치에 요소가 있음
|
||||
// 그 요소의 윗면 높이 계산 (중심 + 높이/2)
|
||||
const topOfOtherElement = p.position_y + pSizeY / 2;
|
||||
// 내가 올라갈 Y 위치는 윗면 + 내 높이/2
|
||||
const myYOnTop = topOfOtherElement + mySizeY / 2;
|
||||
if (isNearby) {
|
||||
// 2단계: 실제로 겹치는지 정확히 판단 (바닥에 둘지, 위에 둘지 결정)
|
||||
const isActuallyOverlapping =
|
||||
Math.abs(x - p.position_x) < myHalfSize + pHalfSize && // X축 실제 겹침
|
||||
Math.abs(z - p.position_z) < myHalfSize + pHalfSize; // Z축 실제 겹침
|
||||
|
||||
// 가장 높은 위치 기록
|
||||
if (myYOnTop > maxYBelow) {
|
||||
maxYBelow = myYOnTop;
|
||||
if (isActuallyOverlapping) {
|
||||
// 실제로 겹침: 위에 배치
|
||||
// 상대방 전체 높이 (박스 + 팔레트)의 윗면 계산
|
||||
const topOfOtherElement = p.position_y + pTotalHeight / 2;
|
||||
// 내 전체 높이의 절반을 더해서 내가 올라갈 Y 위치 계산
|
||||
const myYOnTop = topOfOtherElement + myTotalHeight / 2;
|
||||
|
||||
if (myYOnTop > maxYBelow) {
|
||||
maxYBelow = myYOnTop;
|
||||
}
|
||||
}
|
||||
// 근처에만 있고 실제로 안 겹침: 바닥에 배치 (maxYBelow 유지)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,12 +194,9 @@ function MaterialBox({
|
|||
const snappedX = snapToGrid(finalX, gridSize);
|
||||
const snappedZ = snapToGrid(finalZ, gridSize);
|
||||
|
||||
// 충돌 체크 및 Y 위치 조정 (시각 피드백용)
|
||||
// 충돌 체크 및 Y 위치 조정
|
||||
const { adjustedY } = checkCollisionAndAdjustY(snappedX, dragStartPos.current.y, snappedZ);
|
||||
|
||||
// 시각 피드백: 항상 유효한 위치 (위로 올라가기 때문)
|
||||
setIsValidPosition(true);
|
||||
|
||||
// 즉시 mesh 위치 업데이트 (조정된 Y 위치로)
|
||||
meshRef.current.position.set(finalX, adjustedY, finalZ);
|
||||
|
||||
|
|
@ -285,11 +294,19 @@ function MaterialBox({
|
|||
// 요소가 설정되었는지 확인
|
||||
const isConfigured = !!(placement.material_name && placement.quantity && placement.unit);
|
||||
|
||||
const boxHeight = placement.size_y || gridSize;
|
||||
const boxWidth = placement.size_x || gridSize;
|
||||
const boxDepth = placement.size_z || gridSize;
|
||||
const palletHeight = 0.3; // 팔레트 높이
|
||||
const palletGap = 0.05; // 팔레트와 박스 사이 간격 (매우 작게)
|
||||
|
||||
// 팔레트 위치 계산: 박스 하단부터 시작
|
||||
const palletYOffset = -(boxHeight / 2) - palletHeight / 2 - palletGap;
|
||||
|
||||
return (
|
||||
<Box
|
||||
<group
|
||||
ref={meshRef}
|
||||
position={[placement.position_x, placement.position_y, placement.position_z]}
|
||||
args={[placement.size_x, placement.size_y, placement.size_z]}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent?.stopPropagation();
|
||||
|
|
@ -298,7 +315,6 @@ function MaterialBox({
|
|||
}}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerOver={() => {
|
||||
// 뷰어 모드(onDrag 없음)에서는 기본 커서, 편집 모드에서는 grab 커서
|
||||
if (onDrag) {
|
||||
gl.domElement.style.cursor = isSelected ? "grab" : "pointer";
|
||||
} else {
|
||||
|
|
@ -311,15 +327,142 @@ function MaterialBox({
|
|||
}
|
||||
}}
|
||||
>
|
||||
<meshStandardMaterial
|
||||
color={isDragging ? (isValidPosition ? "#22c55e" : "#ef4444") : placement.color}
|
||||
opacity={isConfigured ? (isSelected ? 1 : 0.8) : 0.5}
|
||||
transparent
|
||||
emissive={isDragging ? (isValidPosition ? "#22c55e" : "#ef4444") : isSelected ? "#ffffff" : "#000000"}
|
||||
emissiveIntensity={isDragging ? 0.5 : isSelected ? 0.2 : 0}
|
||||
wireframe={!isConfigured}
|
||||
/>
|
||||
</Box>
|
||||
{/* 팔레트 그룹 - 박스 하단에 붙어있도록 */}
|
||||
<group position={[0, palletYOffset, 0]}>
|
||||
{/* 상단 가로 판자들 (5개) */}
|
||||
{[-boxDepth * 0.4, -boxDepth * 0.2, 0, boxDepth * 0.2, boxDepth * 0.4].map((zOffset, idx) => (
|
||||
<Box
|
||||
key={`top-${idx}`}
|
||||
args={[boxWidth * 0.95, palletHeight * 0.3, boxDepth * 0.15]}
|
||||
position={[0, palletHeight * 0.35, zOffset]}
|
||||
>
|
||||
<meshStandardMaterial color="#8B4513" roughness={0.95} metalness={0.0} />
|
||||
<lineSegments>
|
||||
<edgesGeometry args={[new THREE.BoxGeometry(boxWidth * 0.95, palletHeight * 0.3, boxDepth * 0.15)]} />
|
||||
<lineBasicMaterial color="#000000" opacity={0.3} transparent />
|
||||
</lineSegments>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{/* 중간 세로 받침대 (3개) */}
|
||||
{[-boxWidth * 0.35, 0, boxWidth * 0.35].map((xOffset, idx) => (
|
||||
<Box
|
||||
key={`middle-${idx}`}
|
||||
args={[boxWidth * 0.12, palletHeight * 0.4, boxDepth * 0.2]}
|
||||
position={[xOffset, 0, 0]}
|
||||
>
|
||||
<meshStandardMaterial color="#654321" roughness={0.98} metalness={0.0} />
|
||||
<lineSegments>
|
||||
<edgesGeometry args={[new THREE.BoxGeometry(boxWidth * 0.12, palletHeight * 0.4, boxDepth * 0.2)]} />
|
||||
<lineBasicMaterial color="#000000" opacity={0.4} transparent />
|
||||
</lineSegments>
|
||||
</Box>
|
||||
))}
|
||||
|
||||
{/* 하단 가로 판자들 (3개) */}
|
||||
{[-boxDepth * 0.3, 0, boxDepth * 0.3].map((zOffset, idx) => (
|
||||
<Box
|
||||
key={`bottom-${idx}`}
|
||||
args={[boxWidth * 0.95, palletHeight * 0.25, boxDepth * 0.18]}
|
||||
position={[0, -palletHeight * 0.35, zOffset]}
|
||||
>
|
||||
<meshStandardMaterial color="#6B4423" roughness={0.97} metalness={0.0} />
|
||||
<lineSegments>
|
||||
<edgesGeometry args={[new THREE.BoxGeometry(boxWidth * 0.95, palletHeight * 0.25, boxDepth * 0.18)]} />
|
||||
<lineBasicMaterial color="#000000" opacity={0.3} transparent />
|
||||
</lineSegments>
|
||||
</Box>
|
||||
))}
|
||||
</group>
|
||||
|
||||
{/* 메인 박스 */}
|
||||
<Box args={[boxWidth, boxHeight, boxDepth]} position={[0, 0, 0]}>
|
||||
{/* 메인 재질 - 골판지 느낌 */}
|
||||
<meshStandardMaterial
|
||||
color={placement.color}
|
||||
opacity={isConfigured ? (isSelected ? 1 : 0.8) : 0.5}
|
||||
transparent
|
||||
emissive={isSelected ? "#ffffff" : "#000000"}
|
||||
emissiveIntensity={isSelected ? 0.2 : 0}
|
||||
wireframe={!isConfigured}
|
||||
roughness={0.95}
|
||||
metalness={0.05}
|
||||
/>
|
||||
|
||||
{/* 외곽선 - 더 진하게 */}
|
||||
<lineSegments>
|
||||
<edgesGeometry args={[new THREE.BoxGeometry(boxWidth, boxHeight, boxDepth)]} />
|
||||
<lineBasicMaterial color="#1a1a1a" opacity={0.8} transparent />
|
||||
</lineSegments>
|
||||
</Box>
|
||||
|
||||
{/* 포장 테이프 (가로) - 윗면 */}
|
||||
{isConfigured && (
|
||||
<>
|
||||
{/* 테이프 세로 */}
|
||||
<Box args={[boxWidth * 0.12, 0.02, boxDepth * 0.95]} position={[0, boxHeight / 2 + 0.01, 0]}>
|
||||
<meshStandardMaterial color="#d4a574" opacity={0.7} transparent roughness={0.3} metalness={0.3} />
|
||||
</Box>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* 자재명 라벨 스티커 (앞면) - 흰색 배경 */}
|
||||
{isConfigured && placement.material_name && (
|
||||
<group position={[0, boxHeight * 0.1, boxDepth / 2 + 0.02]}>
|
||||
{/* 라벨 배경 (흰색 스티커) */}
|
||||
<Box args={[boxWidth * 0.7, boxHeight * 0.25, 0.01]}>
|
||||
<meshStandardMaterial color="#ffffff" roughness={0.4} metalness={0.1} />
|
||||
<lineSegments>
|
||||
<edgesGeometry args={[new THREE.BoxGeometry(boxWidth * 0.7, boxHeight * 0.25, 0.01)]} />
|
||||
<lineBasicMaterial color="#cccccc" opacity={0.8} transparent />
|
||||
</lineSegments>
|
||||
</Box>
|
||||
{/* 라벨 텍스트 */}
|
||||
<Text
|
||||
position={[0, 0, 0.02]}
|
||||
fontSize={0.3}
|
||||
color="#000000"
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
fontWeight="bold"
|
||||
>
|
||||
{placement.material_name}
|
||||
</Text>
|
||||
</group>
|
||||
)}
|
||||
|
||||
{/* 수량 라벨 (윗면) - 큰 글씨 */}
|
||||
{isConfigured && placement.quantity && (
|
||||
<Text
|
||||
position={[0, boxHeight / 2 + 0.03, 0]}
|
||||
rotation={[-Math.PI / 2, 0, 0]}
|
||||
fontSize={0.6}
|
||||
color="#000000"
|
||||
anchorX="center"
|
||||
anchorY="middle"
|
||||
outlineWidth={0.1}
|
||||
outlineColor="#ffffff"
|
||||
fontWeight="bold"
|
||||
>
|
||||
{placement.quantity} {placement.unit || ""}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{/* 디테일 표시 */}
|
||||
{isConfigured && (
|
||||
<>
|
||||
{/* 화살표 표시 (이 쪽이 위) */}
|
||||
<group position={[0, boxHeight * 0.35, boxDepth / 2 + 0.01]}>
|
||||
<Text fontSize={0.6} color="#000000" anchorX="center" anchorY="middle">
|
||||
▲
|
||||
</Text>
|
||||
<Text position={[0, -0.4, 0]} fontSize={0.3} color="#666666" anchorX="center" anchorY="middle">
|
||||
UP
|
||||
</Text>
|
||||
</group>
|
||||
</>
|
||||
)}
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue