feat: add car
This commit is contained in:
parent
658741f889
commit
96e41161b4
|
|
@ -2,7 +2,6 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<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" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>map3d</title>
|
<title>map3d</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useCarStore } from "@/state/carStore";
|
||||||
import { css } from "@emotion/react";
|
import { css } from "@emotion/react";
|
||||||
|
|
||||||
const TOP_PANEL_HEIGHT = "3rem";
|
const TOP_PANEL_HEIGHT = "3rem";
|
||||||
|
|
@ -6,7 +7,9 @@ const BORDER_COLOR = "#ededf290";
|
||||||
const breakpoints = [768];
|
const breakpoints = [768];
|
||||||
const mq = breakpoints.map((bp) => `@media (max-width: ${bp}px)`);
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
css={css({
|
css={css({
|
||||||
|
|
@ -65,7 +68,25 @@ export function TopNav() {
|
||||||
display: "none",
|
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>
|
</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 * as THREE from "three";
|
||||||
import { useActionStore } from "@/state/exportStore";
|
import { useActionStore } from "@/state/exportStore";
|
||||||
import { GLTFExporter } from "three/examples/jsm/Addons.js";
|
import { GLTFExporter } from "three/examples/jsm/Addons.js";
|
||||||
|
import Car from "./Car";
|
||||||
|
|
||||||
function Building({
|
function Building({
|
||||||
shape,
|
shape,
|
||||||
|
|
@ -217,6 +218,7 @@ export function Space() {
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildingsData = areaData();
|
const buildingsData = areaData();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Canvas camera={{ fov: 90, near: 0.1, far: 7000 }}>
|
<Canvas camera={{ fov: 90, near: 0.1, far: 7000 }}>
|
||||||
<ambientLight intensity={Math.PI / 2} />
|
<ambientLight intensity={Math.PI / 2} />
|
||||||
|
|
@ -238,7 +240,7 @@ export function Space() {
|
||||||
|
|
||||||
<Roads area={realCenter} />
|
<Roads area={realCenter} />
|
||||||
<pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />
|
<pointLight position={[-10, -10, -10]} decay={0} intensity={Math.PI} />
|
||||||
<OrbitControls />
|
<Car />
|
||||||
<Export />
|
<Export />
|
||||||
<Sky
|
<Sky
|
||||||
distance={450000}
|
distance={450000}
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div css={css({ height: "100%", width: "100%" })}>
|
<div css={css({ height: "100%", width: "100%" })}>
|
||||||
<TopNav />
|
<TopNav step={step} />
|
||||||
|
|
||||||
<FullscreenModal isOpen={steps[step] == "front"}>
|
<FullscreenModal isOpen={steps[step] == "front"}>
|
||||||
<Column gap="1rem">
|
<Column gap="1rem">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue