camera for zones and levels
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
type BuildingNode,
|
||||
emitter,
|
||||
LevelNode,
|
||||
useScene,
|
||||
type ZoneNode,
|
||||
@@ -7,6 +8,7 @@ import {
|
||||
import { useViewer } from "@pascal-app/viewer";
|
||||
import {
|
||||
Building2,
|
||||
Camera,
|
||||
ChevronDown,
|
||||
Layers,
|
||||
MoreHorizontal,
|
||||
@@ -155,11 +157,13 @@ function BuildingSelector() {
|
||||
function LevelsSection() {
|
||||
const nodes = useScene((state) => state.nodes);
|
||||
const createNode = useScene((state) => state.createNode);
|
||||
const updateNode = useScene((state) => state.updateNode);
|
||||
const selectedBuildingId = useViewer((state) => state.selection.buildingId);
|
||||
const selectedLevelId = useViewer((state) => state.selection.levelId);
|
||||
const setSelection = useViewer((state) => state.setSelection);
|
||||
|
||||
const [referencesLevelId, setReferencesLevelId] = useState<string | null>(null);
|
||||
const [cameraPopoverOpen, setCameraPopoverOpen] = useState<string | null>(null);
|
||||
|
||||
const building = selectedBuildingId
|
||||
? (nodes[selectedBuildingId] as BuildingNode)
|
||||
@@ -215,6 +219,72 @@ function LevelsSection() {
|
||||
<Layers className="w-3.5 h-3.5 shrink-0" />
|
||||
<span className="truncate">{level.name || `Level ${level.level}`}</span>
|
||||
</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>
|
||||
<PopoverTrigger asChild>
|
||||
<button
|
||||
@@ -307,6 +377,7 @@ function calculatePolygonArea(polygon: Array<[number, number]>): number {
|
||||
|
||||
function ZoneItem({ zone }: { zone: ZoneNode }) {
|
||||
const [renameOpen, setRenameOpen] = useState(false);
|
||||
const [cameraPopoverOpen, setCameraPopoverOpen] = useState(false);
|
||||
const deleteNode = useScene((state) => state.deleteNode);
|
||||
const updateNode = useScene((state) => state.updateNode);
|
||||
const selectedZoneId = useViewer((state) => state.selection.zoneId);
|
||||
@@ -394,6 +465,67 @@ function ZoneItem({ zone }: { zone: ZoneNode }) {
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<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
|
||||
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}
|
||||
|
||||
@@ -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 { Hexagon, Trash2 } from "lucide-react";
|
||||
import { Camera, Hexagon, Trash2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import useEditor from "@/store/use-editor";
|
||||
import {
|
||||
@@ -22,6 +23,7 @@ const PRESET_COLORS = [
|
||||
];
|
||||
|
||||
function ZoneItem({ zone }: { zone: ZoneNode }) {
|
||||
const [cameraPopoverOpen, setCameraPopoverOpen] = useState(false);
|
||||
const deleteNode = useScene((state) => state.deleteNode);
|
||||
const updateNode = useScene((state) => state.updateNode);
|
||||
const selectedZoneId = useViewer((state) => state.selection.zoneId);
|
||||
@@ -85,6 +87,67 @@ function ZoneItem({ zone }: { zone: ZoneNode }) {
|
||||
</Popover>
|
||||
<Hexagon className="w-3.5 h-3.5 mr-1.5 shrink-0" />
|
||||
<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
|
||||
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}
|
||||
|
||||
@@ -58,9 +58,34 @@
|
||||
"item_0e9paq67kdbm5ux0",
|
||||
"item_iyu7knyxqe82c3yg",
|
||||
"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": {
|
||||
"object": "node",
|
||||
@@ -100,7 +125,20 @@
|
||||
"children": [
|
||||
"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": {
|
||||
"object": "node",
|
||||
@@ -117,9 +155,9 @@
|
||||
],
|
||||
"rotation": 0,
|
||||
"length": 5.5,
|
||||
"height": 1.5,
|
||||
"leftWidth": 2.5,
|
||||
"rightWidth": 2.5
|
||||
"height": 1.6,
|
||||
"leftWidth": 4.7,
|
||||
"rightWidth": 2.7
|
||||
},
|
||||
"zone_iozx54yy1hmmoads": {
|
||||
"object": "node",
|
||||
@@ -147,7 +185,20 @@
|
||||
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": {
|
||||
"object": "node",
|
||||
@@ -757,7 +808,7 @@
|
||||
"visible": true,
|
||||
"metadata": {},
|
||||
"position": [
|
||||
3.5,
|
||||
4.5,
|
||||
0,
|
||||
0
|
||||
],
|
||||
@@ -1185,9 +1236,9 @@
|
||||
"metadata": {},
|
||||
"children": [
|
||||
"item_s9fs055u0ilri7pi",
|
||||
"item_4dehdkm4vx6r4ghv",
|
||||
"item_0173tdywhm704hah",
|
||||
"item_7ykyzy2yre3761dq"
|
||||
"item_7ykyzy2yre3761dq",
|
||||
"item_4dehdkm4vx6r4ghv"
|
||||
],
|
||||
"start": [
|
||||
1,
|
||||
@@ -1255,7 +1306,7 @@
|
||||
"visible": true,
|
||||
"metadata": {},
|
||||
"position": [
|
||||
9,
|
||||
8.5,
|
||||
0.5,
|
||||
0
|
||||
],
|
||||
@@ -1303,7 +1354,7 @@
|
||||
"visible": true,
|
||||
"metadata": {},
|
||||
"position": [
|
||||
6,
|
||||
6.5,
|
||||
0,
|
||||
0
|
||||
],
|
||||
@@ -2053,7 +2104,20 @@
|
||||
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": {
|
||||
"object": "node",
|
||||
@@ -2193,18 +2257,497 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"guide_vh96481lk2oqzf4d": {
|
||||
"item_oay55zmjjo76s1fs": {
|
||||
"object": "node",
|
||||
"id": "guide_vh96481lk2oqzf4d",
|
||||
"type": "guide",
|
||||
"name": "elestio-postgis-thumbnail.png",
|
||||
"id": "item_oay55zmjjo76s1fs",
|
||||
"type": "item",
|
||||
"name": "High Fence",
|
||||
"parentId": "level_pojp0mw3qssu110w",
|
||||
"visible": true,
|
||||
"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": [
|
||||
0,
|
||||
1.2,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"rotation": [
|
||||
@@ -2213,7 +2756,7 @@
|
||||
0
|
||||
],
|
||||
"scale": 1,
|
||||
"opacity": 100
|
||||
"opacity": 51
|
||||
}
|
||||
},
|
||||
"rootNodeIds": [
|
||||
|
||||
@@ -179,17 +179,12 @@ useScene.temporal.subscribe((state) => {
|
||||
const currentPastLength = state.pastStates.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)
|
||||
// Redo: pastStates increases while futureStates decreases (state moved from future to past)
|
||||
const didUndo = currentFutureLength > prevFutureLength
|
||||
const didRedo = currentPastLength > prevPastLength && currentFutureLength < prevFutureLength
|
||||
|
||||
console.log('Detection:', { didUndo, didRedo })
|
||||
|
||||
if (didUndo || didRedo) {
|
||||
// Use RAF to ensure all middleware and store updates are complete
|
||||
|
||||
Reference in New Issue
Block a user