AIdentity

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

3_vr.tsx (5662B)


      1 "use client";
      2 
      3 import { Canvas, useFrame } from "@react-three/fiber";
      4 import {
      5   OrbitControls,
      6   Sky,
      7   ContactShadows,
      8   Cloud,
      9   Clouds,
     10 } from "@react-three/drei";
     11 import BenchScene from "./BenchScene.tsx";
     12 import { useState, useMemo, useRef, useEffect } from "react";
     13 import * as THREE from "three";
     14 import { StageProps } from "../ctrl/page.tsx";
     15 
     16 const AUDIO_SOURCES: Record<number, string> = {
     17   1: "/audio/003-1.flac",
     18   2: "/audio/003-2.flac",
     19 };
     20 
     21 const useSequentialAudio = (
     22   initialState: number,
     23   audioSources: Record<number, string>,
     24   onComplete: () => void,
     25 ) => {
     26   const [sceneState, setSceneState] = useState(initialState);
     27   const audioRef = useRef<HTMLAudioElement | null>(null);
     28 
     29   const currentAudioUrl = audioSources[sceneState];
     30 
     31   useEffect(() => {
     32     if (sceneState > Math.max(...Object.keys(audioSources).map(Number))) {
     33       console.log("All audio finished. Calling onComplete.");
     34       onComplete();
     35       return;
     36     }
     37 
     38     if (!currentAudioUrl) {
     39       return;
     40     }
     41 
     42     console.log(`Starting setup for state ${sceneState}: ${currentAudioUrl}`);
     43 
     44     const existingAudio = audioRef.current;
     45     if (existingAudio) {
     46       existingAudio.pause();
     47     }
     48 
     49     const audio = new Audio(currentAudioUrl);
     50     audioRef.current = audio;
     51 
     52     const handleAudioEnded = () => {
     53       console.log(`Audio ${sceneState} finished. Transitioning state.`);
     54       // 次の状態へ遷移 (例: 1 -> 2, 2 -> 3)
     55       setSceneState((prev) => prev + 1);
     56     };
     57 
     58     audio.addEventListener("ended", handleAudioEnded);
     59 
     60     audio.play().catch((e) => {
     61       console.warn(
     62         `Audio playback failed for state ${sceneState} (requires user interaction):`,
     63         e,
     64       );
     65     });
     66 
     67     return () => {
     68       if (audio === audioRef.current) {
     69         audio.pause();
     70         audio.removeEventListener("ended", handleAudioEnded);
     71       }
     72     };
     73   }, [sceneState, audioSources, onComplete]);
     74   return sceneState;
     75 };
     76 
     77 interface RandomCloudsProps {
     78   count: number;
     79   radius: number;
     80   height: number;
     81   sceneState: number;
     82 }
     83 
     84 function SceneController({ sceneState }: { sceneState: number }) {
     85   const isFoggy = sceneState === 2;
     86 
     87   const directionalLightIntensity = isFoggy ? 0.1 : 2;
     88 
     89   const ambientLightIntensity = isFoggy ? 0.05 : 0.5;
     90 
     91   useFrame(({ scene }) => {
     92     if (isFoggy) {
     93       scene.fog = new THREE.Fog(0x444444, 5, 40); // Far distanceも少し短くして密度を上げる
     94     } else {
     95       scene.fog = null;
     96     }
     97   });
     98 
     99   return (
    100     <>
    101       <ambientLight intensity={ambientLightIntensity} />
    102 
    103       <directionalLight
    104         position={[5, 10, 5]}
    105         intensity={directionalLightIntensity} // ここで制御
    106         castShadow
    107         shadow-mapSize-width={2048}
    108         shadow-mapSize-height={2048}
    109         shadow-camera-far={50}
    110         shadow-camera-left={-10}
    111         shadow-camera-right={10}
    112         shadow-camera-top={10}
    113         shadow-camera-bottom={-10}
    114       />
    115     </>
    116   );
    117 }
    118 
    119 function AnimatedSky({ sceneState }: { sceneState: number }) {
    120   const [sunPosition, setSunPosition] = useState<[number, number, number]>([
    121     0, 100, 0,
    122   ]);
    123 
    124   useFrame(({ clock }) => {
    125     const time = clock.getElapsedTime() * 0.1;
    126     const x = Math.sin(time) * 100;
    127     const z = Math.cos(time) * 100;
    128     setSunPosition([x, 100, z]);
    129   });
    130 
    131   if (sceneState === 2) {
    132     return null;
    133   }
    134 
    135   return (
    136     <Sky
    137       distance={450000}
    138       sunPosition={new THREE.Vector3(...sunPosition)}
    139       inclination={sceneState === 2 ? 0.001 : 0.6}
    140       azimuth={0.25}
    141       mieCoefficient={0.005}
    142       mieDirectionalG={0.8}
    143       rayleigh={0.5}
    144       turbidity={10}
    145     />
    146   );
    147 }
    148 
    149 function RandomClouds({
    150   count,
    151   radius,
    152   height,
    153   sceneState,
    154 }: RandomCloudsProps) {
    155   const cloudColor = sceneState === 2 ? "#080808" : "#fff";
    156   const baseOpacity = sceneState === 2 ? 0.9 : 0.5;
    157 
    158   const cloudConfigs = useMemo(() => {
    159     return Array.from({ length: count }).map((_, i) => {
    160       const angle = Math.random() * Math.PI * 2;
    161       const distance = Math.sqrt(Math.random()) * radius;
    162 
    163       const x = Math.cos(angle) * distance;
    164       const z = Math.sin(angle) * distance;
    165 
    166       return {
    167         position: [x, height + (Math.random() - 0.5) * 5, z] as [
    168           number,
    169           number,
    170           number,
    171         ],
    172 
    173         bounds: [
    174           Math.random() * 10 + 10,
    175           Math.random() * 2 + 1,
    176           Math.random() * 10 + 10,
    177         ] as [number, number, number],
    178         seed: Math.floor(Math.random() * 1000) + i,
    179         volume: Math.random() * 20 + 5,
    180         opacity: Math.random() * 0.4 + baseOpacity,
    181         color: cloudColor,
    182       };
    183     });
    184   }, [count, radius, height]);
    185 
    186   return (
    187     <>
    188       {cloudConfigs.map((config, index) => (
    189         <Cloud key={index} {...config} />
    190       ))}
    191     </>
    192   );
    193 }
    194 
    195 export default function ThreeVR({ onComplete }: StageProps) {
    196   const sceneState = useSequentialAudio(1, AUDIO_SOURCES, onComplete);
    197 
    198   return (
    199     <Canvas
    200       key="r3f-main-canvas"
    201       camera={{ position: [5, 5, 5], fov: 60 }}
    202       shadows
    203       style={{ background: sceneState === 2 ? "#85555B" : "#d0e0ff" }}
    204     >
    205       <AnimatedSky sceneState={sceneState} />
    206 
    207       <Clouds material={THREE.MeshLambertMaterial} limit={200}>
    208         <RandomClouds
    209           count={100}
    210           radius={80}
    211           height={15}
    212           sceneState={sceneState}
    213         />
    214       </Clouds>
    215 
    216       <SceneController sceneState={sceneState} />
    217 
    218       <BenchScene />
    219       <ContactShadows
    220         position={[0, -0.5, 0]}
    221         opacity={0.7}
    222         scale={10}
    223         blur={1}
    224         far={10}
    225       />
    226       <OrbitControls enableZoom={true} enablePan={true} enableRotate={true} />
    227     </Canvas>
    228   );
    229 }