feat: enhance wall classification and add decal management in wall store
This commit is contained in:
parent
796a4ace45
commit
81664ba765
|
@ -1,5 +1,4 @@
|
|||
import { useMemo } from 'react';
|
||||
import * as THREE from 'three';
|
||||
import * as turf from '@turf/turf';
|
||||
|
||||
export function useWallClassification(walls: Walls) {
|
||||
|
@ -27,12 +26,10 @@ export function useWallClassification(walls: Walls) {
|
|||
|
||||
// Find all minimal cycles (rooms) in the graph
|
||||
const allCycles: string[][] = [];
|
||||
const visited = new Set<string>();
|
||||
|
||||
const findCycles = (startNode: string, currentNode: string, path: string[], depth = 0) => {
|
||||
if (depth > 20) return; // Prevent infinite recursion
|
||||
|
||||
visited.add(currentNode);
|
||||
path.push(currentNode);
|
||||
|
||||
const neighbors = graph.get(currentNode) || [];
|
||||
|
@ -65,10 +62,92 @@ export function useWallClassification(walls: Walls) {
|
|||
.map(cycle => cycle.map(uuid => pointMap.get(uuid)!))
|
||||
.filter(room => isValidRoom(room));
|
||||
|
||||
// Filter out duplicate rooms (same set of points in different orders)
|
||||
const uniqueRooms = removeDuplicateRooms(potentialRooms);
|
||||
|
||||
return uniqueRooms;
|
||||
// ✅ New logic that only removes redundant supersets
|
||||
const filteredRooms = removeSupersetLikeRooms(uniqueRooms, walls);
|
||||
|
||||
return filteredRooms;
|
||||
|
||||
};
|
||||
|
||||
const removeSupersetLikeRooms = (rooms: Point[][], walls: Wall[]): Point[][] => {
|
||||
const toRemove = new Set<number>();
|
||||
|
||||
const getPolygon = (points: Point[]) =>
|
||||
turf.polygon([points.map(p => [p.position[0], p.position[2]])]);
|
||||
|
||||
const getWallSet = (room: Point[]) => {
|
||||
const set = new Set<string>();
|
||||
for (let i = 0; i < room.length - 1; i++) {
|
||||
const p1 = room[i].pointUuid;
|
||||
const p2 = room[i + 1].pointUuid;
|
||||
|
||||
const wall = walls.find(w =>
|
||||
(w.points[0].pointUuid === p1 && w.points[1].pointUuid === p2) ||
|
||||
(w.points[0].pointUuid === p2 && w.points[1].pointUuid === p1)
|
||||
);
|
||||
|
||||
if (wall) {
|
||||
set.add(wall.wallUuid);
|
||||
}
|
||||
}
|
||||
return set;
|
||||
};
|
||||
|
||||
const roomPolygons = rooms.map(getPolygon);
|
||||
const roomAreas = roomPolygons.map(poly => turf.area(poly));
|
||||
const wallSets = rooms.map(getWallSet);
|
||||
|
||||
// First, identify all rooms that are completely contained within others
|
||||
for (let i = 0; i < rooms.length; i++) {
|
||||
for (let j = 0; j < rooms.length; j++) {
|
||||
if (i === j) continue;
|
||||
|
||||
// If room i completely contains room j
|
||||
if (turf.booleanContains(roomPolygons[i], roomPolygons[j])) {
|
||||
// Check if the contained room shares most of its walls with the containing room
|
||||
const sharedWalls = [...wallSets[j]].filter(w => wallSets[i].has(w));
|
||||
const shareRatio = sharedWalls.length / wallSets[j].size;
|
||||
|
||||
// If they share more than 50% of walls, mark the larger one for removal
|
||||
// UNLESS the smaller one is significantly smaller (likely a real room)
|
||||
if (shareRatio > 0.5 && (roomAreas[i] / roomAreas[j] > 2)) {
|
||||
toRemove.add(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: handle cases where a room is divided by segmented walls
|
||||
for (let i = 0; i < rooms.length; i++) {
|
||||
if (toRemove.has(i)) continue;
|
||||
|
||||
for (let j = 0; j < rooms.length; j++) {
|
||||
if (i === j || toRemove.has(j)) continue;
|
||||
|
||||
// Check if these rooms share a significant portion of walls
|
||||
const sharedWalls = [...wallSets[i]].filter(w => wallSets[j].has(w));
|
||||
const shareRatio = Math.max(
|
||||
sharedWalls.length / wallSets[i].size,
|
||||
sharedWalls.length / wallSets[j].size
|
||||
);
|
||||
|
||||
// If they share more than 30% of walls and one is much larger
|
||||
if (shareRatio > 0.3) {
|
||||
const areaRatio = roomAreas[i] / roomAreas[j];
|
||||
if (areaRatio > 2) {
|
||||
// The larger room is likely the undivided version
|
||||
toRemove.add(i);
|
||||
} else if (areaRatio < 0.5) {
|
||||
// The smaller room might be an artifact
|
||||
toRemove.add(j);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rooms.filter((_, idx) => !toRemove.has(idx));
|
||||
};
|
||||
|
||||
// Check if a cycle already exists in our list (considering different orders)
|
||||
|
@ -132,48 +211,10 @@ export function useWallClassification(walls: Walls) {
|
|||
// Rest of the implementation remains the same...
|
||||
const rooms = useMemo(() => findRooms(), [walls]);
|
||||
|
||||
const createPolygon = (points: Point[]) => {
|
||||
const coordinates = points.map(p => [p.position[0], p.position[2]]);
|
||||
return turf.polygon([coordinates]);
|
||||
};
|
||||
|
||||
|
||||
// Get all walls that form part of any room
|
||||
const getRoomWalls = (): Wall[] => {
|
||||
const roomWalls = new Set<Wall>();
|
||||
|
||||
rooms.forEach(room => {
|
||||
for (let i = 0; i < room.length - 1; i++) {
|
||||
const p1 = room[i];
|
||||
const p2 = room[i + 1];
|
||||
|
||||
// Find the wall that connects these two points
|
||||
const wall = walls.find(w =>
|
||||
(w.points[0].pointUuid === p1.pointUuid && w.points[1].pointUuid === p2.pointUuid) ||
|
||||
(w.points[0].pointUuid === p2.pointUuid && w.points[1].pointUuid === p1.pointUuid)
|
||||
);
|
||||
|
||||
if (wall) roomWalls.add(wall);
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(roomWalls);
|
||||
};
|
||||
|
||||
// Determine wall orientation relative to room
|
||||
const getWallOrientation = (wall: Wall) => {
|
||||
const roomWalls = getRoomWalls();
|
||||
const isRoomWall = roomWalls.includes(wall);
|
||||
|
||||
if (!isRoomWall) {
|
||||
return {
|
||||
isRoomWall: false,
|
||||
isOutsideFacing: false
|
||||
};
|
||||
}
|
||||
|
||||
// Find which room this wall belongs to
|
||||
const containingRoom = rooms.find(room => {
|
||||
const findWallType = (wall: Wall) => {
|
||||
// Find all rooms that contain this wall
|
||||
const containingRooms = rooms.filter(room => {
|
||||
for (let i = 0; i < room.length - 1; i++) {
|
||||
const p1 = room[i];
|
||||
const p2 = room[i + 1];
|
||||
|
@ -185,79 +226,39 @@ export function useWallClassification(walls: Walls) {
|
|||
return false;
|
||||
});
|
||||
|
||||
if (!containingRoom) {
|
||||
if (containingRooms.length === 0) {
|
||||
return {
|
||||
isRoomWall: false,
|
||||
isOutsideFacing: false
|
||||
type: 'segment',
|
||||
rooms: []
|
||||
};
|
||||
}
|
||||
|
||||
// Calculate the normal vector to determine outside direction
|
||||
const roomPoints = containingRoom.map(p => new THREE.Vector3(p.position[0], 0, p.position[2]));
|
||||
const centroid = new THREE.Vector3();
|
||||
roomPoints.forEach(p => centroid.add(p));
|
||||
centroid.divideScalar(roomPoints.length);
|
||||
|
||||
const [p1, p2] = wall.points;
|
||||
const wallVector = new THREE.Vector3(
|
||||
p2.position[0] - p1.position[0],
|
||||
0,
|
||||
p2.position[2] - p1.position[2]
|
||||
);
|
||||
const normal = new THREE.Vector3(-wallVector.z, 0, wallVector.x).normalize();
|
||||
|
||||
// Check if normal points away from centroid (outside)
|
||||
const testPoint = new THREE.Vector3(
|
||||
(p1.position[0] + p2.position[0]) / 2 + normal.x,
|
||||
0,
|
||||
(p1.position[2] + p2.position[2]) / 2 + normal.z
|
||||
);
|
||||
|
||||
const pointInside = turf.booleanPointInPolygon(
|
||||
turf.point([testPoint.x, testPoint.z]),
|
||||
createPolygon(containingRoom)
|
||||
);
|
||||
|
||||
// Determine if the wall is in the same order as the room's edge
|
||||
let isSameOrder = false;
|
||||
for (let i = 0; i < containingRoom.length - 1; i++) {
|
||||
const roomP1 = containingRoom[i];
|
||||
const roomP2 = containingRoom[i + 1];
|
||||
if (p1.pointUuid === roomP1.pointUuid && p2.pointUuid === roomP2.pointUuid) {
|
||||
isSameOrder = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (containingRooms.length === 1) {
|
||||
return {
|
||||
isRoomWall: true,
|
||||
isOutsideFacing: isSameOrder ? !pointInside : pointInside
|
||||
type: 'room',
|
||||
rooms: containingRooms
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: 'rooms',
|
||||
rooms: containingRooms
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Rest of the functions remain the same...
|
||||
const getWallType = (wall: Wall): 'room' | 'segment' => {
|
||||
return getWallOrientation(wall).isRoomWall ? 'room' : 'segment';
|
||||
// Update the other functions to use the new return type
|
||||
const getWallType = (wall: Wall): {
|
||||
type: string;
|
||||
rooms: Point[][];
|
||||
} => {
|
||||
return findWallType(wall);
|
||||
};
|
||||
|
||||
const isRoomWall = (wall: Wall): boolean => {
|
||||
return getWallOrientation(wall).isRoomWall;
|
||||
const type = findWallType(wall).type;
|
||||
return type === 'room' || type === 'rooms';
|
||||
};
|
||||
|
||||
const isSegmentWall = (wall: Wall): boolean => {
|
||||
return !getWallOrientation(wall).isRoomWall;
|
||||
};
|
||||
|
||||
const getWallMaterialSide = (wall: Wall): { front: 'inside' | 'outside', back: 'inside' | 'outside' } => {
|
||||
const orientation = getWallOrientation(wall);
|
||||
|
||||
if (!orientation.isRoomWall) {
|
||||
return { front: 'inside', back: 'outside' };
|
||||
}
|
||||
|
||||
return orientation.isOutsideFacing
|
||||
? { front: 'outside', back: 'inside' }
|
||||
: { front: 'inside', back: 'outside' };
|
||||
return findWallType(wall).type === 'segment';
|
||||
};
|
||||
|
||||
return {
|
||||
|
@ -265,7 +266,6 @@ export function useWallClassification(walls: Walls) {
|
|||
getWallType,
|
||||
isRoomWall,
|
||||
isSegmentWall,
|
||||
getWallMaterialSide,
|
||||
findRooms,
|
||||
};
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import * as THREE from 'three';
|
||||
import { useMemo } from 'react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import * as Constants from '../../../../../types/world/worldConstants';
|
||||
|
||||
import insideMaterial from '../../../../../assets/textures/floor/wall-tex.png';
|
||||
|
@ -7,13 +7,20 @@ import outsideMaterial from '../../../../../assets/textures/floor/factory wall t
|
|||
import useWallGeometry from './helpers/useWallGeometry';
|
||||
import { useWallStore } from '../../../../../store/builder/useWallStore';
|
||||
import { useWallClassification } from './helpers/useWallClassification';
|
||||
import { useFrame, useThree } from '@react-three/fiber';
|
||||
import { useWallVisibility } from '../../../../../store/builder/store';
|
||||
import { Decal } from '@react-three/drei';
|
||||
import { Base } from '@react-three/csg';
|
||||
|
||||
function Wall({ wall }: { readonly wall: Wall }) {
|
||||
const { walls } = useWallStore();
|
||||
const { getWallMaterialSide, isRoomWall, rooms } = useWallClassification(walls);
|
||||
console.log('rooms: ', rooms);
|
||||
const materialSide = getWallMaterialSide(wall);
|
||||
const { getWallType } = useWallClassification(walls);
|
||||
const wallType = getWallType(wall);
|
||||
const [startPoint, endPoint] = wall.points;
|
||||
const [visible, setVisible] = useState(true);
|
||||
const { wallVisibility } = useWallVisibility();
|
||||
const meshRef = useRef<any>();
|
||||
const { camera } = useThree();
|
||||
|
||||
const startX = startPoint.position[0];
|
||||
const startZ = startPoint.position[2];
|
||||
|
@ -44,14 +51,9 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
|||
}, [wallLength, wall.wallHeight]);
|
||||
|
||||
const materials = useMemo(() => {
|
||||
// For segment walls (not in a room), use inside material on both sides
|
||||
const frontMaterial = isRoomWall(wall)
|
||||
? (materialSide.front === 'inside' ? insideWallTexture : outsideWallTexture)
|
||||
: insideWallTexture;
|
||||
const frontMaterial = insideWallTexture;
|
||||
|
||||
const backMaterial = isRoomWall(wall)
|
||||
? (materialSide.back === 'inside' ? insideWallTexture : outsideWallTexture)
|
||||
: insideWallTexture;
|
||||
const backMaterial = outsideWallTexture;
|
||||
|
||||
return [
|
||||
new THREE.MeshStandardMaterial({
|
||||
|
@ -69,41 +71,58 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
|||
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide }), // Top
|
||||
new THREE.MeshStandardMaterial({ color: Constants.wallConfig.defaultColor, side: THREE.DoubleSide }) // Bottom
|
||||
];
|
||||
}, [insideWallTexture, outsideWallTexture, materialSide, isRoomWall, wall]);
|
||||
}, [insideWallTexture, outsideWallTexture, wall]);
|
||||
|
||||
const geometry = useWallGeometry(wallLength, wall.wallHeight, wall.wallThickness);
|
||||
|
||||
useFrame(() => {
|
||||
if (!meshRef.current) return;
|
||||
const v = new THREE.Vector3();
|
||||
const u = new THREE.Vector3();
|
||||
|
||||
if (!wallVisibility && wallType.type === 'room') {
|
||||
meshRef.current.getWorldDirection(v);
|
||||
camera.getWorldDirection(u);
|
||||
setVisible((2 * v.dot(u)) <= 0.1);
|
||||
|
||||
} else {
|
||||
setVisible(true);
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<group
|
||||
name={`Wall-${wall.wallUuid}`}
|
||||
userData={wall}
|
||||
position={[centerX, centerY, centerZ]}
|
||||
rotation={[0, -angle, 0]}
|
||||
visible={visible}
|
||||
>
|
||||
<mesh geometry={geometry}>
|
||||
<Base ref={meshRef} geometry={geometry} visible>
|
||||
{materials.map((material, index) => (
|
||||
<primitive key={index} object={material} attach={`material-${index}`} />
|
||||
))}
|
||||
</mesh>
|
||||
|
||||
{wall.decals.map((decal) => {
|
||||
return (
|
||||
<Decal
|
||||
// debug
|
||||
position={[decal.decalPosition[0], decal.decalPosition[1], wall.wallThickness / 2]}
|
||||
rotation={[0, 0, decal.decalRotation]}
|
||||
scale={[decal.decalScale, decal.decalScale, 0.001]}
|
||||
>
|
||||
<meshBasicMaterial
|
||||
map={outsideWallTexture}
|
||||
side={THREE.DoubleSide}
|
||||
polygonOffset
|
||||
polygonOffsetFactor={-1}
|
||||
/>
|
||||
</Decal>
|
||||
)
|
||||
})}
|
||||
</Base>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export default Wall;
|
||||
|
||||
|
||||
// A--------------------------B--------------G
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// | | |
|
||||
// F--------------------------C--------------H
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// | |
|
||||
// E--------------------------D
|
|
@ -6,7 +6,6 @@ import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
|
|||
import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store';
|
||||
import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping';
|
||||
import { usePointSnapping } from '../../point/helpers/usePointSnapping';
|
||||
import * as Constants from '../../../../types/world/worldConstants';
|
||||
import ReferenceLine from '../../line/reference/referenceLine';
|
||||
|
||||
interface ReferenceWallProps {
|
||||
|
@ -73,6 +72,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
|
|||
inSideMaterial: 'default',
|
||||
wallThickness: wallThickness,
|
||||
wallHeight: wallHeight,
|
||||
decals: []
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -90,7 +90,8 @@ function WallCreator() {
|
|||
outSideMaterial: 'default',
|
||||
inSideMaterial: 'default',
|
||||
wallThickness: wallThickness,
|
||||
wallHeight: wallHeight
|
||||
wallHeight: wallHeight,
|
||||
decals: []
|
||||
};
|
||||
addWall(wall);
|
||||
setTempPoints([newPoint]);
|
||||
|
|
|
@ -35,32 +35,32 @@ const SelectionControls: React.FC = () => {
|
|||
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
|
||||
const { projectId } = useParams();
|
||||
|
||||
const isDragging = useRef(false);
|
||||
const isLeftMouseDown = useRef(false);
|
||||
const isSelecting = useRef(false);
|
||||
const isRightClick = useRef(false);
|
||||
const rightClickMoved = useRef(false);
|
||||
const isCtrlSelecting = useRef(false);
|
||||
const isShiftSelecting = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!camera || !scene || toggleView) return;
|
||||
|
||||
const canvasElement = gl.domElement;
|
||||
canvasElement.tabIndex = 0;
|
||||
|
||||
let isDragging = false;
|
||||
let isLeftMouseDown = false;
|
||||
let isSelecting = false;
|
||||
let isRightClick = false;
|
||||
let rightClickMoved = false;
|
||||
let isCtrlSelecting = false;
|
||||
let isShiftSelecting = false;
|
||||
|
||||
const helper = new SelectionHelper(gl);
|
||||
|
||||
const onPointerDown = (event: PointerEvent) => {
|
||||
if (event.button === 2) {
|
||||
isRightClick = true;
|
||||
rightClickMoved = false;
|
||||
isRightClick.current = true;
|
||||
rightClickMoved.current = false;
|
||||
} else if (event.button === 0) {
|
||||
isSelecting = false;
|
||||
isCtrlSelecting = event.ctrlKey;
|
||||
isShiftSelecting = event.shiftKey;
|
||||
isLeftMouseDown = true;
|
||||
isDragging = false;
|
||||
isSelecting.current = false;
|
||||
isCtrlSelecting.current = event.ctrlKey;
|
||||
isShiftSelecting.current = event.shiftKey;
|
||||
isLeftMouseDown.current = true;
|
||||
isDragging.current = false;
|
||||
if (event.ctrlKey && duplicatedObjects.length === 0) {
|
||||
if (controls) (controls as any).enabled = false;
|
||||
selectionBox.startPoint.set(pointer.x, pointer.y, 0);
|
||||
|
@ -69,45 +69,46 @@ const SelectionControls: React.FC = () => {
|
|||
};
|
||||
|
||||
const onPointerMove = (event: PointerEvent) => {
|
||||
if (isRightClick) {
|
||||
rightClickMoved = true;
|
||||
if (isRightClick.current) {
|
||||
rightClickMoved.current = true;
|
||||
}
|
||||
if (isLeftMouseDown) {
|
||||
isDragging = true;
|
||||
if (isLeftMouseDown.current) {
|
||||
isDragging.current = true;
|
||||
}
|
||||
isSelecting = true;
|
||||
if (helper.isDown && event.ctrlKey && duplicatedObjects.length === 0 && isCtrlSelecting) {
|
||||
isSelecting.current = true;
|
||||
if (helper.isDown && event.ctrlKey && duplicatedObjects.length === 0 && isCtrlSelecting.current) {
|
||||
selectionBox.endPoint.set(pointer.x, pointer.y, 0);
|
||||
}
|
||||
};
|
||||
|
||||
const onPointerUp = (event: PointerEvent) => {
|
||||
if (event.button === 2 && !event.ctrlKey && !event.shiftKey) {
|
||||
isRightClick = false;
|
||||
if (!rightClickMoved) {
|
||||
isRightClick.current = false;
|
||||
rightClickMoved.current = false;
|
||||
if (!rightClickMoved.current) {
|
||||
clearSelection();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSelecting && isCtrlSelecting) {
|
||||
isCtrlSelecting = false;
|
||||
isSelecting = false;
|
||||
if (isSelecting.current && isCtrlSelecting.current) {
|
||||
isCtrlSelecting.current = false;
|
||||
isSelecting.current = false;
|
||||
if (event.ctrlKey && duplicatedObjects.length === 0) {
|
||||
selectAssets();
|
||||
}
|
||||
} else if (!isSelecting && selectedAssets.length > 0 && ((!event.ctrlKey && !event.shiftKey && pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) || event.button !== 0)) {
|
||||
} else if (!isSelecting.current && selectedAssets.length > 0 && ((!event.ctrlKey && !event.shiftKey && pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) || event.button !== 0)) {
|
||||
clearSelection();
|
||||
helper.enabled = true;
|
||||
isCtrlSelecting = false;
|
||||
isCtrlSelecting.current = false;
|
||||
} else if (controls) {
|
||||
(controls as any).enabled = true;
|
||||
}
|
||||
|
||||
if (!isDragging && isLeftMouseDown && isShiftSelecting && event.shiftKey) {
|
||||
isShiftSelecting = false;
|
||||
isLeftMouseDown = false;
|
||||
isDragging = false;
|
||||
if (!isDragging.current && isLeftMouseDown.current && isShiftSelecting.current && event.shiftKey) {
|
||||
isShiftSelecting.current = false;
|
||||
isLeftMouseDown.current = false;
|
||||
isDragging.current = false;
|
||||
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersects = raycaster.intersectObjects(scene.children, true)
|
||||
|
@ -167,7 +168,7 @@ const SelectionControls: React.FC = () => {
|
|||
|
||||
const onContextMenu = (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
if (!rightClickMoved) {
|
||||
if (!rightClickMoved.current) {
|
||||
clearSelection();
|
||||
}
|
||||
};
|
||||
|
@ -207,6 +208,7 @@ const SelectionControls: React.FC = () => {
|
|||
}, [activeModule]);
|
||||
|
||||
useFrame(() => {
|
||||
console.log(rightClickMoved.current);
|
||||
if (pastedObjects.length === 0 && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0) {
|
||||
selectionGroup.current.position.set(0, 0, 0);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,12 @@ interface WallStore {
|
|||
addWall: (wall: Wall) => void;
|
||||
updateWall: (uuid: string, updated: Partial<Wall>) => void;
|
||||
removeWall: (uuid: string) => void;
|
||||
addDecal: (wallUuid: string, decal: Decal) => void;
|
||||
updateDecal: (decalUuid: string, decal: Decal) => void;
|
||||
removeDecal: (decalUuid: string) => void;
|
||||
updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void;
|
||||
updateDecalRotation: (decalUuid: string, rotation: number) => void;
|
||||
updateDecalScale: (decalUuid: string, scale: number) => void;
|
||||
|
||||
removePoint: (pointUuid: string) => Wall[];
|
||||
setPosition: (pointUuid: string, position: [number, number, number]) => void;
|
||||
|
@ -40,6 +46,58 @@ export const useWallStore = create<WallStore>()(
|
|||
state.walls = state.walls.filter(w => w.wallUuid !== uuid);
|
||||
}),
|
||||
|
||||
addDecal: (wallUuid, decal) => set((state) => {
|
||||
const wallToUpdate = state.walls.find(w => w.wallUuid === wallUuid);
|
||||
if (wallToUpdate) {
|
||||
wallToUpdate.decals.push(decal);
|
||||
}
|
||||
}),
|
||||
|
||||
updateDecal: (decalUuid, decal) => set((state) => {
|
||||
for (const wall of state.walls) {
|
||||
const decalToUpdate = wall.decals.find(d => d.decalUuid === decalUuid);
|
||||
if (decalToUpdate) {
|
||||
Object.assign(decalToUpdate, decal);
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
removeDecal: (decalUuid) => set((state) => {
|
||||
for (const wall of state.walls) {
|
||||
wall.decals = wall.decals.filter(d => d.decalUuid !== decalUuid);
|
||||
}
|
||||
}),
|
||||
|
||||
updateDecalPosition: (decalUuid, position) => set((state) => {
|
||||
for (const wall of state.walls) {
|
||||
const decal = wall.decals.find(d => d.decalUuid === decalUuid);
|
||||
if (decal) {
|
||||
decal.decalPosition = position;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
updateDecalRotation: (decalUuid, rotation) => set((state) => {
|
||||
for (const wall of state.walls) {
|
||||
const decal = wall.decals.find(d => d.decalUuid === decalUuid);
|
||||
if (decal) {
|
||||
decal.decalRotation = rotation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
updateDecalScale: (decalUuid, scale) => set((state) => {
|
||||
for (const wall of state.walls) {
|
||||
const decal = wall.decals.find(d => d.decalUuid === decalUuid);
|
||||
if (decal) {
|
||||
decal.decalScale = scale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}),
|
||||
|
||||
removePoint: (pointUuid) => {
|
||||
const removedWalls: Wall[] = [];
|
||||
set((state) => {
|
||||
|
|
|
@ -47,6 +47,15 @@ interface Point {
|
|||
|
||||
// Wall
|
||||
|
||||
interface Decal {
|
||||
decalUuid: string;
|
||||
decalName: string;
|
||||
decalId: string;
|
||||
decalPosition: [number, number, number];
|
||||
decalRotation: number;
|
||||
decalScale: number;
|
||||
}
|
||||
|
||||
interface Wall {
|
||||
wallUuid: string;
|
||||
points: [Point, Point];
|
||||
|
@ -54,6 +63,7 @@ interface Wall {
|
|||
inSideMaterial: string;
|
||||
wallThickness: number;
|
||||
wallHeight: number;
|
||||
decals: Decal[]
|
||||
}
|
||||
|
||||
type Walls = Wall[];
|
||||
|
|
Loading…
Reference in New Issue