transforming zones to nodes
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { emitter, type GridEvent, useScene } from "@pascal-app/core";
|
import { useScene, type ZoneNode } from "@pascal-app/core";
|
||||||
import { useViewer } from "@pascal-app/viewer";
|
import { useViewer } from "@pascal-app/viewer";
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
@@ -27,10 +27,11 @@ type DragState = {
|
|||||||
export const ZoneBoundaryEditor: React.FC = () => {
|
export const ZoneBoundaryEditor: React.FC = () => {
|
||||||
const { gl, camera } = useThree();
|
const { gl, camera } = useThree();
|
||||||
const selectedZoneId = useViewer((state) => state.selection.zoneId);
|
const selectedZoneId = useViewer((state) => state.selection.zoneId);
|
||||||
const zone = useScene((state) =>
|
const zoneNode = useScene((state) =>
|
||||||
selectedZoneId ? state.zones[selectedZoneId] : null
|
selectedZoneId ? state.nodes[selectedZoneId] : null
|
||||||
);
|
);
|
||||||
const updateZone = useScene((state) => state.updateZone);
|
const zone = zoneNode?.type === "zone" ? (zoneNode as ZoneNode) : null;
|
||||||
|
const updateNode = useScene((state) => state.updateNode);
|
||||||
|
|
||||||
// Local state for dragging
|
// Local state for dragging
|
||||||
const [dragState, setDragState] = useState<DragState | null>(null);
|
const [dragState, setDragState] = useState<DragState | null>(null);
|
||||||
@@ -89,11 +90,11 @@ export const ZoneBoundaryEditor: React.FC = () => {
|
|||||||
// Commit polygon changes
|
// Commit polygon changes
|
||||||
const commitPolygonChange = useCallback(() => {
|
const commitPolygonChange = useCallback(() => {
|
||||||
if (previewPolygon && selectedZoneId) {
|
if (previewPolygon && selectedZoneId) {
|
||||||
updateZone(selectedZoneId, { polygon: previewPolygon });
|
updateNode(selectedZoneId, { polygon: previewPolygon });
|
||||||
}
|
}
|
||||||
setPreviewPolygon(null);
|
setPreviewPolygon(null);
|
||||||
setDragState(null);
|
setDragState(null);
|
||||||
}, [previewPolygon, selectedZoneId, updateZone]);
|
}, [previewPolygon, selectedZoneId, updateNode]);
|
||||||
|
|
||||||
// Handle adding a new vertex at midpoint
|
// Handle adding a new vertex at midpoint
|
||||||
const handleAddVertex = useCallback(
|
const handleAddVertex = useCallback(
|
||||||
@@ -122,10 +123,10 @@ export const ZoneBoundaryEditor: React.FC = () => {
|
|||||||
if (basePolygon.length <= 3) return; // Need at least 3 points
|
if (basePolygon.length <= 3) return; // Need at least 3 points
|
||||||
|
|
||||||
const newPolygon = basePolygon.filter((_, i) => i !== index);
|
const newPolygon = basePolygon.filter((_, i) => i !== index);
|
||||||
updateZone(selectedZoneId, { polygon: newPolygon });
|
updateNode(selectedZoneId, { polygon: newPolygon });
|
||||||
setPreviewPolygon(null);
|
setPreviewPolygon(null);
|
||||||
},
|
},
|
||||||
[zone, selectedZoneId, previewPolygon, updateZone]
|
[zone, selectedZoneId, previewPolygon, updateNode]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Set up pointer move/up listeners for dragging
|
// Set up pointer move/up listeners for dragging
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { emitter, type GridEvent, useScene, ZoneSchema } from "@pascal-app/core";
|
import { emitter, type GridEvent, useScene, ZoneNode, type LevelNode } from "@pascal-app/core";
|
||||||
import { useViewer } from "@pascal-app/viewer";
|
import { useViewer } from "@pascal-app/viewer";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { BufferGeometry, DoubleSide, type Line, type Mesh, Shape, Vector3 } from "three";
|
import { BufferGeometry, DoubleSide, type Line, type Mesh, Shape, Vector3 } from "three";
|
||||||
@@ -61,26 +61,25 @@ const calculateSnapPoint = (
|
|||||||
* Creates a zone with the given polygon points
|
* Creates a zone with the given polygon points
|
||||||
*/
|
*/
|
||||||
const commitZoneDrawing = (
|
const commitZoneDrawing = (
|
||||||
levelId: string,
|
levelId: LevelNode["id"],
|
||||||
points: Array<[number, number]>
|
points: Array<[number, number]>
|
||||||
) => {
|
) => {
|
||||||
const { createZone, zoneIds } = useScene.getState();
|
const { createNode, nodes } = useScene.getState();
|
||||||
|
|
||||||
// Get next zone number
|
// Count existing zones for naming and color cycling
|
||||||
const zoneCount = zoneIds.length;
|
const zoneCount = Object.values(nodes).filter((n) => n.type === "zone").length;
|
||||||
const name = `Zone ${zoneCount + 1}`;
|
const name = `Zone ${zoneCount + 1}`;
|
||||||
|
|
||||||
// Cycle through colors
|
// Cycle through colors
|
||||||
const color = ZONE_COLORS[zoneCount % ZONE_COLORS.length];
|
const color = ZONE_COLORS[zoneCount % ZONE_COLORS.length];
|
||||||
|
|
||||||
const zone = ZoneSchema.parse({
|
const zone = ZoneNode.parse({
|
||||||
levelId,
|
|
||||||
name,
|
name,
|
||||||
polygon: points,
|
polygon: points,
|
||||||
color,
|
color,
|
||||||
});
|
});
|
||||||
|
|
||||||
createZone(zone);
|
createNode(zone, levelId);
|
||||||
|
|
||||||
// Select the newly created zone
|
// Select the newly created zone
|
||||||
useViewer.getState().setSelection({ zoneId: zone.id });
|
useViewer.getState().setSelection({ zoneId: zone.id });
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useScene, type Zone } from "@pascal-app/core";
|
import { useScene, type ZoneNode } from "@pascal-app/core";
|
||||||
import { useViewer } from "@pascal-app/viewer";
|
import { useViewer } from "@pascal-app/viewer";
|
||||||
import { Hexagon, Trash2 } from "lucide-react";
|
import { Hexagon, Trash2 } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
@@ -21,9 +21,9 @@ const PRESET_COLORS = [
|
|||||||
"#06b6d4", // cyan
|
"#06b6d4", // cyan
|
||||||
];
|
];
|
||||||
|
|
||||||
function ZoneItem({ zone }: { zone: Zone }) {
|
function ZoneItem({ zone }: { zone: ZoneNode }) {
|
||||||
const deleteZone = useScene((state) => state.deleteZone);
|
const deleteNode = useScene((state) => state.deleteNode);
|
||||||
const updateZone = useScene((state) => state.updateZone);
|
const updateNode = useScene((state) => state.updateNode);
|
||||||
const selectedZoneId = useViewer((state) => state.selection.zoneId);
|
const selectedZoneId = useViewer((state) => state.selection.zoneId);
|
||||||
const setSelection = useViewer((state) => state.setSelection);
|
const setSelection = useViewer((state) => state.setSelection);
|
||||||
|
|
||||||
@@ -35,14 +35,14 @@ function ZoneItem({ zone }: { zone: Zone }) {
|
|||||||
|
|
||||||
const handleDelete = (e: React.MouseEvent) => {
|
const handleDelete = (e: React.MouseEvent) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
deleteZone(zone.id);
|
deleteNode(zone.id);
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
setSelection({ zoneId: null });
|
setSelection({ zoneId: null });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleColorChange = (color: string) => {
|
const handleColorChange = (color: string) => {
|
||||||
updateZone(zone.id, { color });
|
updateNode(zone.id, { color });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -96,20 +96,17 @@ function ZoneItem({ zone }: { zone: Zone }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function ZonePanel() {
|
export function ZonePanel() {
|
||||||
const zones = useScene((state) => state.zones);
|
const nodes = useScene((state) => state.nodes);
|
||||||
const zoneIds = useScene((state) => state.zoneIds);
|
|
||||||
const currentLevelId = useViewer((state) => state.selection.levelId);
|
const currentLevelId = useViewer((state) => state.selection.levelId);
|
||||||
const setPhase = useEditor((state) => state.setPhase);
|
const setPhase = useEditor((state) => state.setPhase);
|
||||||
const setMode = useEditor((state) => state.setMode);
|
const setMode = useEditor((state) => state.setMode);
|
||||||
const setTool = useEditor((state) => state.setTool);
|
const setTool = useEditor((state) => state.setTool);
|
||||||
|
|
||||||
// Filter zones to only show those for the current level
|
// Filter nodes to get zones for the current level
|
||||||
const levelZones = zoneIds
|
const levelZones = Object.values(nodes).filter(
|
||||||
.map((id) => zones[id])
|
(node): node is ZoneNode =>
|
||||||
.filter(
|
node.type === "zone" && node.parentId === currentLevelId
|
||||||
(zone): zone is Zone =>
|
);
|
||||||
zone !== undefined && zone.levelId === currentLevelId
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleAddZone = () => {
|
const handleAddZone = () => {
|
||||||
if (currentLevelId) {
|
if (currentLevelId) {
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ export { LevelNode } from './nodes/level'
|
|||||||
export { SiteNode } from './nodes/site'
|
export { SiteNode } from './nodes/site'
|
||||||
export { WallNode } from './nodes/wall'
|
export { WallNode } from './nodes/wall'
|
||||||
// Zones
|
// Zones
|
||||||
export type { ZoneNode, ZonePolygon } from './nodes/zone'
|
export { ZoneNode } from './nodes/zone'
|
||||||
|
export type { ZonePolygon } from './nodes/zone'
|
||||||
export type { AnyNodeId, AnyNodeType } from './types'
|
export type { AnyNodeId, AnyNodeType } from './types'
|
||||||
// Union types
|
// Union types
|
||||||
export { AnyNode } from './types'
|
export { AnyNode } from './types'
|
||||||
|
|||||||
Reference in New Issue
Block a user