From 0e70a06e05844e686fc6b03d0f60e1a10df53b3d Mon Sep 17 00:00:00 2001 From: syui Date: Fri, 21 Nov 2025 13:08:09 +0900 Subject: [PATCH] add tokyo --- atmosphere/package.json | 1 + atmosphere/public/favicon.ico | Bin 0 -> 15406 bytes atmosphere/src/App.jsx | 240 ++++++++++++++++++++++++++++++---- 3 files changed, 215 insertions(+), 26 deletions(-) create mode 100644 atmosphere/public/favicon.ico diff --git a/atmosphere/package.json b/atmosphere/package.json index 1aefc5572..61493e406 100644 --- a/atmosphere/package.json +++ b/atmosphere/package.json @@ -15,6 +15,7 @@ "@react-three/postprocessing": "^3.0.4", "@takram/three-atmosphere": "^0.15.1", "@takram/three-clouds": "^0.5.2", + "3d-tiles-renderer": "^0.4.18", "react": "^19.0.0-rc.1", "react-dom": "^19.0.0-rc.1", "three": "^0.181.2" diff --git a/atmosphere/public/favicon.ico b/atmosphere/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..7f94540e2642b7fcf40e2b45e08b1404d63692e8 GIT binary patch literal 15406 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 NT?=$A@IPyT{{RvJ Altitude: ${distance}, TruePosLength: ${truePosition.length()}`); + + // 3. Apply True Position + worldState.position.copy(truePosition); + + // 4. Apply Rotation (Global -> Local) + // GlobalQ = BasisQ * LocalQ => LocalQ = BasisQ^-1 * GlobalQ + const basis = getSurfaceBasis(truePosition); + const basisInv = basis.clone().invert(); + worldState.quaternion.copy(basisInv.multiply(globalQuaternion)); +} + // キー入力管理 const keys = { w: false, a: false, s: false, d: false, + q: false, + e: false, Shift: false, Space: false, }; @@ -100,6 +189,25 @@ if (typeof window !== 'undefined') { // Scene Components (Inside Canvas) // --------------------------------------------------------- +const dracoLoader = new DRACOLoader(); +dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/'); + +function GoogleMaps3DTiles() { + const apiKey = import.meta.env.VITE_GOOGLE_MAP_API_KEY; + + if (!apiKey) return null; + + return ( + + + + + + + + ); +} + // Canvasの内側で動作するメインシーンコンポーネント function AtmosphereScene() { const { gl } = useThree(); @@ -175,6 +283,7 @@ function AtmosphereScene() { /> + {weather.layers.map((layer, index) => ( @@ -199,9 +308,17 @@ function AtmosphereScene() { // Atmosphere側のカメラをShared Stateに同期させる function FollowCamera() { useFrame((state) => { - // Shared Stateの位置と回転を適用 + if (!worldState.position) return; + + // 1. Calculate Basis Rotation based on current position + const basis = getSurfaceBasis(worldState.position); + + // 2. Apply Position state.camera.position.copy(worldState.position); - state.camera.quaternion.copy(worldState.quaternion); + + // 3. Apply Rotation: GlobalQ = BasisQ * LocalQ + // worldState.quaternion is treated as Local Quaternion here + state.camera.quaternion.copy(basis).multiply(worldState.quaternion); }); return null; } @@ -362,7 +479,6 @@ function VrmCharacter() { function CameraSync() { const { camera } = useThree(); const vec = new Vector3(); - const euler = new Euler(0, 0, 0, 'YXZ'); // Dynamic Zoom State const zoomOffset = useRef(0); @@ -370,34 +486,64 @@ function CameraSync() { const ZOOM_SPEED = 2.0; useFrame((state, delta) => { - // 1. カメラの回転をShared Stateに同期 + // 1. カメラの回転をShared State (Local) に同期 worldState.quaternion.copy(camera.quaternion); - // 2. WASD移動ロジック - const isMoving = keys.w || keys.s || keys.a || keys.d; + // 2. Calculate Basis Rotation + const basis = getSurfaceBasis(worldState.position); + + // 3. WASD移動ロジック (Local -> Global) + const isMoving = keys.w || keys.s || keys.a || keys.d || keys.q || keys.e; const speed = worldState.speed * (keys.Shift ? 2.0 : 1.0) * delta; - // 前後 (W/S) - if (keys.w) { - vec.set(0, 0, -1).applyQuaternion(camera.quaternion); - worldState.position.addScaledVector(vec, speed); + // Local Movement Vector + const moveDir = new Vector3(); + if (keys.w) moveDir.z -= 1; + if (keys.s) moveDir.z += 1; + if (keys.a) moveDir.x -= 1; + if (keys.d) moveDir.x += 1; + + // Altitude Control (Global Up/Down relative to surface) + // We apply this directly to worldState.position along the up vector + const upVector = worldState.position.clone().normalize(); + if (keys.e) { + worldState.position.addScaledVector(upVector, speed); } - if (keys.s) { - vec.set(0, 0, 1).applyQuaternion(camera.quaternion); - worldState.position.addScaledVector(vec, speed); + if (keys.q) { + worldState.position.addScaledVector(upVector, -speed); } - // 左右 (A/D) - if (keys.a) { - vec.set(-1, 0, 0).applyQuaternion(camera.quaternion); - worldState.position.addScaledVector(vec, speed); - } - if (keys.d) { - vec.set(1, 0, 0).applyQuaternion(camera.quaternion); - worldState.position.addScaledVector(vec, speed); + if (moveDir.lengthSq() > 0) { + moveDir.normalize(); + // Apply Camera Rotation (Local) + moveDir.applyQuaternion(camera.quaternion); + + // Apply Basis Rotation (Local -> Global) + moveDir.applyQuaternion(basis); + + // Apply to Global Position + worldState.position.addScaledVector(moveDir, speed); } - // 3. Dynamic Zoom Logic + // 4. Minimum Altitude Safety Check + const currentDist = worldState.position.length(); + + // Calculate local ellipsoid radius + const localRadius = getEllipsoidRadius(worldState.position); + + const minAltitude = localRadius + 10; // Minimum 10m above ellipsoid + + // Debug Log + if (state.clock.elapsedTime % 1.0 < 0.02) { + console.log(`[CameraSync] Dist: ${currentDist.toFixed(1)}, LocalR: ${localRadius.toFixed(1)}, Alt: ${(currentDist - localRadius).toFixed(1)}`); + } + + if (currentDist < minAltitude) { + // console.log(`[CameraSync] Safety Check Triggered! Pushing up to ${minAltitude}`); + worldState.position.setLength(minAltitude); + } + + // 5. Dynamic Zoom Logic const targetZoom = isMoving ? MAX_ZOOM_OFFSET : 0; // Smoothly interpolate current zoom offset towards target const diff = targetZoom - zoomOffset.current; @@ -429,6 +575,45 @@ function AvatarLayer() { ); } +// --------------------------------------------------------- +// UI Components +// --------------------------------------------------------- +function LocationUI() { + return ( +
+ {LOCATIONS.map((loc) => ( + + ))} +
+ ); +} + // --------------------------------------------------------- // Main App // --------------------------------------------------------- @@ -453,6 +638,9 @@ export default function App() { return (
+ {/* UI Layer */} + + {/* Layer 0: Atmosphere */}