戻る
2022/08/20

3D photo 2022 summer version

[実行サンプル](click)

今回は写真の鑑賞に重点を置いた作品。


カメラでは サイズ5184 x 2920ピクセル (16:9)、解像度 350 dpi で写真を撮るが、一枚の写真がおおよそ4.5Mの大きさになる。このまま32枚の写真を3Dで使うと私のパソコンではメモリー使用率が100%になる。そのため試行錯誤の結果 3000 x 1690 、250 dpiに縮小し1/4の大きさにしている。まだ課題があるようだ。


ここでは主にweb audio apiの使い方を記録しておく。特にaudioの fade outに手こずった。

参考) : Web Audio API はウェブ上で音声を扱うための強力で多機能なシステムを提供します。これにより開発者は音源を選択したり、エフェクトを加えたり、ビジュアライゼーションを加えたり、パンニングなどの特殊効果を適用したり、他にもたくさんのいろいろなことができるようになります。


要件

以下作成したhtml、css、js(JavaScript)の実コード

html


<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<meta charset="UTF-8">
<meta name="keywords" content=" photo cube ,  three.js audio visualizer " />
<meta name="description" content=" photo cube" />
<title>photo cube v2</title>
<link rel="stylesheet" type="text/css" href="photocube.css">
<style>
<!--
body {   background-image:  linear-gradient(225deg , #191970 , #000000); }
-->
</style>
</head>
<body>
<div id="root">
<div id="modoru">
<a href="#" onClick="history.back(); return false;">戻る</a>
</div>

</div>
<div id="btn_box">
  <span id="btn_g">
  <input type="button" value="Guide" onclick="clickBtng()"/>
  </span>

  <span id="btn_m1">
    <input type="button" id="../music/J.S.Bach-Air-On-G.mp3%../music/Albeniz-Suite-Espanola-Granada.mp3%../music/Schubert-Serenade.mp3%../music/Chopin-Etude No.3 In E Major Op .10 No.3.mp3" value="Auto1" onclick="clickBtn1(this.id,this.value)">
  </span>
  <span id="btn_m2">
    <input type="button" id="../music/Mascagni-Cavalleria-Rusticana-Intermezzo.mp3%../music/Chopin-Nocturne-No5.mp3%../music/Lange-Blumenlied.mp3%../music/Beethoven-MoonlightSonata-1.mp3" value="Auto2" onclick="clickBtn1(this.id,this.value)">
  </span>
  <span id="btn_mp">
    <input type="button" id="reload" value="Reload" onclick="location.reload()" >
  </span>

  <span id="msgtxt1"> </span> <!-- timer -->
  <span id="msgtxt2"></span>   <!-- manual   -->
 </div>
 <div id="txt_box">
  <span id="msgtxt3"></span>    <!-- img text -->
 </div>

 <div id="canvas_container">
 <canvas id="canvas3d"></canvas>
 </div>
<!-- three.jsを読み込む-->
<script src="js/three.js"></script>
<script src="js/OrbitControls.js"></script>
<script src="js/photocube2.js"></script>

<div style="clear:both;"></div>
<!-- access counter --!>

<table style="visibility:hidden;">
<tr><td><img src="../3ddaycount/daycount.cgi?gif"></td><td><img src="../3ddaycount/daycount.cgi?today"></td><td><img src="../3ddaycount/daycount.cgi?yes"></td></tr>
</table>

<!-- access counter --!>
<table style="visibility:hidden;">
<tr><td><img src="../daycount/daycount.cgi?gif"></td><td><img src="../daycount/daycount.cgi?today"></td><td><img src="../daycount/daycount.cgi?yes"></td></tr>
</table>

</body>
</html>

photocube.css


/* common body 2022.01.05 */
html {
        font-size:12px;    /* base font-size */
      }
body  {
  width:100%;
  background-color :white ;
  margin:0;
  overflow: hidden;
  font-family: "游ゴシック体", "Yu Gothic", YuGothic, "ヒラギノ角ゴ Pro", "Hiragino Kaku Gothic Pro", "メイリオ", "Meiryo", sans-serif;
}
#root {
     width:100%;
     margin:0px;
     }
#select_box {
     width:98%;
     margin:2px auto 0px 10px;
     padding:0px;
     text-align:left;
     }
#btn_box {
     margin:2px auto 0px 10px;
     text-align:left;
     }
#btn_box input {
     width:12%;
     border:1px ridge gold;
     background-color:black;
     color:white;
     }
#txt_box {
     width:100%;
     margin:2px auto;
     text-align:center;
    }
#select_box input {
     color:ivory;
     padding:0px;
     }
#btn_g , #btn_mm , #btn_m1 , #btn_m2 , #btn_mp  {
     border:1px ridge white;
     border-radius:5px;
     background-color:black;
     margin:0px;
     padding:0px;
     font-size:0.8rem;
    }
#canvas_container {
    width:100%;
    height:100%;
    margin:0px;
    padding:0px;
    }
#canvas3d {
    width:100%;
    height: calc(99vw / 1.77 );  
    margin:0px;
    padding:0px;
    display:block; 
    }
form select {
     color:black;
     }
form option value {
     background-color:green;
     }
#msgtxt1 , #msgtxt2 , #msgtxt3  {
      color : ivory ;
      font-size:1.0rem;
      text-align:left;
    }
#msgtxt1 {                               /* timer */
      margin: 0px 5px;
   }
#msgtxt3 {                              /* img text */
      background-color: transparent;
      font-family:'Monotype Corsiva','CourierNewPS-BoldItalicMT';
      text-shadow:2px 2px 12px gold;
      padding:2px;
      letter-spacing:2px;
      font-size:1.2rem;
      color:aliceblue;
      text-shadow:5px 5px 10px lightblue;
   }

/* modoru */
#modoru {
      width:5%;
      margin:0px auto 10px 10px;
      float:left;
      text-align:center;
      font-size:0.8rem;
      padding:1px;
      }
#modoru a:link , #modoru a:visited  {
      display:block;
      text-decoration:none; 
      border:1px ridge gray;
      border-radius:5px;
      color:white;
      }
#modoru a:hover  {
      display:block;
      text-decoration:none; 
      border:1px ridge ivory;
      border-radius:5px;
      color:orange;
      }

/*------------------ mobile responsive -----------------------------*/
@media screen and (max-width: 480px) { 
#btn_box input {
     width:14%;
     padding:0px;
     margin:0px;
     }
#btn_g , #btn_mm , #btn_m1 , #btn_m2 , #btn_mp  {
     border:1px ridge white;
     border-radius:1px;
     margin:0px ;
     padding:0px;
     font-size:0.7rem;
    }
#msgtxt1 , #msgtxt2 , #msgtxt3 {
    font-size:0.8rem;
   }
#msgtxt1  {
    font-size:0.7rem;
   }
#msgtxt3  {
     letter-spacing:1px;
   }
#canvas3d {
     width:100%;
     height:100vw ;  
    }
#modoru   {
    width:10%;
    } 

}
/* --------------------------------------------------------------------*/

photocube2.js(全体)


// ------------------------  photo cube 2022/08/19 ver3  ---------------------- 
//
// --------------------------------------------------------------------------------
//  manual button click
// -------------------------------------------------------------------------------
 function clickBtng(){
   gtxt1 = " ・通常のパソコンの画面比率と同じように写真の横縦比も16:9に設定しているので最初に画面も16:9にしてください"
   gtxt2 = " ・開始するには「Auto」を押して下さい。「Auto1」と「Auto2」は音楽の違いです"
   gtxt3 = " ・マウス操作でカメラを制御 = オービット(周回軌道) : 左ボタンでドラッグ  ズーム : マウスホイール  パン(カメラの向きを変える) : 右ボタンでドラッグ "
   gtxt4 = " ・「Auto」ボタンを再度押すときはその前に「Reload」を押して下さい"
   gtxt5 = " ・機器によって正常に稼働しない場合は、「Reload」「Auto」で再操作すると稼働する場合があります"
   gtxt6 = " ・このメッセージを消すにはサイド「Guide」を押して下さい"
   gtxt = '<br><br>' + gtxt1 + '<br>' +  gtxt2 + '<br>' +  gtxt3 +  '<br>' + gtxt4 +  '<br>' + gtxt5 + '<br>' + gtxt6 ;
   str0 = document.getElementById("msgtxt2").innerHTML
   if ( str0 === "" ) {str = gtxt}
   else {str = "" };
   document.getElementById("msgtxt2").innerHTML = str ;
 };

// --------------------------------------------------------------------------------
//  start procedure
// -------------------------------------------------------------------------------
 function clickBtn1(id , value){
   if (value.match("Auto1"))  { mode  = "a" , yrotspd = 0.0016 , document.getElementById("btn_m1").style.backgroundColor = "orange";}   // auto1
   if (value.match("Auto2"))  { mode  = "a" , yrotspd = 0.0016 , document.getElementById("btn_m2").style.backgroundColor = "orange";}   // auto2
   if (value.match("Reload")) { mode  = "r" , yrotspd = 0 , document.getElementById("btn_mp").style.backgroundColor = "orange";}   // auto2

    // ----------------- set const
    let sst0 = 0
    let sstsv = 0 ;
    const  timg = 15 ;      // each img time
    const imgc = 32 ;       // number of images
    const imgposz = 500 ; // img z position 
    var angles1 = 0 ;
    var im = 1 ;
    let audiovolume =  1.0 ;   // audio volume
    let fadeflg = 0 ;               // audio fade sw 
    var mindex = 0;
    let tmsh = 0 ;                 // text mesh
    var request , source , stopflag , closeflag = 0;
    let resp = 0 ;
    var stx = 0 ;                    // text start
    var tx = 1 ;
    var imgmove = 0 ;           // image move sw 
    var lint0 = 1.0 ;      // spotlight default
    var angletext = 0 ; // text rotaion angle
    const radiustext  = 2000 ;
    let endflag = 0 ;    // end process switch

    let positiony2 = 200 , positionz2 = 0 ;
    mname = new Array();
    let nn = 1 ;     // music name number
    let mtext = 0 ; // put music name sw
 
   // ---------------------- setup ------------------
    defineimgtxt();        // define image and text
    readimg();              // read image
    const imgw   =  "3000";                                // img width
    const imgh    =  "1708";                                // img height
    const imgd  = 2                                            // image depth
    // safari 判定
    let safari = 0 ;
    const agent = window.navigator.userAgent.toLowerCase()
    if (agent.indexOf("safari") != -1 && agent.indexOf("chrome") == -1) { safari = 1 }
    // ---------------------------------------------------------------------------
    //   read music names from html and put in array
    // ---------------------------------------------------------------------------
  if ( mode !== "m") {
    playlist = id.split("%", 8);   // take music name
    audionext ();
    function audionext () {                        // audio setup & next
      if ( mindex < playlist.length ) {
        audiosrc = playlist[mindex]             // audio name
        console.log(audiosrc)
        playSound(audiosrc);                     // <======== play sound
        mindex++;
        mtitle = audiosrc.replace("mp3" , "" ).replace("../music/" , "").replace("." , "").replace("-" , " ");     // music title
        settext(mtitle)                          // mesh text write

        if ( mname.indexOf(mtitle) == -1 ) {      //  check duplicate , save music name
           mname[nn] = mtitle 
           settext2(mname[nn])                         // put music name on wall 
           nn = nn +1 
        };
     }
     else {
        audiosrc = playlist[0]
        playSound(audiosrc)
        mindex = 0;
     };
   };

    // ---------------------------------------------------------------------------
    //   web audio api define  
    //      XMLHttpRequest() : Asynchronous JavaScript + XM
    //      AudioContext()      : audio-processing
    //      decodeAudioData(): decode audio data asynchronously
    // ---------------------------------------------------------------------------
    function playSound(audiosrc) {
      request = new XMLHttpRequest();
      request.open("GET", audiosrc , true);
      request.responseType = "arraybuffer";
      request.onload = completeOnLoad;
      request.send();      // request to server
    };
    function completeOnLoad() {
      context = new AudioContext();
      source  = context.createBufferSource();
      gain     = context.createGain();                                   // control gain
      gain.connect(context.destination);                 // gain destination
      gain.gain.value = audiovolume ;
      context.decodeAudioData(request.response, function (buf) {
        source.buffer = buf;
        if ( safari == 1) { source.loop = true }  // loop for safari
        else {source.loop = false };
        source.connect(gain)                                             // source  gain
        source.onended = function() {                                // audio end event
          closeAudioContext()
          if (endflag == 0 ) {
            audionext()
          };                                                      // next audio play
        };
      });

      source.start(0);              
                
    };
    function playPause() {                                     // audio pause
      if (stopflag == 0) {
        context.suspend()
        stopflag = 1
      } else {
        context.resume()
        stopflag = 0
      }
    };
    function closeAudioContext() {       // audio stop
      if (closeflag == 0) {
        context.close()                          // release resource
        closeflag = 1
      }
    };
    function playVolumnDown(vdtime) {         // volume gain down
       console.log('fade = ' + vdtime )
       currTime = context.currentTime;
       gain.gain.setValueAtTime(1, currTime);
       gain.gain.linearRampToValueAtTime(0, currTime + vdtime);
    };

  };

    // -------------------------------- define 3d ---------------------------------------------------
    // レンダラーを作成
     const canvasElement = document.querySelector('#canvas3d')
     const renderer = new THREE.WebGLRenderer({ canvas: canvasElement , alpha:true });

     // サイズを指定
     //  var width  = canvas3d.clientWidth;
     var width  = window.innerWidth;
     var height = width/1.77 
    // var height = canvas3d.clientHeight;

     renderer.setPixelRatio(window.devicePixelRatio);
     renderer.setSize(width, height);
     if (width  <= 480 ) { resp = 1 }   // smart phone

     // シーンを作成
     const scene = new THREE.Scene();

    // 座標軸を表示 for development
    //  var axes = new THREE.AxesHelper(2500);
    //  scene.add(axes);

    // --------------------- カメラを作成 -------------------------------
     const cpy = 0 ;
     let tcpz = 3500 ;   // z camera position target
     //   if (resp==1) {tcpz = 5000} ;
     var    cpz = 28000 ;   // z camera position initial
     // 視野角,画面サイズ,カメラの見える範囲最小値,最大値
     camera = new THREE.PerspectiveCamera( 45 , width / height , 2000 , 100000);
     camera.position.set(0, cpy , cpz);
     camera.lookAt(new THREE.Vector3(0, 0, 0 ));
     scene.add(camera);

    // カメラコントローラーを作成
     const controls = new THREE.OrbitControls(camera, renderer.domElement);

    // --------------------- 環境光 を作成 -------------------------------
     ambientLight = new THREE.AmbientLight(0x555555);
     scene.add(ambientLight);

     // ------------------ spotlight ---------------------------------------
     // new THREE.SpotLight(色, 光の強さ, 距離, 照射角, ボケ具合, 減衰率)
     spotLight1 = new THREE.SpotLight(0xaaaaaa , lint0 , 0 , Math.PI/3 , 0.2 , 0 ) ;     
     spotLight1.position.set( 0 , 0 , 2000 );   // equal to Object3D
     spotLight1.castShadow = true;
     scene.add( spotLight1); 

     // new THREE.SpotLight(色, 光の強さ, 距離, 照射角, ボケ具合, 減衰率)
     spotLight2 = new THREE.SpotLight(0xfff000 , lint0 , 5000 , Math.PI , 1 , 1 ) ;     
     spotLight2.position.set( 0 , 0 , 6000 );   // equal to Object3D
     spotLight2.castShadow = true;
     scene.add( spotLight2); 
    //  const lightHelper = new THREE.SpotLightHelper( spotLight2);
    //  scene.add(lightHelper);

    // ----------------------------  define each 3d contents -------------------
    // angle , radius , geometry , material , mesh
    // ---------------------------------------------------------------------------
     bsz = 100 ;    // base size
     if (width <= 480) { bsz = bsz * 1.2 } ;  // for smart phone
     const ssz = 2.3 ;
     // -----------center box geometry ----------------------------------
     loader = new THREE.TextureLoader();
     const boxgeometry = new THREE.BoxGeometry(imgw*ssz , imgh*ssz , imgw*ssz )
     boxmaterial =  new THREE.MeshPhongMaterial({map: loader.load('imgcube2/meta_01.jpg') , side: THREE.DoubleSide , shininess:30 });
     boxmesh = new THREE.Mesh(boxgeometry, boxmaterial)
     scene.add(boxmesh);

     document.getElementById("msgtxt3").innerHTML = "~ 2022年 夏風景 町田市薬師池公園 etc  ~" ;   // put text

    // ---------------------------------------------------------------------------
    //   start animation
    // ---------------------------------------------------------------------------
     const s1 = 25 ;                                      //  preview 
     const s2 = s1 +  timg * imgc ;                //  animation  
     const s3 = s2 + 20 ;                              //  after process 

     tick();

  // ---------------------------------------------------------------------------
  //  Tick  the time
  // ---------------------------------------------------------------------------
  function tick() {
      sst0 += 0.0167 ;                // clock
      sst = Math.round(sst0) ;
      if ( imgmove == 1) {
         mesh1.position.z += 1.0;
       };

      if (sst > sstsv ) {
         if ( sst <= s3 ) {
           str = sst + " / " + s3 ;
           document.getElementById("msgtxt1").innerHTML = str ;   // put current time on html
           };
 
       if (sst % timg == 0 &&  stx == 1) {
           if (typeof txt[tx] === "undefined") {txt[tx] = " "};
           document.getElementById("msgtxt3").innerHTML = txt[tx] ;  // put text 
           if ( im <= imgc ) {
               meshupdate() ;                  // ------------------- mesh update 
           };
          imgmove = 1 ;
          if (typeof lint[im] === "undefined") {lint[im] = lint0 };  // light intensity
            spotLight1.intensity = lint[im] ;

          im += 1 ,  tx  += 1;
       };
       sstsv = sst
     };

     if ( sst <= s1 && cpz >= tcpz ){                       // zoom in initial phase
        cpz -= 30 ;
        camera.position.z =  cpz ;
        boxmesh.rotation.y += 0.0055 ;
     };
     if ( cpz <= tcpz && stx == 0 )  { tx = 1 ,  stx = 1 , boxmesh.rotation.y = 0 };   // zoom end 
     if (sst > s1) {textrotation(textMesh)};

     if ( sst >= s2 ){                       // ------------ pre end process
         document.getElementById("msgtxt3").innerHTML = "ending..." ;        // clear text
         stx = 0 ;
         cpz += 40;
         camera.position.z =  cpz ;
         boxmesh.rotation.y += 0.005 ;
 
         if (fadeflg === 0 ) {
            playVolumnDown(10)                          // fade volume seconds 
            fadeflg = 1 ;
         };
      };

      if (sst >=  s3 && endflag ===0 ) {         // ending process
         endflag = 1
         endproc()       // end proc
      };

       renderer.render(scene, camera);                     // ------------------ レンダリング

       if ( endflag == 0 ) {
          tickreq = requestAnimationFrame(tick);     // animation ----
       };

  };    //  tick end

    // -----------------  text define --------------
   function settext(text) {
     if (tmsh == 1){scene.remove(textMesh)}
     const positiony = - 1800 , positionz = - 2200 ;
     const fsize = 80 ;
  //   if (width <= 480) { fsize = 40 } ;  // for smart phone font size
     loadert = new THREE.FontLoader();
     loadert.load('fonts/optimer_regular.typeface.json', function(font){
       textGeometry = new THREE.TextGeometry( text , { font: font,  size: fsize });
       textMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff });
       textMesh = new THREE.Mesh(textGeometry, textMaterial);
       scene.add(textMesh);
       textGeometry.center();         // text on center position set
       textMesh.position.set( 0 , positiony , positionz ) ;
       tmsh = 1 ;
      });
     };

    // --------------------  set final music name
   function settext2(text) {
     const fsize2 = 80 ;
     loadert2 = new THREE.FontLoader();
     loadert2.load('fonts/optimer_regular.typeface.json', function(font){
       textGeometry2 = new THREE.TextGeometry( text , { font: font,  size: fsize2 });
       textMaterial2 = new THREE.MeshPhongMaterial({ color: 0x0000ff });
       textMesh2 = new THREE.Mesh(textGeometry2, textMaterial2);
       scene.add(textMesh2);
       textGeometry2.center();         // text on center position set
       textMesh2.position.set( 0 , positiony2 , positionz2 ) ;
       positiony2 = positiony2 - 200
      });
     };

    // --------------------------- text rotation
    function textrotation (t) {
     angletext += 0.1 ;
     t.position.x =  radiustext * Math.sin(Math.PI / 180 * angletext  );
     t.position.z =  radiustext * Math.cos(Math.PI / 180 * angletext ) ;
    };

    // --------------------------------------------- read image file -----------------------
    function readimg() {
      img = new Array() , txt = new Array() , lint = new Array ;
      for ( let i = 1 ; i < imgtxt.length ; i++) {
         tmp = imgtxt[i].split("%")
         img[i] = tmp[0] , txt[i] =  tmp[1] , lint[i] = tmp[2]      // image , text , light intensity
      };
    };

   // ----------------------- mesh update-------------------------------
    function meshupdate() {
      if (typeof mesh1 !== "undefined")  {scene.remove(mesh1)} ;   // mesh1 remove
      var geometry1 = new THREE.BoxGeometry(imgw,imgh,imgd);   //  geometry 幾何学

      if (typeof img[im] !== "undefined") { 
        var material1 = new THREE.MeshPhongMaterial({ map: loader.load( img[im]) });  // image load
        mesh1 = new THREE.Mesh(geometry1, material1);   
        scene.add(mesh1);
        mesh1.position.set( 0 , 0 , imgposz ) // image position 
      }
      else {console.log('image' + im + 'undefined')};    // log
  
     }; // mesh update end

    // --------------------------- image & text define -------------------------------------------
    function defineimgtxt() {
      imgtxt = new Array();
       // image %コメント%light intensity
       var imgfile = 'imgcube2'
       imgtxt[1] = imgfile + '/' + 'P7011299.jpg%2022/07/01 合歓の木 町田市薬師池公園';
       imgtxt[2] = imgfile + '/' + 'P7011320.jpg%2022/07/01 大賀ハスが咲き出す 町田市薬師池公園';
       imgtxt[3] = imgfile + '/' + 'P7051478.jpg%2022/07/05 カワセミ 町田市薬師池公園';
       imgtxt[4] = imgfile + '/' + 'P7091590.jpg%2022/07/09 大賀ハス 町田市薬師池公園';
       imgtxt[5] = imgfile + '/' + 'P7091592.jpg%2022/07/09 大賀ハス 町田市薬師池公園';
       imgtxt[6] = imgfile + '/' + 'P7091623.jpg%2022/07/09 大賀ハス 町田市薬師池公園';
       imgtxt[7] = imgfile + '/' + 'P7091675.jpg%2022/07/09 大賀ハス 町田市薬師池公園';
       imgtxt[8] = imgfile + '/' + 'P7091698.jpg%2022/07/09 カルガモの親子 町田市薬師池公園';
       imgtxt[9] = imgfile + '/' + 'P7091807.jpg%2022/07/09 涼しさ誘うスイレン 町田市薬師池公園';
       imgtxt[10] = imgfile + '/' + 'P7091815.jpg%2022/07/09 池の主カイツブリ 町田市薬師池公園';
       imgtxt[11] = imgfile + '/' + 'P7121869.jpg%2022/07/12 バックも上手 町田市薬師池公園';
       imgtxt[12] = imgfile + '/' + 'P7121873.jpg%2022/07/12 母ガモの翼の下でおやすみ 町田市薬師池公園';
       imgtxt[13] = imgfile + '/' + 'P7122540.jpg%2022/07/12 夏の顔ひまわり 町田市七国山';
       imgtxt[14] = imgfile + '/' + 'P7172821.jpg%2022/07/17 山百合の香り漂う 町田市薬師池公園';
       imgtxt[15] = imgfile + '/' + 'P7173032.jpg%2022/07/17 ツミの幼鳥 町田市薬師池公園';
       imgtxt[16] = imgfile + '/' + 'P7203096.jpg%2022/07/20 トンボが縄張り監視 町田市薬師池公園';
       imgtxt[17] = imgfile + '/' + 'P7203101.jpg%2022/07/20 大賀ハス 町田市薬師池公園';
       imgtxt[18] = imgfile + '/' + 'P7203104.jpg%2022/07/20「はい、ポーズ」 町田市薬師池公園';
       imgtxt[19] = imgfile + '/' + 'P7223201.jpg%2022/07/22 ツミの幼鳥 町田市薬師池公園';
       imgtxt[20] = imgfile + '/' + 'P7223310.jpg%2022/07/22 ツミの幼鳥 町田市薬師池公園';
       imgtxt[21] = imgfile + '/' + 'P7223438.jpg%2022/07/22 カワセミが眠りにつく 町田市薬師池公園';
       imgtxt[22] = imgfile + '/' + 'P7283493.jpg%2022/07/28 大賀ハス 町田市薬師池公園';
       imgtxt[23] = imgfile + '/' + 'P7283539.jpg%2022/07/28 百日紅 町田市薬師池公園';
       imgtxt[24] = imgfile + '/' + 'P8053555.jpg%2022/08/05 雨の翌朝 町田市薬師池公園';
       imgtxt[25] = imgfile + '/' + 'P8053557.jpg%2022/08/05 百日紅 町田市薬師池公園';
       imgtxt[26] = imgfile + '/' + 'P8053564.jpg%2022/08/05 スイレン 町田市薬師池公園';
       imgtxt[27] = imgfile + '/' + 'P8144048.jpg%2022/08/14 スイレン 町田市薬師池公園';
       imgtxt[28] = imgfile + '/' + 'P8144051.jpg%2022/08/14 スイレン池の少年はトンボとり 町田市薬師池公園';
       imgtxt[29] = imgfile + '/' + 'P8144069.jpg%2022/08/14 タイワンユリ 町田市薬師池公園';
       imgtxt[30] = imgfile + '/' + 'P8144074.jpg%2022/08/14 町田市薬師池公園';
       imgtxt[31] = imgfile + '/' + 'P8144079.jpg%2022/08/14 タマムシ 町田市薬師池公園';
       imgtxt[32] = imgfile + '/' + 'P8144086.jpg%2022/08/14 百日紅 町田市七国山';
    };

    // --------------------------------------------- end process -------------------------------
    function endproc() {
      document.getElementById("msgtxt1").innerHTML = " ";
      document.getElementById("msgtxt3").innerHTML = "Thank you for your viewing";
      spotLight2.intensity = 0 ;
      cancelAnimationFrame(tickreq) ;
      source.stop(3);           // audio stop after 3 seconds
      closeAudioContext()    // release audio resource
      removeobj();             // remove objects
     };

    // ----------------------------- remove mesh , geometry , material , texture oblects -----------------------
     function removeobj() {
        scene.remove(boxmesh , mesh1);
        boxmaterial.dispose();
        boxgeometry.dispose();
     };

    // resize
     onResize();
     window.addEventListener('resize', onResize);
       function onResize() {
         var width  = window.innerWidth;
         var height = width/1.77 
         renderer.setPixelRatio(window.devicePixelRatio);
         renderer.setSize(width, height);
         camera.aspect = width / height;
         camera.updateProjectionMatrix();
     };

   };  // end 


以下、photocube2.js を分けて説明

①ボタンからの情報


// --------------------------------------------------------------------------------
//  manual button click
// ------------------------  photo cube 2022/08/19 ver3  ---------------------- 
//
// --------------------------------------------------------------------------------
//  manual button click
// -------------------------------------------------------------------------------
 function clickBtng(){
   gtxt1 = " ・通常のパソコンの画面比率と同じように写真の横縦比も16:9に設定しているので最初に画面も16:9にしてください"
   gtxt2 = " ・開始するには「Auto」を押して下さい。「Auto1」と「Auto2」は音楽の違いです"
   gtxt3 = " ・マウス操作でカメラを制御 = オービット(周回軌道) : 左ボタンでドラッグ  ズーム : マウスホイール  パン(カメラの向きを変える) : 右ボタンでドラッグ "
   gtxt4 = " ・「Auto」ボタンを再度押すときはその前に「Reload」を押して下さい"
   gtxt5 = " ・機器によって正常に稼働しない場合は、「Reload」「Auto」で再操作すると稼働する場合があります"
   gtxt6 = " ・このメッセージを消すにはサイド「Guide」を押して下さい"
   gtxt = '<br><br>' + gtxt1 + '<br>' +  gtxt2 + '<br>' +  gtxt3 +  '<br>' + gtxt4 +  '<br>' + gtxt5 + '<br>' + gtxt6 ;
   str0 = document.getElementById("msgtxt2").innerHTML
   if ( str0 === "" ) {str = gtxt}
   else {str = "" };
   document.getElementById("msgtxt2").innerHTML = str ;
 };

// --------------------------------------------------------------------------------
//  start procedure
// -------------------------------------------------------------------------------
 function clickBtn1(id , value){
   if (value.match("Auto1"))  { mode  = "a" , yrotspd = 0.0016 , document.getElementById("btn_m1").style.backgroundColor = "orange";}   // auto1
   if (value.match("Auto2"))  { mode  = "a" , yrotspd = 0.0016 , document.getElementById("btn_m2").style.backgroundColor = "orange";}   // auto2
   if (value.match("Reload")) { mode  = "r" , yrotspd = 0 , document.getElementById("btn_mp").style.backgroundColor = "orange";}   // auto2


②定数定義と Web Audio API


    // ----------------- set const
    let sst0 = 0
    let sstsv = 0 ;
    const  timg = 15 ;      // each img time
    const imgc = 32 ;       // number of images
    const imgposz = 500 ; // img z position 
    var angles1 = 0 ;
    var im = 1 ;
    let audiovolume =  1.0 ;   // audio volume
    let fadeflg = 0 ;               // audio fade sw 
    var mindex = 0;
    let tmsh = 0 ;                 // text mesh
    var request , source , stopflag , closeflag = 0;
    let resp = 0 ;
    var stx = 0 ;                    // text start
    var tx = 1 ;
    var imgmove = 0 ;           // image move sw 
    var lint0 = 1.0 ;      // spotlight default
    var angletext = 0 ; // text rotaion angle
    const radiustext  = 2000 ;
    let endflag = 0 ;    // end process switch

    let positiony2 = 200 , positionz2 = 0 ;
    mname = new Array();
    let nn = 1 ;     // music name number
    let mtext = 0 ; // put music name sw
 
   // ---------------------- setup ------------------
    defineimgtxt();        // define image and text
    readimg();              // read image
    const imgw   =  "3000";                                // img width
    const imgh    =  "1708";                                // img height
    const imgd  = 2                                            // image depth
    // safari 判定
    let safari = 0 ;
    const agent = window.navigator.userAgent.toLowerCase()
    if (agent.indexOf("safari") != -1 && agent.indexOf("chrome") == -1) { safari = 1 }
    // ---------------------------------------------------------------------------
    //   read music names from html and put in array
    // ---------------------------------------------------------------------------
  if ( mode !== "m") {
     playlist = id.split("%", 8);   // take music name
     audionext ();
     function audionext () {                        // audio setup & next
       if ( mindex < playlist.length ) {
         audiosrc = playlist[mindex]             // audio name
         console.log(audiosrc)
         playSound(audiosrc);                     // <======== play sound
         mindex++;
         mtitle = audiosrc.replace("mp3" , "" ).replace("../music/" , "").replace("." , "").replace("-" , " ");     // music title
         settext(mtitle)                          // mesh text write

         if ( mname.indexOf(mtitle) == -1 ) {      //  check duplicate , save music name
            mname[nn] = mtitle 
            settext2(mname[nn])                         // put music name on wall 
            nn = nn +1 
         };
      }
      else {
        audiosrc = playlist[0]
        playSound(audiosrc)
        mindex = 0;
       };
     };

    // ---------------------------------------------------------------------------
    //   web audio api define  
    //      XMLHttpRequest() : Asynchronous JavaScript + XM
    //      AudioContext()      : audio-processing
    //      decodeAudioData(): decode audio data asynchronously
    // ---------------------------------------------------------------------------
    function playSound(audiosrc) {
      request = new XMLHttpRequest();
      request.open("GET", audiosrc , true);
      request.responseType = "arraybuffer";
      request.onload = completeOnLoad;
      request.send();      // request to server
    };
    function completeOnLoad() {
      context = new AudioContext();
      source  = context.createBufferSource();
      gain     = context.createGain();                                   // control gain
      gain.connect(context.destination);                 // gain destination
      gain.gain.value = audiovolume ;
      context.decodeAudioData(request.response, function (buf) {
        source.buffer = buf;
        if ( safari == 1) { source.loop = true }  // loop for safari
        else {source.loop = false };
        source.connect(gain)                                             // source  gain
        source.onended = function() {                                // audio end event
          closeAudioContext()
          if (endflag == 0 ) {
            audionext()
          };                                                      // next audio play
        };
      });

      source.start(0);              
                
    };
    function playPause() {                                     // audio pause
      if (stopflag == 0) {
        context.suspend()
        stopflag = 1
      } else {
        context.resume()
        stopflag = 0
      }
    };
    function closeAudioContext() {       // audio stop
      if (closeflag == 0) {
        context.close()                          // release resource
        closeflag = 1
      }
    };
    function playVolumnDown(vdtime) {         // volume gain down
       console.log('fade = ' + vdtime )
       currTime = context.currentTime;
       gain.gain.setValueAtTime(1, currTime);
       gain.gain.linearRampToValueAtTime(0, currTime + vdtime);
    };

  };

③three.jsを使って3D環境を定義


    // -------------------------------- define 3d ---------------------------------------------------
    // レンダラーを作成
     const canvasElement = document.querySelector('#canvas3d')
     const renderer = new THREE.WebGLRenderer({ canvas: canvasElement , alpha:true });

     // サイズを指定
     //  var width  = canvas3d.clientWidth;
     var width  = window.innerWidth;
     var height = width/1.77 
    // var height = canvas3d.clientHeight;

     renderer.setPixelRatio(window.devicePixelRatio);
     renderer.setSize(width, height);
     if (width  <= 480 ) { resp = 1 }   // smart phone

     // シーンを作成
     const scene = new THREE.Scene();

    // 座標軸を表示 for development
    //  var axes = new THREE.AxesHelper(2500);
    //  scene.add(axes);

    // --------------------- カメラを作成 -------------------------------
     const cpy = 0 ;
     let tcpz = 3500 ;   // z camera position target
     //   if (resp==1) {tcpz = 5000} ;
     var    cpz = 28000 ;   // z camera position initial
     // 視野角,画面サイズ,カメラの見える範囲最小値,最大値
     camera = new THREE.PerspectiveCamera( 45 , width / height , 2000 , 100000);
     camera.position.set(0, cpy , cpz);
     camera.lookAt(new THREE.Vector3(0, 0, 0 ));
     scene.add(camera);

    // カメラコントローラーを作成
     const controls = new THREE.OrbitControls(camera, renderer.domElement);

    // --------------------- 環境光 を作成 -------------------------------
     ambientLight = new THREE.AmbientLight(0x555555);
     scene.add(ambientLight);

     // ------------------ spotlight ---------------------------------------
     // new THREE.SpotLight(色, 光の強さ, 距離, 照射角, ボケ具合, 減衰率)
     spotLight1 = new THREE.SpotLight(0xaaaaaa , lint0 , 0 , Math.PI/3 , 0.2 , 0 ) ;     
     spotLight1.position.set( 0 , 0 , 2000 );   // equal to Object3D
     spotLight1.castShadow = true;
     scene.add( spotLight1); 

     // new THREE.SpotLight(色, 光の強さ, 距離, 照射角, ボケ具合, 減衰率)
     spotLight2 = new THREE.SpotLight(0xfff000 , lint0 , 5000 , Math.PI , 1 , 1 ) ;     
     spotLight2.position.set( 0 , 0 , 6000 );   // equal to Object3D
     spotLight2.castShadow = true;
     scene.add( spotLight2); 
    //  const lightHelper = new THREE.SpotLightHelper( spotLight2);
    //  scene.add(lightHelper);

    // ----------------------------  define each 3d contents -------------------
    // angle , radius , geometry , material , mesh
    // ---------------------------------------------------------------------------
     bsz = 100 ;    // base size
     if (width <= 480) { bsz = bsz * 1.2 } ;  // for smart phone
     const ssz = 2.3 ;
     // -----------center box geometry ----------------------------------
     loader = new THREE.TextureLoader();
     const boxgeometry = new THREE.BoxGeometry(imgw*ssz , imgh*ssz , imgw*ssz )
     boxmaterial =  new THREE.MeshPhongMaterial({map: loader.load('imgcube2/meta_01.jpg') , side: THREE.DoubleSide , shininess:30 });
     boxmesh = new THREE.Mesh(boxgeometry, boxmaterial)
     scene.add(boxmesh);

     document.getElementById("msgtxt3").innerHTML = "~ 2022年 夏風景 町田市薬師池公園 etc  ~" ;   // put text


④アニメーション描画(3dコンテンツを表示・動かす)と3D定義


    // ---------------------------------------------------------------------------
    //   start animation
    // ---------------------------------------------------------------------------
     const s1 = 25 ;                                      //  preview 
     const s2 = s1 +  timg * imgc ;                //  animation  
     const s3 = s2 + 20 ;                              //  after process 

     tick();

  // ---------------------------------------------------------------------------
  //  Tick  the time
  // ---------------------------------------------------------------------------
  function tick() {
      sst0 += 0.0167 ;                // clock
      sst = Math.round(sst0) ;
      if ( imgmove == 1) {
         mesh1.position.z += 1.0;
       };

      if (sst > sstsv ) {
         if ( sst <= s3 ) {
           str = sst + " / " + s3 ;
           document.getElementById("msgtxt1").innerHTML = str ;   // put current time on html
           };
 
       if (sst % timg == 0 &&  stx == 1) {
           if (typeof txt[tx] === "undefined") {txt[tx] = " "};
           document.getElementById("msgtxt3").innerHTML = txt[tx] ;  // put text 
           if ( im <= imgc ) {
               meshupdate() ;                  // ------------------- mesh update 
           };
          imgmove = 1 ;
          if (typeof lint[im] === "undefined") {lint[im] = lint0 };  // light intensity
            spotLight1.intensity = lint[im] ;

          im += 1 ,  tx  += 1;
       };
       sstsv = sst
     };

     if ( sst <= s1 && cpz >= tcpz ){                       // zoom in initial phase
        cpz -= 30 ;
        camera.position.z =  cpz ;
        boxmesh.rotation.y += 0.0055 ;
     };
     if ( cpz <= tcpz && stx == 0 )  { tx = 1 ,  stx = 1 , boxmesh.rotation.y = 0 };   // zoom end 
     if (sst > s1) {textrotation(textMesh)};

     if ( sst >= s2 ){                       // ------------ pre end process
         document.getElementById("msgtxt3").innerHTML = "ending..." ;        // clear text
         stx = 0 ;
         cpz += 40;
         camera.position.z =  cpz ;
         boxmesh.rotation.y += 0.005 ;
 
         if (fadeflg === 0 ) {
            playVolumnDown(10)                          // fade volume seconds 
            fadeflg = 1 ;
         };
      };

      if (sst >=  s3 && endflag ===0 ) {         // ending process
         endflag = 1
         endproc()       // end proc
      };

       renderer.render(scene, camera);                     // ------------------ レンダリング

       if ( endflag == 0 ) {
          tickreq = requestAnimationFrame(tick);     // animation ----
       };

  };    //  tick end

    // -----------------  text define --------------
   function settext(text) {
     if (tmsh == 1){scene.remove(textMesh)}
     const positiony = - 1800 , positionz = - 2200 ;
     const fsize = 80 ;
  //   if (width <= 480) { fsize = 40 } ;  // for smart phone font size
     loadert = new THREE.FontLoader();
     loadert.load('fonts/optimer_regular.typeface.json', function(font){
       textGeometry = new THREE.TextGeometry( text , { font: font,  size: fsize });
       textMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff });
       textMesh = new THREE.Mesh(textGeometry, textMaterial);
       scene.add(textMesh);
       textGeometry.center();         // text on center position set
       textMesh.position.set( 0 , positiony , positionz ) ;
       tmsh = 1 ;
      });
     };

    // --------------------  set final music name
   function settext2(text) {
     const fsize2 = 80 ;
     loadert2 = new THREE.FontLoader();
     loadert2.load('fonts/optimer_regular.typeface.json', function(font){
       textGeometry2 = new THREE.TextGeometry( text , { font: font,  size: fsize2 });
       textMaterial2 = new THREE.MeshPhongMaterial({ color: 0x0000ff });
       textMesh2 = new THREE.Mesh(textGeometry2, textMaterial2);
       scene.add(textMesh2);
       textGeometry2.center();         // text on center position set
       textMesh2.position.set( 0 , positiony2 , positionz2 ) ;
       positiony2 = positiony2 - 200
      });
     };

    // --------------------------- text rotation
    function textrotation (t) {
     angletext += 0.1 ;
     t.position.x =  radiustext * Math.sin(Math.PI / 180 * angletext  );
     t.position.z =  radiustext * Math.cos(Math.PI / 180 * angletext ) ;
    };

    // --------------------------------------------- read image file -----------------------
    function readimg() {
      img = new Array() , txt = new Array() , lint = new Array ;
      for ( let i = 1 ; i < imgtxt.length ; i++) {
         tmp = imgtxt[i].split("%")
         img[i] = tmp[0] , txt[i] =  tmp[1] , lint[i] = tmp[2]      // image , text , light intensity
      };
    };

   // ----------------------- mesh update-------------------------------
    function meshupdate() {
      if (typeof mesh1 !== "undefined")  {scene.remove(mesh1)} ;   // mesh1 remove
      var geometry1 = new THREE.BoxGeometry(imgw,imgh,imgd);   //  geometry 幾何学

      if (typeof img[im] !== "undefined") { 
        var material1 = new THREE.MeshPhongMaterial({ map: loader.load( img[im]) });  // image load
        mesh1 = new THREE.Mesh(geometry1, material1);   
        scene.add(mesh1);
        mesh1.position.set( 0 , 0 , imgposz ) // image position 
      }
      else {console.log('image' + im + 'undefined')};    // log
  
     }; // mesh update end

    // --------------------------- image & text define -------------------------------------------
    function defineimgtxt() {
      imgtxt = new Array();
       // image %コメント%light intensity
       var imgfile = 'imgcube2'
       imgtxt[1] = imgfile + '/' + 'P7011299.jpg%2022/07/01 合歓の木 町田市薬師池公園';
       imgtxt[2] = imgfile + '/' + 'P7011320.jpg%2022/07/01 大賀ハスが咲き出す 町田市薬師池公園';
       imgtxt[3] = imgfile + '/' + 'P7051478.jpg%2022/07/05 カワセミ 町田市薬師池公園';
       imgtxt[4] = imgfile + '/' + 'P7091590.jpg%2022/07/09 大賀ハス 町田市薬師池公園';
       imgtxt[5] = imgfile + '/' + 'P7091592.jpg%2022/07/09 大賀ハス 町田市薬師池公園';
       imgtxt[6] = imgfile + '/' + 'P7091623.jpg%2022/07/09 大賀ハス 町田市薬師池公園';
       imgtxt[7] = imgfile + '/' + 'P7091675.jpg%2022/07/09 大賀ハス 町田市薬師池公園';
       imgtxt[8] = imgfile + '/' + 'P7091698.jpg%2022/07/09 カルガモの親子 町田市薬師池公園';
       imgtxt[9] = imgfile + '/' + 'P7091807.jpg%2022/07/09 涼しさ誘うスイレン 町田市薬師池公園';
       imgtxt[10] = imgfile + '/' + 'P7091815.jpg%2022/07/09 池の主カイツブリ 町田市薬師池公園';
       imgtxt[11] = imgfile + '/' + 'P7121869.jpg%2022/07/12 バックも上手 町田市薬師池公園';
       imgtxt[12] = imgfile + '/' + 'P7121873.jpg%2022/07/12 母ガモの翼の下でおやすみ 町田市薬師池公園';
       imgtxt[13] = imgfile + '/' + 'P7122540.jpg%2022/07/12 夏の顔ひまわり 町田市七国山';
       imgtxt[14] = imgfile + '/' + 'P7172821.jpg%2022/07/17 山百合の香り漂う 町田市薬師池公園';
       imgtxt[15] = imgfile + '/' + 'P7173032.jpg%2022/07/17 ツミの幼鳥 町田市薬師池公園';
       imgtxt[16] = imgfile + '/' + 'P7203096.jpg%2022/07/20 トンボが縄張り監視 町田市薬師池公園';
       imgtxt[17] = imgfile + '/' + 'P7203101.jpg%2022/07/20 大賀ハス 町田市薬師池公園';
       imgtxt[18] = imgfile + '/' + 'P7203104.jpg%2022/07/20「はい、ポーズ」 町田市薬師池公園';
       imgtxt[19] = imgfile + '/' + 'P7223201.jpg%2022/07/22 ツミの幼鳥 町田市薬師池公園';
       imgtxt[20] = imgfile + '/' + 'P7223310.jpg%2022/07/22 ツミの幼鳥 町田市薬師池公園';
       imgtxt[21] = imgfile + '/' + 'P7223438.jpg%2022/07/22 カワセミが眠りにつく 町田市薬師池公園';
       imgtxt[22] = imgfile + '/' + 'P7283493.jpg%2022/07/28 大賀ハス 町田市薬師池公園';
       imgtxt[23] = imgfile + '/' + 'P7283539.jpg%2022/07/28 百日紅 町田市薬師池公園';
       imgtxt[24] = imgfile + '/' + 'P8053555.jpg%2022/08/05 雨の翌朝 町田市薬師池公園';
       imgtxt[25] = imgfile + '/' + 'P8053557.jpg%2022/08/05 百日紅 町田市薬師池公園';
       imgtxt[26] = imgfile + '/' + 'P8053564.jpg%2022/08/05 スイレン 町田市薬師池公園';
       imgtxt[27] = imgfile + '/' + 'P8144048.jpg%2022/08/14 スイレン 町田市薬師池公園';
       imgtxt[28] = imgfile + '/' + 'P8144051.jpg%2022/08/14 スイレン池の少年はトンボとり 町田市薬師池公園';
       imgtxt[29] = imgfile + '/' + 'P8144069.jpg%2022/08/14 タイワンユリ 町田市薬師池公園';
       imgtxt[30] = imgfile + '/' + 'P8144074.jpg%2022/08/14 町田市薬師池公園';
       imgtxt[31] = imgfile + '/' + 'P8144079.jpg%2022/08/14 タマムシ 町田市薬師池公園';
       imgtxt[32] = imgfile + '/' + 'P8144086.jpg%2022/08/14 百日紅 町田市七国山';
    };


⑤終了処理


    // --------------------------------------------- end process -------------------------------
    function endproc() {
      document.getElementById("msgtxt1").innerHTML = " ";
      document.getElementById("msgtxt3").innerHTML = "Thank you for your viewing";
      spotLight2.intensity = 0 ;
      cancelAnimationFrame(tickreq) ;
      source.stop(3);           // audio stop after 3 seconds
      closeAudioContext()    // release audio resource
      removeobj();             // remove objects
     };

    // ----------------------------- remove mesh , geometry , material , texture oblects -----------------------
     function removeobj() {
        scene.remove(boxmesh);
        boxmaterial.dispose();
        boxgeometry.dispose();
     };

    // resize
     onResize();
     window.addEventListener('resize', onResize);
       function onResize() {
         var width  = window.innerWidth;
         var height = width/1.77 
         renderer.setPixelRatio(window.devicePixelRatio);
         renderer.setSize(width, height);
         camera.aspect = width / height;
         camera.updateProjectionMatrix();
     };

   };  // end 


戻る