points in virtual-tour

This commit is contained in:
2024-05-28 17:25:50 +05:00
parent 9a489d64af
commit 41bfe23447
10 changed files with 318 additions and 146 deletions
+28
View File
@@ -0,0 +1,28 @@
const WalkHereIcon = () => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="12" cy="3.5" r="2" stroke="currentColor" strokeWidth="1.5" />
<path
d="M18.3547 15.1941C19.5878 15.8755 20.3278 16.6907 20.6118 17.4798C20.8826 18.2321 20.7636 19.0262 20.1767 19.8093C19.5719 20.6162 18.4956 21.3661 17.0181 21.9116C15.549 22.4541 13.8015 22.75 12 22.75C10.1985 22.75 8.45102 22.4541 6.98188 21.9116C5.50435 21.3661 4.4281 20.6162 3.82328 19.8093C3.23636 19.0262 3.11743 18.2321 3.38822 17.4798C3.67223 16.6907 4.41219 15.8755 5.64525 15.1941"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M9.5 7.75H14.5C15.1904 7.75 15.75 8.30964 15.75 9V13.8C15.75 13.8828 15.6828 13.95 15.6 13.95C15.0615 13.95 14.625 14.3865 14.625 14.925V18.75H12H9.375V14.925C9.375 14.3865 8.93848 13.95 8.4 13.95C8.31716 13.95 8.25 13.8828 8.25 13.8V9C8.25 8.30964 8.80964 7.75 9.5 7.75Z"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
/>
</svg>
);
};
export default WalkHereIcon;
@@ -0,0 +1,40 @@
import { Html } from "@react-three/drei";
import { IAppartmentSphere, ISphereLink } from "../../types/apartmentSphere";
import WalkHereIcon from "../icons/WalkHereIcon";
import useSphere from "../../store/useSphere";
interface LaberlMarkerProps {
sphereLink: ISphereLink;
apartment: IAppartmentSphere;
}
const LabelMarker = ({ sphereLink, apartment }: LaberlMarkerProps) => {
const { setSelectedSphere } = useSphere();
const handleOnClick = () => {
const moveToShpere = apartment.spheres.find(
(sph) => sph.id === sphereLink.id
);
if (moveToShpere) {
setSelectedSphere(moveToShpere);
}
};
return (
<>
{
<Html position={sphereLink.labelPosition} center>
<div
className="bg-white w-9 h-9 rounded-full text-[#00BED7] flex items-center justify-center cursor-pointer"
onMouseEnter={() => console.log(sphereLink.id)}
onClick={handleOnClick}
>
<WalkHereIcon />
</div>
</Html>
}
</>
);
};
export default LabelMarker;
+3 -3
View File
@@ -1,13 +1,13 @@
import { Sphere, useTexture } from "@react-three/drei"; import { Sphere, useTexture } from "@react-three/drei";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { BackSide, MeshBasicMaterial } from "three"; import { BackSide, MeshBasicMaterial } from "three";
import { IAppartmentSphere } from "../../types/apartmentSphere"; import { ISphere } from "../../types/apartmentSphere";
import gsap from "gsap"; import gsap from "gsap";
// import gsap from "gsap"; // import gsap from "gsap";
interface SphereTourProps { interface SphereTourProps {
sphere: IAppartmentSphere; sphere: ISphere;
selectedSphere: IAppartmentSphere; selectedSphere: ISphere;
} }
const SphereTour = ({ sphere, selectedSphere }: SphereTourProps) => { const SphereTour = ({ sphere, selectedSphere }: SphereTourProps) => {
@@ -1,32 +1,32 @@
import { OrbitControls, Html, useTexture } from "@react-three/drei"; import { OrbitControls, Html } from "@react-three/drei";
import { Suspense, useEffect, useRef, useState } from "react"; import { Suspense, useEffect, useRef } from "react";
import { OrbitControls as OrbitControlsImpl } from "three-stdlib"; import { OrbitControls as OrbitControlsImpl } from "three-stdlib";
import { useParams } from "react-router-dom";
import useCompass from "../../store/useCompass"; import useCompass from "../../store/useCompass";
import SphereTour from "./SphereTour"; import SphereTour from "./SphereTour";
import { spheres } from "../../consts/spheres"; import { IAppartmentSphere } from "../../types/apartmentSphere";
import useSphere from "../../store/useSphere"; import useSphere from "../../store/useSphere";
import LabelMarker from "./LabelMarker";
// import { spheres } from "../../consts/spheres";
const VirtualTourWrapper = () => { interface VirtualTourWrapperProps {
appartment: IAppartmentSphere;
}
const VirtualTourWrapper = ({ appartment }: VirtualTourWrapperProps) => {
const orbitRef = useRef<OrbitControlsImpl>(null); const orbitRef = useRef<OrbitControlsImpl>(null);
const { setCurrentCompassRotate, currentCompassRotate } = useCompass(); const { setCurrentCompassRotate } = useCompass();
const { id } = useParams();
const [startRotatingPos, setStartRotatingPos] = useState(0);
const { selectedSphere, setSelectedSphere } = useSphere(); const { selectedSphere, setSelectedSphere } = useSphere();
// const [selectedSphere, setSelectedSphere] = useState<IAppartmentSphere>(
// spheres[1]
// );
useEffect(() => { useEffect(() => {
setSelectedSphere(spheres[0]); setSelectedSphere(appartment.spheres[0]);
}, []); }, []);
const handleOnRotating = () => { const handleOnRotating = () => {
const radian = orbitRef.current?.getAzimuthalAngle(); const radian = orbitRef.current?.getAzimuthalAngle();
if (radian) { if (radian) {
const currentCompasDegrees = (radian * 180) / Math.PI + 180; const currentCompasDegrees = (radian * 180) / Math.PI + 180;
const compassOffsetDegres = startRotatingPos - currentCompasDegrees; setCurrentCompassRotate(currentCompasDegrees);
setCurrentCompassRotate(currentCompassRotate + compassOffsetDegres);
} }
}; };
@@ -38,12 +38,23 @@ const VirtualTourWrapper = () => {
</Html> </Html>
} }
> >
{spheres.map((sphere) => ( {appartment.spheres.map((sphere) => {
<SphereTour const isLabelContains =
sphere={sphere} selectedSphere && sphere.id === selectedSphere.id;
selectedSphere={selectedSphere || spheres[0]}
/> return (
))} <>
{isLabelContains &&
sphere.links.map((sphereLink) => (
<LabelMarker sphereLink={sphereLink} apartment={appartment} />
))}
<SphereTour
sphere={sphere}
selectedSphere={selectedSphere || appartment.spheres[0]}
/>
</>
);
})}
<OrbitControls <OrbitControls
ref={orbitRef} ref={orbitRef}
maxDistance={0.001} maxDistance={0.001}
@@ -53,7 +64,7 @@ const VirtualTourWrapper = () => {
// onStart={(e) => console.log("e", e)} // onStart={(e) => console.log("e", e)}
// onChange={(e) => console.log("e", e?.target)} // onChange={(e) => console.log("e", e?.target)}
onChange={handleOnRotating} onChange={handleOnRotating}
target={[-14.16, 0, 24.11]} target={appartment.spheres[0].position}
/> />
</Suspense> </Suspense>
); );
-41
View File
@@ -1,41 +0,0 @@
import { IAppartmentSphere } from "../types/apartmentSphere";
const spheres: IAppartmentSphere[] = [
{
id: "1",
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-01.webp",
roomType: "room 1",
},
{
id: "2",
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-02.webp",
roomType: "room 2",
},
{
id: "3",
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-03.webp",
roomType: "room 3",
},
{
id: "4",
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-04.webp",
roomType: "room 4",
},
{
id: "5",
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-05.webp",
roomType: "room 5",
},
{
id: "6",
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-06.webp",
roomType: "room 6",
},
{
id: "7",
sphereImage: "/images/virtual-tour/studio1/Studio1_w-12_13_sp-07.webp",
roomType: "room 7",
},
];
export { spheres };
+123
View File
@@ -0,0 +1,123 @@
[
{
"id": "appartments-studio-1",
"spheres": [
{
"id": "studio-1_sp-01",
"sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-01.webp",
"roomType": "room 1",
"position": [-14.16, 0, 24.11],
"links": [
{
"id": "studio-1_sp-02",
"type": "default",
"labelPosition": [-15.16, 0, 44.11]
},
{
"id": "studio-1_sp-03",
"type": "default",
"labelPosition": [-0.16, 0, 24.11]
}
]
},
{
"id": "studio-1_sp-02",
"sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-02.webp",
"roomType": "room 2",
"position": [-14.16, 0, 24.11],
"links": [
{
"id": "studio-1_sp-01",
"type": "default",
"labelPosition": [-12.16, 0, 5.11]
}
]
},
{
"id": "studio-1_sp-03",
"sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-03.webp",
"roomType": "room 3",
"position": [-14.16, 0, 24.11],
"links": [
{
"id": "studio-1_sp-04",
"type": "default",
"labelPosition": [-0.16, 0, 24.11]
},
{
"id": "studio-1_sp-01",
"type": "default",
"labelPosition": [-45, 0, 24.11]
}
]
},
{
"id": "studio-1_sp-04",
"sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-04.webp",
"roomType": "room 4",
"position": [-14.16, 0, 24.11],
"links": [
{
"id": "studio-1_sp-05",
"type": "default",
"labelPosition": [-0.16, 0, 21.11]
},
{
"id": "studio-1_sp-03",
"type": "default",
"labelPosition": [-45, 0, 24.11]
}
]
},
{
"id": "studio-1_sp-05",
"sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-05.webp",
"roomType": "room 5",
"position": [-14.16, 0, 24.11],
"links": [
{
"id": "studio-1_sp-06",
"type": "default",
"labelPosition": [-0.16, 0, 24.11]
},
{
"id": "studio-1_sp-04",
"type": "default",
"labelPosition": [-45, 0, 27.11]
}
]
},
{
"id": "studio-1_sp-06",
"sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-06.webp",
"roomType": "room 6",
"position": [-14.16, 0, 24.11],
"links": [
{
"id": "studio-1_sp-07",
"type": "default",
"labelPosition": [-0.16, 0, 40.11]
},
{
"id": "studio-1_sp-05",
"type": "default",
"labelPosition": [-45, 0, 27.11]
}
]
},
{
"id": "studio-1_sp-07",
"sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-07.webp",
"roomType": "room 7",
"position": [-14.16, 0, 24.11],
"links": [
{
"id": "studio-1_sp-06",
"type": "default",
"labelPosition": [-45, 0, 5.11]
}
]
}
]
}
]
+40 -60
View File
@@ -1,77 +1,57 @@
[ [
{ {
"id": "studio-1-sp-01", "id": "studio-1_sp-01",
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-01.webp", "sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-01.webp",
"position": [-23.12, 0, -13.4], "roomType": "room 1",
"mapPosition": [97.61, 281.92],
"links": [
{ "toId": "Dvor_16", "label": "" },
{ "toId": "Dvor_17", "label": "Детская площадка" }
]
},
{
"id": "studio-1-sp-02",
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-02.webp",
"position": [-14.16, 0, 24.11], "position": [-14.16, 0, 24.11],
"mapPosition": [39.57, 347.47],
"links": [ "links": [
{ "toId": "Dvor_16", "label": "" }, {
{ "toId": "Dvor_2", "label": "" }, "id": "studio-1_sp-02",
{ "toId": "Hall_1", "label": "Лобби" } "type": "default",
"labelPosition": [-15.16, 0, 24.11]
}
] ]
}, },
{ {
"id": "studio-1-sp-03", "id": "studio-1_sp-02",
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-03.webp", "sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-02.webp",
"position": [-22.27, 0, 19.69], "roomType": "room 2",
"mapPosition": [36.26, 327.64], "position": [-14.16, 0, 24.11],
"links": [ "links": []
{ "toId": "Dvor_1", "label": "" },
{ "toId": "Dvor_14", "label": "" }
]
}, },
{ {
"id": "studio-1-sp-04", "id": "studio-1_sp-03",
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-04.webp", "sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-03.webp",
"position": [-52.03, 0, -3.63], "roomType": "room 3",
"mapPosition": [41.11, 241.29], "position": [-14.16, 0, 24.11],
"links": [ "links": []
{ "toId": "Dvor_15", "label": "" },
{ "toId": "Dvor_4", "label": "" },
{ "toId": "Dvor_9", "label": "Детская площадка" }
]
}, },
{ {
"id": "studio-1-sp-05", "id": "studio-1_sp-04",
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-05.webp", "sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-04.webp",
"position": [-50.6, 0, -15.06], "roomType": "room 4",
"mapPosition": [64.39, 228.91], "position": [-14.16, 0, 24.11],
"links": [ "links": []
{ "toId": "Dvor_9", "label": "Детская площадка" },
{ "toId": "Dvor_5", "label": "" },
{ "toId": "Dvor_3", "label": "" },
{ "toId": "Dvor_6", "label": "Детская площадка" }
]
}, },
{ {
"id": "studio-1-sp-06", "id": "studio-1_sp-05",
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-06.webp", "sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-05.webp",
"position": [-34.36, 0, -4.72], "roomType": "room 5",
"mapPosition": [66.41, 273.23], "position": [-14.16, 0, 24.11],
"links": [ "links": []
{ "toId": "Dvor_4", "label": "" },
{ "toId": "Dvor_17", "label": "Детская площадка" }
]
}, },
{ {
"id": "studio-1-sp-07", "id": "studio-1_sp-06",
"src": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-07.webp", "sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-06.webp",
"position": [-40.94, 0, -23.93], "roomType": "room 6",
"mapPosition": [93.88, 235.03], "position": [-14.16, 0, 24.11],
"links": [ "links": []
{ "toId": "Dvor_17", "label": "Детская площадка" }, },
{ "toId": "Dvor_7", "label": "" }, {
{ "toId": "Dvor_4", "label": "" } "id": "studio-1_sp-07",
] "sphereImage": "/images/virtual-tour/studio1/Studio1_w-12_13_sp-07.webp",
"roomType": "room 7",
"position": [-14.16, 0, 24.11],
"links": []
} }
] ]
+34 -16
View File
@@ -1,4 +1,4 @@
import { useState } from "react"; import { useEffect, useState } from "react";
import { Canvas } from "@react-three/fiber"; import { Canvas } from "@react-three/fiber";
import VirtualTourWrapper from "../components/virtualTour/VirtualTourWrapper"; import VirtualTourWrapper from "../components/virtualTour/VirtualTourWrapper";
import Button from "../components/Button"; import Button from "../components/Button";
@@ -6,22 +6,37 @@ import HeartIcon from "../components/icons/Heart";
import ChevronDownIcon from "../components/icons/ChevronDownIcon"; import ChevronDownIcon from "../components/icons/ChevronDownIcon";
import ButtomPanelCompass from "../components/ButtomPanelCompass"; import ButtomPanelCompass from "../components/ButtomPanelCompass";
import BookingIcon from "../components/icons/BookingIcon"; import BookingIcon from "../components/icons/BookingIcon";
import { spheres } from "../consts/spheres"; import { IAppartmentSphere, ISphere } from "../types/apartmentSphere";
import { IAppartmentSphere } from "../types/apartmentSphere";
import useSphere from "../store/useSphere"; import useSphere from "../store/useSphere";
import { useParams } from "react-router-dom";
import _appartment from "../data/appartment.json";
const appartments = _appartment as IAppartmentSphere[];
const VirtualTour = () => { const VirtualTour = () => {
const [isActive, setIsActive] = useState(false); const [isActive, setIsActive] = useState(false);
const [currentAppartment, setCurrentAppartment] =
useState<null | IAppartmentSphere>(null);
const { setSelectedSphere } = useSphere(); const { setSelectedSphere } = useSphere();
const { appartmentTypeId } = useParams();
const handleOnShowClick = () => { const handleOnShowClick = () => {
setIsActive((prev) => !prev); setIsActive((prev) => !prev);
}; };
const handleOnLabelClick = (sphere: IAppartmentSphere) => { const handleOnLabelClick = (sphere: ISphere) => {
setSelectedSphere(sphere); setSelectedSphere(sphere);
}; };
useEffect(() => {
const _currentAppartment = appartments.find(
(app) => app.id === appartmentTypeId
);
if (_currentAppartment) {
setCurrentAppartment(_currentAppartment);
}
}, [appartmentTypeId]);
return ( return (
<div className="overflow-hidden h-screen w-screen"> <div className="overflow-hidden h-screen w-screen">
<div className="absolute w-screen h-screen grid grid-cols-12 z-30 pointer-events-none"> <div className="absolute w-screen h-screen grid grid-cols-12 z-30 pointer-events-none">
@@ -83,17 +98,18 @@ const VirtualTour = () => {
</div> </div>
<div className="h-14 absolute -bottom-14 left-0 w-full flex justify-between"> <div className="h-14 absolute -bottom-14 left-0 w-full flex justify-between">
<div className="flex gap-1 py-2 items-start flex-wrap"> <div className="flex gap-1 py-2 items-start flex-wrap">
{spheres.map((sphere) => { {currentAppartment &&
return ( currentAppartment.spheres.map((sphere) => {
<div return (
onClick={() => handleOnLabelClick(sphere)} <div
className="bg-[#F3F3F2] font-semibold text-[#0D1922] text-caption-s py-0.5 px-2 w-fit rounded-full cursor-pointer pointer-events-auto select-none" onClick={() => handleOnLabelClick(sphere)}
key={sphere.id} className="bg-[#F3F3F2] font-semibold text-[#0D1922] text-caption-s py-0.5 px-2 w-fit rounded-full cursor-pointer pointer-events-auto select-none"
> key={sphere.id}
{sphere.roomType} >
</div> {sphere.roomType}
); </div>
})} );
})}
</div> </div>
<div className="transition-all duration-300 bg-[#FFFFFFCC] px-4 py-3 w-fit h-12 rounded-ee-lg rounded-es-lg select-none cursor-pointer pointer-events-auto items-start border border-t-[#0D1922B2] active:border-[#00BED7] hover:bg-[#FFFFFF] text-[#0D1922B2] hover:text-[#0D1922]"> <div className="transition-all duration-300 bg-[#FFFFFFCC] px-4 py-3 w-fit h-12 rounded-ee-lg rounded-es-lg select-none cursor-pointer pointer-events-auto items-start border border-t-[#0D1922B2] active:border-[#00BED7] hover:bg-[#FFFFFF] text-[#0D1922B2] hover:text-[#0D1922]">
<div <div
@@ -128,7 +144,9 @@ const VirtualTour = () => {
</div> </div>
</div> </div>
<Canvas> <Canvas>
<VirtualTourWrapper /> {currentAppartment && (
<VirtualTourWrapper appartment={currentAppartment} />
)}
</Canvas> </Canvas>
<ButtomPanelCompass /> <ButtomPanelCompass />
</div> </div>
+3 -3
View File
@@ -1,9 +1,9 @@
import { create } from "zustand"; import { create } from "zustand";
import { IAppartmentSphere } from "../types/apartmentSphere"; import { ISphere } from "../types/apartmentSphere";
interface SequenceStore { interface SequenceStore {
selectedSphere: null | IAppartmentSphere; selectedSphere: null | ISphere;
setSelectedSphere: (sphere: null | IAppartmentSphere) => void; setSelectedSphere: (sphere: null | ISphere) => void;
} }
const useSphere = create<SequenceStore>((set) => ({ const useSphere = create<SequenceStore>((set) => ({
+15 -2
View File
@@ -1,7 +1,20 @@
interface IAppartmentSphere { interface ISphereLink {
id: string;
type: string;
labelPosition: [number, number, number];
}
interface ISphere {
id: string; id: string;
sphereImage: string; sphereImage: string;
roomType: string; roomType: string;
position: [number, number, number];
links: ISphereLink[];
} }
export type { IAppartmentSphere }; interface IAppartmentSphere {
id: string;
spheres: ISphere[];
}
export type { ISphere, IAppartmentSphere, ISphereLink };