camera for zones and levels
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
type BuildingNode,
|
type BuildingNode,
|
||||||
|
emitter,
|
||||||
LevelNode,
|
LevelNode,
|
||||||
useScene,
|
useScene,
|
||||||
type ZoneNode,
|
type ZoneNode,
|
||||||
@@ -7,6 +8,7 @@ import {
|
|||||||
import { useViewer } from "@pascal-app/viewer";
|
import { useViewer } from "@pascal-app/viewer";
|
||||||
import {
|
import {
|
||||||
Building2,
|
Building2,
|
||||||
|
Camera,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
Layers,
|
Layers,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
@@ -155,11 +157,13 @@ function BuildingSelector() {
|
|||||||
function LevelsSection() {
|
function LevelsSection() {
|
||||||
const nodes = useScene((state) => state.nodes);
|
const nodes = useScene((state) => state.nodes);
|
||||||
const createNode = useScene((state) => state.createNode);
|
const createNode = useScene((state) => state.createNode);
|
||||||
|
const updateNode = useScene((state) => state.updateNode);
|
||||||
const selectedBuildingId = useViewer((state) => state.selection.buildingId);
|
const selectedBuildingId = useViewer((state) => state.selection.buildingId);
|
||||||
const selectedLevelId = useViewer((state) => state.selection.levelId);
|
const selectedLevelId = useViewer((state) => state.selection.levelId);
|
||||||
const setSelection = useViewer((state) => state.setSelection);
|
const setSelection = useViewer((state) => state.setSelection);
|
||||||
|
|
||||||
const [referencesLevelId, setReferencesLevelId] = useState<string | null>(null);
|
const [referencesLevelId, setReferencesLevelId] = useState<string | null>(null);
|
||||||
|
const [cameraPopoverOpen, setCameraPopoverOpen] = useState<string | null>(null);
|
||||||
|
|
||||||
const building = selectedBuildingId
|
const building = selectedBuildingId
|
||||||
? (nodes[selectedBuildingId] as BuildingNode)
|
? (nodes[selectedBuildingId] as BuildingNode)
|
||||||
@@ -215,6 +219,72 @@ function LevelsSection() {
|
|||||||
<Layers className="w-3.5 h-3.5 shrink-0" />
|
<Layers className="w-3.5 h-3.5 shrink-0" />
|
||||||
<span className="truncate">{level.name || `Level ${level.level}`}</span>
|
<span className="truncate">{level.name || `Level ${level.level}`}</span>
|
||||||
</button>
|
</button>
|
||||||
|
{/* Camera snapshot button */}
|
||||||
|
<Popover open={cameraPopoverOpen === level.id} onOpenChange={(open) => setCameraPopoverOpen(open ? level.id : null)}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<button
|
||||||
|
className={cn(
|
||||||
|
"relative opacity-0 group-hover/level:opacity-100 w-6 h-6 mr-1 flex items-center justify-center rounded cursor-pointer shrink-0",
|
||||||
|
selectedLevelId === level.id
|
||||||
|
? "hover:bg-primary-foreground/20"
|
||||||
|
: "hover:bg-accent"
|
||||||
|
)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
title="Camera snapshot"
|
||||||
|
>
|
||||||
|
<Camera className="w-3.5 h-3.5" />
|
||||||
|
{level.camera && (
|
||||||
|
<span className="absolute top-0.5 right-0.5 w-1.5 h-1.5 rounded-full bg-primary" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
side="right"
|
||||||
|
align="start"
|
||||||
|
className="w-auto p-1"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
{level.camera && (
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded cursor-pointer text-popover-foreground hover:bg-accent text-left w-full"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
emitter.emit("camera-controls:view", { nodeId: level.id });
|
||||||
|
setCameraPopoverOpen(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Camera className="w-3.5 h-3.5" />
|
||||||
|
View snapshot
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded cursor-pointer text-popover-foreground hover:bg-accent text-left w-full"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
emitter.emit("camera-controls:capture", { nodeId: level.id });
|
||||||
|
setCameraPopoverOpen(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Camera className="w-3.5 h-3.5" />
|
||||||
|
{level.camera ? "Update snapshot" : "Take snapshot"}
|
||||||
|
</button>
|
||||||
|
{level.camera && (
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded cursor-pointer text-popover-foreground hover:bg-destructive hover:text-destructive-foreground text-left w-full"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
updateNode(level.id, { camera: undefined });
|
||||||
|
setCameraPopoverOpen(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
|
Clear snapshot
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<button
|
<button
|
||||||
@@ -307,6 +377,7 @@ function calculatePolygonArea(polygon: Array<[number, number]>): number {
|
|||||||
|
|
||||||
function ZoneItem({ zone }: { zone: ZoneNode }) {
|
function ZoneItem({ zone }: { zone: ZoneNode }) {
|
||||||
const [renameOpen, setRenameOpen] = useState(false);
|
const [renameOpen, setRenameOpen] = useState(false);
|
||||||
|
const [cameraPopoverOpen, setCameraPopoverOpen] = useState(false);
|
||||||
const deleteNode = useScene((state) => state.deleteNode);
|
const deleteNode = useScene((state) => state.deleteNode);
|
||||||
const updateNode = useScene((state) => state.updateNode);
|
const updateNode = useScene((state) => state.updateNode);
|
||||||
const selectedZoneId = useViewer((state) => state.selection.zoneId);
|
const selectedZoneId = useViewer((state) => state.selection.zoneId);
|
||||||
@@ -394,6 +465,67 @@ function ZoneItem({ zone }: { zone: ZoneNode }) {
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
<span className="truncate flex-1">{zone.name || defaultName}</span>
|
<span className="truncate flex-1">{zone.name || defaultName}</span>
|
||||||
|
{/* Camera snapshot button */}
|
||||||
|
<Popover open={cameraPopoverOpen} onOpenChange={setCameraPopoverOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<button
|
||||||
|
className="relative opacity-0 group-hover/row:opacity-100 w-5 h-5 flex items-center justify-center rounded cursor-pointer hover:bg-primary-foreground/20"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
title="Camera snapshot"
|
||||||
|
>
|
||||||
|
<Camera className="w-3 h-3" />
|
||||||
|
{zone.camera && (
|
||||||
|
<span className="absolute top-0.5 right-0.5 w-1.5 h-1.5 rounded-full bg-primary" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
side="right"
|
||||||
|
align="start"
|
||||||
|
className="w-auto p-1"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
{zone.camera && (
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded cursor-pointer text-popover-foreground hover:bg-accent text-left w-full"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
emitter.emit("camera-controls:view", { nodeId: zone.id });
|
||||||
|
setCameraPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Camera className="w-3.5 h-3.5" />
|
||||||
|
View snapshot
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded cursor-pointer text-popover-foreground hover:bg-accent text-left w-full"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
emitter.emit("camera-controls:capture", { nodeId: zone.id });
|
||||||
|
setCameraPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Camera className="w-3.5 h-3.5" />
|
||||||
|
{zone.camera ? "Update snapshot" : "Take snapshot"}
|
||||||
|
</button>
|
||||||
|
{zone.camera && (
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded cursor-pointer text-popover-foreground hover:bg-destructive hover:text-destructive-foreground text-left w-full"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
updateNode(zone.id, { camera: undefined });
|
||||||
|
setCameraPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
|
Clear snapshot
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
<button
|
<button
|
||||||
className="opacity-0 group-hover/row:opacity-100 w-5 h-5 flex items-center justify-center rounded cursor-pointer hover:bg-primary-foreground/20"
|
className="opacity-0 group-hover/row:opacity-100 w-5 h-5 flex items-center justify-center rounded cursor-pointer hover:bg-primary-foreground/20"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useScene, type ZoneNode } from "@pascal-app/core";
|
import { emitter, 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 { Camera, Hexagon, Trash2 } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import useEditor from "@/store/use-editor";
|
import useEditor from "@/store/use-editor";
|
||||||
import {
|
import {
|
||||||
@@ -22,6 +23,7 @@ const PRESET_COLORS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
function ZoneItem({ zone }: { zone: ZoneNode }) {
|
function ZoneItem({ zone }: { zone: ZoneNode }) {
|
||||||
|
const [cameraPopoverOpen, setCameraPopoverOpen] = useState(false);
|
||||||
const deleteNode = useScene((state) => state.deleteNode);
|
const deleteNode = useScene((state) => state.deleteNode);
|
||||||
const updateNode = useScene((state) => state.updateNode);
|
const updateNode = useScene((state) => state.updateNode);
|
||||||
const selectedZoneId = useViewer((state) => state.selection.zoneId);
|
const selectedZoneId = useViewer((state) => state.selection.zoneId);
|
||||||
@@ -85,6 +87,67 @@ function ZoneItem({ zone }: { zone: ZoneNode }) {
|
|||||||
</Popover>
|
</Popover>
|
||||||
<Hexagon className="w-3.5 h-3.5 mr-1.5 shrink-0" />
|
<Hexagon className="w-3.5 h-3.5 mr-1.5 shrink-0" />
|
||||||
<span className="truncate flex-1">{zone.name}</span>
|
<span className="truncate flex-1">{zone.name}</span>
|
||||||
|
{/* Camera snapshot button */}
|
||||||
|
<Popover open={cameraPopoverOpen} onOpenChange={setCameraPopoverOpen}>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<button
|
||||||
|
className="relative opacity-0 group-hover/row:opacity-100 w-5 h-5 flex items-center justify-center rounded cursor-pointer hover:bg-primary-foreground/20"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
title="Camera snapshot"
|
||||||
|
>
|
||||||
|
<Camera className="w-3 h-3" />
|
||||||
|
{zone.camera && (
|
||||||
|
<span className="absolute top-0.5 right-0.5 w-1.5 h-1.5 rounded-full bg-primary" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent
|
||||||
|
side="right"
|
||||||
|
align="start"
|
||||||
|
className="w-auto p-1"
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
{zone.camera && (
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded cursor-pointer text-popover-foreground hover:bg-accent text-left w-full"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
emitter.emit("camera-controls:view", { nodeId: zone.id });
|
||||||
|
setCameraPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Camera className="w-3.5 h-3.5" />
|
||||||
|
View snapshot
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded cursor-pointer text-popover-foreground hover:bg-accent text-left w-full"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
emitter.emit("camera-controls:capture", { nodeId: zone.id });
|
||||||
|
setCameraPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Camera className="w-3.5 h-3.5" />
|
||||||
|
{zone.camera ? "Update snapshot" : "Take snapshot"}
|
||||||
|
</button>
|
||||||
|
{zone.camera && (
|
||||||
|
<button
|
||||||
|
className="flex items-center gap-2 px-2 py-1.5 text-sm rounded cursor-pointer text-popover-foreground hover:bg-destructive hover:text-destructive-foreground text-left w-full"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
updateNode(zone.id, { camera: undefined });
|
||||||
|
setCameraPopoverOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 className="w-3.5 h-3.5" />
|
||||||
|
Clear snapshot
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
<button
|
<button
|
||||||
className="opacity-0 group-hover/row:opacity-100 w-5 h-5 flex items-center justify-center rounded cursor-pointer hover:bg-primary-foreground/20"
|
className="opacity-0 group-hover/row:opacity-100 w-5 h-5 flex items-center justify-center rounded cursor-pointer hover:bg-primary-foreground/20"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
|
|||||||
@@ -58,9 +58,34 @@
|
|||||||
"item_0e9paq67kdbm5ux0",
|
"item_0e9paq67kdbm5ux0",
|
||||||
"item_iyu7knyxqe82c3yg",
|
"item_iyu7knyxqe82c3yg",
|
||||||
"item_nwe34qk8vzs1vag7",
|
"item_nwe34qk8vzs1vag7",
|
||||||
"guide_vh96481lk2oqzf4d"
|
"item_oay55zmjjo76s1fs",
|
||||||
|
"item_g060bhthvcra992w",
|
||||||
|
"item_38kt7s45alt2vrjg",
|
||||||
|
"item_n10cp9ke7n9hxl96",
|
||||||
|
"item_nt5fxip4a03cmaoi",
|
||||||
|
"item_1quoil3ytsuuarni",
|
||||||
|
"item_e1w89kkg2pql1v45",
|
||||||
|
"item_3akotmiffzdr8ule",
|
||||||
|
"item_1b3tinfswueb6gr8",
|
||||||
|
"item_y164oe3lxfx9qefg",
|
||||||
|
"item_dsalxofuqf96h8t4",
|
||||||
|
"roof_ui8zhim41alg6lq4",
|
||||||
|
"guide_acs9nzz19rm4vl2c"
|
||||||
],
|
],
|
||||||
"level": 0
|
"level": 0,
|
||||||
|
"camera": {
|
||||||
|
"position": [
|
||||||
|
32.19770918094574,
|
||||||
|
13.355189178183336,
|
||||||
|
32.63027548275616
|
||||||
|
],
|
||||||
|
"target": [
|
||||||
|
4.8501257048479305,
|
||||||
|
-6.656412040461838e-16,
|
||||||
|
7.3634421472590255
|
||||||
|
],
|
||||||
|
"mode": "perspective"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"slab_gr6zxi4915gqwjbn": {
|
"slab_gr6zxi4915gqwjbn": {
|
||||||
"object": "node",
|
"object": "node",
|
||||||
@@ -100,7 +125,20 @@
|
|||||||
"children": [
|
"children": [
|
||||||
"roof_jxd8tc6rcuaujl25"
|
"roof_jxd8tc6rcuaujl25"
|
||||||
],
|
],
|
||||||
"level": 1
|
"level": 1,
|
||||||
|
"camera": {
|
||||||
|
"position": [
|
||||||
|
11.709072311989358,
|
||||||
|
25.635955613557638,
|
||||||
|
50.59653427090403
|
||||||
|
],
|
||||||
|
"target": [
|
||||||
|
6.9995635873796855,
|
||||||
|
2.4999999999999996,
|
||||||
|
0.6911898255966076
|
||||||
|
],
|
||||||
|
"mode": "perspective"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"roof_jxd8tc6rcuaujl25": {
|
"roof_jxd8tc6rcuaujl25": {
|
||||||
"object": "node",
|
"object": "node",
|
||||||
@@ -117,9 +155,9 @@
|
|||||||
],
|
],
|
||||||
"rotation": 0,
|
"rotation": 0,
|
||||||
"length": 5.5,
|
"length": 5.5,
|
||||||
"height": 1.5,
|
"height": 1.6,
|
||||||
"leftWidth": 2.5,
|
"leftWidth": 4.7,
|
||||||
"rightWidth": 2.5
|
"rightWidth": 2.7
|
||||||
},
|
},
|
||||||
"zone_iozx54yy1hmmoads": {
|
"zone_iozx54yy1hmmoads": {
|
||||||
"object": "node",
|
"object": "node",
|
||||||
@@ -147,7 +185,20 @@
|
|||||||
6
|
6
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"color": "#3b82f6"
|
"color": "#3b82f6",
|
||||||
|
"camera": {
|
||||||
|
"position": [
|
||||||
|
18.715003971902778,
|
||||||
|
18.8254836683251,
|
||||||
|
12.086656976691291
|
||||||
|
],
|
||||||
|
"target": [
|
||||||
|
6.9995635873796855,
|
||||||
|
1.6971845387795204e-17,
|
||||||
|
0.6911898255966076
|
||||||
|
],
|
||||||
|
"mode": "perspective"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"item_137wje66gax2c6bc": {
|
"item_137wje66gax2c6bc": {
|
||||||
"object": "node",
|
"object": "node",
|
||||||
@@ -757,7 +808,7 @@
|
|||||||
"visible": true,
|
"visible": true,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"position": [
|
"position": [
|
||||||
3.5,
|
4.5,
|
||||||
0,
|
0,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
@@ -1185,9 +1236,9 @@
|
|||||||
"metadata": {},
|
"metadata": {},
|
||||||
"children": [
|
"children": [
|
||||||
"item_s9fs055u0ilri7pi",
|
"item_s9fs055u0ilri7pi",
|
||||||
"item_4dehdkm4vx6r4ghv",
|
|
||||||
"item_0173tdywhm704hah",
|
"item_0173tdywhm704hah",
|
||||||
"item_7ykyzy2yre3761dq"
|
"item_7ykyzy2yre3761dq",
|
||||||
|
"item_4dehdkm4vx6r4ghv"
|
||||||
],
|
],
|
||||||
"start": [
|
"start": [
|
||||||
1,
|
1,
|
||||||
@@ -1255,7 +1306,7 @@
|
|||||||
"visible": true,
|
"visible": true,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"position": [
|
"position": [
|
||||||
9,
|
8.5,
|
||||||
0.5,
|
0.5,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
@@ -1303,7 +1354,7 @@
|
|||||||
"visible": true,
|
"visible": true,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"position": [
|
"position": [
|
||||||
6,
|
6.5,
|
||||||
0,
|
0,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
@@ -2053,7 +2104,20 @@
|
|||||||
15.5
|
15.5
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"color": "#22c55e"
|
"color": "#22c55e",
|
||||||
|
"camera": {
|
||||||
|
"position": [
|
||||||
|
-6.654332076100337,
|
||||||
|
17.996846152830106,
|
||||||
|
22.501743052051737
|
||||||
|
],
|
||||||
|
"target": [
|
||||||
|
5.317880378107912,
|
||||||
|
-2.779128022959309e-17,
|
||||||
|
10.080522989431769
|
||||||
|
],
|
||||||
|
"mode": "perspective"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"item_0e9paq67kdbm5ux0": {
|
"item_0e9paq67kdbm5ux0": {
|
||||||
"object": "node",
|
"object": "node",
|
||||||
@@ -2193,18 +2257,497 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"guide_vh96481lk2oqzf4d": {
|
"item_oay55zmjjo76s1fs": {
|
||||||
"object": "node",
|
"object": "node",
|
||||||
"id": "guide_vh96481lk2oqzf4d",
|
"id": "item_oay55zmjjo76s1fs",
|
||||||
"type": "guide",
|
"type": "item",
|
||||||
"name": "elestio-postgis-thumbnail.png",
|
"name": "High Fence",
|
||||||
"parentId": "level_pojp0mw3qssu110w",
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
"visible": true,
|
"visible": true,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"url": "asset://129b9095-60c3-4239-92b6-3cef391721fb",
|
"position": [
|
||||||
|
23,
|
||||||
|
0,
|
||||||
|
4.25
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
1.5707963267948966,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"asset": {
|
||||||
|
"id": "high-fence",
|
||||||
|
"category": "outdoor",
|
||||||
|
"name": "High Fence",
|
||||||
|
"thumbnail": "/items/high-fence/thumbnail.webp",
|
||||||
|
"src": "/items/high-fence/model.glb",
|
||||||
|
"dimensions": [
|
||||||
|
4,
|
||||||
|
4.1,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"offset": [
|
||||||
|
0,
|
||||||
|
0.01,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"scale": [
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item_g060bhthvcra992w": {
|
||||||
|
"object": "node",
|
||||||
|
"id": "item_g060bhthvcra992w",
|
||||||
|
"type": "item",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
|
"visible": true,
|
||||||
|
"metadata": {},
|
||||||
|
"position": [
|
||||||
|
23,
|
||||||
|
0,
|
||||||
|
-1.25
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"asset": {
|
||||||
|
"id": "medium-fence",
|
||||||
|
"category": "outdoor",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"thumbnail": "/items/medium-fence/thumbnail.webp",
|
||||||
|
"src": "/items/medium-fence/model.glb",
|
||||||
|
"dimensions": [
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"offset": [
|
||||||
|
0,
|
||||||
|
0.01,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"scale": [
|
||||||
|
0.49,
|
||||||
|
0.49,
|
||||||
|
0.49
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item_38kt7s45alt2vrjg": {
|
||||||
|
"object": "node",
|
||||||
|
"id": "item_38kt7s45alt2vrjg",
|
||||||
|
"type": "item",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
|
"visible": true,
|
||||||
|
"metadata": {},
|
||||||
|
"position": [
|
||||||
|
22,
|
||||||
|
0,
|
||||||
|
-2.25
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
1.5707963267948966,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"asset": {
|
||||||
|
"id": "medium-fence",
|
||||||
|
"category": "outdoor",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"thumbnail": "/items/medium-fence/thumbnail.webp",
|
||||||
|
"src": "/items/medium-fence/model.glb",
|
||||||
|
"dimensions": [
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"offset": [
|
||||||
|
0,
|
||||||
|
0.01,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"scale": [
|
||||||
|
0.49,
|
||||||
|
0.49,
|
||||||
|
0.49
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item_n10cp9ke7n9hxl96": {
|
||||||
|
"object": "node",
|
||||||
|
"id": "item_n10cp9ke7n9hxl96",
|
||||||
|
"type": "item",
|
||||||
|
"name": "Low Fence",
|
||||||
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
|
"visible": true,
|
||||||
|
"metadata": {},
|
||||||
|
"position": [
|
||||||
|
23,
|
||||||
|
0,
|
||||||
|
-4.75
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"asset": {
|
||||||
|
"id": "low-fence",
|
||||||
|
"category": "outdoor",
|
||||||
|
"name": "Low Fence",
|
||||||
|
"thumbnail": "/items/low-fence/thumbnail.webp",
|
||||||
|
"src": "/items/low-fence/model.glb",
|
||||||
|
"dimensions": [
|
||||||
|
2,
|
||||||
|
0.8,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"offset": [
|
||||||
|
0,
|
||||||
|
0.01,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"scale": [
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item_nt5fxip4a03cmaoi": {
|
||||||
|
"object": "node",
|
||||||
|
"id": "item_nt5fxip4a03cmaoi",
|
||||||
|
"type": "item",
|
||||||
|
"name": "Low Fence",
|
||||||
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
|
"visible": true,
|
||||||
|
"metadata": {},
|
||||||
|
"position": [
|
||||||
|
22,
|
||||||
|
0,
|
||||||
|
-5.75
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
1.5707963267948966,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"asset": {
|
||||||
|
"id": "low-fence",
|
||||||
|
"category": "outdoor",
|
||||||
|
"name": "Low Fence",
|
||||||
|
"thumbnail": "/items/low-fence/thumbnail.webp",
|
||||||
|
"src": "/items/low-fence/model.glb",
|
||||||
|
"dimensions": [
|
||||||
|
2,
|
||||||
|
0.8,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"offset": [
|
||||||
|
0,
|
||||||
|
0.01,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"scale": [
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item_1quoil3ytsuuarni": {
|
||||||
|
"object": "node",
|
||||||
|
"id": "item_1quoil3ytsuuarni",
|
||||||
|
"type": "item",
|
||||||
|
"name": "High Fence",
|
||||||
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
|
"visible": true,
|
||||||
|
"metadata": {},
|
||||||
|
"position": [
|
||||||
|
15,
|
||||||
|
0,
|
||||||
|
4.25
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
1.5707963267948966,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"asset": {
|
||||||
|
"id": "high-fence",
|
||||||
|
"category": "outdoor",
|
||||||
|
"name": "High Fence",
|
||||||
|
"thumbnail": "/items/high-fence/thumbnail.webp",
|
||||||
|
"src": "/items/high-fence/model.glb",
|
||||||
|
"dimensions": [
|
||||||
|
4,
|
||||||
|
4.1,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"offset": [
|
||||||
|
0,
|
||||||
|
0.01,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"scale": [
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item_e1w89kkg2pql1v45": {
|
||||||
|
"object": "node",
|
||||||
|
"id": "item_e1w89kkg2pql1v45",
|
||||||
|
"type": "item",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
|
"visible": true,
|
||||||
|
"metadata": {},
|
||||||
|
"position": [
|
||||||
|
17,
|
||||||
|
0,
|
||||||
|
0.25
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
1.5707963267948966,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"asset": {
|
||||||
|
"id": "medium-fence",
|
||||||
|
"category": "outdoor",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"thumbnail": "/items/medium-fence/thumbnail.webp",
|
||||||
|
"src": "/items/medium-fence/model.glb",
|
||||||
|
"dimensions": [
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"offset": [
|
||||||
|
0,
|
||||||
|
0.01,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"scale": [
|
||||||
|
0.49,
|
||||||
|
0.49,
|
||||||
|
0.49
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item_3akotmiffzdr8ule": {
|
||||||
|
"object": "node",
|
||||||
|
"id": "item_3akotmiffzdr8ule",
|
||||||
|
"type": "item",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
|
"visible": true,
|
||||||
|
"metadata": {},
|
||||||
|
"position": [
|
||||||
|
17,
|
||||||
|
0,
|
||||||
|
-1.75
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
1.5707963267948966,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"asset": {
|
||||||
|
"id": "medium-fence",
|
||||||
|
"category": "outdoor",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"thumbnail": "/items/medium-fence/thumbnail.webp",
|
||||||
|
"src": "/items/medium-fence/model.glb",
|
||||||
|
"dimensions": [
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"offset": [
|
||||||
|
0,
|
||||||
|
0.01,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"scale": [
|
||||||
|
0.49,
|
||||||
|
0.49,
|
||||||
|
0.49
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item_1b3tinfswueb6gr8": {
|
||||||
|
"object": "node",
|
||||||
|
"id": "item_1b3tinfswueb6gr8",
|
||||||
|
"type": "item",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
|
"visible": true,
|
||||||
|
"metadata": {},
|
||||||
|
"position": [
|
||||||
|
17,
|
||||||
|
0,
|
||||||
|
-4.75
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
4.71238898038469,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"asset": {
|
||||||
|
"id": "medium-fence",
|
||||||
|
"category": "outdoor",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"thumbnail": "/items/medium-fence/thumbnail.webp",
|
||||||
|
"src": "/items/medium-fence/model.glb",
|
||||||
|
"dimensions": [
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"offset": [
|
||||||
|
0,
|
||||||
|
0.01,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"scale": [
|
||||||
|
0.49,
|
||||||
|
0.49,
|
||||||
|
0.49
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"item_y164oe3lxfx9qefg": {
|
||||||
|
"object": "node",
|
||||||
|
"id": "item_y164oe3lxfx9qefg",
|
||||||
|
"type": "item",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
|
"visible": true,
|
||||||
|
"metadata": {},
|
||||||
|
"position": [
|
||||||
|
17,
|
||||||
|
0,
|
||||||
|
-6.75
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
4.71238898038469,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"asset": {
|
||||||
|
"id": "medium-fence",
|
||||||
|
"category": "outdoor",
|
||||||
|
"name": "Medium Fence",
|
||||||
|
"thumbnail": "/items/medium-fence/thumbnail.webp",
|
||||||
|
"src": "/items/medium-fence/model.glb",
|
||||||
|
"dimensions": [
|
||||||
|
2,
|
||||||
|
2,
|
||||||
|
0.5
|
||||||
|
],
|
||||||
|
"offset": [
|
||||||
|
0,
|
||||||
|
0.01,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"rotation": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"scale": [
|
||||||
|
0.49,
|
||||||
|
0.49,
|
||||||
|
0.49
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"roof_ui8zhim41alg6lq4": {
|
||||||
|
"object": "node",
|
||||||
|
"id": "roof_ui8zhim41alg6lq4",
|
||||||
|
"type": "roof",
|
||||||
|
"name": "Roof 2",
|
||||||
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
|
"visible": true,
|
||||||
|
"metadata": {},
|
||||||
|
"position": [
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
-5.5
|
||||||
|
],
|
||||||
|
"rotation": 0,
|
||||||
|
"length": 0.5,
|
||||||
|
"height": 1.5,
|
||||||
|
"leftWidth": 12.1,
|
||||||
|
"rightWidth": 1
|
||||||
|
},
|
||||||
|
"guide_acs9nzz19rm4vl2c": {
|
||||||
|
"object": "node",
|
||||||
|
"id": "guide_acs9nzz19rm4vl2c",
|
||||||
|
"type": "guide",
|
||||||
|
"name": "FaceIt0703_FaceItProductPage.png",
|
||||||
|
"parentId": "level_pojp0mw3qssu110w",
|
||||||
|
"visible": true,
|
||||||
|
"metadata": {},
|
||||||
|
"url": "asset://1e66ba17-99d2-4c5c-ad2b-00dff438b6a7",
|
||||||
"position": [
|
"position": [
|
||||||
0,
|
0,
|
||||||
1.2,
|
0,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"rotation": [
|
"rotation": [
|
||||||
@@ -2213,7 +2756,7 @@
|
|||||||
0
|
0
|
||||||
],
|
],
|
||||||
"scale": 1,
|
"scale": 1,
|
||||||
"opacity": 100
|
"opacity": 51
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rootNodeIds": [
|
"rootNodeIds": [
|
||||||
|
|||||||
@@ -179,17 +179,12 @@ useScene.temporal.subscribe((state) => {
|
|||||||
const currentPastLength = state.pastStates.length
|
const currentPastLength = state.pastStates.length
|
||||||
const currentFutureLength = state.futureStates.length
|
const currentFutureLength = state.futureStates.length
|
||||||
|
|
||||||
console.log('Temporal state changed:', {
|
|
||||||
pastStates: { prev: prevPastLength, current: currentPastLength },
|
|
||||||
futureStates: { prev: prevFutureLength, current: currentFutureLength },
|
|
||||||
})
|
|
||||||
|
|
||||||
// Undo: futureStates increases (state moved from past to future)
|
// Undo: futureStates increases (state moved from past to future)
|
||||||
// Redo: pastStates increases while futureStates decreases (state moved from future to past)
|
// Redo: pastStates increases while futureStates decreases (state moved from future to past)
|
||||||
const didUndo = currentFutureLength > prevFutureLength
|
const didUndo = currentFutureLength > prevFutureLength
|
||||||
const didRedo = currentPastLength > prevPastLength && currentFutureLength < prevFutureLength
|
const didRedo = currentPastLength > prevPastLength && currentFutureLength < prevFutureLength
|
||||||
|
|
||||||
console.log('Detection:', { didUndo, didRedo })
|
|
||||||
|
|
||||||
if (didUndo || didRedo) {
|
if (didUndo || didRedo) {
|
||||||
// Use RAF to ensure all middleware and store updates are complete
|
// Use RAF to ensure all middleware and store updates are complete
|
||||||
|
|||||||
Reference in New Issue
Block a user