{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 */}