feat: add car
This commit is contained in:
parent
658741f889
commit
96e41161b4
|
|
@ -2,7 +2,6 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>map3d</title>
|
||||
</head>
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div
|
||||
css={css({
|
||||
|
|
@ -65,7 +68,25 @@ export function TopNav() {
|
|||
display: "none",
|
||||
},
|
||||
})}
|
||||
></div>
|
||||
>
|
||||
<button
|
||||
style={{
|
||||
display: step == 2 ? "" : "none",
|
||||
color: "#000000",
|
||||
backgroundColor: "#ffffff96",
|
||||
backdropFilter: "blur(8px)",
|
||||
border: "none",
|
||||
padding: "0.5rem 1rem",
|
||||
borderRadius: "8px",
|
||||
fontWeight: "300",
|
||||
fontSize: "12px",
|
||||
outline: "rgba(240, 240, 244, 0.51) solid 0.1rem",
|
||||
}}
|
||||
onClick={() => setThirdMode(true)}
|
||||
>
|
||||
Car Mode
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
import { create } from "zustand";
|
||||
|
||||
type CarStore = {
|
||||
thirdMode: boolean;
|
||||
|
||||
setThirdMode: (thirdMode: boolean) => void;
|
||||
};
|
||||
|
||||
export const useCarStore = create<CarStore>((set) => ({
|
||||
thirdMode: false,
|
||||
setThirdMode: (thirdMode) => set(() => ({ thirdMode: thirdMode })),
|
||||
}));
|
||||
|
|
@ -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 (
|
||||
<>
|
||||
<mesh ref={carRef} position={[0, 0.5, 0]}>
|
||||
<boxGeometry args={[0.2, 0.2, 0.4]} />
|
||||
<meshStandardMaterial color="orange" />
|
||||
</mesh>
|
||||
{!thirdMode && <OrbitControls />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Car;
|
||||
|
|
@ -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 (
|
||||
<Canvas camera={{ fov: 90, near: 0.1, far: 7000 }}>
|
||||
<ambientLight intensity={Math.PI / 2} />
|
||||
|
|
@ -238,7 +240,7 @@ export function Space() {
|
|||
|
||||
<Roads area={realCenter} />
|
||||
<pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />
|
||||
<OrbitControls />
|
||||
<Car />
|
||||
<Export />
|
||||
<Sky
|
||||
distance={450000}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ function App() {
|
|||
|
||||
return (
|
||||
<div css={css({ height: "100%", width: "100%" })}>
|
||||
<TopNav />
|
||||
<TopNav step={step} />
|
||||
|
||||
<FullscreenModal isOpen={steps[step] == "front"}>
|
||||
<Column gap="1rem">
|
||||
|
|
|
|||
Loading…
Reference in New Issue