今回は写真の鑑賞に重点を置いた作品。
カメラでは サイズ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 はウェブ上で音声を扱うための強力で多機能なシステムを提供します。これにより開発者は音源を選択したり、エフェクトを加えたり、ビジュアライゼーションを加えたり、パンニングなどの特殊効果を適用したり、他にもたくさんのいろいろなことができるようになります。
要件
<!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>
ガイドを表示する
clickBtn1(this.id,this.value)音楽名とボタン名を渡す
呼び出すJavascriptプログラムは3個。jsフォルダーに格納しておく
・Three.js : JavaScript 3D Library。事前に導入しておく(導入=>three.js サイト)。ここで使用したバージョンは r132
・OrbitControls.js :マウス操作でカメラを制御。Three.jsには含まれていないので事前に導入しておく。マウス操作を使わなければ不要 。photocube2.jsの const controls = new THREE.OrbitControls(camera, renderer.domElement);と連動
・photocube2.js :今回作成したもの
/* 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%;
}
}
/* --------------------------------------------------------------------*/
はwidth/height 比16対9
@media screen and (max-width: 480px) { .....}モバイル用表示の定義(responsible )
// ------------------------ 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
// --------------------------------------------------------------------------------
// 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
Guideボタンを clickBtng()にわたす。使い方のガイドをhtmlのmsgtxt2に表示
Auto1 , 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);
};
};
稼働するブラウザを検査。safariでは連続してaudioデータを読み込むことができないので一度読み込まれたaudioデータを繰り返す 。chromeでもsafariの文字が含まれているので注意。またバージョンが変わると値が変わることもあるらしい
playlist = id.split("%", 8)idでaudio情報が渡され%で分解してplaylist(配列)に格納
settext2(mname[nn])audio名を3Dテキストでbox中央に表示
XMLHttpRequest()web audio api を使って非同期処理
audioの開始、volumeの調整、停止
gain = context.createGain()ボリュウム調整の準備
source.onended1曲の終了, 配列から次のaudioを取り出す
function playVolumnDown(vdtime)audioのfade outの処理
gain.gain.setValueAtTime(1, currTime)これが必要
gain.gain.linearRampToValueAtTime(0, currTime + vdtime)currTime(現在時刻)からvdtime(10秒後)に0になるように音量を下げていく
ref):linearRampToValueAtTime(value, endTime)
value : A floating point number representing the value the AudioParam will ramp to by the given time.
endTime : A double representing the exact time (in seconds) after the ramping starts that the changing of the value will stop.
// -------------------------------- 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
rendererは絵を描くこと、alpha:trueはcanvasの背景を透明にする
camera.lookAt(new THREE.Vector3(0, 0, 0)) ;カメラの向きを中心に設定
AmbientLight(環境光)とSpotLightは明るさが加算される
// const lightHelper = new THREE.SpotLightHelper( spotLight2);lightを設定するときに有効範囲を示すために使用。決まったらコメントアウトしておく
geometry (幾何学) , texture (生地質) 、material (材料) , mesh (網の目) を定義し、meshをsceneにaddする
3D boxを定義。boxの表裏にmeta_01.jpgを貼り付ける
// ---------------------------------------------------------------------------
// 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 百日紅 町田市七国山';
};
描画メソッド。関数tick()が毎秒60フレーム(60fps)で再描画される。 tickreq は後で停止するときのid
画像処理時間を計測するために試行錯誤の結果sst0 += 0.0167 ;とするのが使いやすい
音楽名を3dで表示。3dテキストを使うには特別なfontを使う。ここではoptimer_regular.typeface.jsonというfontを使うが日本語フォントが無く、画像の説明には使えないのでキャンバス上で曲名表示に使った。このフォントは3Dコンテンツとして使える
json fontはthree.jsをダウンロードするとexamples/fontsフォルダーの中にある
imgtxt[1] = imgfile + '/' + 'P7011299.jpg%2022/07/01 合歓の木 町田市薬師池公園';画像とコメントの定義。画像名とコメントは同じ行にしたほうが使いやすい
function readimg()imgtxt から画像名、コメント、lightの強さを別々の配列(img[i]、txt[i]、lint[i] )に読み込む。(今回はlightの強さは使っていない)
// --------------------------------------------- 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
animationを停止
source.stop(3);3秒後audio停止
onResize();windowサイズを変えた時に対処