diff --git a/index.html b/index.html
index 455df7c..cb942d6 100644
--- a/index.html
+++ b/index.html
@@ -2,7 +2,6 @@
-
map3d
diff --git a/src/components/nav/TopNav.tsx b/src/components/nav/TopNav.tsx
index 0034eba..35774a9 100644
--- a/src/components/nav/TopNav.tsx
+++ b/src/components/nav/TopNav.tsx
@@ -1,3 +1,4 @@
+import { useCarStore } from "@/state/carStore";
import { css } from "@emotion/react";
const TOP_PANEL_HEIGHT = "3rem";
@@ -6,7 +7,9 @@ const BORDER_COLOR = "#ededf290";
const breakpoints = [768];
const mq = breakpoints.map((bp) => `@media (max-width: ${bp}px)`);
-export function TopNav() {
+export function TopNav({ step }: { step: number }) {
+ const setThirdMode = useCarStore((state) => state.setThirdMode);
+
return (
+ >
+
+
);
}
diff --git a/src/state/carStore.ts b/src/state/carStore.ts
new file mode 100644
index 0000000..97b1d6f
--- /dev/null
+++ b/src/state/carStore.ts
@@ -0,0 +1,12 @@
+import { create } from "zustand";
+
+type CarStore = {
+ thirdMode: boolean;
+
+ setThirdMode: (thirdMode: boolean) => void;
+};
+
+export const useCarStore = create((set) => ({
+ thirdMode: false,
+ setThirdMode: (thirdMode) => set(() => ({ thirdMode: thirdMode })),
+}));
diff --git a/src/three/Car.tsx b/src/three/Car.tsx
new file mode 100644
index 0000000..c8a9ee5
--- /dev/null
+++ b/src/three/Car.tsx
@@ -0,0 +1,151 @@
+import React, { useRef, useEffect, useCallback } from "react";
+import { useFrame, useThree } from "@react-three/fiber";
+import { OrbitControls, Html } from "@react-three/drei";
+import { css } from "@emotion/react";
+import * as THREE from "three";
+import { useCarStore } from "@/state/carStore";
+
+const Car = () => {
+ const carRef = useRef();
+ const { camera } = useThree();
+ const thirdMode = useCarStore((state) => state.thirdMode);
+ const setThirdMode = useCarStore((state) => state.setThirdMode);
+ const keys = useRef({ w: false, s: false, a: false, d: false });
+ const velocity = useRef(0);
+
+ const handleKeyDown = useCallback((e) => {
+ switch (e.key.toLowerCase()) {
+ case "w":
+ keys.current.w = true;
+ break;
+ case "s":
+ keys.current.s = true;
+ break;
+ case "a":
+ keys.current.a = true;
+ break;
+ case "d":
+ keys.current.d = true;
+ break;
+ case "escape":
+ setThirdMode(false);
+ if (document.exitPointerLock) {
+ document.exitPointerLock();
+ }
+ break;
+ default:
+ break;
+ }
+ }, []);
+
+ const handleKeyUp = useCallback((e) => {
+ switch (e.key.toLowerCase()) {
+ case "w":
+ keys.current.w = false;
+ break;
+ case "s":
+ keys.current.s = false;
+ break;
+ case "a":
+ keys.current.a = false;
+ break;
+ case "d":
+ keys.current.d = false;
+ break;
+ default:
+ break;
+ }
+ }, []);
+
+ useEffect(() => {
+ window.addEventListener("keydown", handleKeyDown);
+ window.addEventListener("keyup", handleKeyUp);
+ return () => {
+ window.removeEventListener("keydown", handleKeyDown);
+ window.removeEventListener("keyup", handleKeyUp);
+ };
+ }, [handleKeyDown, handleKeyUp]);
+
+ useEffect(() => {
+ if (thirdMode) {
+ const handleClick = () => {
+ if (document.pointerLockElement !== document.body) {
+ document.body.requestPointerLock();
+ }
+ };
+ window.addEventListener("click", handleClick);
+ return () => window.removeEventListener("click", handleClick);
+ }
+ }, [thirdMode]);
+
+ useEffect(() => {
+ if (thirdMode) {
+ const onMouseMove = (event) => {
+ if (document.pointerLockElement === document.body && carRef.current) {
+ carRef.current.rotation.y -= event.movementX * 0.002;
+ }
+ };
+ document.addEventListener("mousemove", onMouseMove);
+ return () => document.removeEventListener("mousemove", onMouseMove);
+ }
+ }, [thirdMode]);
+
+ useFrame((state, delta) => {
+ if (carRef.current) {
+ const accelerationRate = 0.2;
+ const maxSpeed = 3.0;
+ const decelerationRate = 1.0;
+ if (keys.current.w) {
+ velocity.current = Math.min(
+ maxSpeed,
+ velocity.current + accelerationRate * delta
+ );
+ } else if (keys.current.s) {
+ velocity.current = Math.max(
+ -maxSpeed,
+ velocity.current - accelerationRate * delta
+ );
+ } else {
+ if (velocity.current > 0) {
+ velocity.current = Math.max(
+ 0,
+ velocity.current - decelerationRate * delta
+ );
+ } else if (velocity.current < 0) {
+ velocity.current = Math.min(
+ 0,
+ velocity.current + decelerationRate * delta
+ );
+ }
+ }
+ if (keys.current.a) carRef.current.rotation.y += 0.02;
+ if (keys.current.d) carRef.current.rotation.y -= 0.02;
+ const forward = new THREE.Vector3(0, 0, -1);
+ forward.applyQuaternion(carRef.current.quaternion);
+ carRef.current.position.addScaledVector(forward, velocity.current);
+ }
+ if (thirdMode && carRef.current) {
+ const carPos = carRef.current.position;
+ const offset = new THREE.Vector3(0, 1, 2);
+ offset.applyAxisAngle(
+ new THREE.Vector3(0, 1, 0),
+ carRef.current.rotation.y
+ );
+ const desiredPosition = carPos.clone().add(offset);
+ camera.position.lerp(desiredPosition, 0.1);
+ camera.lookAt(carPos);
+ }
+ });
+
+ return (
+ <>
+
+
+
+
+ {!thirdMode && }
+ >
+ );
+};
+
+export default Car;
diff --git a/src/three/Space.tsx b/src/three/Space.tsx
index f4b7516..f6854b4 100644
--- a/src/three/Space.tsx
+++ b/src/three/Space.tsx
@@ -5,6 +5,7 @@ import { OrbitControls, Html, Sky, Environment } from "@react-three/drei";
import * as THREE from "three";
import { useActionStore } from "@/state/exportStore";
import { GLTFExporter } from "three/examples/jsm/Addons.js";
+import Car from "./Car";
function Building({
shape,
@@ -217,6 +218,7 @@ export function Space() {
};
const buildingsData = areaData();
+
return (